Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/optimization-20260322-103653.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: optimization
body: Replace config-based mode setting with runtime detection for interactive/command-line mode
time: 2026-03-22T10:36:53.000000000Z
custom:
Author: ayeshurun
AuthorLink: https://github.com/ayeshurun
47 changes: 20 additions & 27 deletions docs/essentials/modes.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,40 @@
# CLI Modes

The Fabric CLI supports two primary modes to accommodate a variety of workflows: **command line** and **interactive**. The selected mode is preserved between sessions. If you exit and login to the CLI later, it will resume in the same mode you last used.
The Fabric CLI supports two modes: **command-line** and **REPL** (interactive). The active mode is determined automatically at runtime β€” no configuration is required.

Use the following command to see the current stored mode setting:
## Command-Line Mode

```
fab config get mode
```

## Command Line Mode
Command-line mode is best suited for scripted tasks, automation, or when you prefer running single commands without a persistent prompt.

Command line mode is best suited for scripted tasks, automation, or when you prefer running single commands without a prompt.

Typing commands directly in the terminal replicates typical UNIX-style usage.

Use the following command to switch the CLI into command line mode:
Invoke any command directly from your terminal with the `fab` prefix:

```
fab config set mode command_line
fab ls /
fab get /myworkspace.Workspace/mynotebook.Notebook
```

You will be required to log in again after switching modes.

## Interactive Mode
## REPL Mode

Interactive mode provides a shell-like environment in which you can run Fabric CLI commands directly without the `fab` prefix.
REPL mode provides a shell-like interactive environment. Run `fab` without any arguments to enter REPL mode:

Upon entering interactive mode, you see a `fab:/$` prompt. Commands are executed one by one without needing to type `fab` before each command, giving you a more guided experience.
```
fab
```

Use the following command to switch the CLI into interactive mode:
Upon entering REPL mode, you see a `fab:/$` prompt. Commands are executed one by one without needing to type `fab` before each command:

```
fab config set mode interactive
fab:/$ ls
fab:/$ cd myworkspace.Workspace
fab:/myworkspace.Workspace$ get mynotebook.Notebook
fab:/myworkspace.Workspace$ quit
```

You will be required to log in again after switching modes.
Type `help` for a list of available commands, and `quit` or `exit` to leave REPL mode.

## Switching Between Modes

To switch from one mode to the other, enter:

```
fab config set mode <desired_mode>
```
There is no explicit mode switch command. The mode is determined by how you invoke the CLI:

where `<desired_mode>` is either `command_line` or `interactive`. Because the Fabric CLI needs to establish new authentication for each mode, you must re-authenticate after switching. The mode choice then remains in effect until you change it again.
- **Command-line mode** β€” run `fab <command>` with one or more arguments.
- **REPL mode** β€” run `fab` with no arguments.
5 changes: 2 additions & 3 deletions docs/essentials/settings.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Settings

The Fabric CLI provides a comprehensive set of configuration settings that allow you to customize its behavior, performance, and default values. All settings persist across CLI sessions, except for `mode` and `encryption_fallback_enabled`.
The Fabric CLI provides a comprehensive set of configuration settings that allow you to customize its behavior, performance, and default values. All settings persist across CLI sessions, except for `encryption_fallback_enabled`.

## Available Settings

Expand All @@ -9,11 +9,10 @@ The Fabric CLI provides a comprehensive set of configuration settings that allow
| `cache_enabled` | Toggles caching of CLI HTTP responses | `BOOLEAN` | `true` |
| `check_cli_version_updates` | Enables automatic update notifications on login | `BOOLEAN` | `true` |
| `debug_enabled` | Toggles additional diagnostic logs for troubleshooting | `BOOLEAN` | `false` |
| `context_persistence_enabled` | Persists CLI navigation context in command line mode across sessions | `BOOLEAN` | `false` |
| `context_persistence_enabled` | Persists CLI navigation context in command-line mode across sessions | `BOOLEAN` | `false` |
| `encryption_fallback_enabled` | Permits storing tokens in plain text if secure encryption is unavailable | `BOOLEAN` | `false` |
| `job_cancel_ontimeout` | Cancels job runs that exceed the timeout period | `BOOLEAN` | `true` |
| `local_definition_labels` | Indicates the local JSON file path for label definitions mapping | `VARCHAR` | |
| `mode` | Determines the CLI mode (`interactive` or `command_line`) | `VARCHAR` | `command_line` |
| `output_item_sort_criteria` | Defines items output order (`byname` or `bytype`) | `VARCHAR` | `byname`|
| `show_hidden` | Displays all Fabric elements | `BOOLEAN` | `false` |
| `default_az_admin` | Defines the default Fabric administrator email for capacities | `VARCHAR` | |
Expand Down
14 changes: 14 additions & 0 deletions src/fabric_cli/commands/config/fab_config_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@

