zettcode 0.1.9__tar.gz → 0.1.11__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 (310) hide show
  1. {zettcode-0.1.9 → zettcode-0.1.11}/PKG-INFO +6 -3
  2. {zettcode-0.1.9 → zettcode-0.1.11}/pyproject.toml +8 -2
  3. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/__init__.py +6 -6
  4. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/__init__.py +5 -5
  5. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/__init__.py +6 -5
  6. zettcode-0.1.11/src/zettcode/app/agent/agent/__init__.py +69 -0
  7. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/agent/commands.py +32 -19
  8. zettcode-0.1.11/src/zettcode/app/agent/agent/settings.py +132 -0
  9. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/entries.py +3 -0
  10. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/presenters.py +22 -8
  11. zettcode-0.1.11/src/zettcode/app/agent/conversation/projection.py +216 -0
  12. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/rendering.py +59 -3
  13. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/replay.py +42 -18
  14. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/text.py +63 -1
  15. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/tips.py +4 -1
  16. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/transcript/stream.py +18 -0
  17. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/transcript/turn.py +5 -2
  18. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/commands.py +17 -8
  19. zettcode-0.1.11/src/zettcode/app/plugins/__init__.py +111 -0
  20. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/app}/plugins/builtin_commands.py +101 -51
  21. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/app}/plugins/builtins.py +57 -63
  22. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/app}/plugins/container.py +43 -209
  23. zettcode-0.1.11/src/zettcode/app/plugins/host.py +36 -0
  24. zettcode-0.1.11/src/zettcode/app/plugins/loader.py +152 -0
  25. zettcode-0.1.11/src/zettcode/app/plugins/mixins.py +67 -0
  26. zettcode-0.1.11/src/zettcode/app/plugins/plugin.py +64 -0
  27. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/app}/plugins/state.py +46 -61
  28. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/app.py +137 -74
  29. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/bars.py +15 -15
  30. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/commands.py +24 -11
  31. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/config_form.py +65 -10
  32. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/followups.py +16 -10
  33. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/keys.py +31 -3
  34. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/labels.py +19 -2
  35. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/messaging.py +8 -8
  36. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/notices.py +25 -32
  37. zettcode-0.1.11/src/zettcode/app/ui/providers.py +615 -0
  38. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/sessions.py +71 -50
  39. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/settings.py +17 -15
  40. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/shell.py +17 -16
  41. zettcode-0.1.11/src/zettcode/app/ui/turns.py +101 -0
  42. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/__init__.py +14 -0
  43. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/approval_page.py +9 -9
  44. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/ask_page.py +2 -2
  45. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/completer.py +4 -5
  46. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/composer.py +2 -2
  47. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/context_charts.py +2 -1
  48. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/context_page.py +1 -1
  49. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/follow_up.py +4 -6
  50. zettcode-0.1.11/src/zettcode/app/ui/widgets/form_page.py +380 -0
  51. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/model_page.py +9 -9
  52. zettcode-0.1.11/src/zettcode/app/ui/widgets/model_settings_page.py +654 -0
  53. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/pet.py +44 -19
  54. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/sessions_page.py +9 -9
  55. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/statusline_page.py +59 -38
  56. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/transcript.py +47 -1
  57. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/usage_charts.py +43 -33
  58. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/usage_page.py +27 -9
  59. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/finder.py +33 -11
  60. zettcode-0.1.11/src/zettcode/budget.py +24 -0
  61. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/cli.py +193 -30
  62. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/config.py +196 -14
  63. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/config_edit.py +27 -1
  64. zettcode-0.1.11/src/zettcode/daemon/__init__.py +14 -0
  65. zettcode-0.1.11/src/zettcode/daemon/__main__.py +10 -0
  66. zettcode-0.1.11/src/zettcode/daemon/cli.py +314 -0
  67. zettcode-0.1.11/src/zettcode/daemon/process.py +270 -0
  68. zettcode-0.1.11/src/zettcode/daemon/run.py +321 -0
  69. zettcode-0.1.11/src/zettcode/daemon/state.py +302 -0
  70. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/doctor.py +1 -1
  71. zettcode-0.1.11/src/zettcode/engine/__init__.py +42 -0
  72. zettcode-0.1.11/src/zettcode/engine/agent/__init__.py +75 -0
  73. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/base.py +9 -3
  74. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/context.py +5 -4
  75. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/exports.py +3 -2
  76. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/interaction.py +2 -1
  77. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/sessions.py +23 -41
  78. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/settings.py +13 -90
  79. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/agent/turns.py +5 -4
  80. zettcode-0.1.9/src/zettcode/app/agent/runtime.py → zettcode-0.1.11/src/zettcode/engine/core.py +139 -51
  81. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/dialogue/ask.py +17 -0
  82. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/dialogue/side.py +11 -2
  83. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/export/__init__.py +3 -2
  84. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/__init__.py +1 -1
  85. zettcode-0.1.11/src/zettcode/engine/extensions/capabilities.py +68 -0
  86. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/compaction.py +1 -1
  87. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/context.py +2 -18
  88. zettcode-0.1.11/src/zettcode/engine/extensions/files.py +101 -0
  89. zettcode-0.1.11/src/zettcode/engine/extensions/harness.py +89 -0
  90. zettcode-0.1.11/src/zettcode/engine/extensions/harness_switch.py +18 -0
  91. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/mcp.py +2 -1
  92. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/messaging.py +2 -3
  93. zettcode-0.1.11/src/zettcode/engine/extensions/relay.py +93 -0
  94. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/subagents.py +96 -22
  95. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/tool_output.py +8 -7
  96. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/usage.py +12 -58
  97. {zettcode-0.1.9/src/zettcode/app/agent/extensions → zettcode-0.1.11/src/zettcode/engine}/messenger.py +1 -2
  98. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/permission/__init__.py +47 -15
  99. zettcode-0.1.11/src/zettcode/engine/permission/sandbox.py +124 -0
  100. zettcode-0.1.11/src/zettcode/engine/plugins/__init__.py +64 -0
  101. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/engine}/plugins/builtin_agent.py +164 -39
  102. zettcode-0.1.11/src/zettcode/engine/plugins/container.py +223 -0
  103. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/engine}/plugins/extension.py +2 -2
  104. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/engine}/plugins/loader.py +88 -111
  105. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/engine}/plugins/mixins.py +1 -59
  106. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/engine}/plugins/plugin.py +29 -51
  107. {zettcode-0.1.9/src/zettcode → zettcode-0.1.11/src/zettcode/engine}/plugins/services.py +1 -30
  108. zettcode-0.1.11/src/zettcode/engine/plugins/state.py +64 -0
  109. zettcode-0.1.11/src/zettcode/engine/server_tools.py +157 -0
  110. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/ledger.py +1 -1
  111. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/metadata.py +7 -2
  112. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/records.py +3 -1
  113. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/store.py +49 -2
  114. zettcode-0.1.11/src/zettcode/engine/tools/__init__.py +7 -0
  115. zettcode-0.1.11/src/zettcode/engine/tools/boundary.py +51 -0
  116. zettcode-0.1.11/src/zettcode/engine/tools/files.py +267 -0
  117. zettcode-0.1.11/src/zettcode/engine/tools/images.py +32 -0
  118. zettcode-0.1.11/src/zettcode/engine/tools/output.py +74 -0
  119. zettcode-0.1.11/src/zettcode/engine/tools/shell.py +171 -0
  120. zettcode-0.1.11/src/zettcode/engine/tools/workspace.py +268 -0
  121. zettcode-0.1.9/src/zettcode/app/agent/errors.py → zettcode-0.1.11/src/zettcode/error_text.py +3 -3
  122. {zettcode-0.1.9/src/zettcode/app/agent/dialogue → zettcode-0.1.11/src/zettcode}/mentions.py +2 -3
  123. zettcode-0.1.11/src/zettcode/oneshot.py +200 -0
  124. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/paths.py +5 -1
  125. {zettcode-0.1.9/src/zettcode/app/agent/agent → zettcode-0.1.11/src/zettcode}/prompt.py +1 -1
  126. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/providers.py +6 -3
  127. {zettcode-0.1.9/src/zettcode/app → zettcode-0.1.11/src/zettcode}/registry.py +2 -0
  128. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/__init__.py +16 -0
  129. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/client.py +542 -70
  130. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/errors.py +15 -0
  131. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/events.py +92 -14
  132. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/handlers.py +3 -1
  133. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/models.py +36 -19
  134. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/session.py +162 -27
  135. zettcode-0.1.9/src/zettcode/sdk/_translate.py → zettcode-0.1.11/src/zettcode/sdk/translation.py +105 -8
  136. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/sdk/types.py +135 -1
  137. zettcode-0.1.11/src/zettcode/server/__init__.py +33 -0
  138. zettcode-0.1.11/src/zettcode/server/app.py +654 -0
  139. zettcode-0.1.11/src/zettcode/server/cli.py +119 -0
  140. zettcode-0.1.11/src/zettcode/server/runner.py +724 -0
  141. zettcode-0.1.11/src/zettcode/server/schemas.py +628 -0
  142. zettcode-0.1.11/src/zettcode/server/serialize.py +334 -0
  143. zettcode-0.1.11/src/zettcode/server/settings.py +61 -0
  144. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/statusline.py +88 -3
  145. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/__init__.py +3 -1
  146. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/capabilities.py +6 -0
  147. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/theme.py +8 -0
  148. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/renderer.py +45 -7
  149. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/theme_file.py +17 -7
  150. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/textarea.py +1 -1
  151. zettcode-0.1.11/src/zettcode/wording.py +11 -0
  152. zettcode-0.1.9/src/zettcode/app/agent/agent/__init__.py +0 -75
  153. zettcode-0.1.9/src/zettcode/app/agent/conversation/projection.py +0 -230
  154. zettcode-0.1.9/src/zettcode/app/agent/extensions/capabilities.py +0 -50
  155. zettcode-0.1.9/src/zettcode/app/ui/providers.py +0 -567
  156. zettcode-0.1.9/src/zettcode/plugins/__init__.py +0 -112
  157. {zettcode-0.1.9 → zettcode-0.1.11}/.gitignore +0 -0
  158. {zettcode-0.1.9 → zettcode-0.1.11}/README.md +0 -0
  159. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/_compat.py +0 -0
  160. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/__init__.py +0 -0
  161. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/changes.py +0 -0
  162. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/effort_gauge.py +0 -0
  163. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/pulse.py +0 -0
  164. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/shimmer.py +0 -0
  165. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/stops.py +0 -0
  166. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/transcript/__init__.py +0 -0
  167. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/transcript/fold.py +0 -0
  168. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/transcript/log.py +0 -0
  169. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/agent/conversation/transcript/oneoff.py +0 -0
  170. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/brand.py +0 -0
  171. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/commandline/__init__.py +0 -0
  172. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/commandline/arguments.py +0 -0
  173. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/commandline/completion.py +0 -0
  174. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/commandline/history.py +0 -0
  175. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/commandline/runner.py +0 -0
  176. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/__init__.py +0 -0
  177. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/cat.py +0 -0
  178. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/chibi.py +0 -0
  179. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/dog.py +0 -0
  180. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/kangaroo.py +0 -0
  181. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/model.py +0 -0
  182. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/penguin.py +0 -0
  183. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/polarbear.py +0 -0
  184. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/scarfy.py +0 -0
  185. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/seagull.py +0 -0
  186. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/slime.py +0 -0
  187. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/snowbear.py +0 -0
  188. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/sprout.py +0 -0
  189. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/tiger.py +0 -0
  190. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/pets/whale.py +0 -0
  191. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/__init__.py +0 -0
  192. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/boot.py +0 -0
  193. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/clipboard.py +0 -0
  194. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/demo.py +0 -0
  195. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/gc_policy.py +0 -0
  196. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/updating.py +0 -0
  197. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/bash_page.py +0 -0
  198. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/effort_page.py +0 -0
  199. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/input_page.py +0 -0
  200. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/panel.py +0 -0
  201. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/pet_page.py +0 -0
  202. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/root.py +0 -0
  203. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/self_compact_page.py +0 -0
  204. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/send_kind_page.py +0 -0
  205. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/steering.py +0 -0
  206. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/update_page.py +0 -0
  207. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/app/ui/widgets/welcome.py +0 -0
  208. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/__init__.py +0 -0
  209. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/__main__.py +0 -0
  210. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/client.py +0 -0
  211. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/files.py +0 -0
  212. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/hub.py +0 -0
  213. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/log.py +0 -0
  214. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/models.py +0 -0
  215. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/process.py +0 -0
  216. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/protocol.py +0 -0
  217. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/roster.py +0 -0
  218. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/runner.py +0 -0
  219. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/server.py +0 -0
  220. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/text.py +0 -0
  221. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/broker/web.py +0 -0
  222. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/collector.py +0 -0
  223. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/dialogue/__init__.py +0 -0
  224. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/dialogue/title.py +0 -0
  225. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/export/trace/script.js +0 -0
  226. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/export/trace/style.css +0 -0
  227. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/export/trace/template.html +0 -0
  228. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/extensions/mcp_notice.py +0 -0
  229. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/permission/approval.py +0 -0
  230. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/permission/readonly.py +0 -0
  231. {zettcode-0.1.9/src/zettcode/app → zettcode-0.1.11/src/zettcode/engine}/pipeline.py +0 -0
  232. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/__init__.py +0 -0
  233. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/layout.py +0 -0
  234. {zettcode-0.1.9/src/zettcode/app/agent → zettcode-0.1.11/src/zettcode/engine}/storage/locks.py +0 -0
  235. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/filelock.py +0 -0
  236. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/py.typed +0 -0
  237. {zettcode-0.1.9/src/zettcode/app/agent/dialogue → zettcode-0.1.11/src/zettcode}/relay.py +0 -0
  238. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/clipboard.py +0 -0
  239. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/console.py +0 -0
  240. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/__init__.py +0 -0
  241. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/app.py +0 -0
  242. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/events.py +0 -0
  243. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/focus.py +0 -0
  244. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/geometry.py +0 -0
  245. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/host.py +0 -0
  246. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/keymap.py +0 -0
  247. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/scheduler.py +0 -0
  248. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/screen.py +0 -0
  249. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/core/widget.py +0 -0
  250. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/gallery.py +0 -0
  251. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/glyphs.py +0 -0
  252. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/input/__init__.py +0 -0
  253. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/input/bridge.py +0 -0
  254. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/input/decoder.py +0 -0
  255. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/input/events.py +0 -0
  256. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/input/keys.py +0 -0
  257. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/input/reader.py +0 -0
  258. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/layout/__init__.py +0 -0
  259. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/layout/box.py +0 -0
  260. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/layout/overlay.py +0 -0
  261. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/layout/scroll.py +0 -0
  262. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/layout/solver.py +0 -0
  263. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/layout/spacing.py +0 -0
  264. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/__init__.py +0 -0
  265. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/ansi.py +0 -0
  266. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/canvas.py +0 -0
  267. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/code.py +0 -0
  268. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/color.py +0 -0
  269. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/rich_text.py +0 -0
  270. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/style.py +0 -0
  271. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/sweep.py +0 -0
  272. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/render/text.py +0 -0
  273. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/runner.py +0 -0
  274. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/terminal.py +0 -0
  275. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/testing/__init__.py +0 -0
  276. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/testing/harness.py +0 -0
  277. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/testing/snapshot.py +0 -0
  278. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/ayu-mirage.toml +0 -0
  279. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/catppuccin-latte.toml +0 -0
  280. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/catppuccin-mocha.toml +0 -0
  281. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/dracula.toml +0 -0
  282. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/github-dimmed.toml +0 -0
  283. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/github-light.toml +0 -0
  284. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/gruvbox-dark.toml +0 -0
  285. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/gruvbox-light.toml +0 -0
  286. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/kanagawa.toml +0 -0
  287. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/nord.toml +0 -0
  288. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/one-dark.toml +0 -0
  289. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/rose-pine-dawn.toml +0 -0
  290. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/rose-pine.toml +0 -0
  291. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/solarized-dark.toml +0 -0
  292. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/solarized-light.toml +0 -0
  293. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/themes/tokyo-night.toml +0 -0
  294. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/__init__.py +0 -0
  295. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/collapsible.py +0 -0
  296. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/completion.py +0 -0
  297. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/console.py +0 -0
  298. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/dialog.py +0 -0
  299. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/diff.py +0 -0
  300. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/list.py +0 -0
  301. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/list_page.py +0 -0
  302. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/markdown.py +0 -0
  303. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/progress.py +0 -0
  304. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/rich_text.py +0 -0
  305. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/status.py +0 -0
  306. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/table.py +0 -0
  307. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/tasks.py +0 -0
  308. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/text.py +0 -0
  309. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/tui/widgets/toast.py +0 -0
  310. {zettcode-0.1.9 → zettcode-0.1.11}/src/zettcode/update.py +0 -0
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: zettcode
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: A focused terminal coding agent powered by zett-agent
5
5
  Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
