zai-cli 0.1.6__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.
Files changed (110) hide show
  1. {zai_cli-0.1.6 → zai_cli-0.1.8}/CHANGELOG.md +30 -0
  2. {zai_cli-0.1.6/zai_cli.egg-info → zai_cli-0.1.8}/PKG-INFO +71 -7
  3. {zai_cli-0.1.6 → zai_cli-0.1.8}/README.md +70 -6
  4. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_interactive.py +51 -21
  5. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_settings_cli.py +63 -4
  6. zai_cli-0.1.8/zai/__init__.py +1 -0
  7. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/interactive.py +63 -39
  8. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/settings.py +10 -43
  9. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/setup_wizard.py +18 -2
  10. zai_cli-0.1.8/zai/cli/status.py +202 -0
  11. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/main.py +2 -0
  12. {zai_cli-0.1.6 → zai_cli-0.1.8/zai_cli.egg-info}/PKG-INFO +71 -7
  13. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai_cli.egg-info/SOURCES.txt +1 -0
  14. zai_cli-0.1.6/zai/__init__.py +0 -1
  15. {zai_cli-0.1.6 → zai_cli-0.1.8}/LICENSE +0 -0
  16. {zai_cli-0.1.6 → zai_cli-0.1.8}/MANIFEST.in +0 -0
  17. {zai_cli-0.1.6 → zai_cli-0.1.8}/pyproject.toml +0 -0
  18. {zai_cli-0.1.6 → zai_cli-0.1.8}/scripts/release_preflight.py +0 -0
  19. {zai_cli-0.1.6 → zai_cli-0.1.8}/setup.cfg +0 -0
  20. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_agent.py +0 -0
  21. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_auth.py +0 -0
  22. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_browser.py +0 -0
  23. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_code_runner.py +0 -0
  24. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_config_main.py +0 -0
  25. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_core.py +0 -0
  26. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_errors.py +0 -0
  27. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_hooks_skills_session.py +0 -0
  28. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_input.py +0 -0
  29. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_integrations_cli.py +0 -0
  30. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_mcp.py +0 -0
  31. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_plugins.py +0 -0
  32. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_process.py +0 -0
  33. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_project_memory.py +0 -0
  34. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_providers.py +0 -0
  35. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_release_preflight.py +0 -0
  36. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_search.py +0 -0
  37. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_security.py +0 -0
  38. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_storage.py +0 -0
  39. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_streaming.py +0 -0
  40. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_tool_schema.py +0 -0
  41. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_tools.py +0 -0
  42. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_undo.py +0 -0
  43. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_utilities.py +0 -0
  44. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_vision.py +0 -0
  45. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_watch.py +0 -0
  46. {zai_cli-0.1.6 → zai_cli-0.1.8}/tests/test_workflows.py +0 -0
  47. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/__main__.py +0 -0
  48. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/__init__.py +0 -0
  49. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/common.py +0 -0
  50. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/integrations.py +0 -0
  51. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/utilities.py +0 -0
  52. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/cli/workflows.py +0 -0
  53. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/commands/commit.md +0 -0
  54. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/commands/explain.md +0 -0
  55. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/commands/feature.md +0 -0
  56. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/commands/fix.md +0 -0
  57. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/commands/review.md +0 -0
  58. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/config.py +0 -0
  59. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/__init__.py +0 -0
  60. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/agent.py +0 -0
  61. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/auth.py +0 -0
  62. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/cancellation.py +0 -0
  63. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/commands.py +0 -0
  64. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/context.py +0 -0
  65. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/errors.py +0 -0
  66. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/fallback.py +0 -0
  67. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/hooks.py +0 -0
  68. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/memory.py +0 -0
  69. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/process.py +0 -0
  70. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/project_memory.py +0 -0
  71. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/repomap.py +0 -0
  72. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/runtime.py +0 -0
  73. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/security.py +0 -0
  74. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/session.py +0 -0
  75. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/storage.py +0 -0
  76. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/streaming.py +0 -0
  77. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/tool_schema.py +0 -0
  78. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/undo.py +0 -0
  79. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/core/watch.py +0 -0
  80. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/mcp/__init__.py +0 -0
  81. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/mcp/client.py +0 -0
  82. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/mcp/manager.py +0 -0
  83. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/plugins/__init__.py +0 -0
  84. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/plugins/base.py +0 -0
  85. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/plugins/loader.py +0 -0
  86. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/__init__.py +0 -0
  87. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/anthropic.py +0 -0
  88. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/base.py +0 -0
  89. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/cerebras.py +0 -0
  90. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/gemini.py +0 -0
  91. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/groq.py +0 -0
  92. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/ollama.py +0 -0
  93. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/openai.py +0 -0
  94. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/openrouter.py +0 -0
  95. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/providers/qwen.py +0 -0
  96. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/skills/__init__.py +0 -0
  97. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/skills/registry.py +0 -0
  98. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/__init__.py +0 -0
  99. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/browser.py +0 -0
  100. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/code_runner.py +0 -0
  101. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/files.py +0 -0
  102. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/git.py +0 -0
  103. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/search.py +0 -0
  104. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/tools/vision.py +0 -0
  105. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/ui/__init__.py +0 -0
  106. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai/ui/input.py +0 -0
  107. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai_cli.egg-info/dependency_links.txt +0 -0
  108. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai_cli.egg-info/entry_points.txt +0 -0
  109. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai_cli.egg-info/requires.txt +0 -0
  110. {zai_cli-0.1.6 → zai_cli-0.1.8}/zai_cli.egg-info/top_level.txt +0 -0