def exec_command(args: Namespace) -> None:
key = args.key.lower()

# Backward compatibility: 'mode' is no longer a configurable setting.
# Phase 1: warn but still return the runtime mode so existing scripts don't break.
if key == fab_constant.FAB_MODE:
utils_ui.print_warning(
"The 'mode' setting is deprecated and will be removed in a future release. "
"Run 'fab' without arguments to enter REPL mode, "
"or use 'fab <command>' for command-line mode."
)
from fabric_cli.core.fab_context import Context

utils_ui.print_output_format(args, data=Context().get_runtime_mode())
return

if key not in fab_constant.FAB_CONFIG_KEYS_TO_VALID_VALUES:
raise FabricCLIError(
ErrorMessages.Config.unknown_configuration_key(key),
Expand Down
46 changes: 15 additions & 31 deletions src/fabric_cli/commands/config/fab_config_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ def exec_command(args: Namespace) -> None:
key = args.key.lower()
value = args.value.strip().strip("'").strip('"')

# Backward compatibility: 'mode' is no longer a configurable setting.
# Phase 1: warn but still honour the request so existing scripts don't break.
if key == fab_constant.FAB_MODE:
utils_ui.print_warning(
"The 'mode' setting is deprecated and will be removed in a future release. "
"Run 'fab' without arguments to enter REPL mode, "
"or use 'fab <command>' for command-line mode."
)
if value == fab_constant.FAB_MODE_INTERACTIVE:
from fabric_cli.core.fab_interactive import start_interactive_mode

start_interactive_mode()
return

if key not in fab_constant.FAB_CONFIG_KEYS_TO_VALID_VALUES:
raise FabricCLIError(
ErrorMessages.Config.unknown_configuration_key(key),
Expand Down Expand Up @@ -62,16 +76,12 @@ def _set_config(args: Namespace, key: str, value: Any, verbose: bool = True) ->
fab_constant.ERROR_INVALID_PATH,
)

previous_mode = fab_state_config.get_config(key)
fab_state_config.set_config(key, value)
if verbose:
utils_ui.print_output_format(
args, message=f"Configuration '{key}' set to '{value}'"
)

if key == fab_constant.FAB_MODE:
_handle_fab_config_mode(previous_mode, value)


def _set_capacity(args: Namespace, value: str) -> None:
value = utils.remove_dot_suffix(value, ".Capacity")
Expand All @@ -89,30 +99,4 @@ def _set_capacity(args: Namespace, value: str) -> None:
raise FabricCLIError(
ErrorMessages.Config.invalid_capacity(value),
fab_constant.ERROR_INVALID_INPUT,
)


def _handle_fab_config_mode(previous_mode: str, current_mode: str) -> None:
from fabric_cli.core.fab_context import Context
# Clean up context files when changing mode
Context().cleanup_context_files(cleanup_all_stale=True, cleanup_current=True)

if current_mode == fab_constant.FAB_MODE_INTERACTIVE:
# Show deprecation warning
utils_ui.print_warning(
"Mode configuration is deprecated. Running 'fab' now automatically enters interactive mode."
)
utils_ui.print("Starting interactive mode...")
from fabric_cli.core.fab_interactive import start_interactive_mode
start_interactive_mode()

elif current_mode == fab_constant.FAB_MODE_COMMANDLINE:
# Show deprecation warning with better messaging
utils_ui.print_warning(
"Mode configuration is deprecated. Running 'fab' now automatically enters interactive mode."
)
utils_ui.print("Configuration saved for backward compatibility.")

if previous_mode == fab_constant.FAB_MODE_INTERACTIVE:
utils_ui.print("Exiting interactive mode. Goodbye!")
os._exit(0)
)
3 changes: 1 addition & 2 deletions src/fabric_cli/core/fab_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@
FAB_ENCRYPTION_FALLBACK_ENABLED: ["false", "true"],
FAB_JOB_CANCEL_ONTIMEOUT: ["false", "true"],
FAB_LOCAL_DEFINITION_LABELS: [],
FAB_MODE: [FAB_MODE_INTERACTIVE, FAB_MODE_COMMANDLINE],
FAB_OUTPUT_ITEM_SORT_CRITERIA: ["byname", "bytype"],
FAB_SHOW_HIDDEN: ["false", "true"],
FAB_DEFAULT_AZ_SUBSCRIPTION_ID: [],
Expand All @@ -123,7 +122,6 @@
}