6
6
  Project-URL: Issues, https://github.com/Chang-LeHung/zettcode/issues
7
7
  Author: Chang-LeHung
8
8
  License-Expression: MIT
9
9
  Keywords: agent,coding-agent,llm,terminal,tui
10
- Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Environment :: Console
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: FastAPI
12
14
  Classifier: Operating System :: MacOS
13
15
  Classifier: Operating System :: Microsoft :: Windows :: Windows 10
14
16
  Classifier: Operating System :: POSIX :: Linux
@@ -21,6 +23,7 @@ Classifier: Programming Language :: Python :: 3.15
21
23
  Classifier: Typing :: Typed
22
24
  Requires-Python: >=3.10
23
25
  Requires-Dist: exceptiongroup<2,>=1.2; python_version < '3.11'
26
+ Requires-Dist: fastapi<1,>=0.115
24
27
  Requires-Dist: httpx<1,>=0.27
25
28
  Requires-Dist: pydantic<3,>=2
26
29
  Requires-Dist: pyperclip<2,>=1.9
@@ -32,7 +35,7 @@ Requires-Dist: truststore<1,>=0.10
32
35
  Requires-Dist: uvicorn<1,>=0.35
33
36
  Requires-Dist: wcwidth<1,>=0.2
34
37
  Requires-Dist: websockets<17,>=15
