zettcode 0.1.8__tar.gz → 0.1.9__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 (267) hide show
  1. {zettcode-0.1.8 → zettcode-0.1.9}/.gitignore +3 -0
  2. zettcode-0.1.9/PKG-INFO +60 -0
  3. zettcode-0.1.9/README.md +23 -0
  4. {zettcode-0.1.8 → zettcode-0.1.9}/pyproject.toml +2 -47
  5. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/__init__.py +1 -1
  6. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/_compat.py +1 -1
  7. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/mcp.py +14 -6
  8. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/mcp_notice.py +15 -3
  9. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/config.py +47 -2
  10. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/__init__.py +38 -1
  11. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/_translate.py +89 -5
  12. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/client.py +136 -1
  13. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/events.py +3 -0
  14. zettcode-0.1.9/src/zettcode/sdk/models.py +140 -0
  15. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/session.py +92 -22
  16. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/types.py +183 -3
  17. zettcode-0.1.8/LICENSE +0 -21
  18. zettcode-0.1.8/PKG-INFO +0 -149
  19. zettcode-0.1.8/README.md +0 -112
  20. zettcode-0.1.8/README.zh-CN.md +0 -106
  21. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/__init__.py +0 -0
  22. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/__init__.py +0 -0
  23. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/__init__.py +0 -0
  24. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/base.py +0 -0
  25. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/commands.py +0 -0
  26. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/context.py +0 -0
  27. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/exports.py +0 -0
  28. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/interaction.py +0 -0
  29. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/prompt.py +0 -0
  30. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/sessions.py +0 -0
  31. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/settings.py +0 -0
  32. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/agent/turns.py +0 -0
  33. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/__init__.py +0 -0
  34. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/changes.py +0 -0
  35. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/effort_gauge.py +0 -0
  36. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/entries.py +0 -0
  37. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/presenters.py +0 -0
  38. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/projection.py +0 -0
  39. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/pulse.py +0 -0
  40. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/rendering.py +0 -0
  41. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/replay.py +0 -0
  42. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/shimmer.py +0 -0
  43. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/stops.py +0 -0
  44. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/text.py +0 -0
  45. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/tips.py +0 -0
  46. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/transcript/__init__.py +0 -0
  47. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/transcript/fold.py +0 -0
  48. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/transcript/log.py +0 -0
  49. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/transcript/oneoff.py +0 -0
  50. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/transcript/stream.py +0 -0
  51. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/conversation/transcript/turn.py +0 -0
  52. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/dialogue/__init__.py +0 -0
  53. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/dialogue/ask.py +0 -0
  54. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/dialogue/mentions.py +0 -0
  55. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/dialogue/relay.py +0 -0
  56. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/dialogue/side.py +0 -0
  57. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/dialogue/title.py +0 -0
  58. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/errors.py +0 -0
  59. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/export/__init__.py +0 -0
  60. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/export/trace/script.js +0 -0
  61. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/export/trace/style.css +0 -0
  62. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/export/trace/template.html +0 -0
  63. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/__init__.py +0 -0
  64. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/capabilities.py +0 -0
  65. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/compaction.py +0 -0
  66. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/context.py +0 -0
  67. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/messaging.py +0 -0
  68. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/messenger.py +0 -0
  69. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/subagents.py +0 -0
  70. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/tool_output.py +0 -0
  71. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/extensions/usage.py +0 -0
  72. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/permission/__init__.py +0 -0
  73. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/permission/approval.py +0 -0
  74. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/permission/readonly.py +0 -0
  75. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/runtime.py +0 -0
  76. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/__init__.py +0 -0
  77. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/layout.py +0 -0
  78. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/ledger.py +0 -0
  79. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/locks.py +0 -0
  80. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/metadata.py +0 -0
  81. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/records.py +0 -0
  82. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/agent/storage/store.py +0 -0
  83. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/brand.py +0 -0
  84. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/commandline/__init__.py +0 -0
  85. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/commandline/arguments.py +0 -0
  86. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/commandline/completion.py +0 -0
  87. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/commandline/history.py +0 -0
  88. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/commandline/runner.py +0 -0
  89. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/commands.py +0 -0
  90. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/__init__.py +0 -0
  91. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/cat.py +0 -0
  92. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/chibi.py +0 -0
  93. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/dog.py +0 -0
  94. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/kangaroo.py +0 -0
  95. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/model.py +0 -0
  96. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/penguin.py +0 -0
  97. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/polarbear.py +0 -0
  98. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/scarfy.py +0 -0
  99. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/seagull.py +0 -0
  100. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/slime.py +0 -0
  101. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/snowbear.py +0 -0
  102. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/sprout.py +0 -0
  103. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/tiger.py +0 -0
  104. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pets/whale.py +0 -0
  105. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/pipeline.py +0 -0
  106. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/registry.py +0 -0
  107. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/__init__.py +0 -0
  108. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/app.py +0 -0
  109. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/bars.py +0 -0
  110. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/boot.py +0 -0
  111. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/clipboard.py +0 -0
  112. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/commands.py +0 -0
  113. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/config_form.py +0 -0
  114. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/demo.py +0 -0
  115. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/followups.py +0 -0
  116. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/gc_policy.py +0 -0
  117. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/keys.py +0 -0
  118. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/labels.py +0 -0
  119. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/messaging.py +0 -0
  120. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/notices.py +0 -0
  121. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/providers.py +0 -0
  122. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/sessions.py +0 -0
  123. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/settings.py +0 -0
  124. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/shell.py +0 -0
  125. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/updating.py +0 -0
  126. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/__init__.py +0 -0
  127. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/approval_page.py +0 -0
  128. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/ask_page.py +0 -0
  129. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/bash_page.py +0 -0
  130. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/completer.py +0 -0
  131. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/composer.py +0 -0
  132. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/context_charts.py +0 -0
  133. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/context_page.py +0 -0
  134. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/effort_page.py +0 -0
  135. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/follow_up.py +0 -0
  136. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/input_page.py +0 -0
  137. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/model_page.py +0 -0
  138. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/panel.py +0 -0
  139. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/pet.py +0 -0
  140. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/pet_page.py +0 -0
  141. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/root.py +0 -0
  142. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/self_compact_page.py +0 -0
  143. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/send_kind_page.py +0 -0
  144. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/sessions_page.py +0 -0
  145. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/statusline_page.py +0 -0
  146. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/steering.py +0 -0
  147. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/transcript.py +0 -0
  148. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/update_page.py +0 -0
  149. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/usage_charts.py +0 -0
  150. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/usage_page.py +0 -0
  151. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/app/ui/widgets/welcome.py +0 -0
  152. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/__init__.py +0 -0
  153. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/__main__.py +0 -0
  154. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/client.py +0 -0
  155. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/files.py +0 -0
  156. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/finder.py +0 -0
  157. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/hub.py +0 -0
  158. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/log.py +0 -0
  159. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/models.py +0 -0
  160. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/process.py +0 -0
  161. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/protocol.py +0 -0
  162. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/roster.py +0 -0
  163. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/runner.py +0 -0
  164. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/server.py +0 -0
  165. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/text.py +0 -0
  166. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/broker/web.py +0 -0
  167. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/cli.py +0 -0
  168. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/collector.py +0 -0
  169. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/config_edit.py +0 -0
  170. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/doctor.py +0 -0
  171. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/filelock.py +0 -0
  172. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/paths.py +0 -0
  173. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/__init__.py +0 -0
  174. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/builtin_agent.py +0 -0
  175. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/builtin_commands.py +0 -0
  176. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/builtins.py +0 -0
  177. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/container.py +0 -0
  178. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/extension.py +0 -0
  179. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/loader.py +0 -0
  180. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/mixins.py +0 -0
  181. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/plugin.py +0 -0
  182. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/services.py +0 -0
  183. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/plugins/state.py +0 -0
  184. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/providers.py +0 -0
  185. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/py.typed +0 -0
  186. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/errors.py +0 -0
  187. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/sdk/handlers.py +0 -0
  188. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/statusline.py +0 -0
  189. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/__init__.py +0 -0
  190. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/capabilities.py +0 -0
  191. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/clipboard.py +0 -0
  192. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/console.py +0 -0
  193. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/__init__.py +0 -0
  194. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/app.py +0 -0
  195. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/events.py +0 -0
  196. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/focus.py +0 -0
  197. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/geometry.py +0 -0
  198. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/host.py +0 -0
  199. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/keymap.py +0 -0
  200. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/scheduler.py +0 -0
  201. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/screen.py +0 -0
  202. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/theme.py +0 -0
  203. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/core/widget.py +0 -0
  204. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/gallery.py +0 -0
  205. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/glyphs.py +0 -0
  206. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/input/__init__.py +0 -0
  207. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/input/bridge.py +0 -0
  208. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/input/decoder.py +0 -0
  209. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/input/events.py +0 -0
  210. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/input/keys.py +0 -0
  211. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/input/reader.py +0 -0
  212. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/layout/__init__.py +0 -0
  213. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/layout/box.py +0 -0
  214. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/layout/overlay.py +0 -0
  215. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/layout/scroll.py +0 -0
  216. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/layout/solver.py +0 -0
  217. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/layout/spacing.py +0 -0
  218. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/__init__.py +0 -0
  219. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/ansi.py +0 -0
  220. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/canvas.py +0 -0
  221. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/code.py +0 -0
  222. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/color.py +0 -0
  223. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/renderer.py +0 -0
  224. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/rich_text.py +0 -0
  225. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/style.py +0 -0
  226. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/sweep.py +0 -0
  227. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/render/text.py +0 -0
  228. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/runner.py +0 -0
  229. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/terminal.py +0 -0
  230. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/testing/__init__.py +0 -0
  231. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/testing/harness.py +0 -0
  232. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/testing/snapshot.py +0 -0
  233. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/theme_file.py +0 -0
  234. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/ayu-mirage.toml +0 -0
  235. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/catppuccin-latte.toml +0 -0
  236. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/catppuccin-mocha.toml +0 -0
  237. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/dracula.toml +0 -0
  238. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/github-dimmed.toml +0 -0
  239. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/github-light.toml +0 -0
  240. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/gruvbox-dark.toml +0 -0
  241. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/gruvbox-light.toml +0 -0
  242. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/kanagawa.toml +0 -0
  243. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/nord.toml +0 -0
  244. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/one-dark.toml +0 -0
  245. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/rose-pine-dawn.toml +0 -0
  246. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/rose-pine.toml +0 -0
  247. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/solarized-dark.toml +0 -0
  248. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/solarized-light.toml +0 -0
  249. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/themes/tokyo-night.toml +0 -0
  250. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/__init__.py +0 -0
  251. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/collapsible.py +0 -0
  252. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/completion.py +0 -0
  253. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/console.py +0 -0
  254. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/dialog.py +0 -0
  255. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/diff.py +0 -0
  256. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/list.py +0 -0
  257. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/list_page.py +0 -0
  258. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/markdown.py +0 -0
  259. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/progress.py +0 -0
  260. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/rich_text.py +0 -0
  261. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/status.py +0 -0
  262. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/table.py +0 -0
  263. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/tasks.py +0 -0
  264. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/text.py +0 -0
  265. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/textarea.py +0 -0
  266. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/tui/widgets/toast.py +0 -0
  267. {zettcode-0.1.8 → zettcode-0.1.9}/src/zettcode/update.py +0 -0
