zai-cli 0.1.3__tar.gz → 0.1.7__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 (109) hide show
  1. {zai_cli-0.1.3 → zai_cli-0.1.7}/CHANGELOG.md +62 -0
  2. {zai_cli-0.1.3/zai_cli.egg-info → zai_cli-0.1.7}/PKG-INFO +185 -2
  3. {zai_cli-0.1.3 → zai_cli-0.1.7}/README.md +184 -1
  4. zai_cli-0.1.7/tests/test_auth.py +157 -0
  5. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_config_main.py +1 -1
  6. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_input.py +27 -0
  7. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_interactive.py +118 -24
  8. zai_cli-0.1.7/tests/test_project_memory.py +148 -0
  9. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_settings_cli.py +32 -32
  10. zai_cli-0.1.7/zai/__init__.py +1 -0
  11. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/interactive.py +308 -53
  12. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/settings.py +177 -2
  13. zai_cli-0.1.7/zai/cli/setup_wizard.py +315 -0
  14. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/config.py +1 -0
  15. zai_cli-0.1.7/zai/core/auth.py +254 -0
  16. zai_cli-0.1.7/zai/core/project_memory.py +350 -0
  17. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/main.py +2 -0
  18. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/ui/input.py +33 -0
  19. {zai_cli-0.1.3 → zai_cli-0.1.7/zai_cli.egg-info}/PKG-INFO +185 -2
  20. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai_cli.egg-info/SOURCES.txt +5 -0
  21. zai_cli-0.1.3/zai/__init__.py +0 -1
  22. {zai_cli-0.1.3 → zai_cli-0.1.7}/LICENSE +0 -0
  23. {zai_cli-0.1.3 → zai_cli-0.1.7}/MANIFEST.in +0 -0
  24. {zai_cli-0.1.3 → zai_cli-0.1.7}/pyproject.toml +0 -0
  25. {zai_cli-0.1.3 → zai_cli-0.1.7}/scripts/release_preflight.py +0 -0
  26. {zai_cli-0.1.3 → zai_cli-0.1.7}/setup.cfg +0 -0
  27. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_agent.py +0 -0
  28. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_browser.py +0 -0
  29. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_code_runner.py +0 -0
  30. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_core.py +0 -0
  31. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_errors.py +0 -0
  32. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_hooks_skills_session.py +0 -0
  33. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_integrations_cli.py +0 -0
  34. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_mcp.py +0 -0
  35. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_plugins.py +0 -0
  36. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_process.py +0 -0
  37. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_providers.py +0 -0
  38. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_release_preflight.py +0 -0
  39. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_search.py +0 -0
  40. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_security.py +0 -0
  41. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_storage.py +0 -0
  42. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_streaming.py +0 -0
  43. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_tool_schema.py +0 -0
  44. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_tools.py +0 -0
  45. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_undo.py +0 -0
  46. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_utilities.py +0 -0
  47. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_vision.py +0 -0
  48. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_watch.py +0 -0
  49. {zai_cli-0.1.3 → zai_cli-0.1.7}/tests/test_workflows.py +0 -0
  50. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/__main__.py +0 -0
  51. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/__init__.py +0 -0
  52. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/common.py +0 -0
  53. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/integrations.py +0 -0
  54. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/utilities.py +0 -0
  55. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/cli/workflows.py +0 -0
  56. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/commands/commit.md +0 -0
  57. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/commands/explain.md +0 -0
  58. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/commands/feature.md +0 -0
  59. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/commands/fix.md +0 -0
  60. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/commands/review.md +0 -0
  61. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/__init__.py +0 -0
  62. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/agent.py +0 -0
  63. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/cancellation.py +0 -0
  64. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/commands.py +0 -0
  65. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/context.py +0 -0
  66. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/errors.py +0 -0
  67. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/fallback.py +0 -0
  68. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/hooks.py +0 -0
  69. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/memory.py +0 -0
  70. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/process.py +0 -0
  71. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/repomap.py +0 -0
  72. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/runtime.py +0 -0
  73. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/security.py +0 -0
  74. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/session.py +0 -0
  75. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/storage.py +0 -0
  76. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/streaming.py +0 -0
  77. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/tool_schema.py +0 -0
  78. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/undo.py +0 -0
  79. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/core/watch.py +0 -0
  80. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/mcp/__init__.py +0 -0
  81. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/mcp/client.py +0 -0
  82. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/mcp/manager.py +0 -0
  83. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/plugins/__init__.py +0 -0
  84. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/plugins/base.py +0 -0
  85. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/plugins/loader.py +0 -0
  86. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/__init__.py +0 -0
  87. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/anthropic.py +0 -0
  88. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/base.py +0 -0
  89. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/cerebras.py +0 -0
  90. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/gemini.py +0 -0
  91. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/groq.py +0 -0
  92. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/ollama.py +0 -0
  93. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/openai.py +0 -0
  94. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/openrouter.py +0 -0
  95. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/providers/qwen.py +0 -0
  96. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/skills/__init__.py +0 -0
  97. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/skills/registry.py +0 -0
  98. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/__init__.py +0 -0
  99. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/browser.py +0 -0
  100. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/code_runner.py +0 -0
  101. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/files.py +0 -0
  102. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/git.py +0 -0
  103. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/search.py +0 -0
  104. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/tools/vision.py +0 -0
  105. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai/ui/__init__.py +0 -0
  106. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai_cli.egg-info/dependency_links.txt +0 -0
  107. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai_cli.egg-info/entry_points.txt +0 -0
  108. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai_cli.egg-info/requires.txt +0 -0
  109. {zai_cli-0.1.3 → zai_cli-0.1.7}/zai_cli.egg-info/top_level.txt +0 -0
