zettcode 0.1.2__tar.gz → 0.1.3__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 (144) hide show
  1. zettcode-0.1.3/PKG-INFO +142 -0
  2. zettcode-0.1.3/README.md +111 -0
  3. zettcode-0.1.3/README.zh-CN.md +106 -0
  4. {zettcode-0.1.2 → zettcode-0.1.3}/pyproject.toml +10 -1
  5. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/__init__.py +1 -1
  6. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/agent.py +48 -2
  7. zettcode-0.1.3/src/zettcode/app/agent/ask.py +96 -0
  8. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/blocks.py +30 -44
  9. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/compaction.py +14 -0
  10. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/entries.py +3 -1
  11. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/export.py +5 -0
  12. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/projection.py +18 -4
  13. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/replay.py +7 -2
  14. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/runtime.py +52 -15
  15. zettcode-0.1.3/src/zettcode/app/agent/side.py +109 -0
  16. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/store.py +16 -1
  17. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/subagents.py +40 -23
  18. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/transcript.py +17 -6
  19. zettcode-0.1.3/src/zettcode/app/brand.py +63 -0
  20. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/commands.py +12 -0
  21. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/app.py +164 -12
  22. zettcode-0.1.3/src/zettcode/app/ui/boot.py +101 -0
  23. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/commands.py +21 -0
  24. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/keys.py +7 -0
  25. zettcode-0.1.3/src/zettcode/app/ui/labels.py +69 -0
  26. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/notices.py +81 -2
  27. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/rows.py +3 -3
  28. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/shell.py +24 -0
  29. zettcode-0.1.3/src/zettcode/app/ui/updating.py +97 -0
  30. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/__init__.py +12 -0
  31. zettcode-0.1.3/src/zettcode/app/ui/widgets/ask_page.py +386 -0
  32. zettcode-0.1.3/src/zettcode/app/ui/widgets/update_page.py +62 -0
  33. zettcode-0.1.3/src/zettcode/app/ui/widgets/welcome.py +21 -0
  34. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/cli.py +134 -3
  35. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/config.py +24 -19
  36. zettcode-0.1.3/src/zettcode/paths.py +29 -0
  37. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/builtins.py +8 -9
  38. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/app.py +26 -1
  39. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/theme.py +8 -0
  40. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/decoder.py +22 -0
  41. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/events.py +5 -1
  42. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/runner.py +28 -17
  43. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/terminal.py +11 -24
  44. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/theme_file.py +1 -0
  45. zettcode-0.1.3/src/zettcode/update.py +301 -0
  46. zettcode-0.1.2/PKG-INFO +0 -136
  47. zettcode-0.1.2/README.md +0 -107
  48. zettcode-0.1.2/README.zh-CN.md +0 -98
  49. zettcode-0.1.2/src/zettcode/app/ui/widgets/welcome.py +0 -19
  50. {zettcode-0.1.2 → zettcode-0.1.3}/.gitignore +0 -0
  51. {zettcode-0.1.2 → zettcode-0.1.3}/LICENSE +0 -0
  52. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/_compat.py +0 -0
  53. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/__init__.py +0 -0
  54. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/__init__.py +0 -0
  55. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/approval.py +0 -0
  56. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/capabilities.py +0 -0
  57. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/context.py +0 -0
  58. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/mcp.py +0 -0
  59. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/mentions.py +0 -0
  60. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/rendering.py +0 -0
  61. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/rows.py +0 -0
  62. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/__init__.py +0 -0
  63. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/metadata.py +0 -0
  64. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/records.py +0 -0
  65. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/title.py +0 -0
  66. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/trace/script.js +0 -0
  67. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/trace/style.css +0 -0
  68. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/trace/template.html +0 -0
  69. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/usage.py +0 -0
  70. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/registry.py +0 -0
  71. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/__init__.py +0 -0
  72. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/clipboard.py +0 -0
  73. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/demo.py +0 -0
  74. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/sessions.py +0 -0
  75. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/settings.py +0 -0
  76. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/approval_page.py +0 -0
  77. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/completer.py +0 -0
  78. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/composer.py +0 -0
  79. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/context_page.py +0 -0
  80. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/model_page.py +0 -0
  81. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/panel.py +0 -0
  82. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/root.py +0 -0
  83. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/sessions_page.py +0 -0
  84. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/steering.py +0 -0
  85. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/transcript.py +0 -0
  86. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/__init__.py +0 -0
  87. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/container.py +0 -0
  88. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/extension.py +0 -0
  89. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/loader.py +0 -0
  90. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/mixins.py +0 -0
  91. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/plugin.py +0 -0
  92. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/state.py +0 -0
  93. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/__init__.py +0 -0
  94. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/capabilities.py +0 -0
  95. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/console.py +0 -0
  96. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/__init__.py +0 -0
  97. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/events.py +0 -0
  98. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/focus.py +0 -0
  99. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/geometry.py +0 -0
  100. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/host.py +0 -0
  101. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/keymap.py +0 -0
  102. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/scheduler.py +0 -0
  103. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/screen.py +0 -0
  104. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/widget.py +0 -0
  105. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/gallery.py +0 -0
  106. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/glyphs.py +0 -0
  107. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/__init__.py +0 -0
  108. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/bridge.py +0 -0
  109. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/keys.py +0 -0
  110. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/reader.py +0 -0
  111. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/__init__.py +0 -0
  112. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/box.py +0 -0
  113. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/overlay.py +0 -0
  114. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/scroll.py +0 -0
  115. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/solver.py +0 -0
  116. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/spacing.py +0 -0
  117. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/__init__.py +0 -0
  118. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/canvas.py +0 -0
  119. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/code.py +0 -0
  120. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/color.py +0 -0
  121. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/renderer.py +0 -0
  122. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/rich_text.py +0 -0
  123. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/style.py +0 -0
  124. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/sweep.py +0 -0
  125. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/text.py +0 -0
  126. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/testing/__init__.py +0 -0
  127. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/testing/harness.py +0 -0
  128. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/testing/snapshot.py +0 -0
  129. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/__init__.py +0 -0
  130. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/collapsible.py +0 -0
  131. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/completion.py +0 -0
  132. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/dialog.py +0 -0
  133. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/diff.py +0 -0
  134. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/list.py +0 -0
  135. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/list_page.py +0 -0
  136. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/markdown.py +0 -0
  137. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/progress.py +0 -0
  138. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/rich_text.py +0 -0
  139. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/status.py +0 -0
  140. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/table.py +0 -0
  141. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/tasks.py +0 -0
  142. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/text.py +0 -0
  143. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/textarea.py +0 -0
  144. {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/toast.py +0 -0
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.5
2
+ Name: zettcode
3
+ Version: 0.1.3
4
+ Summary: A focused terminal coding agent powered by zett-agent
5
+ Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
6
+ Project-URL: Issues, https://github.com/Chang-LeHung/zettcode/issues
7
+ Author: Chang-LeHung
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent,coding-agent,llm,terminal,tui
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: exceptiongroup<2,>=1.2; python_version < '3.11'
24
+ Requires-Dist: pyperclip<2,>=1.9
25
+ Requires-Dist: setproctitle<2,>=1.3.7; os_name == 'posix'
26
+ Requires-Dist: tomli<3,>=2; python_version < '3.11'
27
+ Requires-Dist: truststore<1,>=0.10
28
+ Requires-Dist: wcwidth<1,>=0.2
29
+ Requires-Dist: zett-agent<0.2,>=0.1.11
30
+ Description-Content-Type: text/markdown
31
+
32
+ <p align="center">
33
+ <img src="docs/public/logo.svg" width="88" height="88" alt="ZettCode pixel robot with a warm heart" />
34
+ </p>
35
+ <h1 align="center">ZettCode</h1>
36
+ <p align="center"><strong>Your terminal. Your coding partner.</strong></p>
37
+ <p align="center">A focused coding agent that reads your project, shows its work, and keeps you in control.</p>
38
+ <p align="center">
39
+ <strong>English</strong> · <a href="README.zh-CN.md">简体中文</a><br />
40
+ <a href="https://chang-lehung.github.io/zettcode/guide/getting-started">Get started</a> ·
41
+ <a href="https://chang-lehung.github.io/zettcode/">Documentation</a> ·
42
+ <a href="https://chang-lehung.github.io/zettcode/guide/commands">Commands</a>
43
+ </p>
44
+
45
+ <p align="center">
46
+ <img src="docs/public/terminal.svg" width="1000" alt="ZettCode showing a task, thinking, file reads, a code edit, passing tests, and a Markdown answer" />
47
+ </p>
48
+ <p align="center"><sub>Illustrative conversation, rendered with ZettCode’s actual terminal widgets.</sub></p>
49
+
50
+ ## Get running
51
+
52
+ You need **Python 3.10+**, a UTF-8 terminal, and an **OpenAI-compatible API**.
53
+ Works on macOS, Linux, and Windows (use Windows Terminal).
54
+
55
+ ### 1. Install
56
+
57
+ ```bash
58
+ uv tool install zettcode
59
+ # or: pip install zettcode
60
+ ```
61
+
62
+ ### 2. Connect a model
63
+
64
+ Create `~/.zettcode/config.toml` (and its parent directory) with one model:
65
+
66
+ ```toml
67
+ [[models]]
68
+ model = "deepseek-v4-pro" # the model id sent to DeepSeek
69
+ display_model = "DeepSeek Pro" # the name shown in ZettCode
70
+ token = "sk-..." # replace with your DeepSeek API key
71
+ base_url = "https://api.deepseek.com" # DeepSeek's OpenAI-compatible API root
72
+ context_window = 1000000 # verify against the endpoint's current limits
73
+ multimodal = false # this model accepts text, not images
74
+ ```
75
+
76
+ Get a key from the [DeepSeek platform](https://platform.deepseek.com/), not from
77
+ the chat website. Alternatively, omit `token` and set `OPENAI_API_KEY` to that
78
+ key. Add more `[[models]]` entries to switch with `/model`; the first is selected
79
+ at startup. See the
80
+ [configuration reference](https://chang-lehung.github.io/zettcode/guide/config)
81
+ for complete examples, defaults, and troubleshooting.
82
+
83
+ ### 3. Start a task
84
+
85
+ ```bash
86
+ cd /path/to/project
87
+ zettcode
88
+ # or: zettcode --workspace /path/to/project
89
+ ```
90
+
91
+ Describe a change and press **Enter**. Type `/` for commands, `@` to reference a
92
+ skill, and **Ctrl-C** to stop a request. **Ctrl-D** with an empty composer exits
93
+ and prints the command to resume your session.
94
+
95
+ ## Built for the way you work
96
+
97
+ | | |
98
+ | --- | --- |
99
+ | **See every step** | Reasoning, answers, and tool calls stream as readable rows — not raw JSON. |
100
+ | **Keep the decision** | Review shell commands before they run. When the agent needs your input, choose an option or type an answer. |
101
+ | **Keep moving** | Send a steering message during a reply. Use `/btw` for a side question that stays out of later context. |
102
+ | **Come back later** | `/resume` restores a conversation; `/export` saves a readable HTML record. |
103
+ | **Understand the context** | Monitor token usage and caching, inspect `/context`, and compact when needed. |
104
+ | **Bring your tools** | Use project `AGENTS.md` instructions, skills, MCP servers, and Python plugins. |
105
+
106
+ The palette follows your terminal’s background, or your own `theme.toml`.
107
+ Select and copy text, scroll through answers, and paste images when your model
108
+ supports them.
109
+
110
+ ## Find your next step
111
+
112
+ The **[user guide](https://chang-lehung.github.io/zettcode/guide/overview)** is
113
+ available in English and [简体中文](https://chang-lehung.github.io/zettcode/zh/guide/overview).
114
+
115
+ | First time | Everyday use | Configuration & extensions |
116
+ | --- | --- | --- |
117
+ | [Getting started](https://chang-lehung.github.io/zettcode/guide/getting-started) | [Keys & mouse](https://chang-lehung.github.io/zettcode/guide/keys) | [Configuration](https://chang-lehung.github.io/zettcode/guide/config) |
118
+ | [The interface](https://chang-lehung.github.io/zettcode/guide/interface) | [Commands](https://chang-lehung.github.io/zettcode/guide/commands) | [Models & context](https://chang-lehung.github.io/zettcode/guide/models) |
119
+ | [Problems & questions](https://chang-lehung.github.io/zettcode/guide/faq) | [Sessions](https://chang-lehung.github.io/zettcode/guide/sessions) | [Project instructions](https://chang-lehung.github.io/zettcode/guide/instructions) |
120
+ | | | [Skills & MCP](https://chang-lehung.github.io/zettcode/guide/skills-and-mcp) · [Plugins](https://chang-lehung.github.io/zettcode/guide/plugins) |
121
+
122
+ ## Development
123
+
124
+ ```bash
125
+ uv sync # install dependencies
126
+ make check # Ruff, mypy, and tests
127
+ make hooks # enforce mypy before commits
128
+ make demo # browse terminal widgets
129
+ make docs # preview the user guide locally
130
+ uv run zettcode --workspace . # run from the checkout
131
+ ```
132
+
133
+ ZettCode provides the terminal application; the agent runtime is
134
+ [`zett-agent`](https://github.com/Chang-LeHung/zett-agent). Contributor notes and
135
+ invariants live in [`AGENTS.md`](AGENTS.md) and
136
+ [`docs/internal/`](docs/internal/), separately from the user guide. See
137
+ [`docs/internal/site-design.md`](docs/internal/site-design.md) for preview and
138
+ asset-generation instructions.
139
+
140
+ ## License
141
+
142
+ MIT. See [`LICENSE`](LICENSE).
@@ -0,0 +1,111 @@
1
+ <p align="center">
2
+ <img src="docs/public/logo.svg" width="88" height="88" alt="ZettCode pixel robot with a warm heart" />
3
+ </p>
4
+ <h1 align="center">ZettCode</h1>
5
+ <p align="center"><strong>Your terminal. Your coding partner.</strong></p>
6
+ <p align="center">A focused coding agent that reads your project, shows its work, and keeps you in control.</p>
7
+ <p align="center">
8
+ <strong>English</strong> · <a href="README.zh-CN.md">简体中文</a><br />
9
+ <a href="https://chang-lehung.github.io/zettcode/guide/getting-started">Get started</a> ·
10
+ <a href="https://chang-lehung.github.io/zettcode/">Documentation</a> ·
11
+ <a href="https://chang-lehung.github.io/zettcode/guide/commands">Commands</a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <img src="docs/public/terminal.svg" width="1000" alt="ZettCode showing a task, thinking, file reads, a code edit, passing tests, and a Markdown answer" />
16
+ </p>
17
+ <p align="center"><sub>Illustrative conversation, rendered with ZettCode’s actual terminal widgets.</sub></p>
18
+
19
+ ## Get running
20
+
21
+ You need **Python 3.10+**, a UTF-8 terminal, and an **OpenAI-compatible API**.
22
+ Works on macOS, Linux, and Windows (use Windows Terminal).
23
+
24
+ ### 1. Install
25
+
26
+ ```bash
27
+ uv tool install zettcode
28
+ # or: pip install zettcode
29
+ ```
30
+
31
+ ### 2. Connect a model
32
+
33
+ Create `~/.zettcode/config.toml` (and its parent directory) with one model:
34
+
35
+ ```toml
36
+ [[models]]
37
+ model = "deepseek-v4-pro" # the model id sent to DeepSeek
38
+ display_model = "DeepSeek Pro" # the name shown in ZettCode
39
+ token = "sk-..." # replace with your DeepSeek API key
40
+ base_url = "https://api.deepseek.com" # DeepSeek's OpenAI-compatible API root
41
+ context_window = 1000000 # verify against the endpoint's current limits
42
+ multimodal = false # this model accepts text, not images
43
+ ```
44
+
45
+ Get a key from the [DeepSeek platform](https://platform.deepseek.com/), not from
46
+ the chat website. Alternatively, omit `token` and set `OPENAI_API_KEY` to that
47
+ key. Add more `[[models]]` entries to switch with `/model`; the first is selected
48
+ at startup. See the
49
+ [configuration reference](https://chang-lehung.github.io/zettcode/guide/config)
50
+ for complete examples, defaults, and troubleshooting.
51
+
52
+ ### 3. Start a task
53
+
54
+ ```bash
55
+ cd /path/to/project
56
+ zettcode
57
+ # or: zettcode --workspace /path/to/project
58
+ ```
59
+
60
+ Describe a change and press **Enter**. Type `/` for commands, `@` to reference a
61
+ skill, and **Ctrl-C** to stop a request. **Ctrl-D** with an empty composer exits
62
+ and prints the command to resume your session.
63
+
64
+ ## Built for the way you work
65
+
66
+ | | |
67
+ | --- | --- |
68
+ | **See every step** | Reasoning, answers, and tool calls stream as readable rows — not raw JSON. |
69
+ | **Keep the decision** | Review shell commands before they run. When the agent needs your input, choose an option or type an answer. |
70
+ | **Keep moving** | Send a steering message during a reply. Use `/btw` for a side question that stays out of later context. |
71
+ | **Come back later** | `/resume` restores a conversation; `/export` saves a readable HTML record. |
72
+ | **Understand the context** | Monitor token usage and caching, inspect `/context`, and compact when needed. |
73
+ | **Bring your tools** | Use project `AGENTS.md` instructions, skills, MCP servers, and Python plugins. |
74
+
75
+ The palette follows your terminal’s background, or your own `theme.toml`.
76
+ Select and copy text, scroll through answers, and paste images when your model
77
+ supports them.
78
+
79
+ ## Find your next step
80
+
81
+ The **[user guide](https://chang-lehung.github.io/zettcode/guide/overview)** is
82
+ available in English and [简体中文](https://chang-lehung.github.io/zettcode/zh/guide/overview).
83
+
84
+ | First time | Everyday use | Configuration & extensions |
85
+ | --- | --- | --- |
86
+ | [Getting started](https://chang-lehung.github.io/zettcode/guide/getting-started) | [Keys & mouse](https://chang-lehung.github.io/zettcode/guide/keys) | [Configuration](https://chang-lehung.github.io/zettcode/guide/config) |
87
+ | [The interface](https://chang-lehung.github.io/zettcode/guide/interface) | [Commands](https://chang-lehung.github.io/zettcode/guide/commands) | [Models & context](https://chang-lehung.github.io/zettcode/guide/models) |
88
+ | [Problems & questions](https://chang-lehung.github.io/zettcode/guide/faq) | [Sessions](https://chang-lehung.github.io/zettcode/guide/sessions) | [Project instructions](https://chang-lehung.github.io/zettcode/guide/instructions) |
89
+ | | | [Skills & MCP](https://chang-lehung.github.io/zettcode/guide/skills-and-mcp) · [Plugins](https://chang-lehung.github.io/zettcode/guide/plugins) |
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ uv sync # install dependencies
95
+ make check # Ruff, mypy, and tests
96
+ make hooks # enforce mypy before commits
97
+ make demo # browse terminal widgets
98
+ make docs # preview the user guide locally
99
+ uv run zettcode --workspace . # run from the checkout
100
+ ```
101
+
102
+ ZettCode provides the terminal application; the agent runtime is
103
+ [`zett-agent`](https://github.com/Chang-LeHung/zett-agent). Contributor notes and
104
+ invariants live in [`AGENTS.md`](AGENTS.md) and
105
+ [`docs/internal/`](docs/internal/), separately from the user guide. See
106
+ [`docs/internal/site-design.md`](docs/internal/site-design.md) for preview and
107
+ asset-generation instructions.
108
+
109
+ ## License
110
+
111
+ MIT. See [`LICENSE`](LICENSE).
@@ -0,0 +1,106 @@
1
+ <p align="center">
2
+ <img src="docs/public/logo.svg" width="88" height="88" alt="ZettCode 像素机器人,中间是一颗暖色的心" />
3
+ </p>
4
+ <h1 align="center">ZettCode</h1>
5
+ <p align="center"><strong>你的终端。你的编码伙伴。</strong></p>
6
+ <p align="center">专注的编码智能体:读懂项目、展示过程,把决定权留给你。</p>
7
+ <p align="center">
8
+ <a href="README.md">English</a> · <strong>简体中文</strong><br />
9
+ <a href="https://chang-lehung.github.io/zettcode/zh/guide/getting-started">快速开始</a> ·
10
+ <a href="https://chang-lehung.github.io/zettcode/zh/">使用文档</a> ·
11
+ <a href="https://chang-lehung.github.io/zettcode/zh/guide/commands">命令参考</a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <img src="docs/public/terminal.svg" width="1000" alt="ZettCode 依次展示任务、思考、读文件、改代码、通过的测试和 Markdown 回答" />
16
+ </p>
17
+ <p align="center"><sub>示例对话由 ZettCode 的实际终端组件渲染。</sub></p>
18
+
19
+ ## 先跑起来
20
+
21
+ 需要 **Python 3.10+**、支持 UTF-8 的终端和 **OpenAI 兼容接口**。
22
+ 支持 macOS、Linux 和 Windows(请使用 Windows Terminal)。
23
+
24
+ ### 1. 安装
25
+
26
+ ```bash
27
+ uv tool install zettcode
28
+ # 或者:pip install zettcode
29
+ ```
30
+
31
+ ### 2. 连接模型
32
+
33
+ 创建 `~/.zettcode/config.toml`(以及父目录),先配置一个模型:
34
+
35
+ ```toml
36
+ [[models]]
37
+ model = "deepseek-v4-pro" # 发给 DeepSeek 的模型 id
38
+ display_model = "DeepSeek Pro" # ZettCode 界面中显示的名字
39
+ token = "sk-..." # 替换成你的 DeepSeek API key
40
+ base_url = "https://api.deepseek.com" # DeepSeek 的 OpenAI 兼容接口根地址
41
+ context_window = 1000000 # 以接入点当前公布的实际限制为准
42
+ multimodal = false # 这个模型接受文本,不接受图片
43
+ ```
44
+
45
+ 在 [DeepSeek 开放平台](https://platform.deepseek.com/)创建 API key,不是聊天网页的登录信息。
46
+ 也可以省略 `token`,改用 `OPENAI_API_KEY` 环境变量保存这个 key。
47
+ 添加更多 `[[models]]`,就能用 `/model` 切换;启动时默认选择第一项。
48
+ 完整示例、默认值和排错步骤见[配置参考](https://chang-lehung.github.io/zettcode/zh/guide/config)。
49
+
50
+ ### 3. 发出任务
51
+
52
+ ```bash
53
+ cd /path/to/project
54
+ zettcode
55
+ # 或者:zettcode --workspace /path/to/project
56
+ ```
57
+
58
+ 描述要改什么,按 **Enter**。`/` 打开命令菜单,`@` 引用 skill,**Ctrl-C** 停止请求。
59
+ 输入框为空时按 **Ctrl-D** 退出,终端会打印恢复当前会话的命令。
60
+
61
+ ## 按你的方式工作
62
+
63
+ | | |
64
+ | --- | --- |
65
+ | **看清每一步** | 思考、回答和工具调用实时呈现为可读的行,不是原始 JSON。 |
66
+ | **决定权在你手上** | 执行 shell 命令前先审阅;智能体需要你的决定时,可以选选项,也可以打字回答。 |
67
+ | **工作不中断** | 回复过程中发送 steering(引导)消息;用 `/btw` 问一句,不让它进入后续上下文。 |
68
+ | **随时接着做** | `/resume` 恢复对话,`/export` 导出便于阅读的 HTML。 |
69
+ | **看懂上下文** | 查看 token 与缓存统计,用 `/context` 检查占用,在需要时压缩。 |
70
+ | **接入你的工具** | 使用项目 `AGENTS.md` 指令、skills、MCP 服务器和 Python 插件。 |
71
+
72
+ 主题跟随终端背景,也可以使用你自己的 `theme.toml`。
73
+ 选中复制文字、滚动查看回答;模型支持多模态时,还能粘贴图片。
74
+
75
+ ## 找到你的下一步
76
+
77
+ **[用户指南](https://chang-lehung.github.io/zettcode/zh/guide/overview)** 提供简体中文与
78
+ [English](https://chang-lehung.github.io/zettcode/guide/overview) 两个版本。
79
+
80
+ | 第一次使用 | 日常使用 | 配置与扩展 |
81
+ | --- | --- | --- |
82
+ | [快速开始](https://chang-lehung.github.io/zettcode/zh/guide/getting-started) | [快捷键与鼠标](https://chang-lehung.github.io/zettcode/zh/guide/keys) | [配置参考](https://chang-lehung.github.io/zettcode/zh/guide/config) |
83
+ | [认识界面](https://chang-lehung.github.io/zettcode/zh/guide/interface) | [命令参考](https://chang-lehung.github.io/zettcode/zh/guide/commands) | [模型与上下文](https://chang-lehung.github.io/zettcode/zh/guide/models) |
84
+ | [常见问题](https://chang-lehung.github.io/zettcode/zh/guide/faq) | [会话管理](https://chang-lehung.github.io/zettcode/zh/guide/sessions) | [项目指令](https://chang-lehung.github.io/zettcode/zh/guide/instructions) |
85
+ | | | [Skills 与 MCP](https://chang-lehung.github.io/zettcode/zh/guide/skills-and-mcp) · [插件](https://chang-lehung.github.io/zettcode/zh/guide/plugins) |
86
+
87
+ ## 开发
88
+
89
+ ```bash
90
+ uv sync # 安装依赖
91
+ make check # Ruff、mypy 与测试
92
+ make hooks # 每次提交前强制检查 mypy
93
+ make demo # 浏览终端组件
94
+ make docs # 本地预览使用文档
95
+ uv run zettcode --workspace . # 从源码运行
96
+ ```
97
+
98
+ ZettCode 提供终端应用,智能体运行时来自
99
+ [`zett-agent`](https://github.com/Chang-LeHung/zett-agent)。贡献者约定和不变量放在
100
+ [`AGENTS.md`](AGENTS.md) 与 [`docs/internal/`](docs/internal/),不进入用户指南。
101
+ 本地预览、重新生成效果图的方法见
102
+ [`docs/internal/site-design.md`](docs/internal/site-design.md)。
103
+
104
+ ## 许可证
105
+
106
+ MIT,见 [`LICENSE`](LICENSE)。
@@ -25,9 +25,18 @@ dependencies = [
25
25
  # The stdlib gained both of these in 3.11; older runtimes take the backports.
26
26
  "exceptiongroup>=1.2,<2; python_version < '3.11'",
27
27
  "pyperclip>=1.9,<2",
28
+ "setproctitle>=1.3.7,<2; os_name == 'posix'",
28
29
  "tomli>=2,<3; python_version < '3.11'",
30
+ # The system trust store, so the release check works behind a proxy.
31
+ "truststore>=0.10,<1",
29
32
  "wcwidth>=0.2,<1",
30
- "zett-agent==0.1.10",
33
+ # A range, not a pin: 0.1.11 is the oldest runtime with the APIs this client
34
+ # uses (``AgentsMdExtension`` among them) and the first whose subagent
35
+ # extension imports its SQLite store lazily, so warming a runtime no longer
36
+ # loads SQLAlchemy. An exact pin would make zettcode fight every other tool
37
+ # that shares the dependency — including an upgrade of zett-agent itself,
38
+ # which pip would downgrade to satisfy it.
39
+ "zett-agent>=0.1.11,<0.2",
31
40
  ]
32
41
 
33
42
  [project.scripts]
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
7
7
 
8
8
  #: The one place the version is written. ``pyproject.toml`` reads it from here
9
9
  #: and the release workflow checks the tag against it, so a bump is one edit.
10
- __version__ = "0.1.2"
10
+ __version__ = "0.1.3"
11
11
 
12
12
  #: Where each public name lives; read by :func:`__getattr__` on first use.
13
13
  _EXPORTS = {
@@ -29,11 +29,13 @@ from zett_agent.model import ReasoningEffort, ToolDefinition
29
29
  from ...config import ModelConfig, ZettCodeConfig
30
30
  from ...plugins import UiRow
31
31
  from ..commands import Command, CommandContext, CommandResult
32
+ from .ask import ASK_USER_RESPONSE_EVENT_NAME, AskUserQuestion, answer_payload, decline_payload
32
33
  from .context import ContextReport
33
34
  from .export import build_trace, render_html, write_export
34
35
  from .mentions import MentionProvider, MentionRegistry
35
36
  from .replay import replay
36
37
  from .runtime import ZettCodeRuntime, build_system_prompt
38
+ from .side import SIDE, question
37
39
  from .storage import Session, SessionInfo
38
40
  from .title import summarize_title
39
41
  from .transcript import Transcript
@@ -256,6 +258,7 @@ class ZettCodeAgent:
256
258
  parts: Sequence[PromptPart],
257
259
  *,
258
260
  hint: str | None = None,
261
+ side: bool = False,
259
262
  ) -> AsyncIterator[AgentEvent]:
260
263
  """Run one turn with the selected session, model, and reasoning effort.
261
264
 
@@ -267,11 +270,13 @@ class ZettCodeAgent:
267
270
  reference contributes. The text the user typed then rides along
268
271
  as the message's ``prompt`` attribute, so the stored message is
269
272
  what was typed and a restored session shows it.
273
+ side: Ask without letting the exchange join the conversation; see
274
+ :mod:`zettcode.app.agent.side`.
270
275
  """
271
276
  await self.runtime.start()
272
277
  client = self.runtime.started
273
278
  async for event in client.stream(
274
- self.request(parts, hint=hint),
279
+ self.request(parts, hint=hint, side=side),
275
280
  config=AgentRunConfig(session_id=self.session_id),
276
281
  model=self.runtime.provider,
277
282
  reasoning_effort=self.runtime.effort,
@@ -279,7 +284,7 @@ class ZettCodeAgent:
279
284
  yield event
280
285
 
281
286
  @staticmethod
282
- def request(parts: Sequence[PromptPart], *, hint: str | None = None) -> str | UserMessage:
287
+ def request(parts: Sequence[PromptPart], *, hint: str | None = None, side: bool = False) -> str | UserMessage:
283
288
  """Return the user turn those ordered parts make up.
284
289
 
285
290
  Text and images keep the order they were written in inside one
@@ -288,7 +293,23 @@ class ZettCodeAgent:
288
293
  ``hint`` is appended last — as a trailing part when an image is in the
289
294
  turn — and the typed text then rides along as the message's ``prompt``
290
295
  attribute, so storage and a restored session keep it.
296
+
297
+ Args:
298
+ parts: The turn in the order it was written.
299
+ hint: Instructions a ``@`` reference contributes.
300
+ side: Mark the turn as a side question, which the store records but
301
+ never replays.
291
302
  """
303
+ message = ZettCodeAgent.turn(parts, hint=hint)
304
+ if not side:
305
+ return message
306
+ if isinstance(message, UserMessage):
307
+ return replace(message, attributes={**message.attributes, SIDE: True}, include_in_messages=False)
308
+ return question(message)
309
+
310
+ @staticmethod
311
+ def turn(parts: Sequence[PromptPart], *, hint: str | None = None) -> str | UserMessage:
312
+ """Return the message those parts make up, before any side marking."""
292
313
  if not carries_image(parts):
293
314
  text = plain_text(parts)
294
315
  if hint is None:
@@ -551,6 +572,31 @@ class ZettCodeAgent:
551
572
  config=AgentRunConfig(session_id=session_id),
552
573
  )
553
574
 
575
+ def answer_ask(self, question: AskUserQuestion, answer: str) -> None:
576
+ """Answer the model's question; the suspended tool call resumes with it.
577
+
578
+ Args:
579
+ question: The question being answered, which carries the session and
580
+ the tool call the runtime routes the reply on.
581
+ answer: What the reader typed, exactly as it should reach the model.
582
+ """
583
+ self._respond_to_ask(question, answer_payload(question, answer))
584
+
585
+ def decline_ask(self, question: AskUserQuestion) -> None:
586
+ """Tell the model the reader cancelled, rather than leaving it waiting.
587
+
588
+ ``ask_user`` has no rejection channel in the runtime, so the tool result
589
+ says the question was declined and why; the model carries on from that.
590
+ """
591
+ self._respond_to_ask(question, decline_payload(question))
592
+
593
+ def _respond_to_ask(self, question: AskUserQuestion, payload: dict[str, object]) -> None:
594
+ """Emit one ``ask_user_response`` for the session the question came from."""
595
+ self.runtime.started.agent.emit_external_event(
596
+ ExternalEvent(name=ASK_USER_RESPONSE_EVENT_NAME, payload=dict(payload)),
597
+ config=AgentRunConfig(session_id=question.session_id),
598
+ )
599
+
554
600
  def steer(self, text: str) -> bool:
555
601
  """Queue an urgent user message for the request that is running.
556
602
 
@@ -0,0 +1,96 @@
1
+ """The ``ask_user`` protocol: what the model asked, and what answers it.
2
+
3
+ ``zett-agent`` owns the tool, the suspension and the routing; this module owns
4
+ the little the shell and the agent have to agree on — the question parsed out of
5
+ the event payload, and the payloads that answer it. Keeping both here means the
6
+ panel never reads a raw payload and the agent never invents one.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from zett_agent.events import AgentEvent
15
+ from zett_agent.extensions.ask_user import ASK_USER_EVENT_NAME, ASK_USER_RESPONSE_EVENT_NAME
16
+
17
+ __all__ = [
18
+ "ASK_USER_RESPONSE_EVENT_NAME",
19
+ "AskUserQuestion",
20
+ "answer_payload",
21
+ "decline_payload",
22
+ "question_from",
23
+ ]
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class AskUserQuestion:
28
+ """One question the model is waiting on.
29
+
30
+ Attributes:
31
+ session_id: Session the question belongs to, echoed back with the answer.
32
+ call_id: Tool call the answer resolves; the agent routes on it.
33
+ question: The question, trimmed.
34
+ options: Choices the model offered, in display order; empty when it
35
+ offered none.
36
+ allow_multiple: Whether more than one option may be chosen.
37
+ """
38
+
39
+ session_id: str
40
+ call_id: str
41
+ question: str
42
+ options: tuple[str, ...] = ()
43
+ allow_multiple: bool = False
44
+
45
+
46
+ def _options(value: object) -> tuple[str, ...]:
47
+ """Return the usable choices in a payload field, dropping anything else."""
48
+ if not isinstance(value, list):
49
+ return ()
50
+ return tuple(item.strip() for item in value if isinstance(item, str) and item.strip())
51
+
52
+
53
+ def question_from(event: AgentEvent) -> AskUserQuestion | None:
54
+ """Return the question an event carries, or ``None`` when it is not one.
55
+
56
+ The payload comes from the runtime, but a malformed one must not take the
57
+ shell down: it is read defensively and reported as "not a question".
58
+ """
59
+ if event.name != ASK_USER_EVENT_NAME:
60
+ return None
61
+ payload = event.payload if isinstance(event.payload, dict) else {}
62
+ question = payload.get("question")
63
+ call_id = payload.get("tool_call_id")
64
+ if not isinstance(question, str) or not question.strip():
65
+ return None
66
+ if not isinstance(call_id, str) or not call_id:
67
+ return None
68
+ session_id = payload.get("session_id") or event.session_id
69
+ return AskUserQuestion(
70
+ session_id=str(session_id or ""),
71
+ call_id=call_id,
72
+ question=question.strip(),
73
+ options=_options(payload.get("options")),
74
+ allow_multiple=bool(payload.get("allow_multiple")),
75
+ )
76
+
77
+
78
+ def answer_payload(question: AskUserQuestion, answer: str) -> dict[str, Any]:
79
+ """Return the response payload for a question the reader answered."""
80
+ return {"tool_call_id": question.call_id, "answer": answer}
81
+
82
+
83
+ def decline_payload(question: AskUserQuestion) -> dict[str, Any]:
84
+ """Return the response payload for a question the reader cancelled.
85
+
86
+ ``ask_user`` has no rejection channel: the tool returns whatever payload the
87
+ UI sends. A cancel therefore answers with a result that says the question was
88
+ declined — the model reads that and carries on, instead of a suspended call
89
+ waiting for a reply that is never coming.
90
+ """
91
+ return {
92
+ "tool_call_id": question.call_id,
93
+ "answer": None,
94
+ "declined": True,
95
+ "reason": "the user cancelled the question",
96
+ }