zai-cli 0.1.9__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zai_cli-0.1.9 → zai_cli-0.2.2}/CHANGELOG.md +45 -0
- {zai_cli-0.1.9/zai_cli.egg-info → zai_cli-0.2.2}/PKG-INFO +59 -2
- {zai_cli-0.1.9 → zai_cli-0.2.2}/README.md +58 -1
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_auth.py +42 -1
- zai_cli-0.2.2/tests/test_file_mentions.py +30 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_input.py +16 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_interactive.py +95 -34
- zai_cli-0.2.2/tests/test_project_rules.py +33 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_settings_cli.py +36 -0
- zai_cli-0.2.2/zai/__init__.py +1 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/interactive.py +44 -15
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/settings.py +60 -10
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/status.py +7 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/auth.py +89 -13
- zai_cli-0.2.2/zai/core/file_mentions.py +99 -0
- zai_cli-0.2.2/zai/core/project_rules.py +111 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/main.py +16 -1
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/ui/input.py +14 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2/zai_cli.egg-info}/PKG-INFO +59 -2
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai_cli.egg-info/SOURCES.txt +4 -0
- zai_cli-0.1.9/zai/__init__.py +0 -1
- {zai_cli-0.1.9 → zai_cli-0.2.2}/LICENSE +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/MANIFEST.in +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/pyproject.toml +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/scripts/release_preflight.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/setup.cfg +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_agent.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_browser.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_code_runner.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_config_main.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_core.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_errors.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_hooks_skills_session.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_integrations_cli.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_mcp.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_plugins.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_process.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_project_memory.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_providers.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_release_preflight.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_search.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_security.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_storage.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_streaming.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_tokens.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_tool_schema.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_tools.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_undo.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_utilities.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_vision.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_watch.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/tests/test_workflows.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/__main__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/common.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/integrations.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/setup_wizard.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/utilities.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/cli/workflows.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/commands/commit.md +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/commands/explain.md +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/commands/feature.md +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/commands/fix.md +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/commands/review.md +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/config.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/agent.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/cancellation.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/commands.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/compaction.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/context.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/errors.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/fallback.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/hooks.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/memory.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/process.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/project_memory.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/repomap.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/runtime.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/security.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/session.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/storage.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/streaming.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/tokens.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/tool_schema.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/undo.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/core/watch.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/mcp/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/mcp/client.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/mcp/manager.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/plugins/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/plugins/base.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/plugins/loader.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/anthropic.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/base.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/cerebras.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/gemini.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/groq.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/ollama.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/openai.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/openrouter.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/providers/qwen.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/skills/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/skills/registry.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/browser.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/code_runner.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/files.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/git.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/search.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/tools/vision.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai/ui/__init__.py +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai_cli.egg-info/dependency_links.txt +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai_cli.egg-info/entry_points.txt +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai_cli.egg-info/requires.txt +0 -0
- {zai_cli-0.1.9 → zai_cli-0.2.2}/zai_cli.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,51 @@ 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.2.2] - 2026-07-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added structured live-provider validation results with the resolved model,
|
|
13
|
+
token usage, failure detail, and a recommended next action.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Authentication login validation now distinguishes missing or invalid
|
|
18
|
+
credentials, quota limits, model configuration errors, network failures, and
|
|
19
|
+
unexpected provider failures.
|
|
20
|
+
- `zai model test` now reports actionable recovery commands and shows the token
|
|
21
|
+
cost of a successful validation request when providers return usage data.
|
|
22
|
+
|
|
23
|
+
## [0.2.1] - 2026-06-30
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Added `@file` completion in normal interactive prompts.
|
|
28
|
+
- Added safe `@file` mention expansion so prompts like `explain @app.py`
|
|
29
|
+
include bounded referenced file content.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Mention expansion skips missing, hidden, oversized, binary, and
|
|
34
|
+
sensitive-looking files without crashing the prompt.
|
|
35
|
+
|
|
36
|
+
## [0.2.0] - 2026-06-30
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- Added project rule loading from `ZAI.md`, `.zai/rules.md`, `AGENTS.md`,
|
|
41
|
+
`CLAUDE.md`, and `GEMINI.md`.
|
|
42
|
+
- Added interactive `/rules` and top-level `zai rules` to show loaded project
|
|
43
|
+
instruction files.
|
|
44
|
+
- Added project-rule visibility to `zai context` and interactive `/context`.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- Interactive and one-shot chat now pin loaded project rules into model context
|
|
49
|
+
so the assistant can follow repo-specific instructions.
|
|
50
|
+
- Project rule loading is bounded, skips unreadable/oversized files, and redacts
|
|
51
|
+
obvious secrets before rules are added to context.
|
|
52
|
+
|
|
8
53
|
## [0.1.9] - 2026-06-30
|
|
9
54
|
|
|
10
55
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zai-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Your personal AI CLI — free, fast, and smart
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/HumaizaNaz/zai_cli
|
|
@@ -198,6 +198,10 @@ zai auth login openai
|
|
|
198
198
|
zai auth login anthropic
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
When prompted, authentication login can send one tiny live request to validate
|
|
202
|
+
the key. The result identifies the resolved model and gives a recovery command
|
|
203
|
+
for invalid credentials, quota limits, model errors, or network failures.
|
|
204
|
+
|
|
201
205
|
Remove a saved API key from `zai`:
|
|
202
206
|
|
|
203
207
|
```bash
|
|
@@ -343,10 +347,49 @@ When `zai` starts in a folder that already has `.zai/project_memory.json`, it
|
|
|
343
347
|
loads a short pinned summary so future answers can use that folder's context.
|
|
344
348
|
Different folders keep different memories.
|
|
345
349
|
|
|
350
|
+
## Project rules
|
|
351
|
+
|
|
352
|
+
Add project instruction files when you want `zai` to consistently follow repo
|
|
353
|
+
rules without copy-pasting them into every prompt.
|
|
354
|
+
|
|
355
|
+
Supported files:
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
ZAI.md
|
|
359
|
+
.zai/rules.md
|
|
360
|
+
AGENTS.md
|
|
361
|
+
CLAUDE.md
|
|
362
|
+
GEMINI.md
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
When `zai` starts, it loads bounded text from these files in the project root
|
|
366
|
+
and current folder, redacts obvious secrets, and pins the rules into model
|
|
367
|
+
context. Use these files for coding style, preferred test commands, folders to
|
|
368
|
+
avoid, release steps, and project-specific constraints.
|
|
369
|
+
|
|
370
|
+
Inspect loaded rules:
|
|
371
|
+
|
|
372
|
+
```text
|
|
373
|
+
zai rules
|
|
374
|
+
/rules
|
|
375
|
+
zai context
|
|
376
|
+
/context
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Project rules are different from project memory:
|
|
380
|
+
|
|
381
|
+
- Project rules are explicit instructions written by the user/repo.
|
|
382
|
+
- Project memory is a generated or remembered summary in
|
|
383
|
+
`.zai/project_memory.json`.
|
|
384
|
+
- Repo maps are generated structure summaries used on demand.
|
|
385
|
+
- Session history is the current or saved conversation.
|
|
386
|
+
|
|
346
387
|
Memory types:
|
|
347
388
|
|
|
348
389
|
- Session history: previous conversation messages saved globally under `~/.zai`.
|
|
349
390
|
- Project memory: small per-folder facts in `.zai/project_memory.json`.
|
|
391
|
+
- Project rules: explicit instruction files such as `ZAI.md` or
|
|
392
|
+
`.zai/rules.md`.
|
|
350
393
|
- Repo map: generated structure summary used for repository questions; it is not
|
|
351
394
|
long-term memory.
|
|
352
395
|
|
|
@@ -387,12 +430,13 @@ zai model add <name> Add a custom model alias
|
|
|
387
430
|
zai model remove <name> Remove a custom model alias
|
|
388
431
|
zai model configure <name> Set timeout and retry policy
|
|
389
432
|
zai model info <name> Show effective model configuration
|
|
390
|
-
zai model test <name> Verify credentials and
|
|
433
|
+
zai model test <name> Verify credentials/model and show recovery guidance
|
|
391
434
|
zai auth status Show API-key and external CLI auth status
|
|
392
435
|
zai auth login <provider> Connect a provider API key
|
|
393
436
|
zai auth logout <provider> Remove a provider API key from zai
|
|
394
437
|
zai auth doctor Show auth troubleshooting guidance
|
|
395
438
|
zai context Show loaded project context status
|
|
439
|
+
zai rules Show loaded project instruction files
|
|
396
440
|
zai memory show Show last task metadata
|
|
397
441
|
zai memory projects List remembered projects
|
|
398
442
|
zai memory project Show project-scoped memory
|
|
@@ -482,6 +526,7 @@ Inside `zai` interactive mode:
|
|
|
482
526
|
/model <name> Change model for this interactive session
|
|
483
527
|
/providers Show provider connection status
|
|
484
528
|
/context Show loaded project context status
|
|
529
|
+
/rules Show loaded project instruction files
|
|
485
530
|
/memory Show last task metadata
|
|
486
531
|
/commands List built-in and plugin slash commands
|
|
487
532
|
```
|
|
@@ -497,6 +542,18 @@ Interactive input also provides:
|
|
|
497
542
|
- `Ctrl+C` to cancel the current input or operation without ending the session;
|
|
498
543
|
- a basic Rich prompt fallback in redirected or unsupported terminals.
|
|
499
544
|
|
|
545
|
+
You can reference files in natural prompts with `@file`:
|
|
546
|
+
|
|
547
|
+
```text
|
|
548
|
+
explain @app.py
|
|
549
|
+
fix @src/main.py
|
|
550
|
+
compare @README.md and @CHANGELOG.md
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
`zai` loads bounded text from valid referenced files and appends it to the
|
|
554
|
+
prompt context. Hidden files, sensitive-looking paths, binary files, and very
|
|
555
|
+
large files are skipped with a note instead of crashing the session.
|
|
556
|
+
|
|
500
557
|
## How much of my project does zai see?
|
|
501
558
|
|
|
502
559
|
`zai` starts from the folder where you run it. It does not load every file in a
|
|
@@ -141,6 +141,10 @@ zai auth login openai
|
|
|
141
141
|
zai auth login anthropic
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
When prompted, authentication login can send one tiny live request to validate
|
|
145
|
+
the key. The result identifies the resolved model and gives a recovery command
|
|
146
|
+
for invalid credentials, quota limits, model errors, or network failures.
|
|
147
|
+
|
|
144
148
|
Remove a saved API key from `zai`:
|
|
145
149
|
|
|
146
150
|
```bash
|
|
@@ -286,10 +290,49 @@ When `zai` starts in a folder that already has `.zai/project_memory.json`, it
|
|
|
286
290
|
loads a short pinned summary so future answers can use that folder's context.
|
|
287
291
|
Different folders keep different memories.
|
|
288
292
|
|
|
293
|
+
## Project rules
|
|
294
|
+
|
|
295
|
+
Add project instruction files when you want `zai` to consistently follow repo
|
|
296
|
+
rules without copy-pasting them into every prompt.
|
|
297
|
+
|
|
298
|
+
Supported files:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
ZAI.md
|
|
302
|
+
.zai/rules.md
|
|
303
|
+
AGENTS.md
|
|
304
|
+
CLAUDE.md
|
|
305
|
+
GEMINI.md
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
When `zai` starts, it loads bounded text from these files in the project root
|
|
309
|
+
and current folder, redacts obvious secrets, and pins the rules into model
|
|
310
|
+
context. Use these files for coding style, preferred test commands, folders to
|
|
311
|
+
avoid, release steps, and project-specific constraints.
|
|
312
|
+
|
|
313
|
+
Inspect loaded rules:
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
zai rules
|
|
317
|
+
/rules
|
|
318
|
+
zai context
|
|
319
|
+
/context
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Project rules are different from project memory:
|
|
323
|
+
|
|
324
|
+
- Project rules are explicit instructions written by the user/repo.
|
|
325
|
+
- Project memory is a generated or remembered summary in
|
|
326
|
+
`.zai/project_memory.json`.
|
|
327
|
+
- Repo maps are generated structure summaries used on demand.
|
|
328
|
+
- Session history is the current or saved conversation.
|
|
329
|
+
|
|
289
330
|
Memory types:
|
|
290
331
|
|
|
291
332
|
- Session history: previous conversation messages saved globally under `~/.zai`.
|
|
292
333
|
- Project memory: small per-folder facts in `.zai/project_memory.json`.
|
|
334
|
+
- Project rules: explicit instruction files such as `ZAI.md` or
|
|
335
|
+
`.zai/rules.md`.
|
|
293
336
|
- Repo map: generated structure summary used for repository questions; it is not
|
|
294
337
|
long-term memory.
|
|
295
338
|
|
|
@@ -330,12 +373,13 @@ zai model add <name> Add a custom model alias
|
|
|
330
373
|
zai model remove <name> Remove a custom model alias
|
|
331
374
|
zai model configure <name> Set timeout and retry policy
|
|
332
375
|
zai model info <name> Show effective model configuration
|
|
333
|
-
zai model test <name> Verify credentials and
|
|
376
|
+
zai model test <name> Verify credentials/model and show recovery guidance
|
|
334
377
|
zai auth status Show API-key and external CLI auth status
|
|
335
378
|
zai auth login <provider> Connect a provider API key
|
|
336
379
|
zai auth logout <provider> Remove a provider API key from zai
|
|
337
380
|
zai auth doctor Show auth troubleshooting guidance
|
|
338
381
|
zai context Show loaded project context status
|
|
382
|
+
zai rules Show loaded project instruction files
|
|
339
383
|
zai memory show Show last task metadata
|
|
340
384
|
zai memory projects List remembered projects
|
|
341
385
|
zai memory project Show project-scoped memory
|
|
@@ -425,6 +469,7 @@ Inside `zai` interactive mode:
|
|
|
425
469
|
/model <name> Change model for this interactive session
|
|
426
470
|
/providers Show provider connection status
|
|
427
471
|
/context Show loaded project context status
|
|
472
|
+
/rules Show loaded project instruction files
|
|
428
473
|
/memory Show last task metadata
|
|
429
474
|
/commands List built-in and plugin slash commands
|
|
430
475
|
```
|
|
@@ -440,6 +485,18 @@ Interactive input also provides:
|
|
|
440
485
|
- `Ctrl+C` to cancel the current input or operation without ending the session;
|
|
441
486
|
- a basic Rich prompt fallback in redirected or unsupported terminals.
|
|
442
487
|
|
|
488
|
+
You can reference files in natural prompts with `@file`:
|
|
489
|
+
|
|
490
|
+
```text
|
|
491
|
+
explain @app.py
|
|
492
|
+
fix @src/main.py
|
|
493
|
+
compare @README.md and @CHANGELOG.md
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
`zai` loads bounded text from valid referenced files and appends it to the
|
|
497
|
+
prompt context. Hidden files, sensitive-looking paths, binary files, and very
|
|
498
|
+
large files are skipped with a note instead of crashing the session.
|
|
499
|
+
|
|
443
500
|
## How much of my project does zai see?
|
|
444
501
|
|
|
445
502
|
`zai` starts from the folder where you run it. It does not load every file in a
|
|
@@ -14,6 +14,7 @@ from zai.core.auth import (
|
|
|
14
14
|
mask_secret,
|
|
15
15
|
save_provider_key,
|
|
16
16
|
validate_provider,
|
|
17
|
+
validate_provider_details,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
@@ -66,18 +67,58 @@ def test_external_login_command_for_codex_and_claude():
|
|
|
66
67
|
def test_validate_provider_normalizes_results():
|
|
67
68
|
provider = MagicMock()
|
|
68
69
|
provider.is_available.return_value = True
|
|
69
|
-
provider.chat.return_value = MagicMock()
|
|
70
|
+
provider.chat.return_value = MagicMock(model="provider/model", tokens_used=7)
|
|
70
71
|
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
71
72
|
assert validate_provider("groq") == "OK"
|
|
73
|
+
details = validate_provider_details("groq")
|
|
74
|
+
assert details.status == "OK"
|
|
75
|
+
assert details.model == "provider/model"
|
|
76
|
+
assert details.tokens_used == 7
|
|
77
|
+
assert "zai model set groq" in details.next_action
|
|
72
78
|
|
|
73
79
|
provider.chat.side_effect = RuntimeError("unauthorized api key")
|
|
74
80
|
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
75
81
|
assert validate_provider("groq") == "invalid"
|
|
82
|
+
details = validate_provider_details("groq")
|
|
83
|
+
assert details.status == "invalid"
|
|
84
|
+
assert "zai auth login groq" in details.next_action
|
|
76
85
|
|
|
77
86
|
provider.chat.side_effect = RuntimeError("rate limit")
|
|
78
87
|
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
79
88
|
assert validate_provider("groq") == "quota/rate limit"
|
|
80
89
|
|
|
90
|
+
provider.chat.side_effect = RuntimeError("unknown model does not exist")
|
|
91
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
92
|
+
details = validate_provider_details("groq")
|
|
93
|
+
assert details.status == "model error"
|
|
94
|
+
assert "zai model info groq" in details.next_action
|
|
95
|
+
|
|
96
|
+
provider.chat.side_effect = RuntimeError("unexpected provider failure")
|
|
97
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
98
|
+
details = validate_provider_details("groq")
|
|
99
|
+
assert details.status == "provider error"
|
|
100
|
+
assert "zai auth doctor" in details.next_action
|
|
101
|
+
|
|
102
|
+
provider.chat.side_effect = RuntimeError("api_key=super-secret-provider-token")
|
|
103
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
104
|
+
details = validate_provider_details("groq")
|
|
105
|
+
assert "super-secret-provider-token" not in details.detail
|
|
106
|
+
assert "[REDACTED_SECRET]" in details.detail
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_validate_provider_reports_unknown_and_unavailable():
|
|
110
|
+
unknown = validate_provider_details("unknown")
|
|
111
|
+
assert unknown.status == "missing"
|
|
112
|
+
assert unknown.detail == "Unknown provider."
|
|
113
|
+
|
|
114
|
+
provider = MagicMock()
|
|
115
|
+
provider.is_available.return_value = False
|
|
116
|
+
with patch("zai.core.auth.get_provider", return_value=provider):
|
|
117
|
+
unavailable = validate_provider_details("groq")
|
|
118
|
+
|
|
119
|
+
assert unavailable.status == "missing"
|
|
120
|
+
assert "zai auth login groq" in unavailable.next_action
|
|
121
|
+
|
|
81
122
|
|
|
82
123
|
def test_top_level_auth_status_does_not_print_secret(monkeypatch):
|
|
83
124
|
monkeypatch.setenv("OPENAI_API_KEY", "sk-live-secret")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from zai.core.file_mentions import expand_file_mentions, extract_file_mentions
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_extract_file_mentions_keeps_order_and_uniqueness():
|
|
5
|
+
text = "explain @app.py then compare @README.md and @app.py."
|
|
6
|
+
|
|
7
|
+
assert extract_file_mentions(text) == ["app.py", "README.md"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_expand_file_mentions_loads_bounded_file_context(tmp_path):
|
|
11
|
+
(tmp_path / "app.py").write_text("print('hi')\n", encoding="utf-8")
|
|
12
|
+
|
|
13
|
+
expanded = expand_file_mentions("explain @app.py", tmp_path)
|
|
14
|
+
|
|
15
|
+
assert "explain @app.py" in expanded
|
|
16
|
+
assert "Referenced @file context" in expanded
|
|
17
|
+
assert "--- @app.py ---" in expanded
|
|
18
|
+
assert "print('hi')" in expanded
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_expand_file_mentions_skips_hidden_and_sensitive_files(tmp_path):
|
|
22
|
+
(tmp_path / ".env").write_text("API_KEY=secret\n", encoding="utf-8")
|
|
23
|
+
(tmp_path / "token.txt").write_text("secret\n", encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
expanded = expand_file_mentions("read @.env and @token.txt", tmp_path)
|
|
26
|
+
|
|
27
|
+
assert "API_KEY=secret" not in expanded
|
|
28
|
+
assert "Mention notes" in expanded
|
|
29
|
+
assert "Skipped @.env" in expanded
|
|
30
|
+
assert "Skipped @token.txt" in expanded
|
|
@@ -97,6 +97,22 @@ def test_path_completion(tmp_path):
|
|
|
97
97
|
assert not any(".secret" in candidate[0] for candidate in candidates)
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def test_file_mention_completion_in_natural_prompt(tmp_path):
|
|
101
|
+
(tmp_path / "src").mkdir()
|
|
102
|
+
(tmp_path / "sample.py").write_text("")
|
|
103
|
+
(tmp_path / ".secret").write_text("")
|
|
104
|
+
|
|
105
|
+
candidates = completion_candidates(
|
|
106
|
+
"explain @s",
|
|
107
|
+
str(tmp_path),
|
|
108
|
+
{"fix"},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
assert ("@src/", "@src/", "file mention") in candidates
|
|
112
|
+
assert ("@sample.py", "@sample.py", "file mention") in candidates
|
|
113
|
+
assert not any(".secret" in candidate[0] for candidate in candidates)
|
|
114
|
+
|
|
115
|
+
|
|
100
116
|
def test_prompt_falls_back_when_prompt_toolkit_missing(tmp_path):
|
|
101
117
|
real_import = __import__
|
|
102
118
|
|
|
@@ -182,21 +182,22 @@ def test_interactive_project_memory_commands(tmp_path, monkeypatch, capsys):
|
|
|
182
182
|
(tmp_path / "pyproject.toml").write_text("[project]\nname='demo'\n")
|
|
183
183
|
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
184
184
|
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
185
|
-
with patch("zai.cli.interactive.
|
|
186
|
-
with patch("zai.cli.interactive.plugin_loader.
|
|
187
|
-
with patch("zai.cli.interactive.
|
|
188
|
-
with patch(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
185
|
+
with patch("zai.cli.interactive.add_project"):
|
|
186
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
187
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
188
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
189
|
+
with patch(
|
|
190
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
191
|
+
side_effect=[
|
|
192
|
+
"/memory project",
|
|
193
|
+
"/memory refresh",
|
|
194
|
+
"/remember uses pytest",
|
|
195
|
+
"/forget 1",
|
|
196
|
+
EOFError(),
|
|
197
|
+
],
|
|
198
|
+
):
|
|
199
|
+
with patch("zai.cli.interactive.run_agent") as run_agent:
|
|
200
|
+
run_interactive("groq")
|
|
200
201
|
|
|
201
202
|
output = capsys.readouterr().out
|
|
202
203
|
assert "Project Memory" in output
|
|
@@ -210,26 +211,27 @@ def test_interactive_auth_commands_do_not_call_ai(tmp_path, monkeypatch, capsys)
|
|
|
210
211
|
monkeypatch.chdir(tmp_path)
|
|
211
212
|
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
212
213
|
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
213
|
-
with patch("zai.cli.interactive.
|
|
214
|
-
with patch("zai.cli.interactive.plugin_loader.
|
|
215
|
-
with patch("zai.cli.interactive.
|
|
216
|
-
with patch(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
with (
|
|
226
|
-
patch("zai.cli.interactive.Prompt.ask", return_value="gemini-key"),
|
|
227
|
-
patch("zai.cli.interactive.Confirm.ask", return_value=False),
|
|
228
|
-
patch("zai.cli.interactive.save_provider_key") as save,
|
|
229
|
-
patch("zai.cli.interactive.delete_provider_key", return_value=True) as delete,
|
|
230
|
-
patch("zai.cli.interactive.run_agent") as run_agent,
|
|
214
|
+
with patch("zai.cli.interactive.add_project"):
|
|
215
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
216
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
217
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
218
|
+
with patch(
|
|
219
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
220
|
+
side_effect=[
|
|
221
|
+
"/auth status",
|
|
222
|
+
"/auth login gemini",
|
|
223
|
+
"/auth logout gemini",
|
|
224
|
+
EOFError(),
|
|
225
|
+
],
|
|
231
226
|
):
|
|
232
|
-
|
|
227
|
+
with (
|
|
228
|
+
patch("zai.cli.interactive.Prompt.ask", return_value="gemini-key"),
|
|
229
|
+
patch("zai.cli.interactive.Confirm.ask", return_value=False),
|
|
230
|
+
patch("zai.cli.interactive.save_provider_key") as save,
|
|
231
|
+
patch("zai.cli.interactive.delete_provider_key", return_value=True) as delete,
|
|
232
|
+
patch("zai.cli.interactive.run_agent") as run_agent,
|
|
233
|
+
):
|
|
234
|
+
run_interactive("groq")
|
|
233
235
|
|
|
234
236
|
output = capsys.readouterr().out
|
|
235
237
|
assert "Provider status" in output
|
|
@@ -242,6 +244,7 @@ def test_interactive_auth_commands_do_not_call_ai(tmp_path, monkeypatch, capsys)
|
|
|
242
244
|
def test_interactive_context_and_providers_do_not_call_ai(tmp_path, monkeypatch, capsys):
|
|
243
245
|
monkeypatch.chdir(tmp_path)
|
|
244
246
|
(tmp_path / "app.py").write_text("print('hi')\n")
|
|
247
|
+
(tmp_path / "ZAI.md").write_text("Use pytest.\n")
|
|
245
248
|
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
246
249
|
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
247
250
|
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
@@ -252,6 +255,7 @@ def test_interactive_context_and_providers_do_not_call_ai(tmp_path, monkeypatch,
|
|
|
252
255
|
side_effect=[
|
|
253
256
|
"/providers",
|
|
254
257
|
"/context",
|
|
258
|
+
"/rules",
|
|
255
259
|
"/context tokens",
|
|
256
260
|
"/context summary",
|
|
257
261
|
EOFError(),
|
|
@@ -263,6 +267,9 @@ def test_interactive_context_and_providers_do_not_call_ai(tmp_path, monkeypatch,
|
|
|
263
267
|
output = capsys.readouterr().out
|
|
264
268
|
assert "Provider status" in output
|
|
265
269
|
assert "Project context" in output
|
|
270
|
+
assert "Project rules loaded:" in output
|
|
271
|
+
assert "ZAI.md" in output
|
|
272
|
+
assert "Project Rules" in output
|
|
266
273
|
assert "unread files are not loaded" in output
|
|
267
274
|
assert "Estimated tokens used" in output
|
|
268
275
|
assert "No compacted summary" in output
|
|
@@ -270,6 +277,60 @@ def test_interactive_context_and_providers_do_not_call_ai(tmp_path, monkeypatch,
|
|
|
270
277
|
run_agent.assert_not_called()
|
|
271
278
|
|
|
272
279
|
|
|
280
|
+
def test_interactive_pins_project_rules_before_agent_call(tmp_path, monkeypatch):
|
|
281
|
+
monkeypatch.chdir(tmp_path)
|
|
282
|
+
(tmp_path / "ZAI.md").write_text("Use the custom test command.\n")
|
|
283
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
284
|
+
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
285
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
286
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
287
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
288
|
+
with patch(
|
|
289
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
290
|
+
side_effect=["do work", EOFError()],
|
|
291
|
+
):
|
|
292
|
+
with (
|
|
293
|
+
patch(
|
|
294
|
+
"zai.cli.interactive.run_agent",
|
|
295
|
+
return_value="done",
|
|
296
|
+
) as run_agent,
|
|
297
|
+
patch("zai.cli.interactive.Confirm.ask", return_value=False),
|
|
298
|
+
):
|
|
299
|
+
run_interactive("groq")
|
|
300
|
+
|
|
301
|
+
history = run_agent.call_args.args[1]
|
|
302
|
+
assert any(
|
|
303
|
+
message.pinned and "Use the custom test command" in message.content
|
|
304
|
+
for message in history
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def test_interactive_expands_file_mentions_before_agent_call(tmp_path, monkeypatch):
|
|
309
|
+
monkeypatch.chdir(tmp_path)
|
|
310
|
+
(tmp_path / "app.py").write_text("print('hello')\n")
|
|
311
|
+
with patch("zai.cli.interactive.has_available_provider", return_value=True):
|
|
312
|
+
with patch("zai.cli.interactive.fire_hook", return_value=True):
|
|
313
|
+
with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
|
|
314
|
+
with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
|
|
315
|
+
with patch("zai.cli.interactive._connect_mcp_servers"):
|
|
316
|
+
with patch(
|
|
317
|
+
"zai.cli.interactive.InteractiveInput.prompt",
|
|
318
|
+
side_effect=["explain @app.py", EOFError()],
|
|
319
|
+
):
|
|
320
|
+
with (
|
|
321
|
+
patch(
|
|
322
|
+
"zai.cli.interactive.run_agent",
|
|
323
|
+
return_value="done",
|
|
324
|
+
) as run_agent,
|
|
325
|
+
patch("zai.cli.interactive.Confirm.ask", return_value=False),
|
|
326
|
+
):
|
|
327
|
+
run_interactive("groq")
|
|
328
|
+
|
|
329
|
+
prompt = run_agent.call_args.args[0]
|
|
330
|
+
assert "Referenced @file context" in prompt
|
|
331
|
+
assert "print('hello')" in prompt
|
|
332
|
+
|
|
333
|
+
|
|
273
334
|
def test_interactive_manual_compact_summarizes_history(tmp_path, monkeypatch, capsys):
|
|
274
335
|
monkeypatch.chdir(tmp_path)
|
|
275
336
|
prompts = [
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from zai.core.project_rules import (
|
|
2
|
+
format_project_rules,
|
|
3
|
+
load_project_rules,
|
|
4
|
+
project_rules_prompt,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_load_project_rules_from_root_and_current_folder(tmp_path):
|
|
9
|
+
(tmp_path / ".git").mkdir()
|
|
10
|
+
(tmp_path / "ZAI.md").write_text("Use pytest.\n", encoding="utf-8")
|
|
11
|
+
nested = tmp_path / "pkg"
|
|
12
|
+
nested.mkdir()
|
|
13
|
+
(nested / "AGENTS.md").write_text("Nested rule.\n", encoding="utf-8")
|
|
14
|
+
|
|
15
|
+
rules = load_project_rules(nested)
|
|
16
|
+
|
|
17
|
+
assert [rule.relative_path for rule in rules] == ["ZAI.md", "pkg/AGENTS.md"]
|
|
18
|
+
assert "Use pytest" in project_rules_prompt(rules)
|
|
19
|
+
assert "pkg/AGENTS.md" in format_project_rules(rules)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_project_rules_redact_secrets_and_skip_large_files(tmp_path):
|
|
23
|
+
(tmp_path / "ZAI.md").write_text(
|
|
24
|
+
"api_key = sk-secret-value-1234567890\nFollow style.\n",
|
|
25
|
+
encoding="utf-8",
|
|
26
|
+
)
|
|
27
|
+
(tmp_path / "CLAUDE.md").write_text("x" * (70 * 1024), encoding="utf-8")
|
|
28
|
+
|
|
29
|
+
rules = load_project_rules(tmp_path)
|
|
30
|
+
|
|
31
|
+
assert len(rules) == 1
|
|
32
|
+
assert "sk-secret" not in rules[0].content
|
|
33
|
+
assert "[REDACTED_SECRET]" in rules[0].content
|
|
@@ -141,18 +141,35 @@ def test_context_command_is_honest_about_unloaded_files(tmp_path, monkeypatch):
|
|
|
141
141
|
app = settings_app()
|
|
142
142
|
monkeypatch.chdir(tmp_path)
|
|
143
143
|
(tmp_path / "app.py").write_text("print('hi')\n")
|
|
144
|
+
(tmp_path / "ZAI.md").write_text("Use pytest.\n")
|
|
144
145
|
|
|
145
146
|
result = CliRunner().invoke(app, ["context"])
|
|
146
147
|
|
|
147
148
|
assert result.exit_code == 0
|
|
148
149
|
assert f"Folder: {tmp_path}" in result.output
|
|
149
150
|
assert "Project memory: missing" in result.output
|
|
151
|
+
assert "Project rules:" in result.output
|
|
152
|
+
assert "ZAI.md" in result.output
|
|
150
153
|
assert "Repo map/index: not loaded yet" in result.output
|
|
151
154
|
assert "unread files are not loaded" in result.output
|
|
152
155
|
assert "Estimated tokens used" in result.output
|
|
153
156
|
assert "app.py is loaded" not in result.output
|
|
154
157
|
|
|
155
158
|
|
|
159
|
+
def test_rules_command_shows_loaded_project_rules(tmp_path, monkeypatch):
|
|
160
|
+
app = settings_app()
|
|
161
|
+
monkeypatch.chdir(tmp_path)
|
|
162
|
+
(tmp_path / ".git").mkdir()
|
|
163
|
+
(tmp_path / ".zai").mkdir()
|
|
164
|
+
(tmp_path / ".zai" / "rules.md").write_text("Never commit secrets.\n")
|
|
165
|
+
|
|
166
|
+
result = CliRunner().invoke(app, ["rules"])
|
|
167
|
+
|
|
168
|
+
assert result.exit_code == 0
|
|
169
|
+
assert "Project Rules" in result.output
|
|
170
|
+
assert ".zai/rules.md" in result.output
|
|
171
|
+
|
|
172
|
+
|
|
156
173
|
def test_context_tokens_and_summary_commands(tmp_path, monkeypatch):
|
|
157
174
|
app = settings_app()
|
|
158
175
|
monkeypatch.chdir(tmp_path)
|
|
@@ -255,6 +272,25 @@ def test_model_test_passes_and_reports_unavailable():
|
|
|
255
272
|
assert "Model unavailable" in unavailable.output
|
|
256
273
|
|
|
257
274
|
|
|
275
|
+
def test_model_test_reports_actionable_auth_failure():
|
|
276
|
+
from zai.core.errors import AuthenticationError
|
|
277
|
+
|
|
278
|
+
app = settings_app()
|
|
279
|
+
runner = CliRunner()
|
|
280
|
+
provider = MagicMock()
|
|
281
|
+
provider.name = "groq"
|
|
282
|
+
provider.is_available.return_value = True
|
|
283
|
+
provider.chat.side_effect = AuthenticationError("groq", "bad key")
|
|
284
|
+
|
|
285
|
+
with patch("zai.core.fallback.get_provider", return_value=provider):
|
|
286
|
+
result = runner.invoke(app, ["model", "test", "groq"])
|
|
287
|
+
|
|
288
|
+
assert result.exit_code == 0
|
|
289
|
+
assert "invalid credentials" in result.output
|
|
290
|
+
assert "zai auth login groq" in result.output
|
|
291
|
+
assert "bad key" in result.output
|
|
292
|
+
|
|
293
|
+
|
|
258
294
|
def test_model_configure_updates_builtin_runtime_settings():
|
|
259
295
|
app = settings_app()
|
|
260
296
|
config = {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.2"
|