mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 17:38:35 +00:00
fix: preserve config during non-interactive defaults onboarding
Maintainer edit: make explicit --defaults refresh existing configs without prompting when no TTY is available, preserving user values for CI and Docker runs.
This commit is contained in:
parent
9c7d1c9507
commit
3d773d9054
@ -8,7 +8,7 @@ Use this page when you know what you want to run and need the command shape. For
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Check the install | `nanobot --version` | If this fails, try `python -m nanobot --version` |
|
| Check the install | `nanobot --version` | If this fails, try `python -m nanobot --version` |
|
||||||
| Use guided setup | `nanobot onboard` | Best when you prefer prompts over hand-editing JSON |
|
| Use guided setup | `nanobot onboard` | Best when you prefer prompts over hand-editing JSON |
|
||||||
| Create or refresh defaults | `nanobot onboard --defaults` | Creates `~/.nanobot/config.json` and `~/.nanobot/workspace/` without prompts |
|
| Create or refresh defaults | `nanobot onboard --defaults` | Creates `~/.nanobot/config.json` and `~/.nanobot/workspace/` without the wizard |
|
||||||
| Check config without calling a model | `nanobot status` | Reads the default config and summarizes the active model/provider |
|
| Check config without calling a model | `nanobot status` | Reads the default config and summarizes the active model/provider |
|
||||||
| Send one test message | `nanobot agent -m "Hello!"` | First proof that install, config, provider, model, and workspace all work |
|
| Send one test message | `nanobot agent -m "Hello!"` | First proof that install, config, provider, model, and workspace all work |
|
||||||
| Chat in the terminal | `nanobot agent` | Interactive local chat; exit with `exit`, `/exit`, `:q`, or `Ctrl+D` |
|
| Chat in the terminal | `nanobot agent` | Interactive local chat; exit with `exit`, `/exit`, `:q`, or `Ctrl+D` |
|
||||||
@ -53,11 +53,13 @@ Long-running commands keep working until you stop them. Press `Ctrl+C` in that t
|
|||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `nanobot onboard` | Use the interactive setup wizard |
|
| `nanobot onboard` | Use the interactive setup wizard |
|
||||||
| `nanobot onboard --defaults` | Initialize or refresh the default config and workspace without prompts |
|
| `nanobot onboard --defaults` | Initialize or refresh the default config and workspace without the wizard |
|
||||||
| `nanobot onboard --defaults --config <path> --workspace <path>` | Initialize or refresh a specific instance without prompts |
|
| `nanobot onboard --defaults --config <path> --workspace <path>` | Initialize or refresh a specific instance without the wizard |
|
||||||
|
|
||||||
Without `--defaults`, `nanobot onboard` opens the wizard only when stdin and stdout are attached to a terminal. In scripts, CI, and Docker non-TTY runs, it falls back to the defaults setup.
|
Without `--defaults`, `nanobot onboard` opens the wizard only when stdin and stdout are attached to a terminal. In scripts, CI, and Docker non-TTY runs, it falls back to the defaults setup.
|
||||||
|
|
||||||
|
When `--defaults` refreshes an existing config in scripts, CI, or Docker non-TTY runs, it preserves existing values without prompting. In an interactive terminal, nanobot still asks before replacing an existing config with fresh defaults.
|
||||||
|
|
||||||
Default paths:
|
Default paths:
|
||||||
|
|
||||||
| Path | Default |
|
| Path | Default |
|
||||||
|
|||||||
@ -452,9 +452,11 @@ def onboard(
|
|||||||
from nanobot.config.loader import get_config_path, load_config, save_config, set_config_path
|
from nanobot.config.loader import get_config_path, load_config, save_config, set_config_path
|
||||||
from nanobot.config.schema import Config
|
from nanobot.config.schema import Config
|
||||||
|
|
||||||
|
can_prompt = _onboard_can_prompt()
|
||||||
wants_wizard = wizard or not defaults
|
wants_wizard = wizard or not defaults
|
||||||
use_wizard = wants_wizard and not defaults and _onboard_can_prompt()
|
use_wizard = wants_wizard and not defaults and can_prompt
|
||||||
default_fallback = wants_wizard and not use_wizard and not defaults
|
default_fallback = wants_wizard and not use_wizard and not defaults
|
||||||
|
default_refresh_without_prompt = defaults and not can_prompt
|
||||||
if default_fallback:
|
if default_fallback:
|
||||||
console.print(
|
console.print(
|
||||||
"[yellow]No interactive terminal detected; using --defaults setup instead.[/yellow]"
|
"[yellow]No interactive terminal detected; using --defaults setup instead.[/yellow]"
|
||||||
@ -476,7 +478,7 @@ def onboard(
|
|||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
if use_wizard:
|
if use_wizard:
|
||||||
config = _apply_workspace_override(load_config(config_path))
|
config = _apply_workspace_override(load_config(config_path))
|
||||||
elif default_fallback:
|
elif default_fallback or default_refresh_without_prompt:
|
||||||
config = _apply_workspace_override(load_config(config_path))
|
config = _apply_workspace_override(load_config(config_path))
|
||||||
save_config(config, config_path)
|
save_config(config, config_path)
|
||||||
console.print(
|
console.print(
|
||||||
|
|||||||
@ -108,10 +108,11 @@ def test_onboard_fresh_install(mock_paths):
|
|||||||
assert mock_ws.call_args.args == (expected_workspace,)
|
assert mock_ws.call_args.args == (expected_workspace,)
|
||||||
|
|
||||||
|
|
||||||
def test_onboard_existing_config_refresh(mock_paths):
|
def test_onboard_existing_config_refresh(mock_paths, monkeypatch):
|
||||||
"""Config exists, user declines overwrite — should refresh (load-merge-save)."""
|
"""Config exists, user declines overwrite — should refresh (load-merge-save)."""
|
||||||
config_file, workspace_dir, _ = mock_paths
|
config_file, workspace_dir, _ = mock_paths
|
||||||
config_file.write_text('{"existing": true}')
|
config_file.write_text('{"existing": true}')
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands._onboard_can_prompt", lambda: True)
|
||||||
|
|
||||||
result = runner.invoke(app, ["onboard", "--defaults"], input="n\n")
|
result = runner.invoke(app, ["onboard", "--defaults"], input="n\n")
|
||||||
|
|
||||||
@ -136,10 +137,46 @@ def test_onboard_non_tty_existing_config_refreshes_without_prompt(mock_paths):
|
|||||||
assert workspace_dir.exists()
|
assert workspace_dir.exists()
|
||||||
|
|
||||||
|
|
||||||
def test_onboard_existing_config_overwrite(mock_paths):
|
def test_onboard_defaults_non_tty_existing_config_preserves_values_without_prompt(
|
||||||
|
tmp_path, monkeypatch
|
||||||
|
):
|
||||||
|
"""Explicit --defaults should refresh existing configs without prompts outside a TTY."""
|
||||||
|
config_path = tmp_path / "config.json"
|
||||||
|
workspace_path = tmp_path / "workspace"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps({"agents": {"defaults": {"model": "custom/keep"}}}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands._onboard_can_prompt", lambda: False)
|
||||||
|
monkeypatch.setattr("nanobot.channels.registry.discover_all", lambda: {})
|
||||||
|
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"onboard",
|
||||||
|
"--defaults",
|
||||||
|
"--config",
|
||||||
|
str(config_path),
|
||||||
|
"--workspace",
|
||||||
|
str(workspace_path),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Config already exists" not in result.stdout
|
||||||
|
assert "Overwrite?" not in result.stdout
|
||||||
|
assert "existing values preserved" in result.stdout
|
||||||
|
saved = Config.model_validate(json.loads(config_path.read_text(encoding="utf-8")))
|
||||||
|
assert saved.agents.defaults.model == "custom/keep"
|
||||||
|
assert saved.workspace_path == workspace_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_onboard_existing_config_overwrite(mock_paths, monkeypatch):
|
||||||
"""Config exists, user confirms overwrite — should reset to defaults."""
|
"""Config exists, user confirms overwrite — should reset to defaults."""
|
||||||
config_file, workspace_dir, _ = mock_paths
|
config_file, workspace_dir, _ = mock_paths
|
||||||
config_file.write_text('{"existing": true}')
|
config_file.write_text('{"existing": true}')
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands._onboard_can_prompt", lambda: True)
|
||||||
|
|
||||||
result = runner.invoke(app, ["onboard", "--defaults"], input="y\n")
|
result = runner.invoke(app, ["onboard", "--defaults"], input="y\n")
|
||||||
|
|
||||||
@ -149,11 +186,12 @@ def test_onboard_existing_config_overwrite(mock_paths):
|
|||||||
assert workspace_dir.exists()
|
assert workspace_dir.exists()
|
||||||
|
|
||||||
|
|
||||||
def test_onboard_existing_workspace_safe_create(mock_paths):
|
def test_onboard_existing_workspace_safe_create(mock_paths, monkeypatch):
|
||||||
"""Workspace exists — should not recreate, but still add missing templates."""
|
"""Workspace exists — should not recreate, but still add missing templates."""
|
||||||
config_file, workspace_dir, _ = mock_paths
|
config_file, workspace_dir, _ = mock_paths
|
||||||
workspace_dir.mkdir(parents=True)
|
workspace_dir.mkdir(parents=True)
|
||||||
config_file.write_text("{}")
|
config_file.write_text("{}")
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands._onboard_can_prompt", lambda: True)
|
||||||
|
|
||||||
result = runner.invoke(app, ["onboard", "--defaults"], input="n\n")
|
result = runner.invoke(app, ["onboard", "--defaults"], input="n\n")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user