Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a9c1621
Add benchmark script for CLI startup performance
ayeshurun Mar 17, 2026
0ee8239
Initial plan
Copilot Mar 17, 2026
884e724
Remove mode settings: detect REPL mode at runtime instead of config
Copilot Mar 17, 2026
6c7e06e
Merge branch 'main' of https://github.com/ayeshurun/fabric-cli
Mar 19, 2026
58408d6
update
Mar 19, 2026
3bcad54
Regression tests and graduated deprecation for mode removal
Mar 22, 2026
4a9d8d0
Merge upstream/main and re-apply branch removals (MAP, shared session)
Mar 22, 2026
7a563ff
Delete scripts/benchmark_startup.py
ayeshurun Mar 22, 2026
dcdec92
Delete file
Mar 22, 2026
20aa53f
revert
Mar 22, 2026
daa49eb
revert
Mar 22, 2026
ef889b3
Add changelog entry
Mar 22, 2026
28ea374
Merge branch 'main' of https://github.com/ayeshurun/fabric-cli
Mar 22, 2026
273a7eb
Merge branch 'main' of https://github.com/ayeshurun/fabric-cli
Mar 23, 2026
e71de12
Merge branch 'main' of https://github.com/ayeshurun/fabric-cli
Mar 25, 2026
65f670f
Merge branch 'main' of https://github.com/ayeshurun/fabric-cli into c…
Mar 25, 2026
17e174c
Address comments
Mar 25, 2026
8da15f4
fix: address review comments - use mock_repl fixture and naming conve…
Mar 29, 2026
85933fe
refactor: remove duplicate mode tests and unused mock_questionary_print
Mar 29, 2026
b2d8a5b
Apply suggestion from @Copilot
ayeshurun Mar 29, 2026
ddb8d4a
fix: add _success suffix to test names in test_fab_state_config
Mar 29, 2026
85a46d2
fix: address copilot review comments
Mar 29, 2026
dc276c9
fix: add _success/_failure suffix to all test names in test_fab_hiear…
Mar 30, 2026
56e5317
Update
Mar 31, 2026
1aaabd5
Add autouse fixture to reset runtime mode in TestConfigModeDeprecated
Apr 12, 2026
e14ec48
Fix interactive mode restore and extract mode deprecation helper
Apr 13, 2026
01a8c61
Add test for interactive mode previous-mode restore
Apr 13, 2026
a63b991
Revert previous-mode save/restore in start_interactive
Apr 13, 2026
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.
Comment thread
ayeshurun marked this conversation as resolved.
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:
Comment thread
ayeshurun marked this conversation as resolved.
"""Return the current runtime mode (FAB_MODE_INTERACTIVE or FAB_MODE_COMMANDLINE)."""
return self._runtime_mode
Comment thread
ayeshurun marked this conversation as resolved.

@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)
Comment thread
ayeshurun marked this conversation as resolved.


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]

Comment thread
ayeshurun marked this conversation as resolved.
Comment thread
ayeshurun marked this conversation as resolved.
for key in fab_constant.FAB_CONFIG_KEYS_TO_VALID_VALUES:
old_key = f"fab_{key}"
if old_key in current_config:
Comment thread
ayeshurun marked this conversation as resolved.
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)

Comment thread
ayeshurun marked this conversation as resolved.
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