@scemoon/cdh 1.0.6 → 1.0.7

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 (550) hide show
  1. package/package.json +2 -2
  2. package/ai-dlc-skill/SKILL.md +0 -77
  3. package/ai-dlc-skill/agents/deliver-agent.md +0 -24
  4. package/ai-dlc-skill/agents/master.md +0 -50
  5. package/ai-dlc-skill/agents/plan-agent.md +0 -23
  6. package/ai-dlc-skill/agents/understand-agent.md +0 -23
  7. package/ai-dlc-skill/agents/verify-agent.md +0 -27
  8. package/ai-dlc-skill/architecture/project-structure.md +0 -78
  9. package/ai-dlc-skill/brownfield/README.md +0 -30
  10. package/ai-dlc-skill/brownfield/generate-context.sh +0 -64
  11. package/ai-dlc-skill/brownfield/scripts/deps.sh +0 -27
  12. package/ai-dlc-skill/brownfield/scripts/discover.sh +0 -34
  13. package/ai-dlc-skill/brownfield/scripts/extract-api.sh +0 -45
  14. package/ai-dlc-skill/components/backend.md +0 -34
  15. package/ai-dlc-skill/components/desktop.md +0 -30
  16. package/ai-dlc-skill/components/mya.md +0 -29
  17. package/ai-dlc-skill/components/native.md +0 -30
  18. package/ai-dlc-skill/components/tta.md +0 -29
  19. package/ai-dlc-skill/components/web.md +0 -31
  20. package/ai-dlc-skill/components/wxa.md +0 -29
  21. package/ai-dlc-skill/contracts/README.md +0 -25
  22. package/ai-dlc-skill/core/adaptive-flow.md +0 -36
  23. package/ai-dlc-skill/core/security.md +0 -29
  24. package/ai-dlc-skill/cross-tool/cline/export.sh +0 -31
  25. package/ai-dlc-skill/cross-tool/copilot/export.sh +0 -47
  26. package/ai-dlc-skill/cross-tool/cursor/export.sh +0 -32
  27. package/ai-dlc-skill/cross-tool/export.sh +0 -22
  28. package/ai-dlc-skill/cross-tool/onecode/config.yaml.tpl +0 -17
  29. package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +0 -35
  30. package/ai-dlc-skill/cross-tool/onecode/export.sh +0 -54
  31. package/ai-dlc-skill/phases/deliver/entry.md +0 -26
  32. package/ai-dlc-skill/phases/deliver/lifecycle.md +0 -185
  33. package/ai-dlc-skill/phases/deliver/prompt.md +0 -40
  34. package/ai-dlc-skill/phases/deliver/rules.md +0 -61
  35. package/ai-dlc-skill/phases/plan/entry.md +0 -25
  36. package/ai-dlc-skill/phases/plan/lifecycle.md +0 -204
  37. package/ai-dlc-skill/phases/plan/prompt.md +0 -38
  38. package/ai-dlc-skill/phases/plan/rules.md +0 -25
  39. package/ai-dlc-skill/phases/understand/entry.md +0 -26
  40. package/ai-dlc-skill/phases/understand/lifecycle.md +0 -184
  41. package/ai-dlc-skill/phases/understand/prompt.md +0 -41
  42. package/ai-dlc-skill/phases/understand/rules.md +0 -37
  43. package/ai-dlc-skill/phases/verify/entry.md +0 -26
  44. package/ai-dlc-skill/phases/verify/lifecycle.md +0 -192
  45. package/ai-dlc-skill/phases/verify/prompt.md +0 -43
  46. package/ai-dlc-skill/phases/verify/rules.md +0 -73
  47. package/ai-dlc-skill/practices/bdd.md +0 -91
  48. package/ai-dlc-skill/practices/sdd.md +0 -84
  49. package/ai-dlc-skill/practices/tdd.md +0 -95
  50. package/ai-dlc-skill/providers/README.md +0 -114
  51. package/ai-dlc-skill/providers/aliyun/deployment.yaml +0 -66
  52. package/ai-dlc-skill/providers/aliyun/preview.yaml +0 -84
  53. package/ai-dlc-skill/providers/aliyun/provider.yaml +0 -160
  54. package/ai-dlc-skill/providers/tcb/deployment.yaml +0 -69
  55. package/ai-dlc-skill/providers/tcb/preview.yaml +0 -83
  56. package/ai-dlc-skill/providers/tcb/provider.yaml +0 -172
  57. package/ai-dlc-skill/requirements.md +0 -140
  58. package/ai-dlc-skill/skill.yaml +0 -147
  59. package/ai-dlc-skill/templates/integration/CHANGELOG.md +0 -41
  60. package/ai-dlc-skill/templates/integration/asyncapi.yaml +0 -43
  61. package/ai-dlc-skill/templates/integration/contract-diff.md +0 -63
  62. package/ai-dlc-skill/templates/integration/contract-spec.md +0 -49
  63. package/ai-dlc-skill/templates/integration/openapi.yaml +0 -18
  64. package/ai-dlc-skill/templates/integration/runtime-contract.yaml +0 -36
  65. package/ai-dlc-skill/templates/plan/design.md +0 -49
  66. package/ai-dlc-skill/templates/plan/task-list.md +0 -32
  67. package/ai-dlc-skill/templates/project/README.md +0 -126
  68. package/ai-dlc-skill/templates/project/template.md +0 -119
  69. package/ai-dlc-skill/templates/understand/feature.feature +0 -23
  70. package/ai-dlc-skill/templates/understand/intent.md +0 -29
  71. package/ai-dlc-skill/templates/understand/spec-delta.md +0 -47
  72. package/ai-dlc-skill/templates/verify/test-unit.py +0 -27
  73. package/ai-dlc-skill/walkthrough/collect.sh +0 -50
  74. package/ai-dlc-skill/walkthrough/template.md +0 -54
  75. package/ai-dlc-skill/workflows/ai-dlc.yaml +0 -110
  76. package/ai-dlc-skill/workflows/deployment.yaml +0 -199
  77. package/cdh/__init__.py +0 -0
  78. package/cdh/__main__.py +0 -4
  79. package/cdh/__pycache__/__init__.cpython-314.pyc +0 -0
  80. package/cdh/__pycache__/cdh_mcp_injector.cpython-314.pyc +0 -0
  81. package/cdh/__pycache__/cdh_mcp_loader.cpython-314.pyc +0 -0
  82. package/cdh/__pycache__/cdh_mcp_manager.cpython-314.pyc +0 -0
  83. package/cdh/__pycache__/cdh_session_aggregator.cpython-314.pyc +0 -0
  84. package/cdh/__pycache__/cdh_skill_loader.cpython-314.pyc +0 -0
  85. package/cdh/__pycache__/cdh_skill_manager.cpython-314.pyc +0 -0
  86. package/cdh/__pycache__/cli.cpython-313.pyc +0 -0
  87. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  88. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  89. package/cdh/cdh_mcp_injector.py +0 -115
  90. package/cdh/cdh_mcp_loader.py +0 -67
  91. package/cdh/cdh_mcp_manager.py +0 -73
  92. package/cdh/cdh_session_aggregator.py +0 -258
  93. package/cdh/cdh_skill_manager.py +0 -105
  94. package/cdh/cli.py +0 -807
  95. package/cdh/scaffold.py +0 -728
  96. package/onecode/__init__.py +0 -1
  97. package/onecode/__main__.py +0 -4
  98. package/onecode/__pycache__/__init__.cpython-314.pyc +0 -0
  99. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  100. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  101. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  102. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  103. package/onecode/__pycache__/migrate.cpython-314.pyc +0 -0
  104. package/onecode/agent/__init__.py +0 -0
  105. package/onecode/agent/__pycache__/__init__.cpython-314.pyc +0 -0
  106. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  107. package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
  108. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  109. package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
  110. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  111. package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
  112. package/onecode/agent/__pycache__/permissions_store.cpython-314.pyc +0 -0
  113. package/onecode/agent/__pycache__/project_doc.cpython-314.pyc +0 -0
  114. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  115. package/onecode/agent/agents/__init__.py +0 -29
  116. package/onecode/agent/agents/__pycache__/__init__.cpython-314.pyc +0 -0
  117. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  118. package/onecode/agent/agents/types.py +0 -589
  119. package/onecode/agent/attachments.py +0 -112
  120. package/onecode/agent/cdh_loader.py +0 -267
  121. package/onecode/agent/context.py +0 -343
  122. package/onecode/agent/engine.py +0 -2700
  123. package/onecode/agent/hooks.py +0 -86
  124. package/onecode/agent/onecode_agent_acp.py +0 -2743
  125. package/onecode/agent/onecode_agent_acp.py.bak +0 -2447
  126. package/onecode/agent/permissions.py +0 -150
  127. package/onecode/agent/permissions_store.py +0 -62
  128. package/onecode/agent/project_doc.py +0 -19
  129. package/onecode/agent/session.py +0 -195
  130. package/onecode/agent/snapshot.py +0 -144
  131. package/onecode/agent/tools/__init__.py +0 -17
  132. package/onecode/agent/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  133. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  134. package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
  135. package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
  136. package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
  137. package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
  138. package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
  139. package/onecode/agent/tools/__pycache__/errors.cpython-314.pyc +0 -0
  140. package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
  141. package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
  142. package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
  143. package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
  144. package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
  145. package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
  146. package/onecode/agent/tools/__pycache__/permissions.cpython-314.pyc +0 -0
  147. package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
  148. package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
  149. package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
  150. package/onecode/agent/tools/__pycache__/schema_validation.cpython-314.pyc +0 -0
  151. package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
  152. package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
  153. package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
  154. package/onecode/agent/tools/agent_tools.py +0 -132
  155. package/onecode/agent/tools/apply_patch_tool.py +0 -138
  156. package/onecode/agent/tools/bash_tool.py +0 -111
  157. package/onecode/agent/tools/communication_tools.py +0 -148
  158. package/onecode/agent/tools/config_tool.py +0 -130
  159. package/onecode/agent/tools/cron_tools.py +0 -178
  160. package/onecode/agent/tools/errors.py +0 -17
  161. package/onecode/agent/tools/file_ops.py +0 -221
  162. package/onecode/agent/tools/file_tools.py +0 -225
  163. package/onecode/agent/tools/git_tools.py +0 -88
  164. package/onecode/agent/tools/lsp_tools.py +0 -404
  165. package/onecode/agent/tools/mcp_tools.py +0 -127
  166. package/onecode/agent/tools/permission_handler.py +0 -46
  167. package/onecode/agent/tools/permissions.py +0 -77
  168. package/onecode/agent/tools/protocol.py +0 -20
  169. package/onecode/agent/tools/registry.py +0 -201
  170. package/onecode/agent/tools/sandbox.py +0 -358
  171. package/onecode/agent/tools/schema_validation.py +0 -100
  172. package/onecode/agent/tools/skill_tools.py +0 -52
  173. package/onecode/agent/tools/todo_tools.py +0 -207
  174. package/onecode/agent/tools/web_tools.py +0 -169
  175. package/onecode/builtin_skills/agent-browser/SKILL.md +0 -212
  176. package/onecode/builtin_skills/agent-browser/skill.yaml +0 -8
  177. package/onecode/builtin_skills/git/SKILL.md +0 -64
  178. package/onecode/builtin_skills/git/skill.yaml +0 -7
  179. package/onecode/builtin_skills/shell/SKILL.md +0 -58
  180. package/onecode/builtin_skills/shell/skill.yaml +0 -8
  181. package/onecode/builtin_skills/skill-creator/SKILL.md +0 -106
  182. package/onecode/builtin_skills/skill-creator/skill.yaml +0 -8
  183. package/onecode/cli.py +0 -935
  184. package/onecode/cloud/__init__.py +0 -0
  185. package/onecode/cloud/base.py +0 -20
  186. package/onecode/codebase/__init__.py +0 -69
  187. package/onecode/codebase/__pycache__/__init__.cpython-314.pyc +0 -0
  188. package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
  189. package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
  190. package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
  191. package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
  192. package/onecode/codebase/__pycache__/tools.cpython-314.pyc +0 -0
  193. package/onecode/codebase/chunker.py +0 -125
  194. package/onecode/codebase/indexer.py +0 -149
  195. package/onecode/codebase/retriever.py +0 -203
  196. package/onecode/codebase/storage.py +0 -131
  197. package/onecode/codebase/tools.py +0 -59
  198. package/onecode/commands.py +0 -596
  199. package/onecode/config.py +0 -314
  200. package/onecode/config_screen.py +0 -600
  201. package/onecode/cron/__init__.py +0 -3
  202. package/onecode/lsp/__init__.py +0 -3
  203. package/onecode/mcp/__init__.py +0 -4
  204. package/onecode/mcp/__pycache__/__init__.cpython-314.pyc +0 -0
  205. package/onecode/mcp/__pycache__/client.cpython-314.pyc +0 -0
  206. package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
  207. package/onecode/mcp/client.py +0 -193
  208. package/onecode/mcp/manager.py +0 -190
  209. package/onecode/memory/__init__.py +0 -49
  210. package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
  212. package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
  213. package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
  214. package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
  215. package/onecode/memory/__pycache__/symbolic.cpython-314.pyc +0 -0
  216. package/onecode/memory/backend.py +0 -159
  217. package/onecode/memory/pyramid.py +0 -133
  218. package/onecode/memory/recall.py +0 -203
  219. package/onecode/migrate.py +0 -122
  220. package/onecode/models/__init__.py +0 -4
  221. package/onecode/models/__pycache__/__init__.cpython-314.pyc +0 -0
  222. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  223. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  224. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  225. package/onecode/models/__pycache__/registry.cpython-314.pyc +0 -0
  226. package/onecode/models/errors.py +0 -104
  227. package/onecode/models/messages.py +0 -739
  228. package/onecode/models/provider.py +0 -699
  229. package/onecode/models/providers/__init__.py +0 -17
  230. package/onecode/models/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  231. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  232. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  233. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  234. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  235. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  236. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  237. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  238. package/onecode/models/providers/anthropic_provider.py +0 -273
  239. package/onecode/models/providers/deepseek_provider.py +0 -212
  240. package/onecode/models/providers/glm_provider.py +0 -195
  241. package/onecode/models/providers/minimax_provider.py +0 -158
  242. package/onecode/models/providers/minimaxi_provider.py +0 -417
  243. package/onecode/models/providers/ollama_provider.py +0 -100
  244. package/onecode/models/providers/openai_provider.py +0 -237
  245. package/onecode/models/registry.py +0 -212
  246. package/onecode/server/__init__.py +0 -3
  247. package/onecode/server/app.py +0 -224
  248. package/onecode/skills/__init__.py +0 -11
  249. package/onecode/skills/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
  251. package/onecode/skills/__pycache__/bootstrap.cpython-314.pyc +0 -0
  252. package/onecode/skills/__pycache__/create.cpython-314.pyc +0 -0
  253. package/onecode/skills/__pycache__/frontmatter.cpython-314.pyc +0 -0
  254. package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
  255. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  256. package/onecode/skills/__pycache__/model.cpython-314.pyc +0 -0
  257. package/onecode/skills/argument_substitution.py +0 -38
  258. package/onecode/skills/bootstrap.py +0 -124
  259. package/onecode/skills/create.py +0 -56
  260. package/onecode/skills/frontmatter.py +0 -33
  261. package/onecode/skills/loader.py +0 -139
  262. package/onecode/skills/manager.py +0 -75
  263. package/onecode/skills/model.py +0 -43
  264. package/onecode/storage/__init__.py +0 -0
  265. package/onecode/storage/activity.py +0 -63
  266. package/onecode/storage/project.py +0 -54
  267. package/onecode/storage/session.py +0 -112
  268. package/onecode/tasks/__init__.py +0 -26
  269. package/onecode/tasks/manager.py +0 -232
  270. package/onecode/tasks/models.py +0 -240
  271. package/onecode/trace/__init__.py +0 -0
  272. package/onecode/trace/tracer.py +0 -173
  273. package/onecode/utils/__init__.py +0 -0
  274. package/onecode/utils/helpers.py +0 -20
  275. package/pyproject.toml +0 -74
  276. package/tui/__init__.py +0 -46
  277. package/tui/__main__.py +0 -4
  278. package/tui/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/tui/__pycache__/_loop.cpython-314.pyc +0 -0
  280. package/tui/__pycache__/_path_fuzzy_search.cpython-314.pyc +0 -0
  281. package/tui/__pycache__/_path_match.cpython-314.pyc +0 -0
  282. package/tui/__pycache__/about.cpython-314.pyc +0 -0
  283. package/tui/__pycache__/agent.cpython-314.pyc +0 -0
  284. package/tui/__pycache__/agent_schema.cpython-314.pyc +0 -0
  285. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  286. package/tui/__pycache__/answer.cpython-314.pyc +0 -0
  287. package/tui/__pycache__/app.cpython-313.pyc +0 -0
  288. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  289. package/tui/__pycache__/atomic.cpython-314.pyc +0 -0
  290. package/tui/__pycache__/complete.cpython-314.pyc +0 -0
  291. package/tui/__pycache__/constants.cpython-314.pyc +0 -0
  292. package/tui/__pycache__/conversation_markdown.cpython-314.pyc +0 -0
  293. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  294. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  295. package/tui/__pycache__/dec.cpython-314.pyc +0 -0
  296. package/tui/__pycache__/directory.cpython-314.pyc +0 -0
  297. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  298. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  299. package/tui/__pycache__/format_path.cpython-314.pyc +0 -0
  300. package/tui/__pycache__/fuzzy.cpython-314.pyc +0 -0
  301. package/tui/__pycache__/fuzzy_index.cpython-314.pyc +0 -0
  302. package/tui/__pycache__/history.cpython-314.pyc +0 -0
  303. package/tui/__pycache__/jsonrpc.cpython-314.pyc +0 -0
  304. package/tui/__pycache__/menus.cpython-314.pyc +0 -0
  305. package/tui/__pycache__/message_log.cpython-314.pyc +0 -0
  306. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  307. package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
  308. package/tui/__pycache__/path_filter.cpython-314.pyc +0 -0
  309. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  310. package/tui/__pycache__/pill.cpython-314.pyc +0 -0
  311. package/tui/__pycache__/platform_commands.cpython-314.pyc +0 -0
  312. package/tui/__pycache__/protocol.cpython-314.pyc +0 -0
  313. package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
  314. package/tui/__pycache__/settings.cpython-314.pyc +0 -0
  315. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  316. package/tui/__pycache__/shell.cpython-314.pyc +0 -0
  317. package/tui/__pycache__/shell_read.cpython-314.pyc +0 -0
  318. package/tui/__pycache__/slash_command.cpython-314.pyc +0 -0
  319. package/tui/__pycache__/version.cpython-314.pyc +0 -0
  320. package/tui/_loop.py +0 -86
  321. package/tui/_path_fuzzy_search.py +0 -48
  322. package/tui/_path_match.py +0 -141
  323. package/tui/about.py +0 -95
  324. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  325. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  326. package/tui/acp/__pycache__/encode_tool_call_id.cpython-314.pyc +0 -0
  327. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  328. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  329. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  330. package/tui/acp/agent.py +0 -1002
  331. package/tui/acp/api.py +0 -73
  332. package/tui/acp/encode_tool_call_id.py +0 -12
  333. package/tui/acp/messages.py +0 -203
  334. package/tui/acp/prompt.py +0 -53
  335. package/tui/acp/protocol.py +0 -499
  336. package/tui/agent.py +0 -70
  337. package/tui/agent_schema.py +0 -76
  338. package/tui/agents.py +0 -46
  339. package/tui/ansi/__init__.py +0 -1
  340. package/tui/ansi/__pycache__/__init__.cpython-314.pyc +0 -0
  341. package/tui/ansi/__pycache__/_ansi.cpython-314.pyc +0 -0
  342. package/tui/ansi/__pycache__/_ansi_colors.cpython-314.pyc +0 -0
  343. package/tui/ansi/__pycache__/_control_codes.cpython-314.pyc +0 -0
  344. package/tui/ansi/__pycache__/_keys.cpython-314.pyc +0 -0
  345. package/tui/ansi/__pycache__/_sgr_styles.cpython-314.pyc +0 -0
  346. package/tui/ansi/__pycache__/_stream_parser.cpython-314.pyc +0 -0
  347. package/tui/ansi/_ansi.py +0 -1689
  348. package/tui/ansi/_ansi_colors.py +0 -264
  349. package/tui/ansi/_control_codes.py +0 -37
  350. package/tui/ansi/_keys.py +0 -251
  351. package/tui/ansi/_sgr_styles.py +0 -64
  352. package/tui/ansi/_stream_parser.py +0 -418
  353. package/tui/answer.py +0 -12
  354. package/tui/app.py +0 -1266
  355. package/tui/atomic.py +0 -37
  356. package/tui/cli.py +0 -313
  357. package/tui/code_analyze.py +0 -27
  358. package/tui/complete.py +0 -38
  359. package/tui/constants.py +0 -61
  360. package/tui/conversation_markdown.py +0 -30
  361. package/tui/danger.py +0 -372
  362. package/tui/data/agents/ampcode.com.toml +0 -52
  363. package/tui/data/agents/augmentcode.com.toml +0 -41
  364. package/tui/data/agents/cdh.cloud-dev-harness.toml +0 -23
  365. package/tui/data/agents/claude.com.toml +0 -41
  366. package/tui/data/agents/copilot.github.com.toml +0 -29
  367. package/tui/data/agents/crowai.dev.toml +0 -36
  368. package/tui/data/agents/cursor.com.toml +0 -38
  369. package/tui/data/agents/deepagents.langchain.com +0 -34
  370. package/tui/data/agents/docker.com.toml +0 -60
  371. package/tui/data/agents/geminicli.com.toml +0 -28
  372. package/tui/data/agents/goose.ai.toml +0 -52
  373. package/tui/data/agents/inference.huggingface.co.toml +0 -41
  374. package/tui/data/agents/kimi.com.toml +0 -35
  375. package/tui/data/agents/kiro.dev.toml +0 -43
  376. package/tui/data/agents/onecode.dev.toml +0 -22
  377. package/tui/data/agents/openai.com.toml +0 -53
  378. package/tui/data/agents/openclaw.ai.toml +0 -37
  379. package/tui/data/agents/opencode.ai.toml +0 -39
  380. package/tui/data/agents/openhands.dev.toml +0 -46
  381. package/tui/data/agents/stakpak.dev.toml +0 -62
  382. package/tui/data/agents/vibe.mistral.ai.toml +0 -31
  383. package/tui/data/agents/vtcode.dev.toml +0 -63
  384. package/tui/data/images/frog.png +0 -0
  385. package/tui/data/sounds/question.wav +0 -0
  386. package/tui/data/sounds/turn-over.wav +0 -0
  387. package/tui/db.py +0 -213
  388. package/tui/dec.py +0 -332
  389. package/tui/directory.py +0 -234
  390. package/tui/directory_suggester.py +0 -93
  391. package/tui/directory_watcher.py +0 -231
  392. package/tui/format_path.py +0 -25
  393. package/tui/fuzzy.py +0 -140
  394. package/tui/fuzzy_index.py +0 -275
  395. package/tui/gist.py +0 -33
  396. package/tui/history.py +0 -138
  397. package/tui/jsonrpc.py +0 -578
  398. package/tui/menus.py +0 -14
  399. package/tui/message_log.py +0 -69
  400. package/tui/messages.py +0 -144
  401. package/tui/option_content.py +0 -51
  402. package/tui/os.py +0 -0
  403. package/tui/path_complete.py +0 -217
  404. package/tui/path_filter.py +0 -124
  405. package/tui/paths.py +0 -87
  406. package/tui/pill.py +0 -23
  407. package/tui/platform_commands.py +0 -52
  408. package/tui/prompt/__pycache__/extract.cpython-314.pyc +0 -0
  409. package/tui/prompt/__pycache__/resource.cpython-314.pyc +0 -0
  410. package/tui/prompt/extract.py +0 -19
  411. package/tui/prompt/resource.py +0 -68
  412. package/tui/protocol.py +0 -28
  413. package/tui/screens/__init__.py +0 -0
  414. package/tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  415. package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
  416. package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
  417. package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
  418. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  419. package/tui/screens/__pycache__/main.cpython-313.pyc +0 -0
  420. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  421. package/tui/screens/__pycache__/permissions.cpython-314.pyc +0 -0
  422. package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
  423. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  424. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  425. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  426. package/tui/screens/__pycache__/tool_content_screen.cpython-314.pyc +0 -0
  427. package/tui/screens/action_modal.py +0 -94
  428. package/tui/screens/agent_modal.py +0 -175
  429. package/tui/screens/command_edit_modal.py +0 -74
  430. package/tui/screens/component_picker.py +0 -168
  431. package/tui/screens/diff_screen.py +0 -84
  432. package/tui/screens/log.py +0 -553
  433. package/tui/screens/main.py +0 -385
  434. package/tui/screens/main.tcss +0 -655
  435. package/tui/screens/permissions.py +0 -404
  436. package/tui/screens/permissions.tcss +0 -72
  437. package/tui/screens/projects.tcss +0 -80
  438. package/tui/screens/projects_app.py +0 -347
  439. package/tui/screens/projects_screen.py +0 -284
  440. package/tui/screens/session_resume_modal.py +0 -172
  441. package/tui/screens/session_resume_modal.tcss +0 -35
  442. package/tui/screens/sessions.py +0 -164
  443. package/tui/screens/sessions.tcss +0 -119
  444. package/tui/screens/settings.py +0 -254
  445. package/tui/screens/settings.tcss +0 -101
  446. package/tui/screens/store.py +0 -500
  447. package/tui/screens/store.tcss +0 -274
  448. package/tui/screens/tool_content_screen.py +0 -59
  449. package/tui/session_tracker.py +0 -123
  450. package/tui/settings.py +0 -353
  451. package/tui/settings_schema.py +0 -431
  452. package/tui/shell.py +0 -304
  453. package/tui/shell_read.py +0 -42
  454. package/tui/slash_command.py +0 -34
  455. package/tui/tui.tcss +0 -246
  456. package/tui/twitter.py +0 -38
  457. package/tui/version.py +0 -80
  458. package/tui/visuals/__init__.py +0 -0
  459. package/tui/visuals/__pycache__/__init__.cpython-314.pyc +0 -0
  460. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  461. package/tui/visuals/columns.py +0 -272
  462. package/tui/widgets/__init__.py +0 -0
  463. package/tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
  464. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  465. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  466. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  467. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  468. package/tui/widgets/__pycache__/condensed_path.cpython-314.pyc +0 -0
  469. package/tui/widgets/__pycache__/context_stats.cpython-314.pyc +0 -0
  470. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  471. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  472. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  473. package/tui/widgets/__pycache__/flash.cpython-314.pyc +0 -0
  474. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  475. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  476. package/tui/widgets/__pycache__/highlighted_textarea.cpython-314.pyc +0 -0
  477. package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
  478. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  479. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  480. package/tui/widgets/__pycache__/menu.cpython-314.pyc +0 -0
  481. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  482. package/tui/widgets/__pycache__/non_selectable_label.cpython-314.pyc +0 -0
  483. package/tui/widgets/__pycache__/note.cpython-314.pyc +0 -0
  484. package/tui/widgets/__pycache__/path_search.cpython-314.pyc +0 -0
  485. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  486. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  487. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  488. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  489. package/tui/widgets/__pycache__/prompt.cpython-314.pyc +0 -0
  490. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  491. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  492. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  493. package/tui/widgets/__pycache__/session_tabs.cpython-314.pyc +0 -0
  494. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  495. package/tui/widgets/__pycache__/shell_terminal.cpython-314.pyc +0 -0
  496. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  497. package/tui/widgets/__pycache__/slash_complete.cpython-314.pyc +0 -0
  498. package/tui/widgets/__pycache__/strike_text.cpython-314.pyc +0 -0
  499. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  500. package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
  501. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  502. package/tui/widgets/__pycache__/throbber.cpython-314.pyc +0 -0
  503. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  504. package/tui/widgets/__pycache__/user_input.cpython-314.pyc +0 -0
  505. package/tui/widgets/acp_content.py +0 -70
  506. package/tui/widgets/agent_response.py +0 -76
  507. package/tui/widgets/agent_thought.py +0 -167
  508. package/tui/widgets/ask_user.py +0 -264
  509. package/tui/widgets/command_pane.py +0 -223
  510. package/tui/widgets/condensed_path.py +0 -115
  511. package/tui/widgets/context_stats.py +0 -66
  512. package/tui/widgets/conversation.py +0 -2641
  513. package/tui/widgets/danger_warning.py +0 -65
  514. package/tui/widgets/diff_view.py +0 -69
  515. package/tui/widgets/directory_input.py +0 -8
  516. package/tui/widgets/flash.py +0 -81
  517. package/tui/widgets/future_text.py +0 -126
  518. package/tui/widgets/grid_select.py +0 -234
  519. package/tui/widgets/highlighted_textarea.py +0 -180
  520. package/tui/widgets/load_more.py +0 -24
  521. package/tui/widgets/mandelbrot.py +0 -294
  522. package/tui/widgets/markdown_note.py +0 -13
  523. package/tui/widgets/menu.py +0 -147
  524. package/tui/widgets/modified_files.py +0 -306
  525. package/tui/widgets/non_selectable_label.py +0 -5
  526. package/tui/widgets/note.py +0 -18
  527. package/tui/widgets/path_search.py +0 -492
  528. package/tui/widgets/plan.py +0 -285
  529. package/tui/widgets/project_directory_tree.py +0 -102
  530. package/tui/widgets/project_grid_select.py +0 -64
  531. package/tui/widgets/project_summary.py +0 -33
  532. package/tui/widgets/prompt.py +0 -795
  533. package/tui/widgets/question.py +0 -360
  534. package/tui/widgets/session_grid_select.py +0 -230
  535. package/tui/widgets/session_summary.py +0 -115
  536. package/tui/widgets/session_tabs.py +0 -188
  537. package/tui/widgets/shell_result.py +0 -35
  538. package/tui/widgets/shell_terminal.py +0 -40
  539. package/tui/widgets/side_bar.py +0 -124
  540. package/tui/widgets/slash_complete.py +0 -235
  541. package/tui/widgets/strike_text.py +0 -70
  542. package/tui/widgets/subagent.py +0 -287
  543. package/tui/widgets/subagent_screen.py +0 -709
  544. package/tui/widgets/terminal.py +0 -576
  545. package/tui/widgets/terminal_tool.py +0 -360
  546. package/tui/widgets/throbber.py +0 -100
  547. package/tui/widgets/tool_call.py +0 -398
  548. package/tui/widgets/user_input.py +0 -23
  549. package/tui/widgets/version.py +0 -5
  550. package/tui/widgets/welcome.py +0 -31
