zai-cli 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zai_cli-0.1.2 → zai_cli-0.1.3}/CHANGELOG.md +9 -0
- {zai_cli-0.1.2/zai_cli.egg-info → zai_cli-0.1.3}/PKG-INFO +1 -1
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_interactive.py +64 -2
- zai_cli-0.1.3/zai/__init__.py +1 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/interactive.py +87 -6
- {zai_cli-0.1.2 → zai_cli-0.1.3/zai_cli.egg-info}/PKG-INFO +1 -1
- zai_cli-0.1.2/zai/__init__.py +0 -1
- {zai_cli-0.1.2 → zai_cli-0.1.3}/LICENSE +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/MANIFEST.in +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/README.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/pyproject.toml +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/scripts/release_preflight.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/setup.cfg +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_agent.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_browser.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_code_runner.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_config_main.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_core.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_errors.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_hooks_skills_session.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_input.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_integrations_cli.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_mcp.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_plugins.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_process.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_providers.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_release_preflight.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_search.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_security.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_settings_cli.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_storage.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_streaming.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_tool_schema.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_tools.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_undo.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_utilities.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_vision.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_watch.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_workflows.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/__main__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/common.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/integrations.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/settings.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/utilities.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/workflows.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/commit.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/explain.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/feature.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/fix.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/review.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/config.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/agent.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/cancellation.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/commands.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/context.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/errors.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/fallback.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/hooks.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/memory.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/process.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/repomap.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/runtime.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/security.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/session.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/storage.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/streaming.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/tool_schema.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/undo.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/watch.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/main.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/mcp/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/mcp/client.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/mcp/manager.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/plugins/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/plugins/base.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/plugins/loader.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/anthropic.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/base.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/cerebras.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/gemini.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/groq.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/ollama.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/openai.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/openrouter.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/qwen.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/skills/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/skills/registry.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/browser.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/code_runner.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/files.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/git.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/search.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/vision.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/ui/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/ui/input.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/SOURCES.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/dependency_links.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/entry_points.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/requires.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,15 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and the project uses [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.1.3] - 2026-06-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Made Ctrl+C exit predictable in interactive mode: one Ctrl+C cancels the
|
|
13
|
+
current prompt/operation, and a second consecutive Ctrl+C exits the session.
|
|
14
|
+
- Added in-chat key setup: starting `zai` without a provider now offers API-key
|
|
15
|
+
setup immediately, and `/setup` can save keys without leaving interactive mode.
|
|
16
|
+
|
|
8
17
|
## [0.1.2] - 2026-06-24
|
|
9
18
|
|
|
10
19
|
### Changed
|
|
@@ -38,15 +38,58 @@ def test_interactive_plain_commands_show_local_help(tmp_path, monkeypatch, capsy
|
|
|
38
38
|
side_effect=["/", "commands", "setup", "/model", EOFError()],
|
|
39
39
|
):
|
|
40
40
|
with patch("zai.cli.interactive.run_agent") as run_agent:
|
|
41
|
-
|
|
41
|
+
with patch(
|
|
42
|
+
"zai.cli.interactive._run_key_setup_interactive",
|
|
43
|
+
return_value=True,
|
|
44
|
+
) as setup:
|
|
45
|
+
run_interactive("groq")
|
|
42
46
|
|
|
43
47
|
output = capsys.readouterr().out
|
|
44
48
|
assert "Interactive slash commands" in output
|
|
45
|
-
|
|
49
|
+
setup.assert_called_once()
|
|
46
50
|
assert "Available models" in output
|
|
47
51
|
run_agent.assert_not_called()
|
|
48
52
|
|
|
49
53
|
|
|
54
|
+
def test_interactive_offers_setup_when_no_provider(tmp_path, monkeypatch, capsys):
|
|
55
|
+
monkeypatch.chdir(tmp_path)
|
|
56
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=False):
|
|
57
|
+
with patch("zai.cli.interactive.Confirm.ask", return_value=True):
|
|
58
|
+
with patch(
|
|
59
|
+
"zai.cli.interactive._run_key_setup_interactive",
|
|
60
|
+
return_value=True,
|
|
61
|
+
) as setup:
|
|
62
|
+
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
63
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
64
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
65
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
66
|
+
with patch(
|
|
67
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
68
|
+
side_effect=EOFError(),
|
|
69
|
+
):
|
|
70
|
+
run_interactive("groq")
|
|
71
|
+
|
|
72
|
+
output = capsys.readouterr().out
|
|
73
|
+
assert "No AI provider available" in output
|
|
74
|
+
setup.assert_called_once()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_interactive_key_setup_saves_entered_key(capsys):
|
|
78
|
+
from zai.cli.interactive import _run_key_setup_interactive
|
|
79
|
+
|
|
80
|
+
answers = iter(["gemini-key", "", "", "", "", "", ""])
|
|
81
|
+
|
|
82
|
+
with patch("zai.cli.interactive.get_api_key", return_value=None):
|
|
83
|
+
with patch("zai.cli.interactive.Prompt.ask", side_effect=lambda *a, **k: next(answers)):
|
|
84
|
+
with patch("zai.cli.interactive.save_api_key") as save_key:
|
|
85
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
86
|
+
assert _run_key_setup_interactive()
|
|
87
|
+
|
|
88
|
+
output = capsys.readouterr().out
|
|
89
|
+
assert "Provider key setup" in output
|
|
90
|
+
save_key.assert_called_once_with("gemini", "gemini-key")
|
|
91
|
+
|
|
92
|
+
|
|
50
93
|
def test_interactive_help_then_exit(tmp_path, monkeypatch, capsys):
|
|
51
94
|
monkeypatch.chdir(tmp_path)
|
|
52
95
|
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
@@ -87,6 +130,25 @@ def test_interactive_fires_session_end(tmp_path, monkeypatch):
|
|
|
87
130
|
assert any(call.args[0] == "SessionEnd" for call in fire.call_args_list)
|
|
88
131
|
|
|
89
132
|
|
|
133
|
+
def test_interactive_exits_after_second_input_cancel(tmp_path, monkeypatch, capsys):
|
|
134
|
+
monkeypatch.chdir(tmp_path)
|
|
135
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
136
|
+
with patch("zai.cli.interactive.fire_hook", return_value=True) as fire:
|
|
137
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
138
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
139
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
140
|
+
with patch(
|
|
141
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
142
|
+
side_effect=[KeyboardInterrupt(), KeyboardInterrupt()],
|
|
143
|
+
):
|
|
144
|
+
run_interactive("groq")
|
|
145
|
+
|
|
146
|
+
output = capsys.readouterr().out
|
|
147
|
+
assert "Press Ctrl+C again to exit" in output
|
|
148
|
+
assert "Goodbye" in output
|
|
149
|
+
assert any(call.args[0] == "SessionEnd" for call in fire.call_args_list)
|
|
150
|
+
|
|
151
|
+
|
|
90
152
|
def test_interactive_session_rename_routes_arguments(tmp_path, monkeypatch):
|
|
91
153
|
monkeypatch.chdir(tmp_path)
|
|
92
154
|
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.3"
|
|
@@ -7,7 +7,7 @@ from rich.panel import Panel
|
|
|
7
7
|
from rich.prompt import Confirm, Prompt
|
|
8
8
|
from rich.table import Table
|
|
9
9
|
|
|
10
|
-
from ..config import get_api_key, get_models, load_config
|
|
10
|
+
from ..config import ENV_FILE, get_api_key, get_models, load_config, save_api_key, save_config
|
|
11
11
|
from ..core.agent import plan_agent, run_agent, undo_last
|
|
12
12
|
from ..core.commands import get_command_prompt, list_commands, load_commands
|
|
13
13
|
from ..core.hooks import fire as fire_hook
|
|
@@ -73,6 +73,16 @@ PLAIN_COMMAND_ALIASES = {
|
|
|
73
73
|
"undo": "/undo",
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
KEY_SETUP_PROVIDERS = [
|
|
77
|
+
("gemini", "Google Gemini (free, recommended)", "https://aistudio.google.com/app/apikey"),
|
|
78
|
+
("groq", "Groq (free, fast)", "https://console.groq.com/keys"),
|
|
79
|
+
("openai", "OpenAI (paid)", "https://platform.openai.com/api-keys"),
|
|
80
|
+
("cerebras", "Cerebras (free)", "https://cloud.cerebras.ai/platform"),
|
|
81
|
+
("openrouter", "OpenRouter", "https://openrouter.ai/keys"),
|
|
82
|
+
("qwen", "Qwen/DashScope", "https://dashscope.aliyun.com/"),
|
|
83
|
+
("anthropic", "Anthropic Claude (paid)", "https://console.anthropic.com/settings/keys"),
|
|
84
|
+
]
|
|
85
|
+
|
|
76
86
|
|
|
77
87
|
def _show_help() -> None:
|
|
78
88
|
console.print(
|
|
@@ -151,6 +161,58 @@ def _show_models() -> None:
|
|
|
151
161
|
console.print(table)
|
|
152
162
|
|
|
153
163
|
|
|
164
|
+
def _run_key_setup_interactive() -> bool:
|
|
165
|
+
console.print(Panel(
|
|
166
|
+
"[bold cyan]Provider key setup[/bold cyan]\n"
|
|
167
|
+
"Add at least one API key. Gemini and Groq are good free starting points; "
|
|
168
|
+
"OpenAI is paid.\n"
|
|
169
|
+
f"[dim]Keys are saved locally in {ENV_FILE}[/dim]",
|
|
170
|
+
border_style="cyan",
|
|
171
|
+
))
|
|
172
|
+
for provider, label, url in KEY_SETUP_PROVIDERS:
|
|
173
|
+
existing = get_api_key(provider)
|
|
174
|
+
if existing:
|
|
175
|
+
console.print(f"[green]✓[/green] {label} already set")
|
|
176
|
+
value = Prompt.ask(
|
|
177
|
+
f"[cyan]{label}[/cyan] new API key [dim](Enter to keep)[/dim]",
|
|
178
|
+
default="",
|
|
179
|
+
password=True,
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
console.print(f"[dim]{label} key: {url}[/dim]")
|
|
183
|
+
value = Prompt.ask(
|
|
184
|
+
f"[cyan]{label}[/cyan] API key [dim](Enter to skip)[/dim]",
|
|
185
|
+
default="",
|
|
186
|
+
password=True,
|
|
187
|
+
)
|
|
188
|
+
if value.strip():
|
|
189
|
+
save_api_key(provider, value.strip())
|
|
190
|
+
console.print("[green]✓ Saved[/green]")
|
|
191
|
+
|
|
192
|
+
config = load_config()
|
|
193
|
+
models = get_models(config)
|
|
194
|
+
available_free = [
|
|
195
|
+
name for name, data in models.items()
|
|
196
|
+
if data["free"] and get_api_key(data["provider"])
|
|
197
|
+
]
|
|
198
|
+
if available_free:
|
|
199
|
+
current = config.get("default_model")
|
|
200
|
+
default = current if current in models else available_free[0]
|
|
201
|
+
selected = Prompt.ask(
|
|
202
|
+
"Default model",
|
|
203
|
+
choices=list(models.keys()),
|
|
204
|
+
default=default,
|
|
205
|
+
)
|
|
206
|
+
config["default_model"] = selected
|
|
207
|
+
save_config(config)
|
|
208
|
+
|
|
209
|
+
if has_available_provider():
|
|
210
|
+
console.print("[green]zai is ready.[/green]")
|
|
211
|
+
return True
|
|
212
|
+
console.print("[yellow]No provider key was saved. Run /setup when ready.[/yellow]")
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
|
|
154
216
|
def _normalize_plain_command(message: str) -> str:
|
|
155
217
|
stripped = message.strip()
|
|
156
218
|
if stripped == "/":
|
|
@@ -243,10 +305,13 @@ def run_interactive(model: str = None) -> None:
|
|
|
243
305
|
if not has_available_provider():
|
|
244
306
|
console.print(Panel(
|
|
245
307
|
"[red]No AI provider available![/red]\n"
|
|
246
|
-
"
|
|
308
|
+
"Set up an API key now, run [cyan]zai setup[/cyan], or start Ollama.",
|
|
247
309
|
border_style="red",
|
|
248
310
|
))
|
|
249
|
-
|
|
311
|
+
if not Confirm.ask("Set up an API key now?", default=True):
|
|
312
|
+
return
|
|
313
|
+
if not _run_key_setup_interactive():
|
|
314
|
+
return
|
|
250
315
|
|
|
251
316
|
try:
|
|
252
317
|
visible = [name for name in sorted(os.listdir(cwd)) if not name.startswith(".")]
|
|
@@ -328,15 +393,23 @@ def run_interactive(model: str = None) -> None:
|
|
|
328
393
|
session_context.compress(preferred)
|
|
329
394
|
save_auto_history(history, cwd)
|
|
330
395
|
|
|
396
|
+
cancelled_once = False
|
|
397
|
+
|
|
331
398
|
while True:
|
|
332
399
|
try:
|
|
333
400
|
try:
|
|
334
401
|
message = terminal_input.prompt()
|
|
335
402
|
except KeyboardInterrupt:
|
|
336
|
-
|
|
403
|
+
if cancelled_once:
|
|
404
|
+
fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
|
|
405
|
+
console.print("\n[dim]Goodbye![/dim]")
|
|
406
|
+
break
|
|
407
|
+
cancelled_once = True
|
|
408
|
+
console.print("[dim]Input cancelled. Press Ctrl+C again to exit.[/dim]")
|
|
337
409
|
continue
|
|
338
410
|
if not message.strip():
|
|
339
411
|
continue
|
|
412
|
+
cancelled_once = False
|
|
340
413
|
message = _correct_slash_command(message)
|
|
341
414
|
message = _normalize_plain_command(message)
|
|
342
415
|
stripped = message.strip()
|
|
@@ -346,7 +419,7 @@ def run_interactive(model: str = None) -> None:
|
|
|
346
419
|
elif stripped == "/commands":
|
|
347
420
|
_show_commands()
|
|
348
421
|
elif stripped == "/setup":
|
|
349
|
-
|
|
422
|
+
_run_key_setup_interactive()
|
|
350
423
|
elif stripped == "/clear":
|
|
351
424
|
console.clear()
|
|
352
425
|
elif stripped == "/files":
|
|
@@ -584,7 +657,15 @@ def run_interactive(model: str = None) -> None:
|
|
|
584
657
|
remember_turn(stripped, result)
|
|
585
658
|
save_session(task=stripped[:80], model=preferred)
|
|
586
659
|
except KeyboardInterrupt:
|
|
587
|
-
|
|
660
|
+
if cancelled_once:
|
|
661
|
+
fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
|
|
662
|
+
console.print("\n[dim]Goodbye![/dim]")
|
|
663
|
+
break
|
|
664
|
+
cancelled_once = True
|
|
665
|
+
console.print(
|
|
666
|
+
"\n[yellow]Current operation cancelled. "
|
|
667
|
+
"Press Ctrl+C again to exit.[/yellow]"
|
|
668
|
+
)
|
|
588
669
|
continue
|
|
589
670
|
except EOFError:
|
|
590
671
|
fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
|
zai_cli-0.1.2/zai/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|