CONFIG_DEFAULT_VALUES = {
FAB_MODE: FAB_MODE_COMMANDLINE,
FAB_CACHE_ENABLED: "true",
FAB_CONTEXT_PERSISTENCE_ENABLED: "false",
FAB_JOB_CANCEL_ONTIMEOUT: "true",
Expand Down Expand Up @@ -344,3 +342,4 @@

# Invalid query parameters for set command across all fabric resources
SET_COMMAND_INVALID_QUERIES = ["id", "type", "workspaceId", "folderId"]

12 changes: 10 additions & 2 deletions src/fabric_cli/core/fab_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,21 @@ class Context:
def __init__(self):
self._context: FabricElement = None
self._command: str = None
self._runtime_mode: str = fab_constant.FAB_MODE_COMMANDLINE
session_id = self._get_context_session_id()
self._context_file = os.path.join(
fab_state_config.config_location(), f"context-{session_id}.json"
)
self._loading_context = False

def set_runtime_mode(self, mode: str) -> None:
"""Set the current runtime mode. Called when entering or leaving the REPL."""
self._runtime_mode = mode

def get_runtime_mode(self) -> str:
"""Return the current runtime mode (FAB_MODE_INTERACTIVE or FAB_MODE_COMMANDLINE)."""
return self._runtime_mode

@property
def context(self) -> FabricElement:
if self._context is None:
Expand Down Expand Up @@ -126,12 +135,11 @@ def _load_context(self) -> None:

def _should_use_context_file(self) -> bool:
"""Determine if the context file should be used based on the current mode and persistence settings."""
mode = fab_state_config.get_config(fab_constant.FAB_MODE)
persistence_enabled = fab_state_config.get_config(
fab_constant.FAB_CONTEXT_PERSISTENCE_ENABLED
)
return (
mode == fab_constant.FAB_MODE_COMMANDLINE
self.get_runtime_mode() == fab_constant.FAB_MODE_COMMANDLINE
and persistence_enabled == "true"
and not self._loading_context
)
Expand Down
2 changes: 2 additions & 0 deletions src/fabric_cli/core/fab_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self, parser=None, subparsers=None):

self.parser = parser
self.parser.set_mode(fab_constant.FAB_MODE_INTERACTIVE)
Context().set_runtime_mode(fab_constant.FAB_MODE_INTERACTIVE)
self.subparsers = subparsers
self.history = InMemoryHistory()
self.session = self.init_session(self.history)
Expand Down Expand Up @@ -147,6 +148,7 @@ def start_interactive(self):
utils_ui.print(fab_constant.INTERACTIVE_EXIT_MESSAGE)
finally:
self._is_running = False
Context().set_runtime_mode(fab_constant.FAB_MODE_COMMANDLINE)


def start_interactive_mode():
Expand Down
4 changes: 4 additions & 0 deletions src/fabric_cli/core/fab_state_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def init_defaults():
current_config = read_config(config_file)
changed = False

# Migration: remove the deprecated 'mode' key (mode is now detected at runtime)
if fab_constant.FAB_MODE in current_config:
del current_config[fab_constant.FAB_MODE]

for key in fab_constant.FAB_CONFIG_KEYS_TO_VALID_VALUES:
old_key = f"fab_{key}"
if old_key in current_config:
Expand Down
10 changes: 1 addition & 9 deletions src/fabric_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,7 @@ def main():
if args.command == "auth" and args.auth_command == "login":
from fabric_cli.commands.auth import fab_auth

if fab_auth.init(args):
if (
fab_state_config.get_config(fab_constant.FAB_MODE)
== fab_constant.FAB_MODE_INTERACTIVE
):
from fabric_cli.core.fab_interactive import start_interactive_mode

start_interactive_mode()
return
fab_auth.init(args)

if args.command == "auth" and args.auth_command == "logout":
from fabric_cli.commands.auth import fab_auth
Expand Down
8 changes: 4 additions & 4 deletions src/fabric_cli/parsers/fab_config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def register_parser(subparsers: _SubParsersAction) -> None:

# Subcommand for 'set'
set_examples = [
"# switch to command line mode",
"$ config set mode command_line\n",
"# enable debug mode",
"$ config set debug_enabled true\n",
"# set default capacity",
"$ config set default_capacity Trial-0000",
]
Expand All @@ -59,8 +59,8 @@ def register_parser(subparsers: _SubParsersAction) -> None:

# Subcommand for 'get'
get_examples = [
"# get current CLI mode",
"$ config get mode\n",
"# get current debug setting",
"$ config get debug_enabled\n",
"# get default capacity",
"$ config get default_capacity",
]
Expand Down
Loading
Loading