zai-cli 0.1.2__tar.gz → 0.1.6__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.6}/CHANGELOG.md +63 -0
- {zai_cli-0.1.2/zai_cli.egg-info → zai_cli-0.1.6}/PKG-INFO +185 -2
- {zai_cli-0.1.2 → zai_cli-0.1.6}/README.md +184 -1
- zai_cli-0.1.6/tests/test_auth.py +157 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_config_main.py +1 -1
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_input.py +27 -0
- zai_cli-0.1.6/tests/test_interactive.py +261 -0
- zai_cli-0.1.6/tests/test_project_memory.py +148 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_settings_cli.py +32 -32
- zai_cli-0.1.6/zai/__init__.py +1 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/interactive.py +308 -10
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/settings.py +177 -2
- zai_cli-0.1.6/zai/cli/setup_wizard.py +315 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/config.py +1 -0
- zai_cli-0.1.6/zai/core/auth.py +254 -0
- zai_cli-0.1.6/zai/core/project_memory.py +350 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/main.py +2 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/ui/input.py +33 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6/zai_cli.egg-info}/PKG-INFO +185 -2
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai_cli.egg-info/SOURCES.txt +5 -0
- zai_cli-0.1.2/tests/test_interactive.py +0 -107
- zai_cli-0.1.2/zai/__init__.py +0 -1
- {zai_cli-0.1.2 → zai_cli-0.1.6}/LICENSE +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/MANIFEST.in +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/pyproject.toml +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/scripts/release_preflight.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/setup.cfg +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_agent.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_browser.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_code_runner.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_core.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_errors.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_hooks_skills_session.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_integrations_cli.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_mcp.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_plugins.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_process.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_providers.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_release_preflight.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_search.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_security.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_storage.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_streaming.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_tool_schema.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_tools.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_undo.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_utilities.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_vision.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_watch.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/tests/test_workflows.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/__main__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/common.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/integrations.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/utilities.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/cli/workflows.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/commands/commit.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/commands/explain.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/commands/feature.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/commands/fix.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/commands/review.md +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/agent.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/cancellation.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/commands.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/context.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/errors.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/fallback.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/hooks.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/memory.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/process.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/repomap.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/runtime.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/security.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/session.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/storage.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/streaming.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/tool_schema.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/undo.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/core/watch.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/mcp/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/mcp/client.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/mcp/manager.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/plugins/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/plugins/base.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/plugins/loader.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/anthropic.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/base.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/cerebras.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/gemini.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/groq.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/ollama.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/openai.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/openrouter.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/providers/qwen.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/skills/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/skills/registry.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/browser.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/code_runner.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/files.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/git.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/search.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/tools/vision.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai/ui/__init__.py +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai_cli.egg-info/dependency_links.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai_cli.egg-info/entry_points.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai_cli.egg-info/requires.txt +0 -0
- {zai_cli-0.1.2 → zai_cli-0.1.6}/zai_cli.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,69 @@ 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.6] - 2026-06-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added `zai auth status`, `zai auth login <provider>`,
|
|
13
|
+
`zai auth logout <provider>`, and `zai auth doctor`.
|
|
14
|
+
- Added interactive `/auth status`, `/auth login <provider>`,
|
|
15
|
+
`/auth logout <provider>`, and `/auth doctor`.
|
|
16
|
+
- Added a provider authentication registry covering Gemini, Groq, OpenAI,
|
|
17
|
+
Anthropic, OpenRouter, Cerebras, Qwen, and Ollama.
|
|
18
|
+
- Added safe external Codex and Claude CLI detection/login prompts without
|
|
19
|
+
reading or copying private token contents.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Routed setup key saving through the shared auth helper.
|
|
24
|
+
- Documented API-key versus external account-login boundaries.
|
|
25
|
+
|
|
26
|
+
## [0.1.5] - 2026-06-26
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Added project-scoped memory stored in `.zai/project_memory.json`.
|
|
31
|
+
- Added `zai memory project`, `zai memory refresh`,
|
|
32
|
+
`zai memory remember <fact>`, and `zai memory forget <fact-or-id>`.
|
|
33
|
+
- Added interactive `/memory project`, `/memory refresh`, `/remember <fact>`,
|
|
34
|
+
and `/forget <fact-or-id>`.
|
|
35
|
+
- Added automatic project detection for common Python, Node/Next.js, Go, and
|
|
36
|
+
Rust project files.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Interactive mode now loads existing project memory into pinned context.
|
|
41
|
+
- Project memory stores summaries and explicit facts only, avoiding secrets,
|
|
42
|
+
`.env` contents, tokens, passwords, and full source files.
|
|
43
|
+
|
|
44
|
+
## [0.1.4] - 2026-06-26
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- Added a shared first-run setup wizard for both `zai setup` and interactive
|
|
49
|
+
`/setup`.
|
|
50
|
+
- Added `zai doctor` for install, key, model, and provider diagnostics.
|
|
51
|
+
- Added optional provider validation from setup with a user-approved tiny test
|
|
52
|
+
request.
|
|
53
|
+
- Added `/exit` and `/quit` in interactive mode.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- Reworked `/help` into a short beginner-focused guide.
|
|
58
|
+
- Reworked `/commands` into grouped setup, project, session, model, and
|
|
59
|
+
advanced command sections.
|
|
60
|
+
- Expanded first-time user README guidance and troubleshooting.
|
|
61
|
+
|
|
62
|
+
## [0.1.3] - 2026-06-24
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- Made Ctrl+C exit predictable in interactive mode: one Ctrl+C cancels the
|
|
67
|
+
current prompt/operation, and a second consecutive Ctrl+C exits the session.
|
|
68
|
+
- Added in-chat key setup: starting `zai` without a provider now offers API-key
|
|
69
|
+
setup immediately, and `/setup` can save keys without leaving interactive mode.
|
|
70
|
+
|
|
8
71
|
## [0.1.2] - 2026-06-24
|
|
9
72
|
|
|
10
73
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zai-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
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
|
|
@@ -74,12 +74,146 @@ plugins and MCP servers.
|
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
pip install zai-cli
|
|
77
|
-
zai setup
|
|
78
77
|
```
|
|
79
78
|
|
|
80
79
|
The PyPI distribution is named `zai-cli`; the installed command and Python
|
|
81
80
|
package remain `zai`.
|
|
82
81
|
|
|
82
|
+
## Start in 60 seconds
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python -m pip install --upgrade --no-cache-dir zai-cli
|
|
86
|
+
zai
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If no provider key is configured, `zai` will offer setup immediately. Follow the
|
|
90
|
+
prompt, choose a provider, paste your own API key, then start working in the
|
|
91
|
+
same terminal.
|
|
92
|
+
|
|
93
|
+
## First run
|
|
94
|
+
|
|
95
|
+
Run `zai` inside the folder you want to work on:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd path/to/project
|
|
99
|
+
zai
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Useful first commands inside interactive mode:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
/ Show commands
|
|
106
|
+
/setup Configure or update provider keys
|
|
107
|
+
/model Show available models
|
|
108
|
+
/review Review current changes
|
|
109
|
+
/exit Leave zai
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
You can also type normally, for example:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
explain this project
|
|
116
|
+
find bugs in the auth code
|
|
117
|
+
create a README for this folder
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Recommended free providers
|
|
121
|
+
|
|
122
|
+
Users must use their own API keys. Do not share your personal key.
|
|
123
|
+
|
|
124
|
+
- Gemini: free starting point for general use,
|
|
125
|
+
<https://aistudio.google.com/app/apikey>
|
|
126
|
+
- Groq: free and fast, <https://console.groq.com/keys>
|
|
127
|
+
- OpenAI: paid, <https://platform.openai.com/api-keys>
|
|
128
|
+
|
|
129
|
+
Keys are saved locally in `~/.zai/.env`. You can inspect current status with:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
zai doctor
|
|
133
|
+
zai model list
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Authentication
|
|
137
|
+
|
|
138
|
+
`zai` uses provider API keys for its own model calls. Account login for external
|
|
139
|
+
tools such as Codex or Claude Code is separate.
|
|
140
|
+
|
|
141
|
+
Check what is connected:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
zai auth status
|
|
145
|
+
zai auth doctor
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Connect a provider API key:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
zai auth login gemini
|
|
152
|
+
zai auth login groq
|
|
153
|
+
zai auth login openai
|
|
154
|
+
zai auth login anthropic
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Remove a saved API key from `zai`:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
zai auth logout groq
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Credential boundaries:
|
|
164
|
+
|
|
165
|
+
- `zai` stores provider API keys in `~/.zai/.env` by default.
|
|
166
|
+
- `zai` never prints raw keys.
|
|
167
|
+
- Do not paste API keys into normal chat prompts, issues, tickets, or logs.
|
|
168
|
+
- `zai` does not copy Codex or Claude private token files.
|
|
169
|
+
- If `codex` or `claude` is installed, `zai auth login openai` or
|
|
170
|
+
`zai auth login anthropic` can offer to run the official external CLI login,
|
|
171
|
+
after asking for confirmation.
|
|
172
|
+
- OpenAI/Anthropic API keys power `zai`; ChatGPT/Codex or Claude account login
|
|
173
|
+
powers those official external CLIs.
|
|
174
|
+
|
|
175
|
+
## Troubleshooting
|
|
176
|
+
|
|
177
|
+
If `zai` still shows an older version:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
python -m pip install --upgrade --force-reinstall --no-cache-dir zai-cli
|
|
181
|
+
zai --version
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
If the `zai` command is not found:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python -m zai --version
|
|
188
|
+
python -m pip show zai-cli
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If no provider is available:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
zai doctor
|
|
195
|
+
zai auth status
|
|
196
|
+
zai setup
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If a key is invalid or missing:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
zai auth login groq
|
|
203
|
+
zai model test groq
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If external Codex or Claude login fails, use their official CLI directly:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
codex login
|
|
210
|
+
codex login --device-auth
|
|
211
|
+
claude login
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If `codex` or `claude` is not found, install the official CLI first or use a
|
|
215
|
+
provider API key with `zai auth login <provider>`.
|
|
216
|
+
|
|
83
217
|
For local development:
|
|
84
218
|
|
|
85
219
|
```bash
|
|
@@ -132,10 +266,51 @@ starting points; OpenAI is paid. Keys are saved locally in `~/.zai/.env`.
|
|
|
132
266
|
Inside interactive mode, type `/` and press Tab to see slash commands, or run
|
|
133
267
|
`/help` and `/commands`.
|
|
134
268
|
|
|
269
|
+
## Project memory
|
|
270
|
+
|
|
271
|
+
`zai` can keep small project-specific memory in the folder you run it from:
|
|
272
|
+
|
|
273
|
+
```text
|
|
274
|
+
.zai/project_memory.json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
This is separate from conversation session history. It stores a compact summary
|
|
278
|
+
of project signals such as languages, frameworks, important files, common setup,
|
|
279
|
+
test, and run commands, plus facts you explicitly ask `zai` to remember. It does
|
|
280
|
+
not store API keys, `.env` contents, tokens, passwords, or full source files.
|
|
281
|
+
|
|
282
|
+
Create or refresh memory:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
zai memory refresh
|
|
286
|
+
zai memory project
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Inside interactive mode:
|
|
290
|
+
|
|
291
|
+
```text
|
|
292
|
+
/memory project Show project memory
|
|
293
|
+
/memory refresh Refresh detected project summary
|
|
294
|
+
/remember <fact> Save a project fact
|
|
295
|
+
/forget <fact-or-id> Delete a saved fact
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
When `zai` starts in a folder that already has `.zai/project_memory.json`, it
|
|
299
|
+
loads a short pinned summary so future answers can use that folder's context.
|
|
300
|
+
Different folders keep different memories.
|
|
301
|
+
|
|
302
|
+
Memory types:
|
|
303
|
+
|
|
304
|
+
- Session history: previous conversation messages saved globally under `~/.zai`.
|
|
305
|
+
- Project memory: small per-folder facts in `.zai/project_memory.json`.
|
|
306
|
+
- Repo map: generated structure summary used for repository questions; it is not
|
|
307
|
+
long-term memory.
|
|
308
|
+
|
|
135
309
|
## Main commands
|
|
136
310
|
|
|
137
311
|
```text
|
|
138
312
|
zai Start the interactive project assistant
|
|
313
|
+
zai doctor Check install, keys, and provider readiness
|
|
139
314
|
zai commands List interactive slash commands
|
|
140
315
|
zai ask <message> Send a one-off message
|
|
141
316
|
zai chat [message] Chat with the configured model
|
|
@@ -169,8 +344,16 @@ zai model remove <name> Remove a custom model alias
|
|
|
169
344
|
zai model configure <name> Set timeout and retry policy
|
|
170
345
|
zai model info <name> Show effective model configuration
|
|
171
346
|
zai model test <name> Verify credentials and model availability
|
|
347
|
+
zai auth status Show API-key and external CLI auth status
|
|
348
|
+
zai auth login <provider> Connect a provider API key
|
|
349
|
+
zai auth logout <provider> Remove a provider API key from zai
|
|
350
|
+
zai auth doctor Show auth troubleshooting guidance
|
|
172
351
|
zai memory show Show last task metadata
|
|
173
352
|
zai memory projects List remembered projects
|
|
353
|
+
zai memory project Show project-scoped memory
|
|
354
|
+
zai memory refresh Create or refresh project-scoped memory
|
|
355
|
+
zai memory remember <fact> Save a project fact
|
|
356
|
+
zai memory forget <id/text> Delete a project fact
|
|
174
357
|
zai memory clear Clear memory metadata
|
|
175
358
|
|
|
176
359
|
zai hook list
|
|
@@ -17,12 +17,146 @@ plugins and MCP servers.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
pip install zai-cli
|
|
20
|
-
zai setup
|
|
21
20
|
```
|
|
22
21
|
|
|
23
22
|
The PyPI distribution is named `zai-cli`; the installed command and Python
|
|
24
23
|
package remain `zai`.
|
|
25
24
|
|
|
25
|
+
## Start in 60 seconds
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m pip install --upgrade --no-cache-dir zai-cli
|
|
29
|
+
zai
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If no provider key is configured, `zai` will offer setup immediately. Follow the
|
|
33
|
+
prompt, choose a provider, paste your own API key, then start working in the
|
|
34
|
+
same terminal.
|
|
35
|
+
|
|
36
|
+
## First run
|
|
37
|
+
|
|
38
|
+
Run `zai` inside the folder you want to work on:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd path/to/project
|
|
42
|
+
zai
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Useful first commands inside interactive mode:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
/ Show commands
|
|
49
|
+
/setup Configure or update provider keys
|
|
50
|
+
/model Show available models
|
|
51
|
+
/review Review current changes
|
|
52
|
+
/exit Leave zai
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You can also type normally, for example:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
explain this project
|
|
59
|
+
find bugs in the auth code
|
|
60
|
+
create a README for this folder
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Recommended free providers
|
|
64
|
+
|
|
65
|
+
Users must use their own API keys. Do not share your personal key.
|
|
66
|
+
|
|
67
|
+
- Gemini: free starting point for general use,
|
|
68
|
+
<https://aistudio.google.com/app/apikey>
|
|
69
|
+
- Groq: free and fast, <https://console.groq.com/keys>
|
|
70
|
+
- OpenAI: paid, <https://platform.openai.com/api-keys>
|
|
71
|
+
|
|
72
|
+
Keys are saved locally in `~/.zai/.env`. You can inspect current status with:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
zai doctor
|
|
76
|
+
zai model list
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Authentication
|
|
80
|
+
|
|
81
|
+
`zai` uses provider API keys for its own model calls. Account login for external
|
|
82
|
+
tools such as Codex or Claude Code is separate.
|
|
83
|
+
|
|
84
|
+
Check what is connected:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
zai auth status
|
|
88
|
+
zai auth doctor
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Connect a provider API key:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
zai auth login gemini
|
|
95
|
+
zai auth login groq
|
|
96
|
+
zai auth login openai
|
|
97
|
+
zai auth login anthropic
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Remove a saved API key from `zai`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
zai auth logout groq
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Credential boundaries:
|
|
107
|
+
|
|
108
|
+
- `zai` stores provider API keys in `~/.zai/.env` by default.
|
|
109
|
+
- `zai` never prints raw keys.
|
|
110
|
+
- Do not paste API keys into normal chat prompts, issues, tickets, or logs.
|
|
111
|
+
- `zai` does not copy Codex or Claude private token files.
|
|
112
|
+
- If `codex` or `claude` is installed, `zai auth login openai` or
|
|
113
|
+
`zai auth login anthropic` can offer to run the official external CLI login,
|
|
114
|
+
after asking for confirmation.
|
|
115
|
+
- OpenAI/Anthropic API keys power `zai`; ChatGPT/Codex or Claude account login
|
|
116
|
+
powers those official external CLIs.
|
|
117
|
+
|
|
118
|
+
## Troubleshooting
|
|
119
|
+
|
|
120
|
+
If `zai` still shows an older version:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
python -m pip install --upgrade --force-reinstall --no-cache-dir zai-cli
|
|
124
|
+
zai --version
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
If the `zai` command is not found:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
python -m zai --version
|
|
131
|
+
python -m pip show zai-cli
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If no provider is available:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
zai doctor
|
|
138
|
+
zai auth status
|
|
139
|
+
zai setup
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If a key is invalid or missing:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
zai auth login groq
|
|
146
|
+
zai model test groq
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If external Codex or Claude login fails, use their official CLI directly:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
codex login
|
|
153
|
+
codex login --device-auth
|
|
154
|
+
claude login
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
If `codex` or `claude` is not found, install the official CLI first or use a
|
|
158
|
+
provider API key with `zai auth login <provider>`.
|
|
159
|
+
|
|
26
160
|
For local development:
|
|
27
161
|
|
|
28
162
|
```bash
|
|
@@ -75,10 +209,51 @@ starting points; OpenAI is paid. Keys are saved locally in `~/.zai/.env`.
|
|
|
75
209
|
Inside interactive mode, type `/` and press Tab to see slash commands, or run
|
|
76
210
|
`/help` and `/commands`.
|
|
77
211
|
|
|
212
|
+
## Project memory
|
|
213
|
+
|
|
214
|
+
`zai` can keep small project-specific memory in the folder you run it from:
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
.zai/project_memory.json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This is separate from conversation session history. It stores a compact summary
|
|
221
|
+
of project signals such as languages, frameworks, important files, common setup,
|
|
222
|
+
test, and run commands, plus facts you explicitly ask `zai` to remember. It does
|
|
223
|
+
not store API keys, `.env` contents, tokens, passwords, or full source files.
|
|
224
|
+
|
|
225
|
+
Create or refresh memory:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
zai memory refresh
|
|
229
|
+
zai memory project
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Inside interactive mode:
|
|
233
|
+
|
|
234
|
+
```text
|
|
235
|
+
/memory project Show project memory
|
|
236
|
+
/memory refresh Refresh detected project summary
|
|
237
|
+
/remember <fact> Save a project fact
|
|
238
|
+
/forget <fact-or-id> Delete a saved fact
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
When `zai` starts in a folder that already has `.zai/project_memory.json`, it
|
|
242
|
+
loads a short pinned summary so future answers can use that folder's context.
|
|
243
|
+
Different folders keep different memories.
|
|
244
|
+
|
|
245
|
+
Memory types:
|
|
246
|
+
|
|
247
|
+
- Session history: previous conversation messages saved globally under `~/.zai`.
|
|
248
|
+
- Project memory: small per-folder facts in `.zai/project_memory.json`.
|
|
249
|
+
- Repo map: generated structure summary used for repository questions; it is not
|
|
250
|
+
long-term memory.
|
|
251
|
+
|
|
78
252
|
## Main commands
|
|
79
253
|
|
|
80
254
|
```text
|
|
81
255
|
zai Start the interactive project assistant
|
|
256
|
+
zai doctor Check install, keys, and provider readiness
|
|
82
257
|
zai commands List interactive slash commands
|
|
83
258
|
zai ask <message> Send a one-off message
|
|
84
259
|
zai chat [message] Chat with the configured model
|
|
@@ -112,8 +287,16 @@ zai model remove <name> Remove a custom model alias
|
|
|
112
287
|
zai model configure <name> Set timeout and retry policy
|
|
113
288
|
zai model info <name> Show effective model configuration
|
|
114
289
|
zai model test <name> Verify credentials and model availability
|
|
290
|
+
zai auth status Show API-key and external CLI auth status
|
|
291
|
+
zai auth login <provider> Connect a provider API key
|
|
292
|
+
zai auth logout <provider> Remove a provider API key from zai
|
|
293
|
+
zai auth doctor Show auth troubleshooting guidance
|
|
115
294
|
zai memory show Show last task metadata
|
|
116
295
|
zai memory projects List remembered projects
|
|
296
|
+
zai memory project Show project-scoped memory
|
|
297
|
+
zai memory refresh Create or refresh project-scoped memory
|
|
298
|
+
zai memory remember <fact> Save a project fact
|
|
299
|
+
zai memory forget <id/text> Delete a project fact
|
|
117
300
|
zai memory clear Clear memory metadata
|
|
118
301
|
|
|
119
302
|
zai hook list
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import typer
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
from zai.cli.settings import register_settings_commands
|
|
9
|
+
from zai.core import auth as auth_mod
|
|
10
|
+
from zai.core.auth import (
|
|
11
|
+
auth_status,
|
|
12
|
+
delete_provider_key,
|
|
13
|
+
external_login_command,
|
|
14
|
+
mask_secret,
|
|
15
|
+
save_provider_key,
|
|
16
|
+
validate_provider,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def auth_app():
|
|
21
|
+
app = typer.Typer()
|
|
22
|
+
register_settings_commands(app)
|
|
23
|
+
return app
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_mask_secret_never_returns_full_value():
|
|
27
|
+
assert mask_secret("sk-123456789") == "sk-…6789"
|
|
28
|
+
assert mask_secret("short") == "••••"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_save_and_delete_provider_key(tmp_path, monkeypatch):
|
|
32
|
+
import zai.config as config_mod
|
|
33
|
+
|
|
34
|
+
monkeypatch.setattr(config_mod, "ENV_FILE", tmp_path / ".env")
|
|
35
|
+
monkeypatch.setattr(auth_mod, "ENV_FILE", tmp_path / ".env")
|
|
36
|
+
monkeypatch.delenv("GROQ_API_KEY", raising=False)
|
|
37
|
+
|
|
38
|
+
assert save_provider_key("groq", "groq-secret")
|
|
39
|
+
assert "GROQ_API_KEY=groq-secret" in (tmp_path / ".env").read_text()
|
|
40
|
+
|
|
41
|
+
assert delete_provider_key("groq")
|
|
42
|
+
assert "groq-secret" not in (tmp_path / ".env").read_text()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_auth_status_with_and_without_keys(monkeypatch):
|
|
46
|
+
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
|
|
47
|
+
missing = next(row for row in auth_status("gemini") if row["provider"] == "gemini")
|
|
48
|
+
monkeypatch.setenv("GEMINI_API_KEY", "gemini-secret")
|
|
49
|
+
present = next(row for row in auth_status("gemini") if row["provider"] == "gemini")
|
|
50
|
+
|
|
51
|
+
assert missing["key_status"] == "missing"
|
|
52
|
+
assert present["key_status"] == "set in environment"
|
|
53
|
+
assert "gemini-secret" not in str(present)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_external_login_command_for_codex_and_claude():
|
|
57
|
+
assert external_login_command("openai") == ["codex", "login"]
|
|
58
|
+
assert external_login_command("openai", device=True) == [
|
|
59
|
+
"codex",
|
|
60
|
+
"login",
|
|
61
|
+
"--device-auth",
|
|
62
|
+
]
|
|
63
|
+
assert external_login_command("anthropic") == ["claude", "login"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_validate_provider_normalizes_results():
|
|
67
|
+
provider = MagicMock()
|
|
68
|
+
provider.is_available.return_value = True
|
|
69
|
+
provider.chat.return_value = MagicMock()
|
|
70
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
71
|
+
assert validate_provider("groq") == "OK"
|
|
72
|
+
|
|
73
|
+
provider.chat.side_effect = RuntimeError("unauthorized api key")
|
|
74
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
75
|
+
assert validate_provider("groq") == "invalid"
|
|
76
|
+
|
|
77
|
+
provider.chat.side_effect = RuntimeError("rate limit")
|
|
78
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
79
|
+
assert validate_provider("groq") == "quota/rate limit"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_top_level_auth_status_does_not_print_secret(monkeypatch):
|
|
83
|
+
monkeypatch.setenv("OPENAI_API_KEY", "sk-live-secret")
|
|
84
|
+
|
|
85
|
+
result = CliRunner().invoke(auth_app(), ["auth", "status", "openai"])
|
|
86
|
+
|
|
87
|
+
assert result.exit_code == 0
|
|
88
|
+
assert "Authentication status" in result.output
|
|
89
|
+
assert "openai" in result.output
|
|
90
|
+
assert "sk-live-secret" not in result.output
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parametrize(
|
|
94
|
+
("provider", "label"),
|
|
95
|
+
[
|
|
96
|
+
("gemini", "Gemini"),
|
|
97
|
+
("groq", "Groq"),
|
|
98
|
+
("openai", "OpenAI"),
|
|
99
|
+
("anthropic", "Anthropic Claude"),
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
def test_top_level_auth_login_saves_api_key(provider, label):
|
|
103
|
+
app = auth_app()
|
|
104
|
+
with (
|
|
105
|
+
patch("zai.cli.settings.Prompt.ask", return_value=f"{provider}-key"),
|
|
106
|
+
patch("zai.cli.settings.Confirm.ask", return_value=False),
|
|
107
|
+
patch("zai.cli.settings.save_provider_key", return_value=True) as save,
|
|
108
|
+
):
|
|
109
|
+
result = CliRunner().invoke(app, ["auth", "login", provider])
|
|
110
|
+
|
|
111
|
+
assert result.exit_code == 0
|
|
112
|
+
save.assert_called_once_with(provider, f"{provider}-key")
|
|
113
|
+
assert f"Saved {label} API key" in result.output
|
|
114
|
+
assert f"{provider}-key" not in result.output
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_top_level_auth_external_cli_requires_confirmation():
|
|
118
|
+
app = auth_app()
|
|
119
|
+
completed = MagicMock(returncode=0)
|
|
120
|
+
with (
|
|
121
|
+
patch("zai.cli.settings.Prompt.ask", return_value=""),
|
|
122
|
+
patch("zai.cli.settings.Confirm.ask", return_value=True),
|
|
123
|
+
patch("zai.cli.settings.run_external_login", return_value=completed) as run,
|
|
124
|
+
):
|
|
125
|
+
result = CliRunner().invoke(app, ["auth", "login", "openai"])
|
|
126
|
+
|
|
127
|
+
assert result.exit_code == 0
|
|
128
|
+
run.assert_called_once_with("openai", device=False)
|
|
129
|
+
assert "External CLI login finished" in result.output
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_top_level_auth_logout_removes_key():
|
|
133
|
+
with patch("zai.cli.settings.delete_provider_key", return_value=True) as delete:
|
|
134
|
+
result = CliRunner().invoke(auth_app(), ["auth", "logout", "groq"])
|
|
135
|
+
|
|
136
|
+
assert result.exit_code == 0
|
|
137
|
+
delete.assert_called_once_with("groq")
|
|
138
|
+
assert "Removed Groq API key" in result.output
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.parametrize(
|
|
142
|
+
("provider_name", "command"),
|
|
143
|
+
[("openai", "codex"), ("anthropic", "claude")],
|
|
144
|
+
)
|
|
145
|
+
def test_external_cli_auth_cache_detected(provider_name, command, tmp_path, monkeypatch):
|
|
146
|
+
cache = tmp_path / "auth.json"
|
|
147
|
+
cache.write_text("{}", encoding="utf-8")
|
|
148
|
+
provider = auth_mod.PROVIDERS[provider_name]
|
|
149
|
+
replacement = auth_mod.AuthProvider(
|
|
150
|
+
**{**provider.__dict__, "cli_auth_paths": (cache,)}
|
|
151
|
+
)
|
|
152
|
+
monkeypatch.setitem(auth_mod.PROVIDERS, provider_name, replacement)
|
|
153
|
+
monkeypatch.setattr(auth_mod, "which", lambda value: str(Path(command)))
|
|
154
|
+
|
|
155
|
+
row = auth_status(provider_name)[0]
|
|
156
|
+
|
|
157
|
+
assert row["external_status"] == "external cli auth cache present"
|
|
@@ -262,7 +262,7 @@ 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", "commands", "search", "file", "git", "repo", "vision", "browser", "skill", "hook", "mcp", "session"]:
|
|
265
|
+
for cmd in ["chat", "ask", "commands", "doctor", "auth", "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
|
|
@@ -25,6 +25,33 @@ def test_model_completion():
|
|
|
25
25
|
assert ("groq", "groq", "model") in candidates
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def test_memory_completion():
|
|
29
|
+
candidates = completion_candidates(
|
|
30
|
+
"/memory r",
|
|
31
|
+
".",
|
|
32
|
+
{"memory"},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert candidates == [("refresh", "refresh", "memory action")]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_auth_completion():
|
|
39
|
+
actions = completion_candidates(
|
|
40
|
+
"/auth l",
|
|
41
|
+
".",
|
|
42
|
+
{"auth"},
|
|
43
|
+
)
|
|
44
|
+
providers = completion_candidates(
|
|
45
|
+
"/auth login g",
|
|
46
|
+
".",
|
|
47
|
+
{"auth"},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
assert ("login", "login", "auth action") in actions
|
|
51
|
+
assert ("gemini", "gemini", "auth provider") in providers
|
|
52
|
+
assert ("groq", "groq", "auth provider") in providers
|
|
53
|
+
|
|
54
|
+
|
|
28
55
|
def test_session_completion():
|
|
29
56
|
candidates = completion_candidates(
|
|
30
57
|
"/resume proj",
|