zai-cli 0.1.0__tar.gz → 0.1.2__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.0 → zai_cli-0.1.2}/CHANGELOG.md +27 -0
- {zai_cli-0.1.0/zai_cli.egg-info → zai_cli-0.1.2}/PKG-INFO +15 -1
- {zai_cli-0.1.0 → zai_cli-0.1.2}/README.md +14 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_config_main.py +13 -1
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_input.py +1 -1
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_interactive.py +34 -1
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_settings_cli.py +2 -0
- zai_cli-0.1.2/zai/__init__.py +1 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/interactive.py +97 -23
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/settings.py +11 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/main.py +48 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/ui/input.py +1 -1
- {zai_cli-0.1.0 → zai_cli-0.1.2/zai_cli.egg-info}/PKG-INFO +15 -1
- zai_cli-0.1.0/zai/__init__.py +0 -1
- {zai_cli-0.1.0 → zai_cli-0.1.2}/LICENSE +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/MANIFEST.in +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/pyproject.toml +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/scripts/release_preflight.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/setup.cfg +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_agent.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_browser.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_code_runner.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_core.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_errors.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_hooks_skills_session.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_integrations_cli.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_mcp.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_plugins.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_process.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_providers.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_release_preflight.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_search.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_security.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_storage.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_streaming.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_tool_schema.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_tools.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_undo.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_utilities.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_vision.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_watch.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_workflows.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/__main__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/common.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/integrations.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/utilities.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/workflows.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/commit.md +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/explain.md +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/feature.md +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/fix.md +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/review.md +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/config.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/agent.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/cancellation.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/commands.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/context.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/errors.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/fallback.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/hooks.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/memory.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/process.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/repomap.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/runtime.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/security.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/session.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/storage.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/streaming.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/tool_schema.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/undo.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/watch.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/mcp/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/mcp/client.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/mcp/manager.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/plugins/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/plugins/base.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/plugins/loader.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/anthropic.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/base.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/cerebras.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/gemini.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/groq.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/ollama.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/openai.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/openrouter.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/qwen.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/skills/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/skills/registry.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/browser.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/code_runner.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/files.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/git.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/search.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/vision.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/ui/__init__.py +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/SOURCES.txt +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/dependency_links.txt +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/entry_points.txt +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/requires.txt +0 -0
- {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,33 @@ 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.2] - 2026-06-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Improved interactive slash-command discovery: typing `/` now shows the
|
|
13
|
+
commands menu.
|
|
14
|
+
- Improved interactive model discovery: bare `/model` now shows the model list.
|
|
15
|
+
- Clarified the interactive startup hint to point users at `/` and `/help`.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Prevented common help words such as `commands`, `setup`, and `model list`
|
|
20
|
+
from being sent to the AI as ordinary project requests.
|
|
21
|
+
|
|
22
|
+
## [0.1.1] - 2026-06-24
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Added `zai commands` so new users can discover interactive slash commands
|
|
27
|
+
before entering the interactive shell.
|
|
28
|
+
- Added `zai setup` onboarding text with recommended free providers, API-key
|
|
29
|
+
links, and the local key storage path.
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- Expanded quick-start guidance for slash commands and provider key setup.
|
|
34
|
+
|
|
8
35
|
## [0.1.0] - 2026-06-23
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zai-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Your personal AI CLI — free, fast, and smart
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/HumaizaNaz/zai_cli
|
|
@@ -103,6 +103,9 @@ quota or download/start external tools.
|
|
|
103
103
|
# Configure providers
|
|
104
104
|
zai setup
|
|
105
105
|
|
|
106
|
+
# See which slash commands are available before entering interactive mode
|
|
107
|
+
zai commands
|
|
108
|
+
|
|
106
109
|
# Start the interactive coding assistant in the current directory
|
|
107
110
|
zai
|
|
108
111
|
|
|
@@ -119,10 +122,21 @@ zai run script.py
|
|
|
119
122
|
zai git status
|
|
120
123
|
```
|
|
121
124
|
|
|
125
|
+
During `zai setup`, add at least one API key. Gemini and Groq are good free
|
|
126
|
+
starting points; OpenAI is paid. Keys are saved locally in `~/.zai/.env`.
|
|
127
|
+
|
|
128
|
+
- Gemini key: <https://aistudio.google.com/app/apikey>
|
|
129
|
+
- Groq key: <https://console.groq.com/keys>
|
|
130
|
+
- OpenAI key: <https://platform.openai.com/api-keys>
|
|
131
|
+
|
|
132
|
+
Inside interactive mode, type `/` and press Tab to see slash commands, or run
|
|
133
|
+
`/help` and `/commands`.
|
|
134
|
+
|
|
122
135
|
## Main commands
|
|
123
136
|
|
|
124
137
|
```text
|
|
125
138
|
zai Start the interactive project assistant
|
|
139
|
+
zai commands List interactive slash commands
|
|
126
140
|
zai ask <message> Send a one-off message
|
|
127
141
|
zai chat [message] Chat with the configured model
|
|
128
142
|
zai --plain ask <message> Emit only plain final response text
|
|
@@ -46,6 +46,9 @@ quota or download/start external tools.
|
|
|
46
46
|
# Configure providers
|
|
47
47
|
zai setup
|
|
48
48
|
|
|
49
|
+
# See which slash commands are available before entering interactive mode
|
|
50
|
+
zai commands
|
|
51
|
+
|
|
49
52
|
# Start the interactive coding assistant in the current directory
|
|
50
53
|
zai
|
|
51
54
|
|
|
@@ -62,10 +65,21 @@ zai run script.py
|
|
|
62
65
|
zai git status
|
|
63
66
|
```
|
|
64
67
|
|
|
68
|
+
During `zai setup`, add at least one API key. Gemini and Groq are good free
|
|
69
|
+
starting points; OpenAI is paid. Keys are saved locally in `~/.zai/.env`.
|
|
70
|
+
|
|
71
|
+
- Gemini key: <https://aistudio.google.com/app/apikey>
|
|
72
|
+
- Groq key: <https://console.groq.com/keys>
|
|
73
|
+
- OpenAI key: <https://platform.openai.com/api-keys>
|
|
74
|
+
|
|
75
|
+
Inside interactive mode, type `/` and press Tab to see slash commands, or run
|
|
76
|
+
`/help` and `/commands`.
|
|
77
|
+
|
|
65
78
|
## Main commands
|
|
66
79
|
|
|
67
80
|
```text
|
|
68
81
|
zai Start the interactive project assistant
|
|
82
|
+
zai commands List interactive slash commands
|
|
69
83
|
zai ask <message> Send a one-off message
|
|
70
84
|
zai chat [message] Chat with the configured model
|
|
71
85
|
zai --plain ask <message> Emit only plain final response text
|
|
@@ -262,12 +262,24 @@ def test_zai_help_shows_all_commands():
|
|
|
262
262
|
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
263
263
|
)
|
|
264
264
|
output = re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", result.stdout)
|
|
265
|
-
for cmd in ["chat", "ask", "search", "file", "git", "repo", "vision", "browser", "skill", "hook", "mcp", "session"]:
|
|
265
|
+
for cmd in ["chat", "ask", "commands", "search", "file", "git", "repo", "vision", "browser", "skill", "hook", "mcp", "session"]:
|
|
266
266
|
assert cmd in output, f"Command missing from --help: {cmd}"
|
|
267
267
|
assert "--debug" in output
|
|
268
268
|
assert "--plain" in output
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
def test_top_level_commands_lists_slash_commands():
|
|
272
|
+
from typer.testing import CliRunner
|
|
273
|
+
from zai.main import app
|
|
274
|
+
|
|
275
|
+
result = CliRunner().invoke(app, ["commands"])
|
|
276
|
+
|
|
277
|
+
assert result.exit_code == 0
|
|
278
|
+
assert "Slash commands" in result.output
|
|
279
|
+
assert "/review" in result.output
|
|
280
|
+
assert "/commands" in result.output
|
|
281
|
+
|
|
282
|
+
|
|
271
283
|
def test_top_level_session_list(tmp_path, monkeypatch):
|
|
272
284
|
from typer.testing import CliRunner
|
|
273
285
|
import zai.core.session as session_mod
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
from unittest.mock import patch
|
|
2
2
|
|
|
3
|
-
from zai.cli.interactive import
|
|
3
|
+
from zai.cli.interactive import (
|
|
4
|
+
_correct_slash_command,
|
|
5
|
+
_normalize_plain_command,
|
|
6
|
+
run_interactive,
|
|
7
|
+
)
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
def test_slash_command_typo_is_corrected():
|
|
@@ -14,6 +18,35 @@ def test_slash_command_typo_is_corrected():
|
|
|
14
18
|
assert corrected == "/resume"
|
|
15
19
|
|
|
16
20
|
|
|
21
|
+
def test_plain_command_aliases_do_not_go_to_ai():
|
|
22
|
+
assert _normalize_plain_command("/") == "/commands"
|
|
23
|
+
assert _normalize_plain_command("commands") == "/commands"
|
|
24
|
+
assert _normalize_plain_command("setup") == "/setup"
|
|
25
|
+
assert _normalize_plain_command("model list") == "/model list"
|
|
26
|
+
assert _normalize_plain_command("build a login page") == "build a login page"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_interactive_plain_commands_show_local_help(tmp_path, monkeypatch, capsys):
|
|
30
|
+
monkeypatch.chdir(tmp_path)
|
|
31
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
32
|
+
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
33
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
34
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
35
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
36
|
+
with patch(
|
|
37
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
38
|
+
side_effect=["/", "commands", "setup", "/model", EOFError()],
|
|
39
|
+
):
|
|
40
|
+
with patch("zai.cli.interactive.run_agent") as run_agent:
|
|
41
|
+
run_interactive("groq")
|
|
42
|
+
|
|
43
|
+
output = capsys.readouterr().out
|
|
44
|
+
assert "Interactive slash commands" in output
|
|
45
|
+
assert "API key setup" in output
|
|
46
|
+
assert "Available models" in output
|
|
47
|
+
run_agent.assert_not_called()
|
|
48
|
+
|
|
49
|
+
|
|
17
50
|
def test_interactive_help_then_exit(tmp_path, monkeypatch, capsys):
|
|
18
51
|
monkeypatch.chdir(tmp_path)
|
|
19
52
|
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
@@ -25,6 +25,8 @@ def test_setup_keeps_existing_keys_and_selects_default():
|
|
|
25
25
|
result = CliRunner().invoke(app, ["setup"])
|
|
26
26
|
|
|
27
27
|
assert result.exit_code == 0
|
|
28
|
+
assert "New user quick start" in result.output
|
|
29
|
+
assert "aistudio.google.com" in result.output
|
|
28
30
|
assert "already set" in result.output
|
|
29
31
|
assert "zai is ready" in result.output
|
|
30
32
|
assert config["default_model"] == "groq"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
|
@@ -36,7 +36,41 @@ console = Console()
|
|
|
36
36
|
CORE_COMMANDS = {
|
|
37
37
|
"help", "clear", "files", "diff", "undo", "plan", "test",
|
|
38
38
|
"watch", "commit", "review", "fix", "explain", "feature",
|
|
39
|
-
"session", "resume", "model", "memory", "commands",
|
|
39
|
+
"session", "resume", "model", "memory", "commands", "setup",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
INTERACTIVE_COMMANDS = [
|
|
43
|
+
("/help", "Show interactive help"),
|
|
44
|
+
("/commands", "List built-in and plugin slash commands"),
|
|
45
|
+
("/setup", "Show API-key setup instructions"),
|
|
46
|
+
("/clear", "Clear the terminal"),
|
|
47
|
+
("/files", "List files in the current folder"),
|
|
48
|
+
("/diff", "Show git diff"),
|
|
49
|
+
("/undo", "Restore the last AI-changed file"),
|
|
50
|
+
("/plan <task>", "Plan first, approve, then execute"),
|
|
51
|
+
("/test", "Run pytest and auto-fix failures"),
|
|
52
|
+
("/watch", "Toggle file-change watcher"),
|
|
53
|
+
("/session ...", "Save, load, search, rename, or delete sessions"),
|
|
54
|
+
("/resume [name]", "Resume latest or named session"),
|
|
55
|
+
("/model list", "Show available models"),
|
|
56
|
+
("/model <name>", "Switch model"),
|
|
57
|
+
("/memory", "Show last session info"),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
PLAIN_COMMAND_ALIASES = {
|
|
61
|
+
"help": "/help",
|
|
62
|
+
"commands": "/commands",
|
|
63
|
+
"command": "/commands",
|
|
64
|
+
"cmd": "/commands",
|
|
65
|
+
"setup": "/setup",
|
|
66
|
+
"keys": "/setup",
|
|
67
|
+
"api keys": "/setup",
|
|
68
|
+
"model list": "/model list",
|
|
69
|
+
"models": "/model list",
|
|
70
|
+
"memory": "/memory",
|
|
71
|
+
"files": "/files",
|
|
72
|
+
"diff": "/diff",
|
|
73
|
+
"undo": "/undo",
|
|
40
74
|
}
|
|
41
75
|
|
|
42
76
|
|
|
@@ -76,6 +110,56 @@ def _show_help() -> None:
|
|
|
76
110
|
)
|
|
77
111
|
|
|
78
112
|
|
|
113
|
+
def _show_commands() -> None:
|
|
114
|
+
table = Table(title="Interactive slash commands", border_style="cyan")
|
|
115
|
+
table.add_column("Command", style="cyan")
|
|
116
|
+
table.add_column("Description")
|
|
117
|
+
for name, description in INTERACTIVE_COMMANDS:
|
|
118
|
+
table.add_row(name, description)
|
|
119
|
+
for command in list_commands():
|
|
120
|
+
table.add_row(f"/{command['name']}", command["description"])
|
|
121
|
+
for name, data in plugin_loader.get_all_commands().items():
|
|
122
|
+
table.add_row(f"/{name}", f"{data.get('description', '')} (plugin)")
|
|
123
|
+
console.print(table)
|
|
124
|
+
console.print("[dim]Tip: type / and press Tab to autocomplete commands.[/dim]")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _show_setup_hint() -> None:
|
|
128
|
+
console.print(Panel(
|
|
129
|
+
"[bold cyan]API key setup[/bold cyan]\n"
|
|
130
|
+
"Exit this chat with Ctrl+C, then run:\n\n"
|
|
131
|
+
"[bold]zai setup[/bold]\n\n"
|
|
132
|
+
"Good free starting points:\n"
|
|
133
|
+
"- Gemini: https://aistudio.google.com/app/apikey\n"
|
|
134
|
+
"- Groq: https://console.groq.com/keys\n"
|
|
135
|
+
"Paid option:\n"
|
|
136
|
+
"- OpenAI: https://platform.openai.com/api-keys\n\n"
|
|
137
|
+
"Keys are saved locally in ~/.zai/.env. After setup, run:\n"
|
|
138
|
+
"[bold]zai model list[/bold]",
|
|
139
|
+
border_style="cyan",
|
|
140
|
+
))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _show_models() -> None:
|
|
144
|
+
table = Table(title="Available models", border_style="cyan")
|
|
145
|
+
table.add_column("Name", style="cyan")
|
|
146
|
+
table.add_column("Model")
|
|
147
|
+
table.add_column("Key")
|
|
148
|
+
for key, data in get_models().items():
|
|
149
|
+
key_status = "ok" if get_api_key(data["provider"]) else "no key"
|
|
150
|
+
table.add_row(key, data["name"], key_status)
|
|
151
|
+
console.print(table)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _normalize_plain_command(message: str) -> str:
|
|
155
|
+
stripped = message.strip()
|
|
156
|
+
if stripped == "/":
|
|
157
|
+
return "/commands"
|
|
158
|
+
if stripped.startswith("/") or not stripped:
|
|
159
|
+
return message
|
|
160
|
+
return PLAIN_COMMAND_ALIASES.get(stripped.lower(), message)
|
|
161
|
+
|
|
162
|
+
|
|
79
163
|
def _correct_slash_command(message: str) -> str:
|
|
80
164
|
stripped = message.strip()
|
|
81
165
|
if not stripped.startswith("/") or stripped.startswith("//"):
|
|
@@ -173,7 +257,7 @@ def run_interactive(model: str = None) -> None:
|
|
|
173
257
|
f"[dim]Folder: {cwd}[/dim]\n"
|
|
174
258
|
f"[dim]Files: {', '.join(visible[:12])}"
|
|
175
259
|
f"{'...' if len(visible) > 12 else ''}[/dim]\n"
|
|
176
|
-
"[dim]Ctrl+C to exit | /help for
|
|
260
|
+
"[dim]Ctrl+C to exit | type / for commands | /help for help[/dim]",
|
|
177
261
|
border_style="cyan",
|
|
178
262
|
))
|
|
179
263
|
|
|
@@ -254,26 +338,23 @@ def run_interactive(model: str = None) -> None:
|
|
|
254
338
|
if not message.strip():
|
|
255
339
|
continue
|
|
256
340
|
message = _correct_slash_command(message)
|
|
341
|
+
message = _normalize_plain_command(message)
|
|
257
342
|
stripped = message.strip()
|
|
258
343
|
|
|
259
344
|
if stripped == "/help":
|
|
260
345
|
_show_help()
|
|
346
|
+
elif stripped == "/commands":
|
|
347
|
+
_show_commands()
|
|
348
|
+
elif stripped == "/setup":
|
|
349
|
+
_show_setup_hint()
|
|
261
350
|
elif stripped == "/clear":
|
|
262
351
|
console.clear()
|
|
263
352
|
elif stripped == "/files":
|
|
264
353
|
_show_files(cwd)
|
|
265
|
-
elif stripped.startswith("/model "):
|
|
266
|
-
name = stripped.split(" ", 1)[1].strip()
|
|
354
|
+
elif stripped == "/model" or stripped.startswith("/model "):
|
|
355
|
+
name = stripped.split(" ", 1)[1].strip() if " " in stripped else "list"
|
|
267
356
|
if name == "list":
|
|
268
|
-
|
|
269
|
-
key_status = (
|
|
270
|
-
"[green]ok[/green]"
|
|
271
|
-
if get_api_key(data["provider"])
|
|
272
|
-
else "[red]no key[/red]"
|
|
273
|
-
)
|
|
274
|
-
console.print(
|
|
275
|
-
f" [cyan]{key}[/cyan] — {data['name']} ({key_status})"
|
|
276
|
-
)
|
|
357
|
+
_show_models()
|
|
277
358
|
elif name in get_models():
|
|
278
359
|
preferred = name
|
|
279
360
|
session_context.set_model(name)
|
|
@@ -490,16 +571,9 @@ def run_interactive(model: str = None) -> None:
|
|
|
490
571
|
)
|
|
491
572
|
save_session(task=f"/{command_name}", model=preferred)
|
|
492
573
|
elif command_name == "commands":
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
f"{command['description']}"
|
|
497
|
-
)
|
|
498
|
-
for name, data in plugin_loader.get_all_commands().items():
|
|
499
|
-
console.print(
|
|
500
|
-
f" [blue]/{name}[/blue] — "
|
|
501
|
-
f"{data.get('description', '')} [dim](plugin)[/dim]"
|
|
502
|
-
)
|
|
574
|
+
_show_commands()
|
|
575
|
+
elif command_name == "setup":
|
|
576
|
+
_show_setup_hint()
|
|
503
577
|
else:
|
|
504
578
|
console.print(f"[red]Unknown command: /{command_name}[/red]")
|
|
505
579
|
console.print("[dim]Type /commands to see available commands[/dim]")
|
|
@@ -8,6 +8,7 @@ from rich.prompt import Confirm, Prompt
|
|
|
8
8
|
from rich.table import Table
|
|
9
9
|
|
|
10
10
|
from ..config import (
|
|
11
|
+
ENV_FILE,
|
|
11
12
|
MEMORY_FILE,
|
|
12
13
|
MODELS,
|
|
13
14
|
get_api_key,
|
|
@@ -41,6 +42,16 @@ console = Console()
|
|
|
41
42
|
def setup():
|
|
42
43
|
"""Setup API keys and preferences."""
|
|
43
44
|
console.print(Panel("[bold cyan]Welcome to zai setup![/bold cyan]", border_style="cyan"))
|
|
45
|
+
console.print(
|
|
46
|
+
"[cyan]New user quick start:[/cyan] add at least one API key. "
|
|
47
|
+
"Gemini or Groq are good free starting points; OpenAI is paid."
|
|
48
|
+
)
|
|
49
|
+
console.print(f"[dim]Keys are saved locally in {ENV_FILE}[/dim]")
|
|
50
|
+
console.print(
|
|
51
|
+
"[dim]Get keys: Gemini https://aistudio.google.com/app/apikey | "
|
|
52
|
+
"Groq https://console.groq.com/keys | "
|
|
53
|
+
"OpenAI https://platform.openai.com/api-keys[/dim]"
|
|
54
|
+
)
|
|
44
55
|
providers = [
|
|
45
56
|
("gemini", "Google Gemini (FREE — Recommended)"),
|
|
46
57
|
("groq", "Groq (FREE — Fast)"),
|
|
@@ -4,8 +4,10 @@ from typer.core import TyperGroup
|
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
from rich.panel import Panel
|
|
6
6
|
from rich.prompt import Prompt, Confirm
|
|
7
|
+
from rich.table import Table
|
|
7
8
|
|
|
8
9
|
from .config import load_config
|
|
10
|
+
from .core.commands import list_commands as list_slash_commands
|
|
9
11
|
from .core.fallback import (
|
|
10
12
|
format_model_selection,
|
|
11
13
|
has_available_provider,
|
|
@@ -22,6 +24,7 @@ from .cli.utilities import register_utility_commands
|
|
|
22
24
|
from .cli.workflows import register_workflow_commands
|
|
23
25
|
from .cli.common import closest_command as _closest_command
|
|
24
26
|
from .cli.interactive import run_interactive
|
|
27
|
+
from .plugins import loader as plugin_loader
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
class FuzzyTyperGroup(TyperGroup):
|
|
@@ -52,6 +55,23 @@ if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
|
|
|
52
55
|
console = Console()
|
|
53
56
|
|
|
54
57
|
context = ContextManager()
|
|
58
|
+
INTERACTIVE_COMMANDS = [
|
|
59
|
+
("/help", "Show interactive help"),
|
|
60
|
+
("/commands", "List built-in and plugin slash commands"),
|
|
61
|
+
("/setup", "Show API-key setup instructions"),
|
|
62
|
+
("/clear", "Clear the terminal"),
|
|
63
|
+
("/files", "List files in the current folder"),
|
|
64
|
+
("/diff", "Show git diff"),
|
|
65
|
+
("/undo", "Restore the last AI-changed file"),
|
|
66
|
+
("/plan <task>", "Plan first, approve, then execute"),
|
|
67
|
+
("/test", "Run pytest and auto-fix failures"),
|
|
68
|
+
("/watch", "Toggle file-change watcher"),
|
|
69
|
+
("/session ...", "Save, load, search, rename, or delete sessions"),
|
|
70
|
+
("/resume [name]", "Resume latest or named session"),
|
|
71
|
+
("/model list", "Show available models"),
|
|
72
|
+
("/model <name>", "Switch model"),
|
|
73
|
+
("/memory", "Show last session info"),
|
|
74
|
+
]
|
|
55
75
|
SYSTEM_PROMPT = (
|
|
56
76
|
"You are zai, a smart AI assistant running in the terminal. Be concise, helpful, and direct. "
|
|
57
77
|
"IMPORTANT: When creating files, ALWAYS use this exact format for each file:\n"
|
|
@@ -184,6 +204,34 @@ def ask(
|
|
|
184
204
|
_chat(message, model)
|
|
185
205
|
|
|
186
206
|
|
|
207
|
+
@app.command("commands")
|
|
208
|
+
def commands():
|
|
209
|
+
"""List interactive slash commands and where to use them."""
|
|
210
|
+
console.print(
|
|
211
|
+
"[cyan]Slash commands are available inside interactive mode.[/cyan]\n"
|
|
212
|
+
"Run [bold]zai[/bold], then type [bold]/[/bold] and press Tab, or use "
|
|
213
|
+
"[bold]/help[/bold] and [bold]/commands[/bold]."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
table = Table(title="Interactive slash commands")
|
|
217
|
+
table.add_column("Command")
|
|
218
|
+
table.add_column("Description")
|
|
219
|
+
for name, description in INTERACTIVE_COMMANDS:
|
|
220
|
+
table.add_row(name, description)
|
|
221
|
+
for command in list_slash_commands():
|
|
222
|
+
table.add_row(f"/{command['name']}", command.get("description", ""))
|
|
223
|
+
console.print(table)
|
|
224
|
+
|
|
225
|
+
plugin_commands = plugin_loader.get_all_commands()
|
|
226
|
+
if plugin_commands:
|
|
227
|
+
plugin_table = Table(title="Plugin slash commands")
|
|
228
|
+
plugin_table.add_column("Command")
|
|
229
|
+
plugin_table.add_column("Description")
|
|
230
|
+
for name, data in sorted(plugin_commands.items()):
|
|
231
|
+
plugin_table.add_row(f"/{name}", data.get("description", ""))
|
|
232
|
+
console.print(plugin_table)
|
|
233
|
+
|
|
234
|
+
|
|
187
235
|
@app.callback(invoke_without_command=True)
|
|
188
236
|
def main(
|
|
189
237
|
ctx: typer.Context,
|
|
@@ -59,7 +59,7 @@ def completion_candidates(
|
|
|
59
59
|
command_token = parts[0]
|
|
60
60
|
if len(parts) == 1 and not before.endswith(" "):
|
|
61
61
|
return [
|
|
62
|
-
(f"/{name}", f"/{name}", "command")
|
|
62
|
+
(f"/{name}", f"/{name}", "slash command")
|
|
63
63
|
for name in sorted(set(commands))
|
|
64
64
|
if f"/{name}".startswith(command_token.lower())
|
|
65
65
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zai-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Your personal AI CLI — free, fast, and smart
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/HumaizaNaz/zai_cli
|
|
@@ -103,6 +103,9 @@ quota or download/start external tools.
|
|
|
103
103
|
# Configure providers
|
|
104
104
|
zai setup
|
|
105
105
|
|
|
106
|
+
# See which slash commands are available before entering interactive mode
|
|
107
|
+
zai commands
|
|
108
|
+
|
|
106
109
|
# Start the interactive coding assistant in the current directory
|
|
107
110
|
zai
|
|
108
111
|
|
|
@@ -119,10 +122,21 @@ zai run script.py
|
|
|
119
122
|
zai git status
|
|
120
123
|
```
|
|
121
124
|
|
|
125
|
+
During `zai setup`, add at least one API key. Gemini and Groq are good free
|
|
126
|
+
starting points; OpenAI is paid. Keys are saved locally in `~/.zai/.env`.
|
|
127
|
+
|
|
128
|
+
- Gemini key: <https://aistudio.google.com/app/apikey>
|
|
129
|
+
- Groq key: <https://console.groq.com/keys>
|
|
130
|
+
- OpenAI key: <https://platform.openai.com/api-keys>
|
|
131
|
+
|
|
132
|
+
Inside interactive mode, type `/` and press Tab to see slash commands, or run
|
|
133
|
+
`/help` and `/commands`.
|
|
134
|
+
|
|
122
135
|
## Main commands
|
|
123
136
|
|
|
124
137
|
```text
|
|
125
138
|
zai Start the interactive project assistant
|
|
139
|
+
zai commands List interactive slash commands
|
|
126
140
|
zai ask <message> Send a one-off message
|
|
127
141
|
zai chat [message] Chat with the configured model
|
|
128
142
|
zai --plain ask <message> Emit only plain final response text
|
zai_cli-0.1.0/zai/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
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
|