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.
Files changed (104) hide show
  1. {zai_cli-0.1.0 → zai_cli-0.1.2}/CHANGELOG.md +27 -0
  2. {zai_cli-0.1.0/zai_cli.egg-info → zai_cli-0.1.2}/PKG-INFO +15 -1
  3. {zai_cli-0.1.0 → zai_cli-0.1.2}/README.md +14 -0
  4. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_config_main.py +13 -1
  5. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_input.py +1 -1
  6. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_interactive.py +34 -1
  7. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_settings_cli.py +2 -0
  8. zai_cli-0.1.2/zai/__init__.py +1 -0
  9. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/interactive.py +97 -23
  10. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/settings.py +11 -0
  11. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/main.py +48 -0
  12. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/ui/input.py +1 -1
  13. {zai_cli-0.1.0 → zai_cli-0.1.2/zai_cli.egg-info}/PKG-INFO +15 -1
  14. zai_cli-0.1.0/zai/__init__.py +0 -1
  15. {zai_cli-0.1.0 → zai_cli-0.1.2}/LICENSE +0 -0
  16. {zai_cli-0.1.0 → zai_cli-0.1.2}/MANIFEST.in +0 -0
  17. {zai_cli-0.1.0 → zai_cli-0.1.2}/pyproject.toml +0 -0
  18. {zai_cli-0.1.0 → zai_cli-0.1.2}/scripts/release_preflight.py +0 -0
  19. {zai_cli-0.1.0 → zai_cli-0.1.2}/setup.cfg +0 -0
  20. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_agent.py +0 -0
  21. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_browser.py +0 -0
  22. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_code_runner.py +0 -0
  23. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_core.py +0 -0
  24. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_errors.py +0 -0
  25. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_hooks_skills_session.py +0 -0
  26. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_integrations_cli.py +0 -0
  27. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_mcp.py +0 -0
  28. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_plugins.py +0 -0
  29. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_process.py +0 -0
  30. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_providers.py +0 -0
  31. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_release_preflight.py +0 -0
  32. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_search.py +0 -0
  33. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_security.py +0 -0
  34. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_storage.py +0 -0
  35. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_streaming.py +0 -0
  36. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_tool_schema.py +0 -0
  37. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_tools.py +0 -0
  38. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_undo.py +0 -0
  39. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_utilities.py +0 -0
  40. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_vision.py +0 -0
  41. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_watch.py +0 -0
  42. {zai_cli-0.1.0 → zai_cli-0.1.2}/tests/test_workflows.py +0 -0
  43. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/__main__.py +0 -0
  44. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/__init__.py +0 -0
  45. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/common.py +0 -0
  46. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/integrations.py +0 -0
  47. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/utilities.py +0 -0
  48. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/cli/workflows.py +0 -0
  49. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/commit.md +0 -0
  50. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/explain.md +0 -0
  51. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/feature.md +0 -0
  52. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/fix.md +0 -0
  53. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/commands/review.md +0 -0
  54. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/config.py +0 -0
  55. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/__init__.py +0 -0
  56. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/agent.py +0 -0
  57. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/cancellation.py +0 -0
  58. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/commands.py +0 -0
  59. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/context.py +0 -0
  60. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/errors.py +0 -0
  61. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/fallback.py +0 -0
  62. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/hooks.py +0 -0
  63. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/memory.py +0 -0
  64. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/process.py +0 -0
  65. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/repomap.py +0 -0
  66. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/runtime.py +0 -0
  67. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/security.py +0 -0
  68. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/session.py +0 -0
  69. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/storage.py +0 -0
  70. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/streaming.py +0 -0
  71. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/tool_schema.py +0 -0
  72. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/undo.py +0 -0
  73. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/core/watch.py +0 -0
  74. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/mcp/__init__.py +0 -0
  75. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/mcp/client.py +0 -0
  76. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/mcp/manager.py +0 -0
  77. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/plugins/__init__.py +0 -0
  78. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/plugins/base.py +0 -0
  79. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/plugins/loader.py +0 -0
  80. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/__init__.py +0 -0
  81. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/anthropic.py +0 -0
  82. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/base.py +0 -0
  83. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/cerebras.py +0 -0
  84. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/gemini.py +0 -0
  85. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/groq.py +0 -0
  86. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/ollama.py +0 -0
  87. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/openai.py +0 -0
  88. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/openrouter.py +0 -0
  89. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/providers/qwen.py +0 -0
  90. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/skills/__init__.py +0 -0
  91. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/skills/registry.py +0 -0
  92. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/__init__.py +0 -0
  93. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/browser.py +0 -0
  94. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/code_runner.py +0 -0
  95. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/files.py +0 -0
  96. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/git.py +0 -0
  97. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/search.py +0 -0
  98. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/tools/vision.py +0 -0
  99. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai/ui/__init__.py +0 -0
  100. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/SOURCES.txt +0 -0
  101. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/dependency_links.txt +0 -0
  102. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/entry_points.txt +0 -0
  103. {zai_cli-0.1.0 → zai_cli-0.1.2}/zai_cli.egg-info/requires.txt +0 -0
  104. {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.0
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
@@ -11,7 +11,7 @@ def test_slash_command_completion():
11
11
  {"resume", "review", "help"},
12
12
  )
13
13
 
14
- assert candidates == [("/resume", "/resume", "command")]
14
+ assert candidates == [("/resume", "/resume", "slash command")]
15
15
 
16
16
 
17
17
  def test_model_completion():
@@ -1,6 +1,10 @@
1
1
  from unittest.mock import patch
2
2
 
3
- from zai.cli.interactive import _correct_slash_command, run_interactive
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 commands[/dim]",
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
- for key, data in get_models().items():
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
- for command in list_commands():
494
- console.print(
495
- f" [cyan]/{command['name']}[/cyan] — "
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.0
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
@@ -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