zai-cli 0.1.7__tar.gz → 0.1.8__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.7 → zai_cli-0.1.8}/CHANGELOG.md +22 -0
- {zai_cli-0.1.7/zai_cli.egg-info → zai_cli-0.1.8}/PKG-INFO +71 -7
- {zai_cli-0.1.7 → zai_cli-0.1.8}/README.md +70 -6
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_interactive.py +33 -5
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_settings_cli.py +63 -4
- zai_cli-0.1.8/zai/__init__.py +1 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/interactive.py +21 -35
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/settings.py +10 -43
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/setup_wizard.py +18 -2
- zai_cli-0.1.8/zai/cli/status.py +202 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/main.py +2 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8/zai_cli.egg-info}/PKG-INFO +71 -7
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai_cli.egg-info/SOURCES.txt +1 -0
- zai_cli-0.1.7/zai/__init__.py +0 -1
- {zai_cli-0.1.7 → zai_cli-0.1.8}/LICENSE +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/MANIFEST.in +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/pyproject.toml +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/scripts/release_preflight.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/setup.cfg +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_agent.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_auth.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_browser.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_code_runner.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_config_main.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_core.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_errors.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_hooks_skills_session.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_input.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_integrations_cli.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_mcp.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_plugins.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_process.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_project_memory.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_providers.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_release_preflight.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_search.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_security.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_storage.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_streaming.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_tool_schema.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_tools.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_undo.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_utilities.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_vision.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_watch.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/tests/test_workflows.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/__main__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/common.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/integrations.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/utilities.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/cli/workflows.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/commands/commit.md +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/commands/explain.md +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/commands/feature.md +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/commands/fix.md +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/commands/review.md +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/config.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/agent.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/auth.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/cancellation.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/commands.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/context.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/errors.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/fallback.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/hooks.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/memory.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/process.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/project_memory.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/repomap.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/runtime.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/security.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/session.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/storage.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/streaming.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/tool_schema.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/undo.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/core/watch.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/mcp/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/mcp/client.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/mcp/manager.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/plugins/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/plugins/base.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/plugins/loader.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/anthropic.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/base.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/cerebras.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/gemini.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/groq.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/ollama.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/openai.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/openrouter.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/providers/qwen.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/skills/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/skills/registry.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/browser.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/code_runner.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/files.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/git.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/search.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/tools/vision.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/ui/__init__.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai/ui/input.py +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai_cli.egg-info/dependency_links.txt +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai_cli.egg-info/entry_points.txt +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai_cli.egg-info/requires.txt +0 -0
- {zai_cli-0.1.7 → zai_cli-0.1.8}/zai_cli.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,28 @@ 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.8] - 2026-06-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added clearer provider/model status output for `zai model list` and
|
|
13
|
+
interactive `/model`, including provider, cloud/local status, actual model
|
|
14
|
+
ID, authentication state, and beginner-friendly notes.
|
|
15
|
+
- Added `zai context` and interactive `/context` to show the current folder,
|
|
16
|
+
project-memory status, repo-map/index status, and honest context visibility
|
|
17
|
+
notes.
|
|
18
|
+
- Added interactive `/providers` for provider connection status and next
|
|
19
|
+
actions.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Improved first-run setup copy to explain that `zai` connects to provider
|
|
24
|
+
backends and that the selected provider decides the model.
|
|
25
|
+
- Improved `zai auth status` and interactive `/auth status` with provider
|
|
26
|
+
next actions while continuing to avoid printing secret values.
|
|
27
|
+
- Expanded README provider/model/context guidance and clarified that repo maps
|
|
28
|
+
and project memory are not the same as loading every file into model context.
|
|
29
|
+
|
|
8
30
|
## [0.1.7] - 2026-06-28
|
|
9
31
|
|
|
10
32
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zai-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
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
|
|
@@ -63,6 +63,50 @@ An extensible AI coding assistant for the terminal.
|
|
|
63
63
|
project-specific sessions, switch between model providers, and integrate with
|
|
64
64
|
plugins and MCP servers.
|
|
65
65
|
|
|
66
|
+
## What is zai?
|
|
67
|
+
|
|
68
|
+
`zai` is the terminal CLI. It is not tied to one AI model. It connects to the
|
|
69
|
+
provider you configure, then uses that provider's selected model ID.
|
|
70
|
+
|
|
71
|
+
Cloud providers need their own API key. Ollama runs on your local machine and
|
|
72
|
+
does not need a cloud API key.
|
|
73
|
+
|
|
74
|
+
## Which model does zai use?
|
|
75
|
+
|
|
76
|
+
Run:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
zai model list
|
|
80
|
+
zai auth status
|
|
81
|
+
zai context
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The model list shows the alias, provider, display name, actual model ID, local
|
|
85
|
+
or cloud status, and whether a key is configured.
|
|
86
|
+
|
|
87
|
+
| Provider | Cloud/local | Key needed | Setup |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| Groq | Cloud | Yes | `zai auth login groq` |
|
|
90
|
+
| Gemini | Cloud | Yes | `zai auth login gemini` |
|
|
91
|
+
| OpenAI | Cloud | Yes | `zai auth login openai` |
|
|
92
|
+
| Anthropic/Claude | Cloud | Yes | `zai auth login anthropic` |
|
|
93
|
+
| OpenRouter | Cloud | Yes | `zai auth login openrouter` |
|
|
94
|
+
| Cerebras | Cloud | Yes | `zai auth login cerebras` |
|
|
95
|
+
| Qwen/DashScope | Cloud | Yes | `zai auth login qwen` |
|
|
96
|
+
| Ollama | Local | No cloud key | Start Ollama, then `zai model test ollama` |
|
|
97
|
+
|
|
98
|
+
Quick tester flow:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install --upgrade --no-cache-dir zai-cli
|
|
102
|
+
zai --version
|
|
103
|
+
zai
|
|
104
|
+
/help
|
|
105
|
+
/model
|
|
106
|
+
/auth status
|
|
107
|
+
/context
|
|
108
|
+
```
|
|
109
|
+
|
|
66
110
|
## Requirements
|
|
67
111
|
|
|
68
112
|
- Python 3.10 or newer
|
|
@@ -348,6 +392,7 @@ zai auth status Show API-key and external CLI auth status
|
|
|
348
392
|
zai auth login <provider> Connect a provider API key
|
|
349
393
|
zai auth logout <provider> Remove a provider API key from zai
|
|
350
394
|
zai auth doctor Show auth troubleshooting guidance
|
|
395
|
+
zai context Show loaded project context status
|
|
351
396
|
zai memory show Show last task metadata
|
|
352
397
|
zai memory projects List remembered projects
|
|
353
398
|
zai memory project Show project-scoped memory
|
|
@@ -435,6 +480,8 @@ Inside `zai` interactive mode:
|
|
|
435
480
|
|
|
436
481
|
/model list List models
|
|
437
482
|
/model <name> Change model for this interactive session
|
|
483
|
+
/providers Show provider connection status
|
|
484
|
+
/context Show loaded project context status
|
|
438
485
|
/memory Show last task metadata
|
|
439
486
|
/commands List built-in and plugin slash commands
|
|
440
487
|
```
|
|
@@ -450,22 +497,39 @@ Interactive input also provides:
|
|
|
450
497
|
- `Ctrl+C` to cancel the current input or operation without ending the session;
|
|
451
498
|
- a basic Rich prompt fallback in redirected or unsupported terminals.
|
|
452
499
|
|
|
453
|
-
##
|
|
500
|
+
## How much of my project does zai see?
|
|
501
|
+
|
|
502
|
+
`zai` starts from the folder where you run it. It does not load every file in a
|
|
503
|
+
large repository into every prompt.
|
|
504
|
+
|
|
505
|
+
Use `zai context` or `/context` to inspect the current folder, whether project
|
|
506
|
+
memory exists, whether a repo-map cache is available, and how many conversation
|
|
507
|
+
messages are loaded in the current interactive session.
|
|
508
|
+
|
|
509
|
+
Project memory is separate from current model context. It is a small
|
|
510
|
+
`.zai/project_memory.json` summary created with `zai memory refresh` or
|
|
511
|
+
`/memory refresh`.
|
|
512
|
+
|
|
513
|
+
Repo maps are summaries/indexes used on demand for repository questions. They
|
|
514
|
+
are not full source files loaded into every model call. If a file has not been
|
|
515
|
+
read or indexed for the current task, treat it as not loaded yet.
|
|
516
|
+
|
|
517
|
+
Token tracking and automatic long-session compaction are planned for `0.1.9`.
|
|
454
518
|
|
|
455
|
-
All chat, workflow, utility, and agent requests use
|
|
456
|
-
|
|
519
|
+
All chat, workflow, utility, and agent requests use a model-aware context
|
|
520
|
+
pipeline:
|
|
457
521
|
|
|
458
522
|
- the active provider's configured context window sets the request budget;
|
|
459
523
|
- system prompts, tool schemas, and output space are reserved before messages;
|
|
460
524
|
- initial project instructions and explicitly pinned messages are retained;
|
|
461
525
|
- assistant tool calls remain paired with their tool-result messages;
|
|
462
526
|
- oversized tool results keep their beginning and end with an omission marker;
|
|
463
|
-
-
|
|
464
|
-
verbatim;
|
|
527
|
+
- requests are bounded to fit the selected model's context limit;
|
|
465
528
|
- context usage uses a conservative shared token estimate, while provider
|
|
466
529
|
response usage is retained where the SDK reports it.
|
|
467
530
|
|
|
468
|
-
This
|
|
531
|
+
This keeps requests bounded, but it is not a replacement for future
|
|
532
|
+
long-session token tracking and automatic compaction.
|
|
469
533
|
|
|
470
534
|
## Providers and fallback
|
|
471
535
|
|
|
@@ -6,6 +6,50 @@ An extensible AI coding assistant for the terminal.
|
|
|
6
6
|
project-specific sessions, switch between model providers, and integrate with
|
|
7
7
|
plugins and MCP servers.
|
|
8
8
|
|
|
9
|
+
## What is zai?
|
|
10
|
+
|
|
11
|
+
`zai` is the terminal CLI. It is not tied to one AI model. It connects to the
|
|
12
|
+
provider you configure, then uses that provider's selected model ID.
|
|
13
|
+
|
|
14
|
+
Cloud providers need their own API key. Ollama runs on your local machine and
|
|
15
|
+
does not need a cloud API key.
|
|
16
|
+
|
|
17
|
+
## Which model does zai use?
|
|
18
|
+
|
|
19
|
+
Run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
zai model list
|
|
23
|
+
zai auth status
|
|
24
|
+
zai context
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The model list shows the alias, provider, display name, actual model ID, local
|
|
28
|
+
or cloud status, and whether a key is configured.
|
|
29
|
+
|
|
30
|
+
| Provider | Cloud/local | Key needed | Setup |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| Groq | Cloud | Yes | `zai auth login groq` |
|
|
33
|
+
| Gemini | Cloud | Yes | `zai auth login gemini` |
|
|
34
|
+
| OpenAI | Cloud | Yes | `zai auth login openai` |
|
|
35
|
+
| Anthropic/Claude | Cloud | Yes | `zai auth login anthropic` |
|
|
36
|
+
| OpenRouter | Cloud | Yes | `zai auth login openrouter` |
|
|
37
|
+
| Cerebras | Cloud | Yes | `zai auth login cerebras` |
|
|
38
|
+
| Qwen/DashScope | Cloud | Yes | `zai auth login qwen` |
|
|
39
|
+
| Ollama | Local | No cloud key | Start Ollama, then `zai model test ollama` |
|
|
40
|
+
|
|
41
|
+
Quick tester flow:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install --upgrade --no-cache-dir zai-cli
|
|
45
|
+
zai --version
|
|
46
|
+
zai
|
|
47
|
+
/help
|
|
48
|
+
/model
|
|
49
|
+
/auth status
|
|
50
|
+
/context
|
|
51
|
+
```
|
|
52
|
+
|
|
9
53
|
## Requirements
|
|
10
54
|
|
|
11
55
|
- Python 3.10 or newer
|
|
@@ -291,6 +335,7 @@ zai auth status Show API-key and external CLI auth status
|
|
|
291
335
|
zai auth login <provider> Connect a provider API key
|
|
292
336
|
zai auth logout <provider> Remove a provider API key from zai
|
|
293
337
|
zai auth doctor Show auth troubleshooting guidance
|
|
338
|
+
zai context Show loaded project context status
|
|
294
339
|
zai memory show Show last task metadata
|
|
295
340
|
zai memory projects List remembered projects
|
|
296
341
|
zai memory project Show project-scoped memory
|
|
@@ -378,6 +423,8 @@ Inside `zai` interactive mode:
|
|
|
378
423
|
|
|
379
424
|
/model list List models
|
|
380
425
|
/model <name> Change model for this interactive session
|
|
426
|
+
/providers Show provider connection status
|
|
427
|
+
/context Show loaded project context status
|
|
381
428
|
/memory Show last task metadata
|
|
382
429
|
/commands List built-in and plugin slash commands
|
|
383
430
|
```
|
|
@@ -393,22 +440,39 @@ Interactive input also provides:
|
|
|
393
440
|
- `Ctrl+C` to cancel the current input or operation without ending the session;
|
|
394
441
|
- a basic Rich prompt fallback in redirected or unsupported terminals.
|
|
395
442
|
|
|
396
|
-
##
|
|
443
|
+
## How much of my project does zai see?
|
|
444
|
+
|
|
445
|
+
`zai` starts from the folder where you run it. It does not load every file in a
|
|
446
|
+
large repository into every prompt.
|
|
447
|
+
|
|
448
|
+
Use `zai context` or `/context` to inspect the current folder, whether project
|
|
449
|
+
memory exists, whether a repo-map cache is available, and how many conversation
|
|
450
|
+
messages are loaded in the current interactive session.
|
|
451
|
+
|
|
452
|
+
Project memory is separate from current model context. It is a small
|
|
453
|
+
`.zai/project_memory.json` summary created with `zai memory refresh` or
|
|
454
|
+
`/memory refresh`.
|
|
455
|
+
|
|
456
|
+
Repo maps are summaries/indexes used on demand for repository questions. They
|
|
457
|
+
are not full source files loaded into every model call. If a file has not been
|
|
458
|
+
read or indexed for the current task, treat it as not loaded yet.
|
|
459
|
+
|
|
460
|
+
Token tracking and automatic long-session compaction are planned for `0.1.9`.
|
|
397
461
|
|
|
398
|
-
All chat, workflow, utility, and agent requests use
|
|
399
|
-
|
|
462
|
+
All chat, workflow, utility, and agent requests use a model-aware context
|
|
463
|
+
pipeline:
|
|
400
464
|
|
|
401
465
|
- the active provider's configured context window sets the request budget;
|
|
402
466
|
- system prompts, tool schemas, and output space are reserved before messages;
|
|
403
467
|
- initial project instructions and explicitly pinned messages are retained;
|
|
404
468
|
- assistant tool calls remain paired with their tool-result messages;
|
|
405
469
|
- oversized tool results keep their beginning and end with an omission marker;
|
|
406
|
-
-
|
|
407
|
-
verbatim;
|
|
470
|
+
- requests are bounded to fit the selected model's context limit;
|
|
408
471
|
- context usage uses a conservative shared token estimate, while provider
|
|
409
472
|
response usage is retained where the SDK reports it.
|
|
410
473
|
|
|
411
|
-
This
|
|
474
|
+
This keeps requests bounded, but it is not a replacement for future
|
|
475
|
+
long-session token tracking and automatic compaction.
|
|
412
476
|
|
|
413
477
|
## Providers and fallback
|
|
414
478
|
|
|
@@ -83,10 +83,15 @@ def test_interactive_key_setup_saves_entered_key(capsys):
|
|
|
83
83
|
|
|
84
84
|
with patch("zai.cli.setup_wizard.get_api_key", return_value=None):
|
|
85
85
|
with patch("zai.cli.setup_wizard.Confirm.ask", return_value=False):
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
with patch("zai.cli.setup_wizard.Prompt.ask", side_effect=lambda *a, **k: next(answers)):
|
|
87
|
+
with patch("zai.cli.setup_wizard.save_provider_key") as save_key:
|
|
88
|
+
with patch("zai.cli.setup_wizard.has_available_provider", side_effect=[False, True, True]):
|
|
89
|
+
with patch(
|
|
90
|
+
"zai.cli.setup_wizard.load_config",
|
|
91
|
+
return_value={"default_model": "gemini"},
|
|
92
|
+
):
|
|
93
|
+
with patch("zai.cli.setup_wizard.save_config"):
|
|
94
|
+
assert _run_key_setup_interactive()
|
|
90
95
|
|
|
91
96
|
output = capsys.readouterr().out
|
|
92
97
|
assert "zai setup" in output
|
|
@@ -227,13 +232,36 @@ def test_interactive_auth_commands_do_not_call_ai(tmp_path, monkeypatch, capsys)
|
|
|
227
232
|
run_interactive("groq")
|
|
228
233
|
|
|
229
234
|
output = capsys.readouterr().out
|
|
230
|
-
assert "
|
|
235
|
+
assert "Provider status" in output
|
|
231
236
|
assert "Saved Gemini API key" in output
|
|
232
237
|
save.assert_called_once_with("gemini", "gemini-key")
|
|
233
238
|
delete.assert_called_once_with("gemini")
|
|
234
239
|
run_agent.assert_not_called()
|
|
235
240
|
|
|
236
241
|
|
|
242
|
+
def test_interactive_context_and_providers_do_not_call_ai(tmp_path, monkeypatch, capsys):
|
|
243
|
+
monkeypatch.chdir(tmp_path)
|
|
244
|
+
(tmp_path / "app.py").write_text("print('hi')\n")
|
|
245
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
246
|
+
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
247
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
248
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
249
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
250
|
+
with patch(
|
|
251
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
252
|
+
side_effect=["/providers", "/context", EOFError()],
|
|
253
|
+
):
|
|
254
|
+
with patch("zai.cli.interactive.run_agent") as run_agent:
|
|
255
|
+
run_interactive("groq")
|
|
256
|
+
|
|
257
|
+
output = capsys.readouterr().out
|
|
258
|
+
assert "Provider status" in output
|
|
259
|
+
assert "Project context" in output
|
|
260
|
+
assert "unread files are not loaded" in output
|
|
261
|
+
assert "app.py is loaded" not in output
|
|
262
|
+
run_agent.assert_not_called()
|
|
263
|
+
|
|
264
|
+
|
|
237
265
|
def test_interactive_offers_project_memory_after_agent_task(tmp_path, monkeypatch, capsys):
|
|
238
266
|
from zai.core.project_memory import load_project_memory
|
|
239
267
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from io import StringIO
|
|
1
2
|
from unittest.mock import MagicMock, patch
|
|
2
3
|
|
|
3
4
|
import typer
|
|
@@ -26,6 +27,7 @@ def test_setup_wizard_saves_entered_key():
|
|
|
26
27
|
from zai.cli.setup_wizard import run_setup_wizard
|
|
27
28
|
|
|
28
29
|
answers = iter(["groq", "groq-key", "groq", "done"])
|
|
30
|
+
console_file = StringIO()
|
|
29
31
|
with (
|
|
30
32
|
patch("zai.cli.setup_wizard.has_available_provider", side_effect=[False, True, True]),
|
|
31
33
|
patch("zai.cli.setup_wizard.get_api_key", return_value=None),
|
|
@@ -38,8 +40,12 @@ def test_setup_wizard_saves_entered_key():
|
|
|
38
40
|
patch("zai.cli.setup_wizard.load_config", return_value={"default_model": "gemini"}),
|
|
39
41
|
patch("zai.cli.setup_wizard.save_config") as save_config,
|
|
40
42
|
):
|
|
41
|
-
assert run_setup_wizard(console=Console(file=
|
|
43
|
+
assert run_setup_wizard(console=Console(file=console_file)) is True
|
|
42
44
|
|
|
45
|
+
output = console_file.getvalue()
|
|
46
|
+
assert "zai is the CLI" in output
|
|
47
|
+
assert "Cloud providers need their own API key" in output
|
|
48
|
+
assert "Active provider/model" in output
|
|
43
49
|
save_key.assert_called_once_with("groq", "groq-key")
|
|
44
50
|
save_config.assert_called()
|
|
45
51
|
|
|
@@ -79,11 +85,13 @@ def test_model_list_set_fallback_and_usage():
|
|
|
79
85
|
):
|
|
80
86
|
selected = runner.invoke(app, ["model", "set", "groq"])
|
|
81
87
|
fallback = runner.invoke(app, ["model", "fallback", "off"])
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
with patch("zai.cli.settings.load_config", return_value=config):
|
|
89
|
+
unknown = runner.invoke(app, ["model", "set", "missing"])
|
|
90
|
+
usage = runner.invoke(app, ["model", "fallback", "maybe"])
|
|
84
91
|
|
|
85
92
|
assert "Available Models" in listed.output
|
|
86
|
-
assert "
|
|
93
|
+
assert "Model ID" in listed.output
|
|
94
|
+
assert "gemini" in listed.output
|
|
87
95
|
assert "Default model set to" in selected.output
|
|
88
96
|
assert "Automatic model fallback" in fallback.output
|
|
89
97
|
assert config["default_model"] == "groq"
|
|
@@ -93,6 +101,57 @@ def test_model_list_set_fallback_and_usage():
|
|
|
93
101
|
assert "Usage: zai model" in usage.output
|
|
94
102
|
|
|
95
103
|
|
|
104
|
+
def test_model_list_explains_provider_model_and_auth_without_secret(monkeypatch):
|
|
105
|
+
app = settings_app()
|
|
106
|
+
config = {
|
|
107
|
+
"default_model": "groq",
|
|
108
|
+
"fallback_order": ["groq"],
|
|
109
|
+
"model_overrides": {},
|
|
110
|
+
"auto_fallback": True,
|
|
111
|
+
}
|
|
112
|
+
monkeypatch.setenv("GROQ_API_KEY", "sk-secret-value")
|
|
113
|
+
|
|
114
|
+
with patch("zai.cli.settings.load_config", return_value=config):
|
|
115
|
+
result = CliRunner().invoke(app, ["model", "list"])
|
|
116
|
+
|
|
117
|
+
assert result.exit_code == 0
|
|
118
|
+
assert "Provider" in result.output
|
|
119
|
+
assert "Model ID" in result.output
|
|
120
|
+
assert "cloud" in result.output
|
|
121
|
+
assert "openai/gpt-oss-120b" in result.output
|
|
122
|
+
assert "key configured" in result.output
|
|
123
|
+
assert "sk-secret-value" not in result.output
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_auth_status_shows_provider_next_actions_without_secret(monkeypatch):
|
|
127
|
+
app = settings_app()
|
|
128
|
+
monkeypatch.setenv("GEMINI_API_KEY", "gemini-secret")
|
|
129
|
+
|
|
130
|
+
result = CliRunner().invoke(app, ["auth", "status"])
|
|
131
|
+
|
|
132
|
+
assert result.exit_code == 0
|
|
133
|
+
assert "Provider status" in result.output
|
|
134
|
+
assert "auth login" in result.output
|
|
135
|
+
assert "groq" in result.output
|
|
136
|
+
assert "key configured" in result.output
|
|
137
|
+
assert "gemini-secret" not in result.output
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_context_command_is_honest_about_unloaded_files(tmp_path, monkeypatch):
|
|
141
|
+
app = settings_app()
|
|
142
|
+
monkeypatch.chdir(tmp_path)
|
|
143
|
+
(tmp_path / "app.py").write_text("print('hi')\n")
|
|
144
|
+
|
|
145
|
+
result = CliRunner().invoke(app, ["context"])
|
|
146
|
+
|
|
147
|
+
assert result.exit_code == 0
|
|
148
|
+
assert f"Folder: {tmp_path}" in result.output
|
|
149
|
+
assert "Project memory: missing" in result.output
|
|
150
|
+
assert "Repo map/index: not loaded yet" in result.output
|
|
151
|
+
assert "unread files are not loaded" in result.output
|
|
152
|
+
assert "app.py is loaded" not in result.output
|
|
153
|
+
|
|
154
|
+
|
|
96
155
|
def test_model_check_reports_valid_and_invalid_configuration():
|
|
97
156
|
app = settings_app()
|
|
98
157
|
runner = CliRunner()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.8"
|
|
@@ -7,10 +7,9 @@ from rich.panel import Panel
|
|
|
7
7
|
from rich.prompt import Confirm, Prompt
|
|
8
8
|
from rich.table import Table
|
|
9
9
|
|
|
10
|
-
from ..config import
|
|
10
|
+
from ..config import get_models, load_config
|
|
11
11
|
from ..core.auth import (
|
|
12
12
|
PROVIDERS,
|
|
13
|
-
auth_status,
|
|
14
13
|
delete_provider_key,
|
|
15
14
|
external_login_command,
|
|
16
15
|
get_auth_provider,
|
|
@@ -50,14 +49,15 @@ from ..tools.git import get_diff
|
|
|
50
49
|
from ..ui.input import InteractiveInput
|
|
51
50
|
from .common import closest_command
|
|
52
51
|
from .setup_wizard import run_setup_wizard
|
|
52
|
+
from .status import context_panel, print_model_status, print_provider_status
|
|
53
53
|
|
|
54
54
|
console = Console()
|
|
55
55
|
|
|
56
56
|
CORE_COMMANDS = {
|
|
57
57
|
"help", "clear", "files", "diff", "undo", "plan", "test",
|
|
58
58
|
"watch", "commit", "review", "fix", "explain", "feature",
|
|
59
|
-
"session", "resume", "model", "
|
|
60
|
-
"auth", "setup", "exit", "quit",
|
|
59
|
+
"session", "resume", "model", "providers", "context", "memory", "remember",
|
|
60
|
+
"forget", "commands", "auth", "setup", "exit", "quit",
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
INTERACTIVE_COMMANDS = [
|
|
@@ -66,6 +66,8 @@ INTERACTIVE_COMMANDS = [
|
|
|
66
66
|
("/setup", "Show API-key setup instructions"),
|
|
67
67
|
("/auth status", "Show authentication status"),
|
|
68
68
|
("/auth login <provider>", "Connect a provider"),
|
|
69
|
+
("/providers", "Show provider connection status"),
|
|
70
|
+
("/context", "Show loaded project context"),
|
|
69
71
|
("/exit", "Exit interactive mode"),
|
|
70
72
|
("/quit", "Exit interactive mode"),
|
|
71
73
|
("/clear", "Clear the terminal"),
|
|
@@ -94,6 +96,8 @@ PLAIN_COMMAND_ALIASES = {
|
|
|
94
96
|
"setup": "/setup",
|
|
95
97
|
"auth": "/auth status",
|
|
96
98
|
"auth status": "/auth status",
|
|
99
|
+
"providers": "/providers",
|
|
100
|
+
"context": "/context",
|
|
97
101
|
"keys": "/setup",
|
|
98
102
|
"api keys": "/setup",
|
|
99
103
|
"model list": "/model list",
|
|
@@ -178,9 +182,11 @@ def _show_commands() -> None:
|
|
|
178
182
|
("Setup", "/auth status", "Show authentication status"),
|
|
179
183
|
("Setup", "/auth login <provider>", "Connect a provider"),
|
|
180
184
|
("Setup", "/auth logout <provider>", "Remove a provider API key"),
|
|
185
|
+
("Setup", "/providers", "Show provider connection status"),
|
|
181
186
|
("Setup", "/model", "Show available models"),
|
|
182
187
|
("Setup", "/model <name>", "Switch model"),
|
|
183
188
|
("Setup", "/exit", "Exit interactive mode"),
|
|
189
|
+
("Project", "/context", "Show loaded project context"),
|
|
184
190
|
("Project", "/files", "List files in the current folder"),
|
|
185
191
|
("Project", "/diff", "Show git diff"),
|
|
186
192
|
("Project", "/review", "Review code"),
|
|
@@ -227,40 +233,11 @@ def _show_setup_hint() -> None:
|
|
|
227
233
|
|
|
228
234
|
|
|
229
235
|
def _show_models() -> None:
|
|
230
|
-
|
|
231
|
-
table.add_column("Name", style="cyan")
|
|
232
|
-
table.add_column("Model")
|
|
233
|
-
table.add_column("Key")
|
|
234
|
-
for key, data in get_models().items():
|
|
235
|
-
key_status = "ok" if get_api_key(data["provider"]) else "no key"
|
|
236
|
-
table.add_row(key, data["name"], key_status)
|
|
237
|
-
console.print(table)
|
|
236
|
+
print_model_status(console)
|
|
238
237
|
|
|
239
238
|
|
|
240
239
|
def _show_auth_status(provider: str | None = None) -> None:
|
|
241
|
-
|
|
242
|
-
if provider and not rows:
|
|
243
|
-
console.print(f"[red]Unknown auth provider: {provider}[/red]")
|
|
244
|
-
return
|
|
245
|
-
table = Table(title="Authentication status", border_style="cyan")
|
|
246
|
-
table.add_column("Provider", style="cyan")
|
|
247
|
-
table.add_column("Methods")
|
|
248
|
-
table.add_column("Key")
|
|
249
|
-
table.add_column("External CLI")
|
|
250
|
-
table.add_column("External auth")
|
|
251
|
-
for row in rows:
|
|
252
|
-
table.add_row(
|
|
253
|
-
row["provider"],
|
|
254
|
-
row["methods"],
|
|
255
|
-
row["key_status"],
|
|
256
|
-
row["external_cli"],
|
|
257
|
-
row["external_status"],
|
|
258
|
-
)
|
|
259
|
-
console.print(table)
|
|
260
|
-
console.print(
|
|
261
|
-
"[dim]API keys power zai. Codex/Claude login stays with their "
|
|
262
|
-
"official CLIs; zai does not copy tokens.[/dim]"
|
|
263
|
-
)
|
|
240
|
+
print_provider_status(console, provider)
|
|
264
241
|
|
|
265
242
|
|
|
266
243
|
def _run_auth_login(provider: str, *, device: bool = False) -> None:
|
|
@@ -626,6 +603,15 @@ def run_interactive(model: str = None) -> None:
|
|
|
626
603
|
_run_key_setup_interactive()
|
|
627
604
|
elif stripped == "/auth" or stripped.startswith("/auth "):
|
|
628
605
|
_run_auth_command(stripped)
|
|
606
|
+
elif stripped == "/providers":
|
|
607
|
+
_show_auth_status()
|
|
608
|
+
elif stripped == "/context":
|
|
609
|
+
messages = session_context.get_messages()
|
|
610
|
+
console.print(context_panel(
|
|
611
|
+
cwd,
|
|
612
|
+
message_count=len(messages),
|
|
613
|
+
pinned_count=sum(1 for item in messages if item.pinned),
|
|
614
|
+
))
|
|
629
615
|
elif stripped in {"/exit", "/quit"}:
|
|
630
616
|
fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
|
|
631
617
|
console.print("\n[dim]Goodbye![/dim]")
|
|
@@ -9,7 +9,6 @@ from rich.table import Table
|
|
|
9
9
|
|
|
10
10
|
from ..core.auth import (
|
|
11
11
|
PROVIDERS,
|
|
12
|
-
auth_status,
|
|
13
12
|
delete_provider_key,
|
|
14
13
|
external_login_command,
|
|
15
14
|
get_auth_provider,
|
|
@@ -53,6 +52,7 @@ from ..core.undo import (
|
|
|
53
52
|
undo_action,
|
|
54
53
|
)
|
|
55
54
|
from .setup_wizard import print_doctor, run_setup_wizard
|
|
55
|
+
from .status import context_lines, print_model_status, print_provider_status
|
|
56
56
|
|
|
57
57
|
console = Console()
|
|
58
58
|
|
|
@@ -116,29 +116,7 @@ def doctor():
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def _print_auth_status(provider: str | None = None) -> None:
|
|
119
|
-
|
|
120
|
-
if provider and not rows:
|
|
121
|
-
console.print(f"[red]Unknown auth provider: {provider}[/red]")
|
|
122
|
-
return
|
|
123
|
-
table = Table(title="Authentication status", border_style="cyan")
|
|
124
|
-
table.add_column("Provider", style="cyan")
|
|
125
|
-
table.add_column("Methods")
|
|
126
|
-
table.add_column("Key")
|
|
127
|
-
table.add_column("External CLI")
|
|
128
|
-
table.add_column("External auth")
|
|
129
|
-
for row in rows:
|
|
130
|
-
table.add_row(
|
|
131
|
-
row["provider"],
|
|
132
|
-
row["methods"],
|
|
133
|
-
row["key_status"],
|
|
134
|
-
row["external_cli"],
|
|
135
|
-
row["external_status"],
|
|
136
|
-
)
|
|
137
|
-
console.print(table)
|
|
138
|
-
console.print(
|
|
139
|
-
"[dim]API keys power zai providers. Codex/Claude account login powers "
|
|
140
|
-
"their official external CLIs; zai does not copy their tokens.[/dim]"
|
|
141
|
-
)
|
|
119
|
+
print_provider_status(console, provider)
|
|
142
120
|
|
|
143
121
|
|
|
144
122
|
def auth(
|
|
@@ -244,25 +222,8 @@ def model(
|
|
|
244
222
|
):
|
|
245
223
|
"""List, configure, inspect, or test model aliases."""
|
|
246
224
|
if action == "list":
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
table.add_column("Model", style="dim")
|
|
250
|
-
table.add_column("Context", justify="right")
|
|
251
|
-
table.add_column("Free", justify="center")
|
|
252
|
-
table.add_column("Key Set", justify="center")
|
|
253
|
-
config = load_config()
|
|
254
|
-
for key in get_models(config):
|
|
255
|
-
data = get_model_config(key, config)
|
|
256
|
-
has_key = "[green]✓[/green]" if get_api_key(data["provider"]) else "[red]✗[/red]"
|
|
257
|
-
free_label = "[green]Yes[/green]" if data["free"] else "[yellow]No[/yellow]"
|
|
258
|
-
table.add_row(
|
|
259
|
-
key,
|
|
260
|
-
data["model_id"],
|
|
261
|
-
f"{data['context_window']:,}",
|
|
262
|
-
free_label,
|
|
263
|
-
has_key,
|
|
264
|
-
)
|
|
265
|
-
console.print(table)
|
|
225
|
+
print_model_status(console, title="Available Models", config=load_config())
|
|
226
|
+
return
|
|
266
227
|
elif action == "set" and name:
|
|
267
228
|
config = load_config()
|
|
268
229
|
if name not in get_models(config):
|
|
@@ -470,6 +431,11 @@ def memory(
|
|
|
470
431
|
)
|
|
471
432
|
|
|
472
433
|
|
|
434
|
+
def context():
|
|
435
|
+
"""Show what project context is currently visible to zai."""
|
|
436
|
+
console.print("\n".join(context_lines(".")), soft_wrap=True)
|
|
437
|
+
|
|
438
|
+
|
|
473
439
|
def session(
|
|
474
440
|
action: str = typer.Argument("list", help="list, show, search, rename, delete"),
|
|
475
441
|
name: Optional[str] = typer.Argument(None),
|
|
@@ -617,6 +583,7 @@ def register_settings_commands(app: typer.Typer) -> None:
|
|
|
617
583
|
app.command()(doctor)
|
|
618
584
|
app.command()(auth)
|
|
619
585
|
app.command()(model)
|
|
586
|
+
app.command()(context)
|
|
620
587
|
app.command()(memory)
|
|
621
588
|
app.command()(session)
|
|
622
589
|
app.command()(undo)
|