@@ -21,6 +21,9 @@ docs/.vitepress/dist/
21
21
  docs/.vitepress/cache/
22
22
  docs/.vitepress/.temp/
23
23
 
24
+ # The TypeScript client is built from its sources; only dist/ is published.
25
+ clients/typescript/dist/
26
+
24
27
  # The palette gallery is generated by `make palettes`: every widget painted in
25
28
  # every palette, written by docs/scripts/palette_gallery.py. Regenerate it
26
29
  # rather than committing a 2 MB copy that goes stale the moment a colour moves.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.5
2
+ Name: zettcode
3
+ Version: 0.1.9
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
+ Keywords: agent,coding-agent,llm,terminal,tui
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: exceptiongroup<2,>=1.2; python_version < '3.11'
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Requires-Dist: pydantic<3,>=2
26
+ Requires-Dist: pyperclip<2,>=1.9
27
+ Requires-Dist: setproctitle<2,>=1.3.7; os_name == 'posix'
28
+ Requires-Dist: starlette<2,>=0.48
29
+ Requires-Dist: tiktoken<1,>=0.12
30
+ Requires-Dist: tomli<3,>=2; python_version < '3.11'
31
+ Requires-Dist: truststore<1,>=0.10
32
+ Requires-Dist: uvicorn<1,>=0.35
33
+ Requires-Dist: wcwidth<1,>=0.2
34
+ Requires-Dist: websockets<17,>=15
35
+ Requires-Dist: zett-agent<0.2,>=0.1.17
36
+ Description-Content-Type: text/markdown
37
+
38
+ # ZettCode
39
+
40
+ A focused terminal coding agent powered by [zett-agent](https://pypi.org/project/zett-agent/): it reads and edits your
41
+ code, runs commands with your permission, and is driven by a model you choose. The repository also ships a Python SDK
42
+ and an HTTP server.
43
+
44
+ ```bash
45
+ pip install zettcode
46
+ zettcode
47
+ ```
48
+
49
+ - [The guide](https://chang-lehung.github.io/zettcode/) — commands, keys, plugins, configuration
50
+ - [The SDK](https://chang-lehung.github.io/zettcode/sdk/) — drive the agent from Python
51
+ - [The server](https://chang-lehung.github.io/zettcode/server/) — drive it over HTTP
52
+
53
+ The full README, with the screenshots, is at the root of the repository.
54
+
55
+ ```python
56
+ import zettcode.sdk as zc
57
+
58
+ result = await zc.prompt("summarize the README", permission="deny")
59
+ print(result.text)
60
+ ```
@@ -0,0 +1,23 @@
1
+ # ZettCode
2
+
3
+ A focused terminal coding agent powered by [zett-agent](https://pypi.org/project/zett-agent/): it reads and edits your
4
+ code, runs commands with your permission, and is driven by a model you choose. The repository also ships a Python SDK
5
+ and an HTTP server.
6
+
7
+ ```bash
8
+ pip install zettcode
9
+ zettcode
10
+ ```
11
+
12
+ - [The guide](https://chang-lehung.github.io/zettcode/) — commands, keys, plugins, configuration
13
+ - [The SDK](https://chang-lehung.github.io/zettcode/sdk/) — drive the agent from Python
14
+ - [The server](https://chang-lehung.github.io/zettcode/server/) — drive it over HTTP
15
+
16
+ The full README, with the screenshots, is at the root of the repository.
17
+
18
+ ```python
19
+ import zettcode.sdk as zc
20
+
21
+ result = await zc.prompt("summarize the README", permission="deny")
22
+ print(result.text)
23
+ ```
@@ -5,7 +5,6 @@ readme = "README.md"
5
5
  requires-python = ">=3.10"
6
6
  dynamic = ["version"]
7
7
  license = "MIT"
8
- license-files = ["LICENSE"]
9
8
  authors = [{name = "Chang-LeHung"}]
10
9
  keywords = ["agent", "llm", "tui", "terminal", "coding-agent"]
11
10
  classifiers = [
@@ -19,6 +18,7 @@ classifiers = [
19
18
  "Programming Language :: Python :: 3.12",
20
19
  "Programming Language :: Python :: 3.13",
21
20
  "Programming Language :: Python :: 3.14",
21
+ "Programming Language :: Python :: 3.15",
22
22
  "Typing :: Typed",
23
23
  ]
24
24
  dependencies = [
@@ -75,53 +75,8 @@ path = "src/zettcode/__init__.py"
75
75
  packages = ["src/zettcode"]
76
76
 
77
77
  [tool.hatch.build.targets.sdist]
78
- include = ["/src", "/README.md", "/README.zh-CN.md", "/LICENSE", "/pyproject.toml"]
79
-
80
- [dependency-groups]
81
- dev = [
82
- # Unlike the conditional runtime deps, these are installed on every
83
- # interpreter so mypy can check the 3.10 branch of ``_compat`` for real.
84
- "exceptiongroup>=1.2,<2",
85
- "mypy>=1.14,<2",
86
- "pytest>=8,<9",
87
- "pytest-asyncio>=0.26,<2",
88
- "pytest-cov>=7.1.0",
89
- "ruff>=0.11,<1",
90
- "tomli>=2,<3",
91
- "viztracer>=1.1.1",
92
- ]
78
+ include = ["/src", "/README.md", "/pyproject.toml"]
93
79
 
94
80
  [tool.pytest.ini_options]
95
81
  testpaths = ["tests"]
96
82
  asyncio_mode = "auto"
97
-
98
- [tool.mypy]
99
- python_version = "3.10"
100
- files = ["src", "perf"]
101
- # Check the pydantic storage models as models, not as plain classes.
102
- plugins = ["pydantic.mypy"]
103
- warn_unused_ignores = true
104
- warn_redundant_casts = true
105
- no_implicit_optional = true
106
-
107
- [[tool.mypy.overrides]]
108
- # Ships no stubs; the runner only calls the two documented functions.
109
- module = ["pyperclip"]
110
- ignore_missing_imports = true
111
-
112
- [tool.ruff]
113
- line-length = 120
114
- target-version = "py310"
115
- src = ["src", "tests", "perf"]
116
-
117
- [tool.ruff.lint]
118
- select = ["E", "F", "I", "UP", "B"]
119
- ignore = ["E501"]
120
-
121
- [tool.ruff.lint.isort]
122
- known-first-party = ["zettcode"]
123
-
124
- [tool.ruff.format]
125
- quote-style = "double"
126
- indent-style = "space"
127
- line-ending = "lf"
@@ -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.8"
10
+ __version__ = "0.1.9"
11
11
 
12
12
  #: Where each public name lives; read by :func:`__getattr__` on first use.
13
13
  _EXPORTS = {
@@ -1,6 +1,6 @@
1
1
  """Small shims for the language versions this project supports.
2
2
 
3
- ZettCode runs on Python 3.10 through 3.14, so the few things the standard
3
+ ZettCode runs on Python 3.10 through 3.15, so the few things the standard
4
4
  library grew in between live here instead of behind version checks at each use.
5
5
  """
6
6
 
@@ -137,12 +137,15 @@ class ReportingMcpExtension(McpExtension):
137
137
  """Return whether this is the first request its session has made, in a session the reader is in."""
138
138
  return self._is_a_reader_session(context) and cast(str, context.config.session_id) not in self._narrated
139
139
 
140
- async def _say_quietly(self, context: AgentRunContext | None, text: str) -> None:
141
- """Say a line without letting the saying fail the discovery it reports on."""
140
+ async def _say_quietly(self, context: AgentRunContext | None, text: str, level: str = "info") -> None:
141
+ """Say a line without letting the saying fail the discovery it reports on.
142
+
143
+ These are progress lines (a server is starting, or ready), so they are ``info`` unless said otherwise.
144
+ """
142
145
  if context is None:
143
146
  return
144
147
  try:
145
- await self._say(context, text)
148
+ await self._say(context, text, level)
146
149
  except Exception: # noqa: BLE001 - news about a server must never be what stops it
147
150
  pass
148
151
 
@@ -188,14 +191,19 @@ class ReportingMcpExtension(McpExtension):
188
191
  return [f"MCP server {name!r} in {path.name} is ignored: {reason}" for name, reason in skipped.items()]
189
192
 
190
193
  @staticmethod
191
- async def _say(context: AgentRunContext, text: str) -> None:
192
- """Publish one line for the transcript."""
194
+ async def _say(context: AgentRunContext, text: str, level: str = "warn") -> None:
195
+ """Publish one line for the transcript.
196
+
197
+ A line is a warning unless it says otherwise: what reaches here from the checks (a server that is down, an
198
+ entry that is ignored, a file that cannot be read) is something the reader may need to act on, and only the
199
+ progress lines of a start say ``info``.
200
+ """
193
201
  await context.emit(
194
202
  AgentEvent(
195
203
  type=AgentEventType.CUSTOM,
196
204
  session_id=cast(str, context.config.session_id),
197
205
  name=MCP_NOTICE_EVENT_NAME,
198
- payload=notice_payload(text),
206
+ payload=notice_payload(text, level),
199
207
  )
200
208
  )
201
209
 
@@ -13,9 +13,21 @@ from zett_agent.events import AgentEvent
13
13
  MCP_NOTICE_EVENT_NAME = "mcp_notice"
14
14
 
15
15
 
16
- def notice_payload(text: str) -> dict[str, str]:
17
- """Return the JSON-serializable payload that carries one line of MCP news."""
18
- return {"text": text}
16
+ #: How serious a line of news is: ``info`` is progress (a server is starting, or ready), ``warn`` is something the
17
+ #: reader may need to act on (a server is down, an entry is ignored). A payload that names neither is ``info``.
18
+ LEVELS = ("info", "warn")
19
+
20
+
21
+ def notice_payload(text: str, level: str = "info") -> dict[str, str]:
22
+ """Return the JSON-serializable payload that carries one line of MCP news and how serious it is."""
23
+ return {"text": text, "level": level if level in LEVELS else "info"}
24
+
25
+
26
+ def notice_level_from(event: AgentEvent) -> str:
27
+ """Return how serious the notice an event carries is; ``info`` for a payload that does not say."""
28
+ payload = event.payload
29
+ level = payload.get("level") if isinstance(payload, dict) else None
30
+ return level if level in LEVELS else "info"
19
31
 
20
32
 
21
33
  def notice_from(event: AgentEvent) -> str | None:
@@ -135,10 +135,23 @@ DEFAULT_SKILL_ROOT = "~/.zettcode/skills"
135
135
  #: or ``both``, ahead of the user's own, so a project's version of a skill wins in that project.
136
136
  WORKSPACE_SKILL_ROOT = ".zettcode/skills"
137
137
 
138
+ #: The same two places under the ``.agents`` directory that other coding agents share: ``~/.agents/skills`` for
139
+ #: the user and ``<workspace>/.agents/skills`` for a project. Each is scanned right after the ``.zettcode``
140
+ #: directory of its own scope, so a skill written for one of those tools is found with no copying, and a
141
+ #: ZettCode skill of the same name in the same scope still wins.
142
+ DEFAULT_AGENTS_SKILL_ROOT = "~/.agents/skills"
143
+ WORKSPACE_AGENTS_SKILL_ROOT = ".agents/skills"
144
+
138
145
  #: The MCP server file a workspace may ship, relative to it. Read for ``[mcp] scope`` of
139
146
  #: ``workspace`` or ``both``, and merged over the user's file by server name.
140
147
  WORKSPACE_MCP_CONFIG = ".zettcode/mcp.json"
141
148
 
149
+ #: The same file under the shared ``.agents`` directory, for the workspace and for the user. Each is read right
150
+ #: after the ``.zettcode`` file of its own scope, so it only adds servers a ZettCode file does not name. The user's
151
+ #: is not read when ``[mcp] config`` names a file: whoever points at a team file means that file.
152
+ WORKSPACE_AGENTS_MCP_CONFIG = ".agents/mcp.json"
153
+ DEFAULT_AGENTS_MCP_CONFIG = "~/.agents/mcp.json"
154
+
142
155
  #: API root of an Anthropic model whose entry names no ``base_url``. The
143
156
  #: Messages API lives at the root itself, not under ``/v1``.
144
157
  DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com"
@@ -326,8 +339,8 @@ class ZettCodeConfig:
326
339
  skills_enabled: Discover local skills and advertise them to the model.
327
340
  skill_roots: Extra skill directories, searched before the directory :attr:`skills_scope`
328
341
  names; a relative entry is resolved against the workspace.
329
- skills_scope: Which of the workspace's ``.zettcode/skills`` and the user's
330
- ``~/.zettcode/skills`` to scan: ``"both"`` (the default) the project's first, so its version of
342
+ skills_scope: Which of the workspace's ``.zettcode/skills`` and ``.agents/skills`` and the user's
343
+ ``~/.zettcode/skills`` and ``~/.agents/skills`` to scan: ``"both"`` (the default) the project's first, so its version of
331
344
  a skill wins inside it; ``"workspace"`` the project's only, ``"global"`` the user's only.
332
345
  mcp_enabled: Load MCP servers from :attr:`mcp_config` and the workspace's ``.zettcode/mcp.json``.
333
346
  mcp_config: JSON file naming MCP servers; ``None`` uses ``~/.zettcode/mcp.json``.
@@ -440,8 +453,10 @@ class ZettCodeConfig:
440
453
  roots: list[Path] = list(self.skill_roots)
441
454
  if scope in ("workspace", "both"):
442
455
  roots.append(_resolve_root(WORKSPACE_SKILL_ROOT, self.workspace))
456
+ roots.append(_resolve_root(WORKSPACE_AGENTS_SKILL_ROOT, self.workspace))
443
457
  if scope in ("global", "both"):
444
458
  roots.append(_resolve_root(DEFAULT_SKILL_ROOT, self.workspace))
459
+ roots.append(_resolve_root(DEFAULT_AGENTS_SKILL_ROOT, self.workspace))
445
460
  # Ordered, one of each: a configured root may name the same directory a scope does.
446
461
  return tuple(dict.fromkeys(roots))
447
462
 
@@ -456,8 +471,11 @@ class ZettCodeConfig:
456
471
  files: list[Path] = []
457
472
  if scope in ("workspace", "both"):
458
473
  files.append(_resolve_root(WORKSPACE_MCP_CONFIG, self.workspace))
474
+ files.append(_resolve_root(WORKSPACE_AGENTS_MCP_CONFIG, self.workspace))
459
475
  if scope in ("global", "both"):
460
476
  files.append((self.mcp_config or Path(DEFAULT_MCP_CONFIG)).expanduser().resolve())
477
+ if self.mcp_config is None:
478
+ files.append(_resolve_root(DEFAULT_AGENTS_MCP_CONFIG, self.workspace))
461
479
  return tuple(dict.fromkeys(files))
462
480
 
463
481
 
@@ -577,6 +595,33 @@ def _read_models(raw: object, source: Path) -> tuple[ModelConfig, ...]:
577
595
  return tuple(models)
578
596
 
579
597
 
598
+ def read_models(path: str | Path | None = None) -> tuple[ModelConfig, ...]:
599
+ """Return the models a config file lists, and nothing else it holds.
600
+
601
+ Unlike :func:`load_config`, no models is an answer (the file is new, or a program is being set up) and a
602
+ missing file is an empty one. A model that is configured wrongly is still an error, because it would fail on
603
+ the first request otherwise.
604
+
605
+ Args:
606
+ path: The file to read; ``$ZETTCODE_CONFIG`` or ``~/.zettcode/config.toml`` by default.
607
+ """
608
+ source = config_path(path)
609
+ if not source.is_file():
610
+ return ()
611
+ try:
612
+ data = tomllib.loads(source.read_text(encoding="utf-8"))
613
+ except tomllib.TOMLDecodeError as error:
614
+ raise ValueError(f"Invalid config file {source}: {error}") from error
615
+ raw = data.get("models", [])
616
+ if raw is None:
617
+ return ()
618
+ if not isinstance(raw, list):
619
+ raise ValueError("Config key 'models' must be an array of tables ([[models]])")
620
+ if any(not isinstance(entry, dict) for entry in raw):
621
+ raise ValueError("Config key 'models' must be an array of tables ([[models]])")
622
+ return tuple(_entry_model(entry, index) for index, entry in enumerate(raw, start=1))
623
+
624
+
580
625
  def _read_table(raw: object, key: str, source: Path, allowed: frozenset[str]) -> dict[str, object]:
581
626
  """Return one optional config table, rejecting unknown keys and wrong shapes."""
582
627
  if raw is None:
@@ -59,8 +59,21 @@ if TYPE_CHECKING: # pragma: no cover - for type checkers and editors; at runtim
59
59
  ToolStarted,
60
60
  )
61
61
  from .handlers import approve_all, approve_commands, deny_all
62
+ from .models import add_model, list_models, remove_model, update_model
62
63
  from .session import RunStream, Session
63
- from .types import Image, RunResult, Usage
64
+ from .types import (
65
+ DayTotals,
66
+ Image,
67
+ MentionInfo,
68
+ ModelCheck,
69
+ ModelTotals,
70
+ RunResult,
71
+ SessionStatsInfo,
72
+ SessionTotals,
73
+ Totals,
74
+ Usage,
75
+ WorkspaceUsage,
76
+ )
64
77
 
65
78
  #: Where each public name lives. The package resolves a name by importing only that module.
66
79
  _EXPORTS: dict[str, str] = {
@@ -72,6 +85,14 @@ _EXPORTS: dict[str, str] = {
72
85
  "RunResult": ".types",
73
86
  "Usage": ".types",
74
87
  "Image": ".types",
88
+ "MentionInfo": ".types",
89
+ "DayTotals": ".types",
90
+ "ModelCheck": ".types",
91
+ "ModelTotals": ".types",
92
+ "SessionStatsInfo": ".types",
93
+ "SessionTotals": ".types",
94
+ "Totals": ".types",
95
+ "WorkspaceUsage": ".types",
75
96
  "TextDelta": ".events",
76
97
  "ReasoningDelta": ".events",
77
98
  "ToolStarted": ".events",
@@ -85,6 +106,10 @@ _EXPORTS: dict[str, str] = {
85
106
  "ZettCodeError": ".errors",
86
107
  "ConfigurationError": ".errors",
87
108
  "RunError": ".errors",
109
+ "add_model": ".models",
110
+ "list_models": ".models",
111
+ "remove_model": ".models",
112
+ "update_model": ".models",
88
113
  }
89
114
 
90
115
  #: Kept literal so linters and editors can read it; a test checks it names exactly the keys of ``_EXPORTS``.
@@ -92,24 +117,36 @@ __all__ = [
92
117
  "ApprovalRequested",
93
118
  "Client",
94
119
  "ConfigurationError",
120
+ "DayTotals",
95
121
  "Finished",
96
122
  "Image",
123
+ "MentionInfo",
124
+ "ModelCheck",
125
+ "ModelTotals",
97
126
  "QuestionAsked",
98
127
  "ReasoningDelta",
99
128
  "RunError",
100
129
  "RunResult",
101
130
  "RunStream",
102
131
  "Session",
132
+ "SessionStatsInfo",
133
+ "SessionTotals",
103
134
  "TextDelta",
104
135
  "ToolFinished",
105
136
  "ToolStarted",
137
+ "Totals",
106
138
  "Usage",
139
+ "WorkspaceUsage",
107
140
  "ZettCodeError",
141
+ "add_model",
108
142
  "approve_all",
109
143
  "approve_commands",
110
144
  "deny_all",
145
+ "list_models",
111
146
  "prompt",
112
147
  "prompt_sync",
148
+ "remove_model",
149
+ "update_model",
113
150
  ]
114
151
 
115
152
 
@@ -7,15 +7,17 @@ custom events, so it imports both, and is itself imported only when a run starts
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from collections.abc import Mapping
10
+ from collections.abc import Mapping, Sequence
11
+ from typing import TYPE_CHECKING
11
12
 
12
13
  from zett_agent.events import AgentEvent, AgentEventType
13
14
  from zett_agent.extensions.shell_approval import SHELL_APPROVAL_EVENT_NAME
14
15
  from zett_agent.messages import TextContent
15
16
 
16
17
  from ..app.agent.dialogue.ask import question_from
17
- from ..app.agent.extensions.mcp_notice import notice_from
18
+ from ..app.agent.extensions.mcp_notice import notice_from, notice_level_from
18
19
  from ..app.agent.extensions.usage import USAGE_EVENT_NAME, UsageSnapshot
20
+ from ..app.agent.storage.ledger import Ledger, SessionStats, SessionUsage, Tally
19
21
  from .events import (
20
22
  AnyEvent,
21
23
  ApprovalRequested,
@@ -34,9 +36,19 @@ from .events import (
34
36
  UsageUpdated,
35
37
  _Reply,
36
38
  )
37
- from .types import Compaction, Usage
39
+ from .types import (
40
+ Compaction,
41
+ DayTotals,
42
+ ModelCheck,
43
+ ModelTotals,
44
+ SessionStatsInfo,
45
+ SessionTotals,
46
+ Totals,
47
+ Usage,
48
+ WorkspaceUsage,
49
+ )
38
50
 
39
- __all__ = ["translate", "usage_from"]
51
+ __all__ = ["checks_from", "ledger_from", "stats_from", "translate", "usage_from"]
40
52
 
41
53
 
42
54
  def usage_from(snapshot: UsageSnapshot) -> Usage:
@@ -77,6 +89,78 @@ def _optional_int(value: object) -> int | None:
77
89
  return int(value) if isinstance(value, (int, float)) and not isinstance(value, bool) else None
78
90
 
79
91
 
92
+ if TYPE_CHECKING: # pragma: no cover - annotations only
93
+ from ..doctor import Probe
94
+
95
+
96
+ def totals_from(tally: Tally) -> Totals:
97
+ """Return one tally as the SDK's own counters."""
98
+ return Totals(
99
+ requests=tally.requests,
100
+ input_tokens=tally.input_tokens,
101
+ output_tokens=tally.output_tokens,
102
+ cache_read_tokens=tally.cache_read_tokens,
103
+ reasoning_tokens=tally.reasoning_tokens,
104
+ )
105
+
106
+
107
+ def ledger_from(ledger: Ledger) -> WorkspaceUsage:
108
+ """Return the workspace ledger as plain SDK values, ordered the way a page shows it."""
109
+ sessions = sorted(ledger.sessions, key=lambda item: item.updated, reverse=True)
110
+ models = sorted(ledger.models.items(), key=lambda pair: pair[1].total, reverse=True)
111
+ return WorkspaceUsage(
112
+ sessions=tuple(_session_totals(item) for item in sessions),
113
+ models=tuple(ModelTotals(name, totals_from(tally)) for name, tally in models),
114
+ days=tuple(DayTotals(day, totals_from(tally)) for day, tally in sorted(ledger.days.items())),
115
+ total=totals_from(ledger.total),
116
+ )
117
+
118
+
119
+ def _session_totals(usage: SessionUsage) -> SessionTotals:
120
+ """Return one session's ledger row."""
121
+ return SessionTotals(
122
+ id=usage.session_id,
123
+ title=usage.title,
124
+ started=usage.started,
125
+ updated=usage.updated,
126
+ tokens=totals_from(usage.tally),
127
+ models=usage.models,
128
+ entries=usage.entries,
129
+ )
130
+
131
+
132
+ def stats_from(stats: SessionStats) -> SessionStatsInfo:
133
+ """Return one session's stored totals."""
134
+ return SessionStatsInfo(
135
+ id=stats.session_id,
136
+ entries=stats.entries,
137
+ requests=stats.requests,
138
+ input_tokens=stats.input_tokens,
139
+ output_tokens=stats.output_tokens,
140
+ cache_read_tokens=stats.cache_read_tokens,
141
+ reasoning_tokens=stats.reasoning_tokens,
142
+ )
143
+
144
+
145
+ def checks_from(probes: Sequence[Probe]) -> tuple[ModelCheck, ...]:
146
+ """Return the doctor probes as SDK values."""
147
+ checks: list[ModelCheck] = []
148
+ for probe in probes:
149
+ tokens = probe.tokens
150
+ checks.append(
151
+ ModelCheck(
152
+ model=probe.model.shown_name,
153
+ model_id=probe.model.model,
154
+ ok=probe.ok,
155
+ seconds=probe.seconds,
156
+ detail=probe.detail,
157
+ input_tokens=tokens[0] if tokens else None,
158
+ output_tokens=tokens[1] if tokens else None,
159
+ )
160
+ )
161
+ return tuple(checks)
162
+
163
+
80
164
  def translate(event: AgentEvent, bridge: Bridge) -> list[AnyEvent]:
81
165
  """Return the SDK events one ``zett-agent`` event stands for; usually one, sometimes none or several.
82
166
 
@@ -176,4 +260,4 @@ def _custom(event: AgentEvent, session: str, bridge: Bridge) -> list[AnyEvent]:
176
260
  )
177
261
  ]
178
262
  line = notice_from(event)
179
- return [Notice(session, line)] if line is not None else []
263
+ return [Notice(session, line, notice_level_from(event))] if line is not None else []