@@ -5,6 +5,36 @@ 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
+
30
+ ## [0.1.7] - 2026-06-28
31
+
32
+ ### Changed
33
+
34
+ - Changed first-run interactive onboarding so `zai` offers provider login inside
35
+ the interactive startup flow instead of sending users back to an external
36
+ command.
37
+
8
38
  ## [0.1.6] - 2026-06-28
9
39
 
10
40
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zai-cli
3
- Version: 0.1.6
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
- ## Context management
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 the same model-aware
456
- context pipeline:
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
- - older turns are compacted into a structured summary while recent turns stay
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 replaces the previous fixed 100k limit and manual last-message truncation.
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
- ## Context management
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 the same model-aware
399
- context pipeline:
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
- - older turns are compacted into a structured summary while recent turns stay
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 replaces the previous fixed 100k limit and manual last-message truncation.
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
 
@@ -53,25 +53,27 @@ def test_interactive_plain_commands_show_local_help(tmp_path, monkeypatch, capsy
53
53
 
54
54
  def test_interactive_offers_setup_when_no_provider(tmp_path, monkeypatch, capsys):
55
55
  monkeypatch.chdir(tmp_path)
56
- with patch("zai.cli.interactive.has_available_provider", return_value=False):
57
- with patch("zai.cli.interactive.Confirm.ask", return_value=True):
58
- with patch(
59
- "zai.cli.interactive._run_key_setup_interactive",
60
- return_value=True,
61
- ) as setup:
62
- with patch("zai.cli.interactive.fire_hook", return_value=True):
63
- with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
64
- with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
65
- with patch("zai.cli.interactive._connect_mcp_servers"):
66
- with patch(
67
- "zai.cli.interactive.InteractiveInput.prompt",
68
- side_effect=EOFError(),
69
- ):
70
- run_interactive("groq")
56
+ with patch(
57
+ "zai.cli.interactive.has_available_provider",
58
+ side_effect=[False, False, True],
59
+ ):
60
+ with patch("zai.cli.interactive.Prompt.ask", return_value="groq"):
61
+ with patch("zai.cli.interactive.Confirm.ask", return_value=False):
62
+ with patch("zai.cli.interactive.save_provider_key") as save:
63
+ with patch("zai.cli.interactive.fire_hook", return_value=True):
64
+ with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
65
+ with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
66
+ with patch("zai.cli.interactive._connect_mcp_servers"):
67
+ with patch(
68
+ "zai.cli.interactive.InteractiveInput.prompt",
69
+ side_effect=EOFError(),
70
+ ):
71
+ run_interactive("groq")
71
72
 
72
73
  output = capsys.readouterr().out
73
74
  assert "No AI provider available" in output
74
- setup.assert_called_once()
75
+ assert "Auth login" in output
76
+ save.assert_called_once_with("groq", "groq")
75
77
 
76
78
 
77
79
  def test_interactive_key_setup_saves_entered_key(capsys):
@@ -81,10 +83,15 @@ def test_interactive_key_setup_saves_entered_key(capsys):
81
83
 
82
84
  with patch("zai.cli.setup_wizard.get_api_key", return_value=None):
83
85
  with patch("zai.cli.setup_wizard.Confirm.ask", return_value=False):
84
- with patch("zai.cli.setup_wizard.Prompt.ask", side_effect=lambda *a, **k: next(answers)):
85
- with patch("zai.cli.setup_wizard.save_provider_key") as save_key:
86
- with patch("zai.cli.setup_wizard.has_available_provider", side_effect=[False, True, True]):
87
- assert _run_key_setup_interactive()
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()
88
95
 
89
96
  output = capsys.readouterr().out
90
97
  assert "zai setup" in output
@@ -225,13 +232,36 @@ def test_interactive_auth_commands_do_not_call_ai(tmp_path, monkeypatch, capsys)
225
232
  run_interactive("groq")
226
233
 
227
234
  output = capsys.readouterr().out
228
- assert "Authentication status" in output
235
+ assert "Provider status" in output
229
236
  assert "Saved Gemini API key" in output
230
237
  save.assert_called_once_with("gemini", "gemini-key")
231
238
  delete.assert_called_once_with("gemini")
232
239
  run_agent.assert_not_called()
233
240
 
234
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
+
235
265
  def test_interactive_offers_project_memory_after_agent_task(tmp_path, monkeypatch, capsys):
236
266
  from zai.core.project_memory import load_project_memory
237
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=MagicMock())) is True
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
- unknown = runner.invoke(app, ["model", "set", "missing"])
83
- usage = runner.invoke(app, ["model", "fallback", "maybe"])
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 "gemini-3.5-flash" in listed.output
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 get_api_key, get_models, load_config
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", "memory", "remember", "forget", "commands",
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
- table = Table(title="Available models", border_style="cyan")
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
- rows = auth_status(provider)
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:
@@ -341,6 +318,45 @@ def _run_auth_command(stripped: str) -> None:
341
318
  )