35
- Requires-Dist: zett-agent<0.2,>=0.1.17
38
+ Requires-Dist: zett-agent<0.3,>=0.2.0
36
39
  Description-Content-Type: text/markdown
37
40
 
38
41
  # ZettCode
@@ -8,8 +8,10 @@ license = "MIT"
8
8
  authors = [{name = "Chang-LeHung"}]
9
9
  keywords = ["agent", "llm", "tui", "terminal", "coding-agent"]
10
10
  classifiers = [
11
- "Development Status :: 3 - Alpha",
11
+ "Development Status :: 5 - Production/Stable",
12
12
  "Environment :: Console",
13
+ "Environment :: Web Environment",
14
+ "Framework :: FastAPI",
13
15
  "Operating System :: MacOS",
14
16
  "Operating System :: Microsoft :: Windows :: Windows 10",
15
17
  "Operating System :: POSIX :: Linux",
@@ -36,6 +38,9 @@ dependencies = [
36
38
  "uvicorn>=0.35,<1",
37
39
  "httpx>=0.27,<1",
38
40
  "websockets>=15,<17",
41
+ # The HTTP server is part of this package and always installed: `pip install zettcode` writes both console
42
+ # scripts, so `zettcode-server` may not be a command whose dependencies are an optional extra.
43
+ "fastapi>=0.115,<1",
39
44
  # The counter behind ``/context`` measures a request with it, and imports it
40
45
  # itself: the floor matches the runtime's, so neither resolver has to fight.
41
46
  "tiktoken>=0.12,<1",
@@ -53,11 +58,12 @@ dependencies = [
53
58
  # than it did. An exact pin would make zettcode fight every other tool that
54
59
  # shares the dependency — including an upgrade of zett-agent itself, which pip
55
60
  # would downgrade to satisfy it.
56
- "zett-agent>=0.1.17,<0.2",
61
+ "zett-agent>=0.2.0,<0.3",
57
62
  ]
58
63
 
59
64
  [project.scripts]
60
65
  zettcode = "zettcode.cli:main"
66
+ zettcode-server = "zettcode.server.cli:main"
61
67
 
62
68
  [project.urls]
63
69
  Repository = "https://github.com/Chang-LeHung/zettcode"
@@ -7,21 +7,21 @@ 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.9"
10
+ __version__ = "0.1.11"
11
11
 
12
12
  #: Where each public name lives; read by :func:`__getattr__` on first use.
13
13
  _EXPORTS = {
14
14
  "ModelConfig": ".config",
15
- "ZettCodeAgent": ".app.agent.agent",
15
+ "ZettCodeAgent": ".engine.agent",
16
16
  "ZettCodeConfig": ".config",
17
- "ZettCodeRuntime": ".app.agent.runtime",
17
+ "ZettCodeEngine": ".engine.core",
18
18
  }
19
19
 
20
20
 
21
21
  if TYPE_CHECKING: # pragma: no cover - for type checkers, not the runtime
22
- from .app.agent.agent import ZettCodeAgent
23
- from .app.agent.runtime import ZettCodeRuntime
24
22
  from .config import ModelConfig, ZettCodeConfig
23
+ from .engine.agent import ZettCodeAgent
24
+ from .engine.core import ZettCodeEngine
25
25
 
26
26
 
27
27
  def __getattr__(name: str) -> object:
@@ -44,4 +44,4 @@ def __dir__() -> list[str]:
44
44
  return sorted(__all__)
45
45
 
46
46
 
47
- __all__ = ["ModelConfig", "ZettCodeAgent", "ZettCodeConfig", "ZettCodeRuntime"]
47
+ __all__ = ["ModelConfig", "ZettCodeAgent", "ZettCodeConfig", "ZettCodeEngine"]
@@ -18,15 +18,15 @@ _EXPORTS = {
18
18
  "TranscriptProjector": ".agent",
19
19
  "TranscriptSource": ".ui",
20
20
  "TranscriptView": ".ui",
21
- "ZettCodeAgent": ".agent",
21
+ "TerminalAgent": ".agent",
22
22
  "ZettCodeApp": ".ui",
23
23
  "ZettCodeRoot": ".ui",
24
- "ZettCodeRuntime": ".agent",
24
+ "ZettCodeEngine": ".agent",
25
25
  }
26
26
 
27
27
 
28
28
  if TYPE_CHECKING: # pragma: no cover - for type checkers, not the runtime
29
- from .agent import Entry, Transcript, TranscriptProjector, ZettCodeAgent, ZettCodeRuntime
29
+ from .agent import Entry, TerminalAgent, Transcript, TranscriptProjector, ZettCodeEngine
30
30
  from .ui import TranscriptSource, TranscriptView, ZettCodeApp, ZettCodeRoot
31
31
 
32
32
 
@@ -57,7 +57,7 @@ __all__ = [
57
57
  "TranscriptSource",
58
58
  "TranscriptView",
59
59
  "ZettCodeApp",
60
- "ZettCodeAgent",
60
+ "TerminalAgent",
61
61
  "ZettCodeRoot",
62
- "ZettCodeRuntime",
62
+ "ZettCodeEngine",
63
63
  ]
@@ -10,17 +10,18 @@ _EXPORTS = {
10
10
  "Entry": ".conversation.entries",
11
11
  "Transcript": ".conversation.transcript",
12
12
  "TranscriptProjector": ".conversation.projection",
13
- "ZettCodeAgent": ".agent",
14
- "ZettCodeRuntime": ".runtime",
13
+ "TerminalAgent": ".agent",
14
+ "ZettCodeEngine": "zettcode.engine.core",
15
15
  }
16
16
 
17
17
 
18
18
  if TYPE_CHECKING: # pragma: no cover - for type checkers, not the runtime
19
- from .agent import ZettCodeAgent
19
+ from zettcode.engine.core import ZettCodeEngine
20
+
21
+ from .agent import TerminalAgent
20
22
  from .conversation.entries import Entry
21
23
  from .conversation.projection import TranscriptProjector
22
24
  from .conversation.transcript import Transcript
23
- from .runtime import ZettCodeRuntime
24
25
 
25
26
 
26
27
  def __getattr__(name: str) -> object:
@@ -43,4 +44,4 @@ def __dir__() -> list[str]:
43
44
  return sorted(__all__)
44
45
 
45
46
 
46
- __all__ = ["Entry", "Transcript", "TranscriptProjector", "ZettCodeAgent", "ZettCodeRuntime"]
47
+ __all__ = ["Entry", "Transcript", "TranscriptProjector", "TerminalAgent", "ZettCodeEngine"]
@@ -0,0 +1,69 @@
1
+ """The terminal's agent: the engine's agent, plus the commands and preferences only a terminal has.
2
+
3
+ The engine's agent (:class:`zettcode.engine.agent.ZettCodeAgent`) does the work. This subclass adds what the shell
4
+ asks of it that is not work: the slash commands and what plugins contributed (``commands``), and the theme, the pet
5
+ and the status line the reader can change (``settings``). Its engine is built with the terminal's plugin loader, so
6
+ the commands, palettes, pets, presenters and rows of the plugins are there to be read.
7
+
8
+ This package is also where the terminal is put together from the engine's parts, so it is what names, for the views, the
9
+ engine objects they hold: ``SessionLink`` (the line to the other sessions) and ``StoredSession`` (a session read from
10
+ the store). The views import those names from here and not from the engine.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ from zettcode.engine.agent import ZettCodeAgent
18
+ from zettcode.engine.agent.sessions import StoredSession
19
+ from zettcode.engine.core import PluginLoader, ZettCodeEngine
20
+ from zettcode.engine.messenger import Messenger as SessionLink
21
+
22
+ from .commands import CommandsMixin
23
+
24
+ if TYPE_CHECKING: # pragma: no cover - annotations only
25
+ from zettcode.config import ZettCodeConfig
26
+ from zettcode.sdk.client import Client
27
+
28
+ __all__ = ["SessionLink", "StoredSession", "TerminalAgent"]
29
+
30
+
31
+ def _terminal_loader() -> PluginLoader:
32
+ """Return the terminal's plugin loader, imported when an agent is built and not before."""
33
+ from zettcode.app.plugins.loader import terminal_plugins
34
+
35
+ return terminal_plugins
36
+
37
+
38
+ class TerminalAgent(CommandsMixin, ZettCodeAgent):
39
+ """The agent the terminal application drives."""
40
+
41
+ _client: Client | None = None
42
+
43
+ @property
44
+ def messenger(self) -> SessionLink | None:
45
+ """Return the line to the other sessions, or ``None`` when the configuration switches messaging off."""
46
+ return self.runtime.messenger
47
+
48
+ @property
49
+ def client(self) -> Client:
50
+ """Return the SDK client over this agent, which is how the shell reaches everything the SDK offers.
51
+
52
+ Made on first use and then kept, so the shell and the commands it runs read the same client. The agent still
53
+ owns closing: the client is a view of it.
54
+ """
55
+ if self._client is None:
56
+ from zettcode.sdk.client import Client
57
+
58
+ self._client = Client._around(self)
59
+ return self._client
60
+
61
+ @classmethod
62
+ async def create(cls, config: ZettCodeConfig) -> TerminalAgent:
63
+ """Build the engine with the terminal's plugins and start it."""
64
+ return cls(await ZettCodeEngine.create(config, _terminal_loader()))
65
+
66
+ @classmethod
67
+ def preview(cls, config: ZettCodeConfig) -> TerminalAgent:
68
+ """Wrap an engine that still has to start, built with the terminal's plugins, so the shell can paint first."""
69
+ return cls(ZettCodeEngine.preview(config, _terminal_loader()))
@@ -9,23 +9,32 @@ from __future__ import annotations
9
9
 
10
10
  from zettcode.app.commands import Command, CommandContext, CommandHost, CommandResult
11
11
  from zettcode.app.pets import PetCatalog, bundled_pets
12
- from zettcode.plugins import UiRow
13
- from zettcode.plugins.services import HOST_LINK, HostLink
12
+ from zettcode.app.plugins.host import HOST_LINK, HostLink
13
+ from zettcode.app.plugins.plugin import TerminalPlugins
14
+ from zettcode.app.plugins.state import TurnSegment, UiRow
15
+ from zettcode.engine.agent.exports import ExportsMixin
14
16
  from zettcode.providers import BUILTIN_PROVIDERS, Provider, merge_providers
15
17
  from zettcode.statusline import NAMES, unknown
16
18
  from zettcode.tui import ThemeCatalog
17
19
 
18
20
  from ..conversation.presenters import Presenters
19
- from ..dialogue.mentions import MentionProvider
20
- from .exports import ExportsMixin
21
+ from .settings import AppearanceMixin
21
22
 
22
23
  #: The words ``/self-compact`` takes after it, or after ``global``.
23
24
  _SELF_COMPACT_WORDS = frozenset({"on", "off", "default"})
24
25
 
25
26
 
26
- class CommandsMixin(ExportsMixin):
27
+ class CommandsMixin(AppearanceMixin, ExportsMixin):
27
28
  """The ``/new``, ``/compact``, and ``/export`` commands, and the plugins' commands, mentions, and rows."""
28
29
 
30
+ @property
31
+ def terminal_plugins(self) -> TerminalPlugins:
32
+ """Return what the plugins contributed to the terminal; the engine was built with the terminal's loader."""
33
+ plugins = self.runtime.plugins
34
+ if not isinstance(plugins, TerminalPlugins):
35
+ raise TypeError("This agent's engine was not built with the terminal's plugin loader")
36
+ return plugins
37
+
29
38
  @property
30
39
  def commands(self) -> tuple[Command, ...]:
31
40
  """Return the conversation commands the agent owns.
@@ -52,17 +61,12 @@ class CommandsMixin(ExportsMixin):
52
61
  The shell keeps them after everything built in, so a plugin cannot take over a built-in name;
53
62
  plugin-versus-plugin duplicates are rejected when the plugins load.
54
63
  """
55
- return self.runtime.plugins.installed_commands
64
+ return self.terminal_plugins.installed_commands
56
65
 
57
66
  @property
58
67
  def builtin_plugin_commands(self) -> tuple[Command, ...]:
59
68
  """Return the commands the program's own plugins registered: ``/resume``, ``/model``, ``/usage`` and the rest."""
60
- return self.runtime.plugins.builtin_commands
61
-
62
- @property
63
- def plugin_mentions(self) -> tuple[MentionProvider, ...]:
64
- """Return the ``@`` resource providers the installed plugins registered."""
65
- return self.runtime.plugins.mentions
69
+ return self.terminal_plugins.builtin_commands
66
70
 
67
71
  @property
68
72
  def providers(self) -> tuple[Provider, ...]:
@@ -76,7 +80,7 @@ class CommandsMixin(ExportsMixin):
76
80
  Built fresh each time it is asked: it lists names and builds nothing, and the reader's directory may have
77
81
  gained a file since the last look.
78
82
  """
79
- return ThemeCatalog(self.runtime.plugins.themes, self.runtime.config.themes_dir)
83
+ return ThemeCatalog(self.terminal_plugins.themes, self.runtime.config.themes_dir)
80
84
 
81
85
  @property
82
86
  def pets(self) -> PetCatalog:
@@ -84,13 +88,18 @@ class CommandsMixin(ExportsMixin):
84
88
 
85
89
  A plugin bundle that offers none (one built by hand, without the built-in plugin) gets the program's own.
86
90
  """
87
- sources = self.runtime.plugins.pets
91
+ sources = self.terminal_plugins.pets
88
92
  return PetCatalog(sources or bundled_pets())
89
93
 
94
+ @property
95
+ def turn_segments(self) -> tuple[TurnSegment, ...]:
96
+ """Return the items of the line a request ends on, the program's own first, then the plugins'."""
97
+ return self.terminal_plugins.turn_segments
98
+
90
99
  @property
91
100
  def plugin_failures(self) -> tuple[str, ...]:
92
101
  """Return one message per plugin that could not be loaded or activated."""
93
- return self.runtime.plugins.failures
102
+ return self.terminal_plugins.failures
94
103
 
95
104
  @property
96
105
  def presenters(self) -> Presenters:
@@ -100,12 +109,12 @@ class CommandsMixin(ExportsMixin):
100
109
  program's own rows are registered through the plugin route, so a plugin that draws a kind or a family of
101
110
  rows reaches the transcript without a second path only plugins could not take.
102
111
  """
103
- return Presenters(self.runtime.plugins.presenters)
112
+ return Presenters(self.terminal_plugins.presenters)
104
113
 
105
114
  @property
106
115
  def plugin_rows(self) -> tuple[UiRow, ...]:
107
116
  """Return the header and status rows plugins asked the shell to draw."""
108
- return self.runtime.plugins.rows
117
+ return self.terminal_plugins.rows
109
118
 
110
119
  async def _command_new(self, context: CommandContext) -> CommandResult:
111
120
  """Start a fresh session."""
@@ -174,10 +183,14 @@ class CommandsMixin(ExportsMixin):
174
183
 
175
184
  def statusline_summary(self) -> str:
176
185
  """Say in one line which parts of the header and the status line are hidden."""
177
- hidden = [name for name in NAMES if name in self.statusline_hidden]
186
+ hidden = [
187
+ *(name for name in NAMES if name in self.statusline_hidden),
188
+ *sorted(self.statusline_hidden - set(NAMES)),
189
+ ]
178
190
  if not hidden:
179
191
  return "Status line: every part is shown"
180
- return f"Status line: {len(hidden)} of {len(NAMES)} parts hidden ({', '.join(hidden)})"
192
+ total = len(NAMES) + len({item.name for item in self.turn_segments} - set(NAMES))
193
+ return f"Status line: {len(hidden)} of {total} parts hidden ({', '.join(hidden)})"
181
194
 
182
195
  def change_statusline(self, argument: str) -> str:
183
196
  """Hide or show parts of the header and status line from the words ``/statusline`` takes.
@@ -0,0 +1,132 @@
1
+ """What the reader can change about how the terminal looks: theme, pet, which parts of the status line are shown.
2
+
3
+ These are the terminal's own preferences. They are kept in the same config file as the engine's settings, and are
4
+ written the same way, which is why this reads the file through the engine's helpers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+ from dataclasses import replace
11
+ from pathlib import Path
12
+
13
+ from zettcode.config_edit import (
14
+ ConfigEditError,
15
+ set_pet,
16
+ set_pet_position,
17
+ set_statusline,
18
+ set_theme,
19
+ write_text_atomically,
20
+ )
21
+ from zettcode.engine.agent.settings import SettingsMixin
22
+ from zettcode.statusline import NAMES
23
+
24
+
25
+ class AppearanceMixin(SettingsMixin):
26
+ """Read and remember the terminal's theme, pet and status line."""
27
+
28
+ @property
29
+ def reduced_motion(self) -> bool:
30
+ """Return whether decorative animation is disabled."""
31
+ return self.runtime.config.reduced_motion
32
+
33
+ def remember_theme(self, name: str) -> Path:
34
+ """Write ``[ui] theme = name`` into the config file, so the next start opens in that palette.
35
+
36
+ Only that line changes. A config file that does not exist is not created for it: a file with nothing but
37
+ ``[ui]`` in it would stand where a reader's environment-variable setup is, and say nothing about models.
38
+
39
+ Returns:
40
+ The file written.
41
+
42
+ Raises:
43
+ ConfigEditError: When there is no config file, it cannot be read or edited safely, or it cannot be
44
+ written. Nothing is written then.
45
+ """
46
+ text = set_theme(self._read_config_file(), name, where=self.config_file)
47
+ try:
48
+ write_text_atomically(self.config_file, text)
49
+ except OSError as error:
50
+ raise ConfigEditError(f"{self.config_file} could not be written: {error}") from error
51
+ return self.config_file
52
+
53
+ def remember_pet(self, name: str) -> Path:
54
+ """Write ``[ui] pet = name`` into the config file, so the next start opens with that pet.
55
+
56
+ Only that line changes, and a config file that does not exist is not created for it. The pet in force
57
+ for this run is the shell's to switch; what this keeps up to date is the setting the shell reads back.
58
+
59
+ Returns:
60
+ The file written.
61
+
62
+ Raises:
63
+ ConfigEditError: When there is no config file, it cannot be read or edited safely, or it cannot be
64
+ written. Nothing is written then.
65
+ """
66
+ text = set_pet(self._read_config_file(), name, where=self.config_file)
67
+ try:
68
+ write_text_atomically(self.config_file, text)
69
+ except OSError as error:
70
+ raise ConfigEditError(f"{self.config_file} could not be written: {error}") from error
71
+ self.runtime.config = replace(self.runtime.config, pet=name)
72
+ return self.config_file
73
+
74
+ def remember_pet_position(self, across: float, down: float) -> Path:
75
+ """Write ``[ui] pet_position`` into the config file, so the next start puts the pet back where it was left.
76
+
77
+ The file is global to the reader (``~/.zettcode/config.toml``), not to a project: a pet is the reader's
78
+ companion, and it does not move house when they open another folder. Only that line changes, and a
79
+ config file that does not exist is not created for it.
80
+
81
+ Returns:
82
+ The file written.
83
+
84
+ Raises:
85
+ ConfigEditError: When there is no config file, it cannot be read or edited safely, or it cannot be
86
+ written. Nothing is written then.
87
+ """
88
+ text = set_pet_position(self._read_config_file(), across, down, where=self.config_file)
89
+ try:
90
+ write_text_atomically(self.config_file, text)
91
+ except OSError as error:
92
+ raise ConfigEditError(f"{self.config_file} could not be written: {error}") from error
93
+ position = (round(min(1.0, max(0.0, across)), 3), round(min(1.0, max(0.0, down)), 3))
94
+ self.runtime.config = replace(self.runtime.config, pet_position=position)
95
+ return self.config_file
96
+
97
+ @property
98
+ def statusline_hidden(self) -> frozenset[str]:
99
+ """Return the names of the header and status-line parts the reader switched off."""
100
+ return frozenset(self.runtime.config.statusline_hidden)
101
+
102
+ def remember_statusline(self, shown: Mapping[str, bool]) -> Path:
103
+ """Write which parts of the header and status line are shown into ``[statusline]``.
104
+
105
+ Only the parts that differ from what is in force are written. The file is written first and the
106
+ running configuration changed second, so a write that fails leaves both as they were; the rows
107
+ follow at the next paint. A config file that does not exist is not created for it, as for the theme.
108
+
109
+ Args:
110
+ shown: Part name to whether it is shown. A part left out keeps what it has.
111
+
112
+ Returns:
113
+ The file written.
114
+
115
+ Raises:
116
+ ConfigEditError: When there is no config file, it cannot be read or edited safely, or it cannot
117
+ be written.
118
+ """
119
+ hidden = set(self.statusline_hidden)
120
+ changes = {name: visible for name, visible in shown.items() if (name not in hidden) != visible}
121
+ if not changes:
122
+ return self.config_file
123
+ text = set_statusline(self._read_config_file(), changes, where=self.config_file)
124
+ try:
125
+ write_text_atomically(self.config_file, text)
126
+ except OSError as error:
127
+ raise ConfigEditError(f"{self.config_file} could not be written: {error}") from error
128
+ hidden = {name for name in hidden if name not in changes} | {name for name, v in changes.items() if not v}
129
+ # The program's parts in their order, then the ones a plugin added (their names are not in NAMES).
130
+ ordered = (*(name for name in NAMES if name in hidden), *sorted(hidden - set(NAMES)))
131
+ self.runtime.config = replace(self.runtime.config, statusline_hidden=ordered)
132
+ return self.config_file
@@ -195,6 +195,9 @@ class WaitEntry(TimedEntry):
195
195
  tips: tuple[str, ...] = ()
196
196
  #: The line the request ended on, written when the row is settled.
197
197
  text: str = ""
198
+ #: The items of the line, one string each, written with the line; the presenter joins them with the separator
199
+ #: and breaks the row between two, never inside one. Empty means the line is ``text`` alone.
200
+ parts: tuple[str, ...] = ()
198
201
 
199
202
 
200
203
  @dataclass(slots=True, kw_only=True)
@@ -80,6 +80,7 @@ from .text import (
80
80
  bounded_rows,
81
81
  duration_text,
82
82
  elapsed_text,
83
+ pack_segments,
83
84
  quiet_text,
84
85
  terminal_safe,
85
86
  tip_text,
@@ -106,6 +107,8 @@ TOOL_ROLES = {
106
107
  "view_image": "image",
107
108
  "glob": "search",
108
109
  "grep": "search",
110
+ "web_search": "web-search",
111
+ "ask_user": "ask-user",
109
112
  "write_file": "write",
110
113
  "edit": "write",
111
114
  "replace_in_file": "write", # what a session stored before the rename called it
@@ -120,8 +123,12 @@ TOOL_ROLES = {
120
123
 
121
124
 
122
125
  def tool_color(tool: str, theme: Theme) -> str:
123
- """Return the colour one tool paints its rows with."""
124
- return getattr(theme.tools, TOOL_ROLES.get(tool, ""), theme.accent)
126
+ """Return the colour one tool paints its rows with.
127
+
128
+ A role spelled with a hyphen (``web-search``) names the theme field it stands for (``web_search``).
129
+ """
130
+ role = TOOL_ROLES.get(tool, "").replace("-", "_")
131
+ return getattr(theme.tools, role, theme.accent)
125
132
 
126
133
 
127
134
  #: The entry fields that are not part of how a row looks: its identity, and the
@@ -483,13 +490,15 @@ class WaitPresenter(Presenter[WaitEntry]):
483
490
  """
484
491
  if not entry.running:
485
492
  settled = entry.text or f"Processed for {elapsed_text(entry.duration or 0.0)}"
493
+ ink = Style(foreground=theme.muted)
494
+ room = max(1, width - CONTENT_INDENT)
495
+ packed = pack_segments(list(entry.parts) or settled.split(f" {SEPARATOR} "), room)
486
496
  return [
487
497
  TextLine(),
488
- layout_rich_lines(
489
- (TextLine((Span(" " * CONTENT_INDENT + settled, Style(foreground=theme.muted)),)),),
490
- width,
491
- wrap=False,
492
- )[0],
498
+ *(
499
+ layout_rich_lines((TextLine((Span(" " * CONTENT_INDENT + row, ink),)),), width, wrap=False)[0]
500
+ for row in packed
501
+ ),
493
502
  ]
494
503
  spans = list(_running_label(entry.title, theme, frame))
495
504
  if entry.duration is not None:
@@ -575,7 +584,12 @@ class ToolPresenter(Presenter[T]):
575
584
  }.get(entry.status, STATUS)
576
585
  )
577
586
  hue = tool_color(entry.tool, theme)
578
- colour = theme.error if entry.status is EntryStatus.FAILED else hue
587
+ # A failure is the error colour, and a row that did not happen at all — a skipped tool, a cancelled
588
+ # question — is a caution rather than a result, so it is amber and not the tool's own hue.
589
+ colour = {
590
+ EntryStatus.FAILED: theme.error,
591
+ EntryStatus.SKIPPED: theme.warning,
592
+ }.get(entry.status, hue)
579
593
  marker = Style(foreground=colour)
580
594
  label = Style(foreground=colour)
581
595
  muted = Style(foreground=theme.muted)