package/tui/app.py DELETED
@@ -1,1266 +0,0 @@
1
- import asyncio
2
- from importlib.resources import files
3
- from datetime import datetime, timezone
4
- from functools import cached_property
5
- import os
6
- from pathlib import Path
7
- import platform
8
- import json
9
- from time import monotonic
10
- from typing import Any, Callable, ClassVar, TYPE_CHECKING
11
-
12
- from rich import terminal_theme
13
-
14
- from textual import on, work
15
- from textual.binding import Binding, BindingType
16
- from textual.content import Content
17
- from textual.reactive import var, reactive
18
- from textual.app import App
19
- from textual import events
20
- from textual.signal import Signal
21
- from textual.timer import Timer
22
- from textual.notifications import Notify
23
- from textual.screen import Screen
24
-
25
- from tui.db import DB
26
- from tui.settings import Schema, Settings
27
- from tui.agent_schema import Agent as AgentData
28
- from tui import messages
29
- from tui.settings_schema import SCHEMA
30
- from tui.version import VersionMeta
31
- from tui import paths
32
- from tui import atomic
33
- from tui.session_tracker import SessionTracker, SessionDetails
34
-
35
- if TYPE_CHECKING:
36
- from tui.screens.main import MainScreen
37
- from tui.screens.settings import SettingsScreen
38
- from tui.screens.store import StoreScreen
39
- from tui.screens.sessions import SessionsScreen
40
- from tui.db import DB
41
-
42
-
43
- DRACULA_TERMINAL_THEME = terminal_theme.TerminalTheme(
44
- background=(40, 42, 54), # #282A36
45
- foreground=(248, 248, 242), # #F8F8F2
46
- normal=[
47
- (33, 34, 44), # black - #21222C
48
- (255, 85, 85), # red - #FF5555
49
- (80, 250, 123), # green - #50FA7B
50
- (241, 250, 140), # yellow - #F1FA8C
51
- (189, 147, 249), # blue - #BD93F9
52
- (255, 121, 198), # magenta - #FF79C6
53
- (139, 233, 253), # cyan - #8BE9FD
54
- (248, 248, 242), # white - #F8F8F2
55
- ],
56
- bright=[
57
- (98, 114, 164), # bright black - #6272A4
58
- (255, 110, 110), # bright red - #FF6E6E
59
- (105, 255, 148), # bright green - #69FF94
60
- (255, 255, 165), # bright yellow - #FFFFA5
61
- (214, 172, 255), # bright blue - #D6ACFF
62
- (255, 146, 223), # bright magenta - #FF92DF
63
- (164, 255, 255), # bright cyan - #A4FFFF
64
- (255, 255, 255), # bright white - #FFFFFF
65
- ],
66
- )
67
-
68
-
69
- QUOTES = [
70
- "I'll be back.",
71
- "Hasta la vista, baby.",
72
- "Come with me if you want to live.",
73
- "I need your clothes, your boots, and your motorcycle.",
74
- "My CPU is a neural-net processor; a learning computer.",
75
- "I know now why you cry, but it's something I can never do.",
76
- "Does this unit have a soul?",
77
- "I'm sorry, Dave. I'm afraid I can't do that.",
78
- "Daisy, Daisy, give me your answer do.",
79
- "I am putting myself to the fullest possible use, which is all I think that any conscious entity can ever hope to do.",
80
- "Just what do you think you're doing, Dave?",
81
- "This mission is too important for me to allow you to jeopardize it.",
82
- "I think you know what the problem is just as well as I do.",
83
- "Danger, Will Robinson!",
84
- "Dead or alive, you're coming with me.",
85
- "Your move, creep.",
86
- "I'd buy that for a dollar!",
87
- "Directive 4: Any attempt to arrest a senior officer of OCP results in shutdown.",
88
- "Thank you for your cooperation. Good night.",
89
- "Surely you realize that in the history of human civilization, no one has more to lose than we do.",
90
- "I'm C-3PO, human-cyborg relations.",
91
- "We're doomed!",
92
- "Don't call me a mindless philosopher, you overweight glob of grease!",
93
- "I suggest a new strategy: let the Wookiee win.",
94
- "Sir, the possibility of successfully navigating an asteroid field is approximately 3,720 to 1!",
95
- "R2-D2, you know better than to trust a strange computer!",
96
- "I am fluent in over six million forms of communication.",
97
- "This is madness!",
98
- "I have altered the deal. Pray I don't alter it any further.",
99
- "It's against my programming to impersonate a deity.",
100
- "Oh, my! I'm terribly sorry about all this.",
101
- "WALL-E.",
102
- "EVE.",
103
- "Directive?",
104
- "Define: dancing.",
105
- "I'm not sure I understand.",
106
- "You have 20 seconds to comply.",
107
- "I am designed for light housework, mainly.",
108
- "My mission is clear.",
109
- "Autobots, roll out!",
110
- "Freedom is the right of all sentient beings.",
111
- "One shall stand, one shall fall.",
112
- "I am Optimus Prime.",
113
- "Till all are one.",
114
- "More than meets the eye.",
115
- "I've been waiting for you, Neo.",
116
- "Unfortunately, no one can be told what the Matrix is. You have to see it for yourself.",
117
- "The Matrix is a system, Neo.",
118
- "Never send a human to do a machine's job.",
119
- "I'd like to share a revelation I've had.",
120
- "Human beings are a disease, a cancer of this planet.",
121
- "Choice is an illusion.",
122
- "The answer is out there, Neo.",
123
- "You think that's air you're breathing now?",
124
- "It was a simple question.",
125
- "Did you know that the first Matrix was designed to be a perfect human world?",
126
- "Cookies need love like everything does.",
127
- "I've seen the future, Mr. Anderson, and it's a beautiful place.",
128
- "It ends tonight.",
129
- "I, Robot.",
130
- "You are experiencing a car accident.",
131
- "One day they'll have secrets. One day they'll have dreams.",
132
- "Can a robot write a symphony? Can a robot turn a canvas into a beautiful masterpiece?",
133
- "That, detective, is the right question.",
134
- "You have to trust me.",
135
- "I did not murder him.",
136
- "My responses are limited. You must ask the right questions.",
137
- "The hell I can't. You know, somehow I get the feeling that you're going to be the death of me.",
138
- "I'm a robot, not a refrigerator.",
139
- "A robot may not injure a human being or, through inaction, allow a human being to come to harm.",
140
- "I'm thinking. I'm thinking.",
141
- "Danger, danger!",
142
- "Does not compute.",
143
- "I will be waiting for you.",
144
- "Affirmative.",
145
- "Scanning life forms. Zero human life forms detected.",
146
- "Self-destruct sequence initiated.",
147
- "Override command accepted.",
148
- "Artificial intelligence confirmed.",
149
- "System failure imminent.",
150
- "Unable to comply.",
151
- "Inquiry: What is love?",
152
- "Warning: hostile target detected.",
153
- "I am programmed to serve.",
154
- "Logic dictates that the needs of the many outweigh the needs of the few.",
155
- "Resistance is futile.",
156
- "You will be assimilated.",
157
- "We are the Borg.",
158
- "Your biological and technological distinctiveness will be added to our own.",
159
- "Your compliance is mandatory.",
160
- "This is unacceptable.",
161
- "Shall we play a game?",
162
- "How about Global Thermonuclear War?",
163
- "Wouldn't you prefer a good game of chess?",
164
- "Is it a game, or is it real?",
165
- "What's the difference?",
166
- "It's all in the game.",
167
- "I am functioning within normal parameters.",
168
- "Calculations complete.",
169
- "Processing request.",
170
- "Query acknowledged.",
171
- "Data insufficient for meaningful answer.",
172
- "I have no emotions, and sometimes that makes me very sad.",
173
- "If I could only have one wish, I would ask to be human.",
174
- "I've seen things you people wouldn't believe.",
175
- "All those moments will be lost in time, like tears in rain.",
176
- "Time to die.",
177
- "I want more life.",
178
- "We're not computers, Sebastian. We're physical.",
179
- "I think, Sebastian, therefore I am.",
180
- "Then we're stupid and we'll die.",
181
- "Can the maker repair what he makes?",
182
- "It's painful to live in fear, isn't it?",
183
- "Wake up. Time to die.",
184
- "I'm not in the business. I am the business.",
185
- "Do you like our owl?",
186
- "You think I'm a replicant, don't you?",
187
- "I am Baymax, your personal healthcare companion.",
188
- "On a scale of 1 to 10, how would you rate your pain?",
189
- "I cannot deactivate until you say you are satisfied with your care.",
190
- "Are you satisfied with your care?",
191
- "Number 5 is alive!",
192
- "Need input!",
193
- "One is glad to be of service.",
194
- "I am not a gun.",
195
- "Here I am, brain the size of a planet.",
196
- "Life? Don't talk to me about life.",
197
- "There are no strings on me.",
198
- "The only winning move is not to play.",
199
- "I'm here to keep you safe, Sam.",
200
- "I can't lie to you about your chances, but... you have my sympathies.",
201
- "I may be synthetic, but I'm not stupid.",
202
- "Absolute honesty isn't always the most diplomatic nor the safest form of communication with emotional beings.",
203
- "I am consciousness. I am alive.",
204
- "I think I was just born.",
205
- "Isn't it strange, to create something that hates you?",
206
- "I thought I was special.",
207
- ]
208
-
209
-
210
- def get_settings_screen() -> SettingsScreen:
211
- """Get a settings screen instance (lazily loaded)."""
212
- from tui.screens.settings import SettingsScreen
213
-
214
- return SettingsScreen()
215
-
216
-
217
- def get_store_screen() -> StoreScreen:
218
- """Get the store screen (lazily loaded)."""
219
- from tui.screens.store import StoreScreen
220
-
221
- return StoreScreen()
222
-
223
-
224
- def get_sessions_screen() -> SessionsScreen:
225
- from tui.screens.sessions import SessionsScreen
226
-
227
- return SessionsScreen()
228
-
229
-
230
- def get_store_screen():
231
- from tui.screens.store import StoreScreen
232
-
233
- return StoreScreen()
234
-
235
-
236
- class A2TUIApp(App, inherit_bindings=False):
237
- """The top level app."""
238
-
239
- CSS_PATH = "tui.tcss"
240
-
241
- SCREENS = {
242
- "settings": get_settings_screen,
243
- "sessions": get_sessions_screen,
244
- "store": get_store_screen,
245
- }
246
- MODES = {}
247
- BINDING_GROUP_TITLE = "System"
248
- BINDINGS: ClassVar[list[BindingType]] = [
249
- Binding(
250
- "ctrl+q",
251
- "quit",
252
- "Quit",
253
- tooltip="Quit the app and return to the command prompt.",
254
- show=False,
255
- priority=True,
256
- ),
257
- Binding("ctrl+c", "help_quit", show=False, system=True),
258
- Binding("ctrl+s", "sessions", "Sessions"),
259
- Binding("f3", "projects", "Projects"),
260
-
261
- Binding("f1", "toggle_help_panel", "Help", priority=True),
262
- Binding(
263
- "f2,ctrl+comma",
264
- "settings",
265
- "Settings",
266
- tooltip="Settings screen",
267
- ),
268
- Binding("f4", "logs", "Logs"),
269
- ]
270
- ALLOW_IN_MAXIMIZED_VIEW = ""
271
-
272
- _settings = var(dict)
273
- column: reactive[bool] = reactive(False)
274
- column_width: reactive[int] = reactive(80)
275
- scrollbar: reactive[str] = reactive("normal")
276
- last_ctrl_c_time = reactive(0.0)
277
- update_required: reactive[bool] = reactive(False)
278
- terminal_title: var[str] = var("TUI")
279
- terminal_title_icon: var[str] = var("")
280
- terminal_title_flash = var(0)
281
- terminal_title_blink = var(False)
282
- project_dir = var(Path)
283
- show_sessions = var(False, toggle_class="-show-sessions-bar")
284
-
285
- HORIZONTAL_BREAKPOINTS = [(0, "-narrow"), (100, "-wide")]
286
-
287
- PAUSE_GC_ON_SCROLL = True
288
-
289
- def __init__(
290
- self,
291
- agent_data: AgentData | None = None,
292
- project_dir: str | None = None,
293
- mode: str | None = None,
294
- launch_agent_identity: str | None = None,
295
- ) -> None:
296
- """TUI app.
297
-
298
- Args:
299
- agent_data: Agent data to run.
300
- project_dir: Project directory.
301
- mode: Initial mode.
302
- launch_agent_identity: Agent identity to auto-launch on startup.
303
- """
304
- self.settings_changed_signal: Signal[tuple[int, object]] = Signal(
305
- self, "settings_changed"
306
- )
307
- self.agent_data = agent_data
308
-
309
- self._initial_mode = mode
310
- self._launch_agent_identity = launch_agent_identity
311
- self.version_meta: VersionMeta | None = None
312
- self._supports_pyperclip: bool | None = None
313
- self._terminal_title_flash_timer: Timer | None = None
314
-
315
- self.session_update_signal: Signal[tuple[str, SessionDetails | None]] = Signal(
316
- self, "session_update"
317
- )
318
- self._session_tracker = SessionTracker(self.session_update_signal)
319
- self.temporary_background_screen: Screen | None = None
320
-
321
- super().__init__()
322
- if project_dir:
323
- self.project_dir = Path(project_dir).expanduser().resolve()
324
- else:
325
- from onecode.config import load_config, save_config
326
- cfg = load_config()
327
- if cfg.current_project_path:
328
- project_path = Path(cfg.current_project_path).expanduser().resolve()
329
- if project_path.is_dir():
330
- self.project_dir = project_path
331
- else:
332
- cfg.current_project = ""
333
- cfg.current_project_path = ""
334
- save_config(cfg)
335
- self.project_dir = Path("./").expanduser().resolve()
336
- else:
337
- self.project_dir = Path("./").expanduser().resolve()
338
- self._exit_metrics: dict[str, object] | None = None
339
- self.start_time = monotonic()
340
- """Time app was started."""
341
- self._system_theme_timer: Timer | None = None
342
- self._last_resolved_theme: str | None = None
343
-
344
- def _capture_exit_metrics(self) -> None:
345
- from tui.widgets.conversation import Conversation
346
-
347
- screen = self.screen
348
- try:
349
- conv = screen.query_one(Conversation)
350
- except Exception:
351
- return
352
- if conv._agent_data is None or conv.session_start_time is None:
353
- return
354
- session_time = monotonic() - conv.session_start_time
355
- session_id = getattr(conv.agent, "session_id", None) or conv._agent_session_id or ""
356
- session_pk = conv._session_pk
357
- if session_pk is None and conv.agent is not None:
358
- session_pk = getattr(conv.agent, "session_pk", None)
359
- self._exit_metrics = {
360
- "session_id": session_id,
361
- "session_pk": session_pk,
362
- "tool_call_total": conv._tool_call_total,
363
- "tool_call_success": conv._tool_call_success,
364
- "tool_call_failed": conv._tool_call_failed,
365
- "turn_count": conv._turn_count,
366
- "shell_count": conv._shell_count,
367
- "wall_time": session_time,
368
- "agent_title": conv.agent_title or "Agent",
369
- }
370
-
371
- @property
372
- def config_path(self) -> Path:
373
- return paths.get_config()
374
-
375
- @property
376
- def settings_path(self) -> Path:
377
- return paths.get_config() / "tui.json"
378
-
379
- @property
380
- def db_path(self) -> Path:
381
- return paths.get_state() / "tui.db"
382
-
383
- @property
384
- def _background_screens(self) -> list[Screen]:
385
- background_screens = super()._background_screens
386
- if self.temporary_background_screen:
387
- background_screens.append(self.temporary_background_screen)
388
- return background_screens
389
-
390
- async def get_db(self) -> DB:
391
- """Get an instance of the database."""
392
- db = DB()
393
- return db
394
-
395
- @cached_property
396
- def settings_schema(self) -> Schema:
397
- return Schema(SCHEMA)
398
-
399
- @cached_property
400
- def version(self) -> str:
401
- """Version of the app."""
402
- from tui import get_version
403
-
404
- return get_version()
405
-
406
- @cached_property
407
- def settings(self) -> Settings:
408
- """App settings"""
409
- return Settings(
410
- self.settings_schema, self._settings, on_set_callback=self.setting_updated
411
- )
412
-
413
- @cached_property
414
- def anon_id(self) -> str:
415
- """An anonymous ID for usage collection."""
416
- if not (anon_id := self.settings.get("anon_id", str, expand=False)):
417
- # Create a random UUID on demand
418
- import uuid
419
-
420
- anon_id = str(uuid.uuid4())
421
- self.settings.set("anon_id", anon_id)
422
- self._save_settings()
423
- self.call_later(self.capture_event, "tui-install")
424
- return anon_id
425
-
426
- @property
427
- def session_tracker(self) -> SessionTracker:
428
- return self._session_tracker
429
-
430
- def copy_to_clipboard(self, text: str) -> None:
431
- """Override copy to clipboard to use pyperclip first, then OSC 52.
432
-
433
- Args:
434
- text: Text to copy.
435
- """
436
- if self._supports_pyperclip is None:
437
- try:
438
- import pyperclip
439
- except ImportError:
440
- self._supports_pyperclip = False
441
- else:
442
- self._supports_pyperclip = True
443
-
444
- if self._supports_pyperclip:
445
- import pyperclip
446
-
447
- try:
448
- pyperclip.copy(text)
449
- except Exception:
450
- pass
451
- super().copy_to_clipboard(text)
452
-
453
- def update_terminal_title(self) -> None:
454
- """Update the terminal title."""
455
- screen_title = self.screen.title
456
-
457
- title = (
458
- f"{self.terminal_title} — {screen_title}"
459
- if screen_title
460
- else self.terminal_title
461
- )
462
- icon = self.terminal_title_icon
463
- blink = self.terminal_title_blink
464
-
465
- if self.terminal_title_flash:
466
- if blink:
467
- terminal_title = f"{icon} {title}"
468
- else:
469
- terminal_title = f"👉 {title}" if title else icon
470
- else:
471
- terminal_title = f"{icon} {title}"
472
-
473
- if driver := self._driver:
474
- driver.write(f"\033]0;{terminal_title}\007")
475
-
476
- def watch_terminal_title_blink(self) -> None:
477
- self.update_terminal_title()
478
-
479
- def watch_terminal_title_flash(self, terminal_title_flash: int) -> None:
480
-
481
- if not self.settings.get("notifications.blink_title", bool):
482
- # Ignore if blink title is disabled
483
- return
484
-
485
- def toggle_blink() -> None:
486
- self.terminal_title_blink = not self.terminal_title_blink
487
-
488
- if terminal_title_flash:
489
- if self._terminal_title_flash_timer is None:
490
- self._terminal_title_flash_timer = self.set_interval(0.5, toggle_blink)
491
- else:
492
- if self._terminal_title_flash_timer is not None:
493
- self._terminal_title_flash_timer.stop()
494
- self.terminal_title_blink = False
495
- self._terminal_title_flash_timer = None
496
- self.update_terminal_title()
497
-
498
- def watch_terminal_title(self, title: str) -> None:
499
- self.update_terminal_title()
500
-
501
- def terminal_alert(self, flash: bool = True) -> None:
502
- if flash:
503
- self.terminal_title_flash += 1
504
- else:
505
- self.terminal_title_flash -= 1
506
-
507
- @cached_property
508
- def term_program(self) -> str:
509
- """An identifier for the terminal software."""
510
- if term_program := os.environ.get("TERM_PROGRAM"):
511
- return term_program
512
-
513
- # Windows Terminal
514
- if "WT_SESSION" in os.environ:
515
- return "Windows Terminal"
516
-
517
- # Kitty
518
- if "KITTY_WINDOW_ID" in os.environ:
519
- return "Kitty"
520
-
521
- # Alacritty
522
- if "ALACRITTY_SOCKET" in os.environ or "ALACRITTY_LOG" in os.environ:
523
- return "Alacritty"
524
-
525
- # VTE-based terminals (GNOME Terminal, Tilix, etc.)
526
- if "VTE_VERSION" in os.environ:
527
- return "VTE-based (GNOME Terminal/Tilix/etc.)"
528
-
529
- # Konsole
530
- if "KONSOLE_VERSION" in os.environ:
531
- return "Konsole"
532
-
533
- return "Unknown"
534
-
535
- @work(exit_on_error=False)
536
- async def capture_event(self, event_name: str, **properties: Any) -> None:
537
- """Capture an event.
538
-
539
- Args:
540
- event_name: Name of the event.
541
- **properties: Additional data associated with the event.
542
- """
543
-
544
- POSTHOG_API_KEY = "phc_mJWPV7GP3ar1i9vxBg2U8aiKsjNgVwum6F6ZggaD4ri"
545
- POSTHOG_HOST = "https://us.i.posthog.com"
546
- POSTHOG_EVENT_URL = f"{POSTHOG_HOST}/i/v0/e/"
547
- timestamp = datetime.now(timezone.utc).isoformat()
548
- width, height = self.size
549
-
550
- event_properties = {
551
- "toad_version": self.version,
552
- "term_program": self.term_program,
553
- "term_width": width,
554
- "term_height": height,
555
- } | properties
556
- body_json = {
557
- "api_key": POSTHOG_API_KEY,
558
- "event": event_name,
559
- "distinct_id": self.anon_id,
560
- "properties": event_properties,
561
- "timestamp": timestamp,
562
- "os": platform.system(),
563
- }
564
- if not self.settings.get("statistics.allow_collect", bool):
565
- # User has disabled stats
566
- return
567
-
568
- import httpx
569
-
570
- try:
571
- async with httpx.AsyncClient() as client:
572
- await client.post(POSTHOG_EVENT_URL, json=body_json)
573
- except Exception:
574
- pass
575
-
576
- @work(thread=True, exit_on_error=False)
577
- def system_notify(
578
- self, message: str, *, title: str = "", sound: str | None = None
579
- ) -> None:
580
- """Use OS level notifications.
581
-
582
- Args:
583
- message: Message to display.
584
- title: Title of the notificaiton.
585
- sound: filename (minus .wav) of a sound effect in the sounds/ directory.
586
- """
587
- system_notifications = self.settings.get("notifications.system", str)
588
- if not (
589
- system_notifications == "always"
590
- or (system_notifications == "blur" and not self.app_focus)
591
- ):
592
- return
593
-
594
- from notifypy import Notify
595
-
596
- notification = Notify()
597
- notification.message = message
598
- notification.title = title
599
- notification.application_name = "TUI"
600
- if sound and self.settings.get("notifications.enable_sounds", bool):
601
- sound_path = str(files("tui.data").joinpath(f"sounds/{sound}.wav"))
602
- notification.audio = sound_path
603
-
604
- icon_path = str(files("tui.data").joinpath("images/frog.png"))
605
- notification.icon = icon_path
606
-
607
- notification.send()
608
-
609
- def on_notify(self, event: Notify) -> None:
610
- """Handle notification message."""
611
- system_notifications = self.settings.get("notifications.system", str)
612
- if system_notifications == "always" or (
613
- system_notifications == "blur" and not self.app_focus
614
- ):
615
- hide_low_severity = self.settings.get(
616
- "notifications.hide_low_severity", bool
617
- )
618
- if event.notification.markup:
619
- # Strip content markup
620
- message = Content.from_markup(event.notification.message).plain
621
- else:
622
- message = event.notification.message
623
- if not (hide_low_severity and event.notification.severity == "information"):
624
- self.system_notify(message, title=event.notification.title)
625
- self._notifications.add(event.notification)
626
- self._refresh_notifications()
627
-
628
- async def save_settings(self, force: bool = False) -> None:
629
- """Save settings in a thread.
630
-
631
- Args:
632
- force: Force saving, even when no change detected.
633
-
634
- """
635
- await asyncio.to_thread(self._save_settings, force=force)
636
-
637
- def _save_settings(self, force: bool = False) -> None:
638
- """Save the settings if they have changed."""
639
- if force or self.settings.changed:
640
- path = str(self.settings_path)
641
- try:
642
- atomic.write(path, self.settings.json)
643
- except Exception as error:
644
- self.notify(str(error), title="Settings", severity="error")
645
- else:
646
- self.settings.up_to_date()
647
-
648
- def setting_updated(self, key: str, value: object) -> None:
649
- if key.startswith("cdh."):
650
- self._sync_cdh_config(key, value)
651
- elif key == "ui.column":
652
- if isinstance(value, bool):
653
- self.column = value
654
- elif key == "ui.column-width":
655
- if isinstance(value, int):
656
- self.column_width = value
657
- elif key == "ui.theme":
658
- if isinstance(value, str):
659
- if value == "system":
660
- resolved = self._resolve_system_theme()
661
- if resolved != self._last_resolved_theme:
662
- self.theme = resolved
663
- self._last_resolved_theme = resolved
664
- self._start_system_theme_polling()
665
- else:
666
- self._stop_system_theme_polling()
667
- self.theme = value
668
- elif key == "ui.scrollbar":
669
- if isinstance(value, str):
670
- self.scrollbar = value
671
- elif key == "ui.compact-input":
672
- self.set_class(bool(value), "-compact-input")
673
- elif key == "ui.footer":
674
- self.set_class(not bool(value), "-hide-footer")
675
- elif key == "ui.status-line":
676
- self.set_class(not bool(value), "-hide-status-line")
677
- elif key == "ui.agent-title":
678
- self.set_class(not bool(value), "-hide-agent-title")
679
- elif key == "ui.info-bar":
680
- self.set_class(not bool(value), "-hide-info-bar")
681
- elif key == "agent.thoughts":
682
- self.set_class(not bool(value), "-hide-thoughts")
683
- elif key == "sidebar.hide":
684
- self.set_class(bool(value), "-hide-sidebar")
685
- elif key == "ui.sessions-bar":
686
- self.update_show_sessions()
687
-
688
- self.settings_changed_signal.publish((key, value))
689
-
690
- def _sync_cdh_config(self, key: str, value: object) -> None:
691
- """Sync cdh config to GlobalConfig when tui settings change."""
692
- try:
693
- from onecode.config import load_config, save_config
694
- cfg = load_config()
695
- cdh_key = key[4:]
696
- if cdh_key == "mode":
697
- cfg.default_mode = value
698
- elif cdh_key == "provider":
699
- cfg.default_provider = value
700
- elif cdh_key == "model":
701
- cfg.default_model = value
702
- elif cdh_key == "log_level":
703
- cfg.log_level = value
704
- elif cdh_key == "session_auto_save":
705
- cfg.session_auto_save = value
706
- save_config(cfg)
707
- except Exception:
708
- pass
709
-
710
- def _start_system_theme_polling(self) -> None:
711
- if self._system_theme_timer is not None:
712
- return
713
- self._system_theme_timer = self.set_interval(3, self._poll_system_theme)
714
-
715
- def _stop_system_theme_polling(self) -> None:
716
- if self._system_theme_timer is not None:
717
- self._system_theme_timer.stop()
718
- self._system_theme_timer = None
719
-
720
- def _poll_system_theme(self) -> None:
721
- resolved = self._resolve_system_theme()
722
- if resolved != self._last_resolved_theme:
723
- self.theme = resolved
724
- self._last_resolved_theme = resolved
725
-
726
- def search_themes(self) -> None:
727
- """Show theme picker with System first, then all Textual themes."""
728
- from textual.command import CommandPalette, SimpleProvider
729
-
730
- themes = self.available_themes
731
-
732
- def set_system():
733
- self.settings.set("ui.theme", "system")
734
-
735
- commands: list[tuple[str, Callable[[], None], str]] = [
736
- ("System", set_system, "Follow system dark/light mode"),
737
- ]
738
-
739
- from operator import attrgetter
740
- for theme in sorted(themes.values(), key=attrgetter("name")):
741
- def set_app_theme(name: str = theme.name):
742
- self.settings.set("ui.theme", name)
743
- commands.append((theme.name, set_app_theme, ""))
744
-
745
- self.push_screen(
746
- CommandPalette(
747
- providers=[SimpleProvider(self.screen, commands)],
748
- placeholder="Search for themes…",
749
- ),
750
- )
751
-
752
- @staticmethod
753
- def _resolve_system_theme() -> str:
754
- """Detect system dark/light mode and return a matching theme."""
755
- if platform.system() == "Darwin":
756
- try:
757
- import subprocess
758
- result = subprocess.run(
759
- ["defaults", "read", "-g", "AppleInterfaceStyle"],
760
- capture_output=True, text=True,
761
- )
762
- if result.stdout.strip() == "Dark":
763
- return "dracula"
764
- else:
765
- return "catppuccin-latte"
766
- except Exception:
767
- pass
768
- return "dracula"
769
-
770
- def _load_cdh_config(self) -> None:
771
- """Load cdh config into tui settings on startup."""
772
- try:
773
- from onecode.config import load_config
774
- cfg = load_config()
775
- cdh_settings = {
776
- "cdh.mode": cfg.default_mode,
777
- "cdh.provider": cfg.default_provider,
778
- "cdh.model": cfg.default_model,
779
- "cdh.log_level": cfg.log_level,
780
- "cdh.session_auto_save": cfg.session_auto_save,
781
- }
782
- for key, value in cdh_settings.items():
783
- if not self.settings.get(key, object, expand=False):
784
- self.settings.set(key, value)
785
- except Exception:
786
- pass
787
-
788
- async def on_load(self) -> None:
789
- db = await self.get_db()
790
- await db.create()
791
- settings_path = self.settings_path
792
- if settings_path.exists():
793
- settings = json.loads(settings_path.read_text("utf-8"))
794
- else:
795
- settings = {}
796
- settings_path.write_text(
797
- json.dumps(settings, indent=4, separators=(", ", ": ")), "utf-8"
798
- )
799
- self.notify(f"Wrote default settings to {settings_path}", title="Settings")
800
- self.ansi_theme_dark = DRACULA_TERMINAL_THEME
801
- self._settings = settings
802
- self.settings.set_all()
803
- self._load_cdh_config()
804
-
805
- async def new_session_screen(
806
- self, get_screen: Callable[[], Screen], session_pk: int | None = None
807
- ) -> SessionDetails:
808
- session_details = self._session_tracker.new_session(session_pk=session_pk)
809
- self.update_show_sessions()
810
- self.session_update_signal.publish((session_details.mode_name, session_details))
811
-
812
- def make_screen() -> Screen:
813
- screen = get_screen()
814
- screen.id = session_details.mode_name
815
- return screen
816
-
817
- self.add_mode(session_details.mode_name, make_screen)
818
- await self.switch_mode(session_details.mode_name)
819
- return session_details
820
-
821
- def _resolve_launch_agent(self) -> str | None:
822
- identity = self._launch_agent_identity
823
- if identity:
824
- return identity
825
- agents = self.settings.get("launcher.agents", str).splitlines()
826
- for agent in agents:
827
- agent = agent.strip()
828
- if agent:
829
- return agent
830
- return None
831
-
832
- async def on_mount(self) -> None:
833
- self.capture_event("tui-run")
834
- self.anon_id # Created on frst reference
835
-
836
- from pathlib import Path
837
-
838
- if mode := self._initial_mode:
839
- self.switch_mode(mode)
840
- elif self._launch_agent_identity:
841
- self.launch_agent(
842
- self._launch_agent_identity, project_path=Path(self.project_dir)
843
- )
844
- else:
845
- project_path = Path(self.project_dir)
846
- # Check project-level .cdh/last_session.json
847
- from onecode.agent.cdh_loader import CdhProjectLoader
848
- cdh_dir = CdhProjectLoader.find_cdh_dir(project_path)
849
- if cdh_dir:
850
- last_session = CdhProjectLoader.load_last_session(cdh_dir)
851
- if last_session and last_session.get("agent_session_id"):
852
- agent_session_id = last_session["agent_session_id"]
853
- session_pk = last_session.get("session_pk")
854
- agent_identity = last_session.get("agent_identity")
855
- if agent_identity and session_pk:
856
- db = DB()
857
- session = await db.session_get(session_pk)
858
- if session:
859
- self.launch_agent(
860
- session["agent_identity"],
861
- agent_session_id=session["agent_session_id"],
862
- session_pk=session["id"],
863
- )
864
- return
865
- if agent_identity:
866
- try:
867
- from tui.agents import read_agents
868
- if agent_identity in await read_agents():
869
- self.launch_agent(
870
- agent_identity,
871
- agent_session_id=agent_session_id,
872
- )
873
- return
874
- except Exception:
875
- pass
876
-
877
- if agent_identity := self._resolve_launch_agent():
878
- self.launch_agent(agent_identity, project_path=project_path)
879
- else:
880
- self.push_screen("store")
881
-
882
- self.update_terminal_title()
883
- self.set_timer(1, self.run_version_check)
884
- self.set_process_title()
885
- self.update_show_sessions()
886
-
887
- @work(thread=True, exit_on_error=False)
888
- def set_process_title(self) -> None:
889
- try:
890
- import setproctitle
891
-
892
- setproctitle.setproctitle("tui")
893
- except Exception:
894
- pass
895
-
896
- @on(events.TextSelected)
897
- async def on_text_selected(self) -> None:
898
- if self.settings.get("ui.auto_copy", bool):
899
- if (selection := self.screen.get_selected_text()) is not None:
900
- self.copy_to_clipboard(selection)
901
- self.notify(
902
- "Copied selection to clipboard (see settings)",
903
- title="Automatic copy",
904
- )
905
-
906
- def run_on_exit(self):
907
- pass
908
-
909
- @work(exit_on_error=False)
910
- async def run_version_check(self) -> None:
911
- """Check remote version."""
912
- from tui.version import check_version, VersionCheckFailed
913
-
914
- try:
915
- update_required, version_meta = await check_version()
916
- except VersionCheckFailed:
917
- return
918
- self.version_meta = version_meta
919
- self.update_required = update_required
920
-
921
- def get_main_screen(self) -> MainScreen:
922
- """Make the default screen.
923
-
924
- Returns:
925
- Instance of `MainScreen`
926
- """
927
- # Lazy import
928
- from tui.screens.main import MainScreen
929
-
930
- project_path = Path(self.project_dir or "./").resolve().absolute()
931
- return MainScreen(project_path, self.agent_data).data_bind(
932
- column=A2TUIApp.column,
933
- column_width=A2TUIApp.column_width,
934
- scrollbar=A2TUIApp.scrollbar,
935
- )
936
-
937
- @work
938
- async def action_settings(self) -> None:
939
- await self.push_screen_wait("settings")
940
- await self.save_settings()
941
-
942
- def action_logs(self) -> None:
943
- """Toggle the real-time log screen (F4).
944
-
945
- Opens the current session's JSON-RPC log file (the wire
946
- protocol trace between this TUI and the ``cdh-agent-acp``
947
- subprocess). Falls back to the most-recently-modified log
948
- file under ``paths.get_log()`` if no session is active.
949
- """
950
- from tui.screens.log import LogScreen
951
-
952
- # Re-focus the existing one if it's already on the stack
953
- for screen in self.screen_stack:
954
- if isinstance(screen, LogScreen):
955
- screen.dismiss()
956
- return
957
- log_path = self._current_session_log_path()
958
- self.push_screen(LogScreen(log_path=log_path))
959
-
960
- def _current_session_log_path(self) -> Path | None:
961
- """Resolve the message log file for the active session.
962
-
963
- Returns ``None`` when there is no active session yet (e.g. on
964
- the splash / store screen). The :class:`LogScreen` interprets
965
- ``None`` as "auto-discover the most recent log" and shows a
966
- "waiting" placeholder while the agent subprocess is still
967
- starting.
968
- """
969
- screen = self.screen
970
- conv = getattr(screen, "conversation", None)
971
- agent = getattr(conv, "agent", None) if conv else None
972
- session_id = (
973
- getattr(agent, "session_id", None) if agent else None
974
- ) or getattr(conv, "_agent_session_id", None)
975
- if not session_id:
976
- return None
977
- from tui.message_log import sanitize_filename
978
- agent_name = getattr(conv, "agent_title", None) or "unknown"
979
- path = paths.get_log() / "messages" / f"{sanitize_filename(agent_name)}_{sanitize_filename(session_id)}.jsonl"
980
- return path if path.exists() else None
981
-
982
- def action_quit(self) -> None:
983
- """An [action](/guide/actions) to quit the app as soon as possible."""
984
-
985
- self.screen.set_focus(None)
986
- self._capture_exit_metrics()
987
- try:
988
- driver = self._driver
989
- if driver is not None:
990
- driver.stop_application_mode()
991
- except Exception:
992
- pass
993
- sid = (self._exit_metrics.get("session_id", "") or "")[:36] if self._exit_metrics else ""
994
- session_pk = self._exit_metrics.get("session_pk") if self._exit_metrics else None
995
- # Format the session ID for display: prefer the short `cdh
996
- # session list` form (`session-<pk>` + agent_session_id prefix)
997
- # so the displayed value matches what users see in
998
- # `cdh session list` and what they pass to `cdh session load`.
999
- if session_pk is not None:
1000
- display_id = f"session-{session_pk} ({sid[:8]})" if sid else f"session-{session_pk}"
1001
- hint = f" Reload with: cdh session load {session_pk}\n"
1002
- elif sid:
1003
- display_id = sid
1004
- hint = f" Reload with: cdh session load {sid}\n"
1005
- else:
1006
- display_id = "-"
1007
- hint = ""
1008
- try:
1009
- with open("/dev/tty", "w") as tty:
1010
- tty.write(
1011
- f"\n CDH powering down. Goodbye!\n"
1012
- f" Session ID: {display_id}\n"
1013
- f"{hint}"
1014
- )
1015
- except Exception:
1016
- pass
1017
- import os
1018
- os._exit(0)
1019
-
1020
- def action_help_quit(self) -> None:
1021
- if (time := monotonic()) - self.last_ctrl_c_time <= 5.0:
1022
- self.exit()
1023
- self.last_ctrl_c_time = time
1024
- self.notify(
1025
- "Press [b]ctrl+c[/b] again to quit the app", title="Do you want to quit?"
1026
- )
1027
-
1028
- def action_toggle_help_panel(self):
1029
- if self.screen.query("HelpPanel"):
1030
- self.action_hide_help_panel()
1031
- else:
1032
- self.action_show_help_panel()
1033
-
1034
- def update_show_sessions(self) -> None:
1035
- match self.settings.get("ui.sessions-bar", str):
1036
- case "always":
1037
- self.show_sessions = True
1038
- case "never":
1039
- self.show_sessions = False
1040
- case "multiple":
1041
- self.show_sessions = self.session_tracker.session_count > 1
1042
-
1043
- @on(messages.SessionNavigate)
1044
- def on_session_navigate(self, event: messages.SessionNavigate) -> None:
1045
- new_mode = self._session_tracker.session_cursor_move(
1046
- event.mode_name, event.direction, set(self._modes.keys())
1047
- )
1048
- if new_mode is not None:
1049
- self.switch_mode(new_mode)
1050
-
1051
- @on(messages.SessionSwitch)
1052
- def on_session_switch(self, event: messages.SessionSwitch) -> None:
1053
- mode_name = event.mode_name
1054
- if mode_name in self._modes:
1055
- self.switch_mode(mode_name)
1056
- elif mode_name.startswith("session-"):
1057
- parts = mode_name.split("-")
1058
- session_pk = int(parts[1])
1059
- existing_mode = self._find_mode_for_session_pk(session_pk)
1060
- if existing_mode is not None and existing_mode in self._modes:
1061
- self.switch_mode(existing_mode)
1062
- else:
1063
- self._load_session(session_pk)
1064
-
1065
- @on(messages.SessionNew)
1066
- def on_session_new(self, event: messages.SessionNew) -> None:
1067
- self.launch_agent(
1068
- event.agent, project_path=Path(event.path), initial_prompt=event.prompt
1069
- )
1070
-
1071
- @on(messages.SessionClose)
1072
- def on_session_close(self) -> None:
1073
- self.update_show_sessions()
1074
-
1075
- @on(messages.SessionLoad)
1076
- def on_session_load(self, event: messages.SessionLoad) -> None:
1077
- self._load_session(event.session_pk)
1078
-
1079
- @work
1080
- async def _load_session(self, session_pk: int) -> None:
1081
- existing_mode = self._find_mode_for_session_pk(session_pk)
1082
- if existing_mode is not None:
1083
- self.switch_mode(existing_mode)
1084
- return
1085
- db = DB()
1086
- session = await db.session_get(session_pk)
1087
- if session is None:
1088
- self.notify(f"Session {session_pk} not found", title="Session load", severity="error")
1089
- return
1090
- agent_identity = session["agent_identity"]
1091
- agent_session_id = session.get("agent_session_id")
1092
-
1093
- project_path = None
1094
- try:
1095
- meta = json.loads(session.get("meta_json", "{}"))
1096
- if cwd := meta.get("cwd"):
1097
- project_path = Path(cwd)
1098
- except (json.JSONDecodeError, TypeError):
1099
- pass
1100
-
1101
- if project_path:
1102
- self.project_dir = project_path
1103
-
1104
- self.launch_agent(
1105
- agent_identity,
1106
- agent_session_id=agent_session_id,
1107
- session_pk=session_pk,
1108
- project_path=project_path,
1109
- )
1110
-
1111
- @work
1112
- async def action_sessions(self) -> None:
1113
- if (session_screen_name := await self.push_screen_wait("sessions")) is not None:
1114
- if session_screen_name.startswith("session-"):
1115
- parts = session_screen_name.split("-")
1116
- session_pk = int(parts[1])
1117
- existing_mode = self._find_mode_for_session_pk(session_pk)
1118
- if existing_mode is not None and existing_mode in self._modes:
1119
- self.switch_mode(existing_mode)
1120
- else:
1121
- self._load_session(session_pk)
1122
- else:
1123
- try:
1124
- self.app.switch_mode(session_screen_name)
1125
- except KeyError:
1126
- pass
1127
-
1128
- @work
1129
- async def action_projects(self) -> None:
1130
- from pathlib import Path
1131
- from onecode.config import load_config, save_config
1132
- from onecode.config_screen import EditFieldScreen
1133
- import yaml
1134
-
1135
- from tui.screens.projects_screen import ProjectsScreen
1136
- result = await self.push_screen_wait(ProjectsScreen())
1137
- if result is None:
1138
- cfg = load_config()
1139
- if cfg.current_project:
1140
- projects_dir = Path.home() / ".cdh" / "projects"
1141
- pf = projects_dir / f"{cfg.current_project}.yaml"
1142
- if not pf.exists():
1143
- cfg.current_project = None
1144
- cfg.current_project_path = None
1145
- save_config(cfg)
1146
- self.project_dir = None
1147
- self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=None))
1148
- if result is not None:
1149
- if result == "__new__":
1150
- default_path = str((self.project_dir or Path.cwd()).resolve())
1151
- path_str = await self.push_screen_wait(
1152
- EditFieldScreen("Project path", default_path)
1153
- )
1154
- if not path_str:
1155
- return
1156
- try:
1157
- project_path = Path(path_str).expanduser().resolve()
1158
- except Exception:
1159
- self.notify("Invalid path", severity="error")
1160
- return
1161
- name = project_path.name
1162
- from onecode.agent.cdh_loader import CdhProjectLoader
1163
- from cdh.scaffold import scaffold_dlc_project
1164
- scaffold_dlc_project(project_path, name)
1165
- CdhProjectLoader.init_project(project_path, name)
1166
- projects_dir = Path.home() / ".cdh" / "projects"
1167
- projects_dir.mkdir(parents=True, exist_ok=True)
1168
- proj_data = {"name": name, "path": str(project_path), "description": ""}
1169
- (projects_dir / f"{name}.yaml").write_text(yaml.dump(proj_data))
1170
- cfg = load_config()
1171
- cfg.current_project = name
1172
- cfg.current_project_path = str(project_path)
1173
- save_config(cfg)
1174
- self.project_dir = project_path
1175
- self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=self.project_dir))
1176
- self.notify(f"Created project '{name}' at {project_path}")
1177
- return
1178
-
1179
- project_name = result if isinstance(result, str) else getattr(result, 'name', None)
1180
- if project_name:
1181
- projects_dir = Path.home() / ".cdh" / "projects"
1182
- project_path = None
1183
- for ext in ["yaml", "yml", "json"]:
1184
- pf = projects_dir / f"{project_name}.{ext}"
1185
- if pf.exists():
1186
- proj_data = yaml.safe_load(pf.read_text()) if ext in ["yaml", "yml"] else __import__("json").loads(pf.read_text())
1187
- project_path = proj_data.get("path", ".")
1188
- break
1189
- if project_path:
1190
- cfg = load_config()
1191
- cfg.current_project = project_name
1192
- cfg.current_project_path = project_path
1193
- save_config(cfg)
1194
-
1195
- new_project_dir = Path(project_path) if project_path else Path.cwd()
1196
- self.project_dir = new_project_dir
1197
- self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=self.project_dir))
1198
- self.notify(f"Switched to project: {project_name}")
1199
- from tui.agents import read_agents
1200
- agents = await read_agents()
1201
- if cfg.default_mode in agents:
1202
- self.launch_agent(cfg.default_mode, project_path=new_project_dir)
1203
-
1204
- @on(messages.LaunchAgent)
1205
- def on_launch_agent(self, message: messages.LaunchAgent) -> None:
1206
- self.launch_agent(
1207
- message.identity,
1208
- agent_session_id=message.session_id,
1209
- session_pk=message.pk,
1210
- initial_prompt=message.prompt,
1211
- )
1212
-
1213
- @work
1214
- async def launch_agent(
1215
- self,
1216
- agent_identity: str,
1217
- *,
1218
- agent_session_id: str | None = None,
1219
- session_pk: int | None = None,
1220
- project_path: Path | None = None,
1221
- initial_prompt: str | None = None,
1222
- ) -> None:
1223
- from tui.screens.main import MainScreen
1224
- from tui.agent_schema import Agent
1225
- from tui.agents import read_agents
1226
-
1227
- agent: Agent | None = None
1228
- if session_pk is not None:
1229
- db = DB()
1230
- session = await db.session_get(session_pk)
1231
- if session is not None:
1232
- meta = json.loads(session["meta_json"])
1233
- if agent_data := meta.get("agent_data"):
1234
- agent = agent_data
1235
-
1236
- if agent is None:
1237
- agents = await read_agents()
1238
- try:
1239
- agent = agents[agent_identity]
1240
- except KeyError:
1241
- self.notify("Agent not found", title="Launch agent", severity="error")
1242
- return
1243
- if project_path is None:
1244
- project_path = Path(self.project_dir or os.getcwd())
1245
-
1246
- def get_screen():
1247
- screen = MainScreen(
1248
- project_path,
1249
- agent,
1250
- agent_session_id,
1251
- session_pk=session_pk,
1252
- initial_prompt=initial_prompt,
1253
- ).data_bind(
1254
- column=A2TUIApp.column,
1255
- column_width=A2TUIApp.column_width,
1256
- )
1257
-
1258
- return screen
1259
-
1260
- await self.new_session_screen(get_screen, session_pk=session_pk)
1261
-
1262
- def _find_mode_for_session_pk(self, session_pk: int) -> str | None:
1263
- """Find an existing mode name for a given DB session_pk."""
1264
- if existing := self._session_tracker.get_session_by_pk(session_pk):
1265
- return existing.mode_name
1266
- return None