342
319
 
343
320
 
321
+ def _run_first_run_auth_menu() -> bool:
322
+ while not has_available_provider():
323
+ choice = Prompt.ask(
324
+ "Connect provider",
325
+ choices=[
326
+ "groq",
327
+ "gemini",
328
+ "openai",
329
+ "anthropic",
330
+ "ollama",
331
+ "setup",
332
+ "status",
333
+ "exit",
334
+ ],
335
+ default="groq",
336
+ )
337
+ if choice == "exit":
338
+ return False
339
+ if choice == "status":
340
+ _show_auth_status()
341
+ continue
342
+ if choice == "setup":
343
+ return _run_key_setup_interactive()
344
+ if choice == "ollama":
345
+ console.print(
346
+ "[cyan]Ollama does not need an API key.[/cyan]\n"
347
+ "Start Ollama locally, then run [bold]zai model test ollama[/bold]."
348
+ )
349
+ return has_available_provider()
350
+ _run_auth_login(choice)
351
+ if has_available_provider():
352
+ return True
353
+ console.print(
354
+ "[yellow]No provider is connected yet. Choose another provider "
355
+ "or type exit.[/yellow]"
356
+ )
357
+ return True
358
+
359
+
344
360
  def _run_key_setup_interactive() -> bool:
345
361
  return run_setup_wizard(console=console)
346
362
 
@@ -443,12 +459,11 @@ def run_interactive(model: str = None) -> None:
443
459
  " 3. Connect Anthropic API key\n"
444
460
  " 4. Use local Ollama\n"
445
461
  " 5. Advanced: run Codex/Claude official CLI login\n\n"
446
- "Run [cyan]zai auth status[/cyan] to inspect providers.",
462
+ "Choose a provider below. You can also use [cyan]status[/cyan] "
463
+ "or [cyan]setup[/cyan].",
447
464
  border_style="red",
448
465
  ))
449
- if not Confirm.ask("Set up an API key now?", default=True):
450
- return
451
- if not _run_key_setup_interactive():
466
+ if not _run_first_run_auth_menu():
452
467
  return
453
468
 
454
469
  try:
@@ -588,6 +603,15 @@ def run_interactive(model: str = None) -> None:
588
603
  _run_key_setup_interactive()
589
604
  elif stripped == "/auth" or stripped.startswith("/auth "):
590
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
+ ))
591
615
  elif stripped in {"/exit", "/quit"}:
592
616
  fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
593
617
  console.print("\n[dim]Goodbye![/dim]")