@@ -5,6 +5,68 @@ 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.7] - 2026-06-28
9
+
10
+ ### Changed
11
+
12
+ - Changed first-run interactive onboarding so `zai` offers provider login inside
13
+ the interactive startup flow instead of sending users back to an external
14
+ command.
15
+
16
+ ## [0.1.6] - 2026-06-28
17
+
18
+ ### Added
19
+
20
+ - Added `zai auth status`, `zai auth login <provider>`,
21
+ `zai auth logout <provider>`, and `zai auth doctor`.
22
+ - Added interactive `/auth status`, `/auth login <provider>`,
23
+ `/auth logout <provider>`, and `/auth doctor`.
24
+ - Added a provider authentication registry covering Gemini, Groq, OpenAI,
25
+ Anthropic, OpenRouter, Cerebras, Qwen, and Ollama.
26
+ - Added safe external Codex and Claude CLI detection/login prompts without
27
+ reading or copying private token contents.
28
+
29
+ ### Changed
30
+
31
+ - Routed setup key saving through the shared auth helper.
32
+ - Documented API-key versus external account-login boundaries.
33
+
34
+ ## [0.1.5] - 2026-06-26
35
+
36
+ ### Added
37
+
38
+ - Added project-scoped memory stored in `.zai/project_memory.json`.
39
+ - Added `zai memory project`, `zai memory refresh`,
40
+ `zai memory remember <fact>`, and `zai memory forget <fact-or-id>`.
41
+ - Added interactive `/memory project`, `/memory refresh`, `/remember <fact>`,
42
+ and `/forget <fact-or-id>`.
43
+ - Added automatic project detection for common Python, Node/Next.js, Go, and
44
+ Rust project files.
45
+
46
+ ### Changed
47
+
48
+ - Interactive mode now loads existing project memory into pinned context.
49
+ - Project memory stores summaries and explicit facts only, avoiding secrets,
50
+ `.env` contents, tokens, passwords, and full source files.
51
+
52
+ ## [0.1.4] - 2026-06-26
53
+
54
+ ### Added
55
+
56
+ - Added a shared first-run setup wizard for both `zai setup` and interactive
57
+ `/setup`.
58
+ - Added `zai doctor` for install, key, model, and provider diagnostics.
59
+ - Added optional provider validation from setup with a user-approved tiny test
60
+ request.
61
+ - Added `/exit` and `/quit` in interactive mode.
62
+
63
+ ### Changed
64
+
65
+ - Reworked `/help` into a short beginner-focused guide.
66
+ - Reworked `/commands` into grouped setup, project, session, model, and
67
+ advanced command sections.
68
+ - Expanded first-time user README guidance and troubleshooting.
69
+
8
70
  ## [0.1.3] - 2026-06-24
9
71
 
10
72
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zai-cli
3
- Version: 0.1.3
3
+ Version: 0.1.7
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",