@scemoon/cdh 1.0.0

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 (528) hide show
  1. package/ai-dlc-skill/SKILL.md +75 -0
  2. package/ai-dlc-skill/agents/deliver-agent.md +24 -0
  3. package/ai-dlc-skill/agents/master.md +44 -0
  4. package/ai-dlc-skill/agents/plan-agent.md +23 -0
  5. package/ai-dlc-skill/agents/understand-agent.md +23 -0
  6. package/ai-dlc-skill/agents/verify-agent.md +27 -0
  7. package/ai-dlc-skill/architecture/project-structure.md +78 -0
  8. package/ai-dlc-skill/brownfield/README.md +30 -0
  9. package/ai-dlc-skill/brownfield/generate-context.sh +64 -0
  10. package/ai-dlc-skill/brownfield/scripts/deps.sh +27 -0
  11. package/ai-dlc-skill/brownfield/scripts/discover.sh +34 -0
  12. package/ai-dlc-skill/brownfield/scripts/extract-api.sh +45 -0
  13. package/ai-dlc-skill/components/backend.md +34 -0
  14. package/ai-dlc-skill/components/desktop.md +30 -0
  15. package/ai-dlc-skill/components/mya.md +29 -0
  16. package/ai-dlc-skill/components/native.md +30 -0
  17. package/ai-dlc-skill/components/tta.md +29 -0
  18. package/ai-dlc-skill/components/web.md +31 -0
  19. package/ai-dlc-skill/components/wxa.md +29 -0
  20. package/ai-dlc-skill/contracts/README.md +25 -0
  21. package/ai-dlc-skill/core/adaptive-flow.md +36 -0
  22. package/ai-dlc-skill/core/security.md +29 -0
  23. package/ai-dlc-skill/cross-tool/cline/export.sh +31 -0
  24. package/ai-dlc-skill/cross-tool/copilot/export.sh +47 -0
  25. package/ai-dlc-skill/cross-tool/cursor/export.sh +32 -0
  26. package/ai-dlc-skill/cross-tool/export.sh +22 -0
  27. package/ai-dlc-skill/cross-tool/onecode/config.yaml.tpl +17 -0
  28. package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +31 -0
  29. package/ai-dlc-skill/cross-tool/onecode/export.sh +48 -0
  30. package/ai-dlc-skill/phases/deliver/entry.md +26 -0
  31. package/ai-dlc-skill/phases/deliver/lifecycle.md +185 -0
  32. package/ai-dlc-skill/phases/deliver/prompt.md +40 -0
  33. package/ai-dlc-skill/phases/deliver/rules.md +61 -0
  34. package/ai-dlc-skill/phases/plan/entry.md +25 -0
  35. package/ai-dlc-skill/phases/plan/lifecycle.md +204 -0
  36. package/ai-dlc-skill/phases/plan/prompt.md +38 -0
  37. package/ai-dlc-skill/phases/plan/rules.md +25 -0
  38. package/ai-dlc-skill/phases/understand/entry.md +26 -0
  39. package/ai-dlc-skill/phases/understand/lifecycle.md +184 -0
  40. package/ai-dlc-skill/phases/understand/prompt.md +41 -0
  41. package/ai-dlc-skill/phases/understand/rules.md +37 -0
  42. package/ai-dlc-skill/phases/verify/entry.md +26 -0
  43. package/ai-dlc-skill/phases/verify/lifecycle.md +192 -0
  44. package/ai-dlc-skill/phases/verify/prompt.md +43 -0
  45. package/ai-dlc-skill/phases/verify/rules.md +73 -0
  46. package/ai-dlc-skill/practices/bdd.md +91 -0
  47. package/ai-dlc-skill/practices/sdd.md +84 -0
  48. package/ai-dlc-skill/practices/tdd.md +95 -0
  49. package/ai-dlc-skill/providers/README.md +114 -0
  50. package/ai-dlc-skill/providers/aliyun/deployment.yaml +66 -0
  51. package/ai-dlc-skill/providers/aliyun/preview.yaml +84 -0
  52. package/ai-dlc-skill/providers/aliyun/provider.yaml +160 -0
  53. package/ai-dlc-skill/providers/tcb/deployment.yaml +69 -0
  54. package/ai-dlc-skill/providers/tcb/preview.yaml +83 -0
  55. package/ai-dlc-skill/providers/tcb/provider.yaml +172 -0
  56. package/ai-dlc-skill/requirements.md +140 -0
  57. package/ai-dlc-skill/skill.yaml +147 -0
  58. package/ai-dlc-skill/templates/integration/CHANGELOG.md +41 -0
  59. package/ai-dlc-skill/templates/integration/asyncapi.yaml +43 -0
  60. package/ai-dlc-skill/templates/integration/contract-diff.md +63 -0
  61. package/ai-dlc-skill/templates/integration/contract-spec.md +49 -0
  62. package/ai-dlc-skill/templates/integration/openapi.yaml +18 -0
  63. package/ai-dlc-skill/templates/integration/runtime-contract.yaml +36 -0
  64. package/ai-dlc-skill/templates/plan/design.md +49 -0
  65. package/ai-dlc-skill/templates/plan/task-list.md +32 -0
  66. package/ai-dlc-skill/templates/project/README.md +127 -0
  67. package/ai-dlc-skill/templates/project/template.md +119 -0
  68. package/ai-dlc-skill/templates/understand/feature.feature +23 -0
  69. package/ai-dlc-skill/templates/understand/intent.md +29 -0
  70. package/ai-dlc-skill/templates/understand/spec-delta.md +47 -0
  71. package/ai-dlc-skill/templates/verify/test-unit.py +27 -0
  72. package/ai-dlc-skill/walkthrough/collect.sh +50 -0
  73. package/ai-dlc-skill/walkthrough/template.md +54 -0
  74. package/ai-dlc-skill/workflows/ai-dlc.yaml +110 -0
  75. package/ai-dlc-skill/workflows/deployment.yaml +199 -0
  76. package/cdh/__init__.py +0 -0
  77. package/cdh/__main__.py +4 -0
  78. package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/cdh/__pycache__/__init__.cpython-314.pyc +0 -0
  80. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  81. package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
  82. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  83. package/cdh/cli.py +361 -0
  84. package/cdh/scaffold.py +312 -0
  85. package/cli.js +2 -0
  86. package/onecode/__init__.py +1 -0
  87. package/onecode/__main__.py +4 -0
  88. package/onecode/__pycache__/__init__.cpython-314.pyc +0 -0
  89. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  90. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  91. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  92. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  93. package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
  94. package/onecode/agent/__init__.py +0 -0
  95. package/onecode/agent/__pycache__/__init__.cpython-314.pyc +0 -0
  96. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
  97. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
  98. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  99. package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
  100. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  101. package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
  102. package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
  103. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  104. package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
  105. package/onecode/agent/__pycache__/permissions_store.cpython-314.pyc +0 -0
  106. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  107. package/onecode/agent/agents/__init__.py +29 -0
  108. package/onecode/agent/agents/__pycache__/__init__.cpython-314.pyc +0 -0
  109. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  110. package/onecode/agent/agents/types.py +662 -0
  111. package/onecode/agent/attachments.py +113 -0
  112. package/onecode/agent/cdh_loader.py +262 -0
  113. package/onecode/agent/context.py +338 -0
  114. package/onecode/agent/engine.py +1894 -0
  115. package/onecode/agent/hooks.py +86 -0
  116. package/onecode/agent/onecode_agent_acp.py +2203 -0
  117. package/onecode/agent/permissions.py +151 -0
  118. package/onecode/agent/permissions_store.py +62 -0
  119. package/onecode/agent/session.py +186 -0
  120. package/onecode/agent/snapshot.py +145 -0
  121. package/onecode/agent/tools/__init__.py +17 -0
  122. package/onecode/agent/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  123. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  124. package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
  125. package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
  126. package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
  127. package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
  128. package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
  129. package/onecode/agent/tools/__pycache__/errors.cpython-314.pyc +0 -0
  130. package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
  131. package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
  132. package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
  133. package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
  134. package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
  135. package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
  136. package/onecode/agent/tools/__pycache__/permissions.cpython-314.pyc +0 -0
  137. package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
  138. package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
  139. package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
  140. package/onecode/agent/tools/__pycache__/schema_validation.cpython-314.pyc +0 -0
  141. package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
  142. package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
  143. package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
  144. package/onecode/agent/tools/agent_tools.py +134 -0
  145. package/onecode/agent/tools/apply_patch_tool.py +144 -0
  146. package/onecode/agent/tools/bash_tool.py +91 -0
  147. package/onecode/agent/tools/communication_tools.py +149 -0
  148. package/onecode/agent/tools/config_tool.py +130 -0
  149. package/onecode/agent/tools/cron_tools.py +179 -0
  150. package/onecode/agent/tools/errors.py +17 -0
  151. package/onecode/agent/tools/file_ops.py +187 -0
  152. package/onecode/agent/tools/file_tools.py +226 -0
  153. package/onecode/agent/tools/git_tools.py +88 -0
  154. package/onecode/agent/tools/lsp_tools.py +404 -0
  155. package/onecode/agent/tools/mcp_tools.py +129 -0
  156. package/onecode/agent/tools/permission_handler.py +46 -0
  157. package/onecode/agent/tools/permissions.py +77 -0
  158. package/onecode/agent/tools/protocol.py +20 -0
  159. package/onecode/agent/tools/registry.py +134 -0
  160. package/onecode/agent/tools/sandbox.py +264 -0
  161. package/onecode/agent/tools/schema_validation.py +100 -0
  162. package/onecode/agent/tools/skill_tools.py +52 -0
  163. package/onecode/agent/tools/task_tools.py +249 -0
  164. package/onecode/agent/tools/web_tools.py +170 -0
  165. package/onecode/builtin_skills/git/SKILL.md +64 -0
  166. package/onecode/builtin_skills/git/skill.yaml +7 -0
  167. package/onecode/builtin_skills/shell/SKILL.md +58 -0
  168. package/onecode/builtin_skills/shell/skill.yaml +8 -0
  169. package/onecode/cli.py +755 -0
  170. package/onecode/cloud/__init__.py +0 -0
  171. package/onecode/cloud/base.py +20 -0
  172. package/onecode/codebase/__init__.py +69 -0
  173. package/onecode/codebase/__pycache__/__init__.cpython-314.pyc +0 -0
  174. package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
  175. package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
  176. package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
  177. package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
  178. package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
  179. package/onecode/codebase/__pycache__/tools.cpython-314.pyc +0 -0
  180. package/onecode/codebase/chunker.py +130 -0
  181. package/onecode/codebase/indexer.py +136 -0
  182. package/onecode/codebase/retriever.py +192 -0
  183. package/onecode/codebase/storage.py +130 -0
  184. package/onecode/codebase/tools.py +59 -0
  185. package/onecode/commands.py +276 -0
  186. package/onecode/config.py +285 -0
  187. package/onecode/config_screen.py +594 -0
  188. package/onecode/cron/__init__.py +3 -0
  189. package/onecode/lsp/__init__.py +3 -0
  190. package/onecode/mcp/__init__.py +4 -0
  191. package/onecode/mcp/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/onecode/mcp/__pycache__/client.cpython-314.pyc +0 -0
  193. package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
  194. package/onecode/mcp/client.py +193 -0
  195. package/onecode/mcp/manager.py +191 -0
  196. package/onecode/memory/__init__.py +95 -0
  197. package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
  198. package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
  199. package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
  200. package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
  201. package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
  202. package/onecode/memory/__pycache__/symbolic.cpython-314.pyc +0 -0
  203. package/onecode/memory/backend.py +185 -0
  204. package/onecode/memory/offload.py +114 -0
  205. package/onecode/memory/pyramid.py +202 -0
  206. package/onecode/memory/recall.py +168 -0
  207. package/onecode/memory/symbolic.py +164 -0
  208. package/onecode/models/__init__.py +4 -0
  209. package/onecode/models/__pycache__/__init__.cpython-314.pyc +0 -0
  210. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  211. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  212. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  213. package/onecode/models/__pycache__/registry.cpython-314.pyc +0 -0
  214. package/onecode/models/errors.py +94 -0
  215. package/onecode/models/messages.py +662 -0
  216. package/onecode/models/provider.py +450 -0
  217. package/onecode/models/providers/__init__.py +17 -0
  218. package/onecode/models/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  219. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  220. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  221. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  222. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  223. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  224. package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
  225. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  226. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  227. package/onecode/models/providers/anthropic_provider.py +224 -0
  228. package/onecode/models/providers/deepseek_provider.py +210 -0
  229. package/onecode/models/providers/glm_provider.py +154 -0
  230. package/onecode/models/providers/minimax_provider.py +154 -0
  231. package/onecode/models/providers/minimaxi_provider.py +363 -0
  232. package/onecode/models/providers/ollama_provider.py +96 -0
  233. package/onecode/models/providers/openai_provider.py +233 -0
  234. package/onecode/models/registry.py +212 -0
  235. package/onecode/server/__init__.py +3 -0
  236. package/onecode/server/app.py +224 -0
  237. package/onecode/skills/__init__.py +11 -0
  238. package/onecode/skills/__pycache__/__init__.cpython-314.pyc +0 -0
  239. package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
  240. package/onecode/skills/__pycache__/create.cpython-314.pyc +0 -0
  241. package/onecode/skills/__pycache__/frontmatter.cpython-314.pyc +0 -0
  242. package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
  243. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  244. package/onecode/skills/__pycache__/model.cpython-314.pyc +0 -0
  245. package/onecode/skills/argument_substitution.py +39 -0
  246. package/onecode/skills/create.py +56 -0
  247. package/onecode/skills/frontmatter.py +33 -0
  248. package/onecode/skills/loader.py +157 -0
  249. package/onecode/skills/manager.py +75 -0
  250. package/onecode/skills/model.py +43 -0
  251. package/onecode/storage/__init__.py +0 -0
  252. package/onecode/storage/activity.py +64 -0
  253. package/onecode/storage/project.py +54 -0
  254. package/onecode/storage/session.py +113 -0
  255. package/onecode/tasks/__init__.py +26 -0
  256. package/onecode/tasks/manager.py +219 -0
  257. package/onecode/tasks/models.py +240 -0
  258. package/onecode/trace/__init__.py +0 -0
  259. package/onecode/trace/tracer.py +174 -0
  260. package/onecode/utils/__init__.py +0 -0
  261. package/onecode/utils/helpers.py +20 -0
  262. package/onecode-cli.js +2 -0
  263. package/package.json +32 -0
  264. package/pyproject.toml +60 -0
  265. package/run.js +86 -0
  266. package/tui/__init__.py +46 -0
  267. package/tui/__main__.py +4 -0
  268. package/tui/__pycache__/__init__.cpython-314.pyc +0 -0
  269. package/tui/__pycache__/_loop.cpython-314.pyc +0 -0
  270. package/tui/__pycache__/_path_fuzzy_search.cpython-314.pyc +0 -0
  271. package/tui/__pycache__/_path_match.cpython-314.pyc +0 -0
  272. package/tui/__pycache__/agent.cpython-314.pyc +0 -0
  273. package/tui/__pycache__/agent_schema.cpython-314.pyc +0 -0
  274. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  275. package/tui/__pycache__/answer.cpython-314.pyc +0 -0
  276. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  277. package/tui/__pycache__/atomic.cpython-314.pyc +0 -0
  278. package/tui/__pycache__/complete.cpython-314.pyc +0 -0
  279. package/tui/__pycache__/constants.cpython-314.pyc +0 -0
  280. package/tui/__pycache__/conversation_markdown.cpython-314.pyc +0 -0
  281. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  282. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  283. package/tui/__pycache__/dec.cpython-314.pyc +0 -0
  284. package/tui/__pycache__/directory.cpython-314.pyc +0 -0
  285. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  286. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  287. package/tui/__pycache__/format_path.cpython-314.pyc +0 -0
  288. package/tui/__pycache__/fuzzy.cpython-314.pyc +0 -0
  289. package/tui/__pycache__/fuzzy_index.cpython-314.pyc +0 -0
  290. package/tui/__pycache__/history.cpython-314.pyc +0 -0
  291. package/tui/__pycache__/jsonrpc.cpython-314.pyc +0 -0
  292. package/tui/__pycache__/menus.cpython-314.pyc +0 -0
  293. package/tui/__pycache__/message_log.cpython-314.pyc +0 -0
  294. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  295. package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
  296. package/tui/__pycache__/path_filter.cpython-314.pyc +0 -0
  297. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  298. package/tui/__pycache__/pill.cpython-314.pyc +0 -0
  299. package/tui/__pycache__/platform_commands.cpython-314.pyc +0 -0
  300. package/tui/__pycache__/protocol.cpython-314.pyc +0 -0
  301. package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
  302. package/tui/__pycache__/settings.cpython-314.pyc +0 -0
  303. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  304. package/tui/__pycache__/shell.cpython-314.pyc +0 -0
  305. package/tui/__pycache__/shell_read.cpython-314.pyc +0 -0
  306. package/tui/__pycache__/slash_command.cpython-314.pyc +0 -0
  307. package/tui/__pycache__/version.cpython-314.pyc +0 -0
  308. package/tui/_loop.py +86 -0
  309. package/tui/_path_fuzzy_search.py +48 -0
  310. package/tui/_path_match.py +141 -0
  311. package/tui/about.py +95 -0
  312. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  313. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  314. package/tui/acp/__pycache__/encode_tool_call_id.cpython-314.pyc +0 -0
  315. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  316. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  317. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  318. package/tui/acp/agent.py +886 -0
  319. package/tui/acp/api.py +55 -0
  320. package/tui/acp/encode_tool_call_id.py +12 -0
  321. package/tui/acp/messages.py +190 -0
  322. package/tui/acp/prompt.py +110 -0
  323. package/tui/acp/protocol.py +462 -0
  324. package/tui/agent.py +70 -0
  325. package/tui/agent_schema.py +76 -0
  326. package/tui/agents.py +45 -0
  327. package/tui/ansi/__init__.py +1 -0
  328. package/tui/ansi/__pycache__/__init__.cpython-314.pyc +0 -0
  329. package/tui/ansi/__pycache__/_ansi.cpython-314.pyc +0 -0
  330. package/tui/ansi/__pycache__/_ansi_colors.cpython-314.pyc +0 -0
  331. package/tui/ansi/__pycache__/_control_codes.cpython-314.pyc +0 -0
  332. package/tui/ansi/__pycache__/_keys.cpython-314.pyc +0 -0
  333. package/tui/ansi/__pycache__/_sgr_styles.cpython-314.pyc +0 -0
  334. package/tui/ansi/__pycache__/_stream_parser.cpython-314.pyc +0 -0
  335. package/tui/ansi/_ansi.py +1689 -0
  336. package/tui/ansi/_ansi_colors.py +264 -0
  337. package/tui/ansi/_control_codes.py +37 -0
  338. package/tui/ansi/_keys.py +251 -0
  339. package/tui/ansi/_sgr_styles.py +64 -0
  340. package/tui/ansi/_stream_parser.py +418 -0
  341. package/tui/answer.py +12 -0
  342. package/tui/app.py +1236 -0
  343. package/tui/atomic.py +37 -0
  344. package/tui/cli.py +313 -0
  345. package/tui/code_analyze.py +27 -0
  346. package/tui/complete.py +38 -0
  347. package/tui/constants.py +61 -0
  348. package/tui/conversation_markdown.py +30 -0
  349. package/tui/danger.py +372 -0
  350. package/tui/data/agents/ampcode.com.toml +51 -0
  351. package/tui/data/agents/augmentcode.com.toml +40 -0
  352. package/tui/data/agents/cdh.cloud-dev-harness.toml +22 -0
  353. package/tui/data/agents/claude.com.toml +41 -0
  354. package/tui/data/agents/copilot.github.com.toml +29 -0
  355. package/tui/data/agents/crowai.dev.toml +35 -0
  356. package/tui/data/agents/cursor.com.toml +38 -0
  357. package/tui/data/agents/deepagents.langchain.com +33 -0
  358. package/tui/data/agents/docker.com.toml +59 -0
  359. package/tui/data/agents/geminicli.com.toml +28 -0
  360. package/tui/data/agents/goose.ai.toml +51 -0
  361. package/tui/data/agents/inference.huggingface.co.toml +41 -0
  362. package/tui/data/agents/kimi.com.toml +35 -0
  363. package/tui/data/agents/kiro.dev.toml +42 -0
  364. package/tui/data/agents/openai.com.toml +53 -0
  365. package/tui/data/agents/openclaw.ai.toml +37 -0
  366. package/tui/data/agents/opencode.ai.toml +39 -0
  367. package/tui/data/agents/openhands.dev.toml +45 -0
  368. package/tui/data/agents/stakpak.dev.toml +61 -0
  369. package/tui/data/agents/vibe.mistral.ai.toml +30 -0
  370. package/tui/data/agents/vtcode.dev.toml +62 -0
  371. package/tui/data/images/frog.png +0 -0
  372. package/tui/data/sounds/question.wav +0 -0
  373. package/tui/data/sounds/turn-over.wav +0 -0
  374. package/tui/db.py +190 -0
  375. package/tui/dec.py +332 -0
  376. package/tui/directory.py +234 -0
  377. package/tui/directory_suggester.py +93 -0
  378. package/tui/directory_watcher.py +197 -0
  379. package/tui/format_path.py +25 -0
  380. package/tui/fuzzy.py +140 -0
  381. package/tui/fuzzy_index.py +275 -0
  382. package/tui/gist.py +33 -0
  383. package/tui/history.py +138 -0
  384. package/tui/jsonrpc.py +578 -0
  385. package/tui/menus.py +14 -0
  386. package/tui/message_log.py +69 -0
  387. package/tui/messages.py +139 -0
  388. package/tui/option_content.py +51 -0
  389. package/tui/os.py +0 -0
  390. package/tui/path_complete.py +217 -0
  391. package/tui/path_filter.py +124 -0
  392. package/tui/paths.py +71 -0
  393. package/tui/pill.py +23 -0
  394. package/tui/platform_commands.py +52 -0
  395. package/tui/prompt/__pycache__/extract.cpython-314.pyc +0 -0
  396. package/tui/prompt/__pycache__/resource.cpython-314.pyc +0 -0
  397. package/tui/prompt/extract.py +19 -0
  398. package/tui/prompt/resource.py +68 -0
  399. package/tui/protocol.py +28 -0
  400. package/tui/screens/__init__.py +0 -0
  401. package/tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  402. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  403. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  404. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  405. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  406. package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
  407. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  408. package/tui/screens/action_modal.py +94 -0
  409. package/tui/screens/agent_modal.py +175 -0
  410. package/tui/screens/command_edit_modal.py +74 -0
  411. package/tui/screens/log.py +553 -0
  412. package/tui/screens/main.py +411 -0
  413. package/tui/screens/main.tcss +655 -0
  414. package/tui/screens/permissions.py +404 -0
  415. package/tui/screens/permissions.tcss +72 -0
  416. package/tui/screens/projects.tcss +66 -0
  417. package/tui/screens/projects_app.py +319 -0
  418. package/tui/screens/projects_screen.py +142 -0
  419. package/tui/screens/session_resume_modal.py +172 -0
  420. package/tui/screens/session_resume_modal.tcss +35 -0
  421. package/tui/screens/sessions.py +166 -0
  422. package/tui/screens/sessions.tcss +119 -0
  423. package/tui/screens/settings.py +254 -0
  424. package/tui/screens/settings.tcss +101 -0
  425. package/tui/screens/store.py +500 -0
  426. package/tui/screens/store.tcss +274 -0
  427. package/tui/screens/tool_content_screen.py +59 -0
  428. package/tui/session_tracker.py +120 -0
  429. package/tui/settings.py +353 -0
  430. package/tui/settings_schema.py +431 -0
  431. package/tui/shell.py +304 -0
  432. package/tui/shell_read.py +42 -0
  433. package/tui/slash_command.py +34 -0
  434. package/tui/tui.tcss +246 -0
  435. package/tui/twitter.py +38 -0
  436. package/tui/version.py +80 -0
  437. package/tui/visuals/__init__.py +0 -0
  438. package/tui/visuals/__pycache__/__init__.cpython-314.pyc +0 -0
  439. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  440. package/tui/visuals/columns.py +273 -0
  441. package/tui/widgets/__init__.py +0 -0
  442. package/tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
  443. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  444. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  445. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  446. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  447. package/tui/widgets/__pycache__/condensed_path.cpython-314.pyc +0 -0
  448. package/tui/widgets/__pycache__/context_stats.cpython-314.pyc +0 -0
  449. package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
  450. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  451. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  452. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  453. package/tui/widgets/__pycache__/flash.cpython-314.pyc +0 -0
  454. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  455. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  456. package/tui/widgets/__pycache__/highlighted_textarea.cpython-314.pyc +0 -0
  457. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  458. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  459. package/tui/widgets/__pycache__/menu.cpython-314.pyc +0 -0
  460. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  461. package/tui/widgets/__pycache__/non_selectable_label.cpython-314.pyc +0 -0
  462. package/tui/widgets/__pycache__/note.cpython-314.pyc +0 -0
  463. package/tui/widgets/__pycache__/path_search.cpython-314.pyc +0 -0
  464. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  465. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  466. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  467. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  468. package/tui/widgets/__pycache__/prompt.cpython-314.pyc +0 -0
  469. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  470. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  471. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  472. package/tui/widgets/__pycache__/session_tabs.cpython-314.pyc +0 -0
  473. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  474. package/tui/widgets/__pycache__/shell_terminal.cpython-314.pyc +0 -0
  475. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  476. package/tui/widgets/__pycache__/slash_complete.cpython-314.pyc +0 -0
  477. package/tui/widgets/__pycache__/strike_text.cpython-314.pyc +0 -0
  478. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  479. package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
  480. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  481. package/tui/widgets/__pycache__/throbber.cpython-314.pyc +0 -0
  482. package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
  483. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  484. package/tui/widgets/__pycache__/user_input.cpython-314.pyc +0 -0
  485. package/tui/widgets/acp_content.py +70 -0
  486. package/tui/widgets/agent_response.py +77 -0
  487. package/tui/widgets/agent_thought.py +168 -0
  488. package/tui/widgets/ask_user.py +264 -0
  489. package/tui/widgets/command_pane.py +223 -0
  490. package/tui/widgets/condensed_path.py +115 -0
  491. package/tui/widgets/context_stats.py +66 -0
  492. package/tui/widgets/conversation.py +2441 -0
  493. package/tui/widgets/danger_warning.py +65 -0
  494. package/tui/widgets/diff_view.py +59 -0
  495. package/tui/widgets/directory_input.py +8 -0
  496. package/tui/widgets/flash.py +81 -0
  497. package/tui/widgets/future_text.py +126 -0
  498. package/tui/widgets/grid_select.py +234 -0
  499. package/tui/widgets/highlighted_textarea.py +180 -0
  500. package/tui/widgets/mandelbrot.py +294 -0
  501. package/tui/widgets/markdown_note.py +13 -0
  502. package/tui/widgets/menu.py +147 -0
  503. package/tui/widgets/modified_files.py +106 -0
  504. package/tui/widgets/non_selectable_label.py +5 -0
  505. package/tui/widgets/note.py +18 -0
  506. package/tui/widgets/path_search.py +492 -0
  507. package/tui/widgets/plan.py +263 -0
  508. package/tui/widgets/project_directory_tree.py +90 -0
  509. package/tui/widgets/project_grid_select.py +35 -0
  510. package/tui/widgets/project_summary.py +33 -0
  511. package/tui/widgets/prompt.py +795 -0
  512. package/tui/widgets/question.py +360 -0
  513. package/tui/widgets/session_grid_select.py +224 -0
  514. package/tui/widgets/session_summary.py +115 -0
  515. package/tui/widgets/session_tabs.py +188 -0
  516. package/tui/widgets/shell_result.py +35 -0
  517. package/tui/widgets/shell_terminal.py +40 -0
  518. package/tui/widgets/side_bar.py +114 -0
  519. package/tui/widgets/slash_complete.py +235 -0
  520. package/tui/widgets/strike_text.py +70 -0
  521. package/tui/widgets/subagent.py +87 -0
  522. package/tui/widgets/terminal.py +577 -0
  523. package/tui/widgets/terminal_tool.py +351 -0
  524. package/tui/widgets/throbber.py +100 -0
  525. package/tui/widgets/tool_call.py +368 -0
  526. package/tui/widgets/user_input.py +23 -0
  527. package/tui/widgets/version.py +5 -0
  528. package/tui/widgets/welcome.py +31 -0
@@ -0,0 +1,795 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import shlex
5
+ from typing import TYPE_CHECKING, Callable, Literal, Self
6
+
7
+ from textual import on
8
+ from textual.reactive import var, Initialize
9
+ from textual.app import ComposeResult
10
+
11
+ from textual.actions import SkipAction
12
+ from textual.binding import Binding
13
+
14
+ from textual.content import Content
15
+ from textual import getters
16
+ from textual.message import Message
17
+ from textual.widgets import OptionList, TextArea, Label
18
+ from textual import containers
19
+ from textual.widget import Widget
20
+ from textual.widgets.option_list import Option
21
+ from textual.widgets.text_area import Selection
22
+ from textual import events
23
+
24
+ from tui.app import A2TUIApp
25
+ from tui import messages
26
+ from tui.widgets.highlighted_textarea import HighlightedTextArea
27
+ from tui.widgets.condensed_path import CondensedPath
28
+ from tui.widgets.path_search import PathSearch
29
+ from tui.widgets.plan import Plan
30
+ from tui.answer import Answer
31
+ from tui.widgets.question import Ask, Question
32
+ from tui.widgets.slash_complete import SlashComplete
33
+ from tui.messages import UserInputSubmitted
34
+ from tui.slash_command import SlashCommand
35
+ from tui.prompt.extract import extract_paths_from_prompt
36
+ from tui.path_complete import PathComplete
37
+
38
+ if TYPE_CHECKING:
39
+ from tui.acp.agent import Mode
40
+
41
+
42
+ class ModeSwitcher(OptionList):
43
+ BINDING_GROUP_TITLE = "Mode switcher"
44
+ BINDINGS = [Binding("escape", "dismiss", "Dismiss mode switcher")]
45
+
46
+ @on(OptionList.OptionSelected)
47
+ def on_option_selected(self, event: OptionList.OptionSelected):
48
+ self.post_message(messages.ChangeMode(event.option_id))
49
+ self.blur()
50
+
51
+ def action_dismiss(self):
52
+ self.blur()
53
+
54
+
55
+ class InvokeFileSearch(Message):
56
+ pass
57
+
58
+
59
+ class InvokeSlashComplete(Message):
60
+ pass
61
+
62
+
63
+ class AgentInfo(Label):
64
+ pass
65
+
66
+
67
+ class ModeInfo(Label):
68
+ pass
69
+
70
+
71
+ class StatusLine(Label):
72
+ status: var[str] = var("")
73
+
74
+ def watch_status(self, status: str) -> None:
75
+ self.set_class(not bool(status), "-hidden")
76
+ self.update(status)
77
+ self.tooltip = status
78
+
79
+
80
+ class PromptContainer(containers.HorizontalGroup):
81
+ def on_mouse_down(self, event: events.MouseUp) -> None:
82
+ for child in self.query("*"):
83
+ if child.has_focus:
84
+ return
85
+ prompt_text_area = self.query_one(PromptTextArea)
86
+ if not prompt_text_area.has_focus:
87
+ prompt_text_area.focus()
88
+
89
+
90
+ class PromptTextArea(HighlightedTextArea):
91
+ HELP = """\
92
+ ## Prompt
93
+
94
+ Talk to your agent in natural language.
95
+ See on-screen instructions for details.
96
+
97
+ - Be simple
98
+ - Be direct
99
+ - Nothing fancy
100
+ """
101
+
102
+ BINDING_GROUP_TITLE = "Prompt"
103
+
104
+ BINDINGS = [
105
+ Binding(
106
+ "enter",
107
+ "submit",
108
+ "Send",
109
+ key_display="⏎",
110
+ priority=True,
111
+ tooltip="Send the prompt to the agent",
112
+ ),
113
+ Binding(
114
+ "ctrl+j,shift+enter",
115
+ "newline",
116
+ "Line",
117
+ key_display="⇧+⏎",
118
+ tooltip="Insert a new line character",
119
+ ),
120
+ Binding(
121
+ "ctrl+j,shift+enter",
122
+ "multiline_submit",
123
+ "Send",
124
+ key_display="⇧+⏎",
125
+ tooltip="Send the prompt to the agent",
126
+ ),
127
+ Binding(
128
+ "tab",
129
+ "tab_complete",
130
+ "Complete",
131
+ tooltip="Complete path (if possible)",
132
+ priority=True,
133
+ show=False,
134
+ ),
135
+ ]
136
+
137
+ app = getters.app(A2TUIApp)
138
+
139
+ auto_completes: var[list[Option]] = var(list)
140
+ multi_line = var(False, bindings=True)
141
+ shell_mode = var(False, bindings=True)
142
+ agent_ready: var[bool] = var(False)
143
+ path_complete: var[PathComplete] = var(Initialize(lambda obj: PathComplete()))
144
+ suggestions: var[list[str] | None] = var(None)
145
+ suggestions_index: var[int] = var(0)
146
+
147
+ project_path = var(Path())
148
+ working_directory = var("")
149
+
150
+ slash_commands: var[list[SlashCommand]] = var([])
151
+ slash_command_prefixes: var[tuple[str, ...]] = var(())
152
+
153
+ class Submitted(Message):
154
+ def __init__(self, markdown: str) -> None:
155
+ self.markdown = markdown
156
+ super().__init__()
157
+
158
+ class RequestShellMode(Message):
159
+ pass
160
+
161
+ class CancelShell(Message):
162
+ pass
163
+
164
+ def watch_slash_commands(self, slash_commands: list[SlashCommand]) -> None:
165
+ """A tuple of slash commands for performance reasons (used with `str.startswith`)."""
166
+ self.slash_command_prefixes = tuple(
167
+ [slash_command.command for slash_command in slash_commands]
168
+ )
169
+
170
+ def highlight_slash_command(self, text: str) -> Content:
171
+ """Override slash command highlighting."""
172
+
173
+ if text.startswith(self.slash_command_prefixes):
174
+ content = Content(text)
175
+ for slash_command in self.slash_commands:
176
+ if text.startswith(slash_command.command + " "):
177
+ content = content.stylize(
178
+ "$text-success", 0, len(slash_command.command)
179
+ )
180
+ if (
181
+ slash_command.hint
182
+ and len(text) - (len(slash_command.command) + 1) == 0
183
+ ):
184
+ content += Content.styled(
185
+ slash_command.hint, "$text-secondary 70%"
186
+ )
187
+ break
188
+ return content
189
+ return Content(text)
190
+
191
+ def highlight_shell(self, text: str) -> Content:
192
+ """Override shell highlighting with additional danger detection."""
193
+ content = super().highlight_shell(text)
194
+
195
+ if not self.app.settings.get("shell.warn_dangerous", bool):
196
+ return content
197
+
198
+ from tui import danger
199
+
200
+ spans, _danger_level = danger.detect(
201
+ str(self.project_path), self.working_directory, content.plain
202
+ )
203
+ content = content.add_spans(spans)
204
+ return content
205
+
206
+ def on_mount(self) -> None:
207
+ self.highlight_cursor_line = False
208
+ self.hide_suggestion_on_blur = False
209
+
210
+ def on_key(self, event: events.Key) -> None:
211
+ if (
212
+ not self.shell_mode
213
+ and self.cursor_location == (0, 0)
214
+ and event.character in {"!", "$"}
215
+ ):
216
+ self.post_message(self.RequestShellMode())
217
+ event.prevent_default()
218
+ elif self.shell_mode and event.key == "tab":
219
+ event.prevent_default()
220
+ elif event.key != "escape":
221
+ self.suggestions = None
222
+ self.suggestion = ""
223
+
224
+ def update_suggestion(self) -> None:
225
+ prompt = self.query_ancestor(Prompt)
226
+
227
+ if self.selection.start == self.selection.end and self.text.startswith("/"):
228
+ return
229
+
230
+ if self.shell_mode and self.cursor_at_end_of_text and "\n" not in self.text:
231
+ if prompt.complete_callback is not None:
232
+ if completes := prompt.complete_callback(self.text):
233
+ if self.text not in completes:
234
+ self.suggestion = completes[-1]
235
+
236
+ def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
237
+ if action == "newline" and self.multi_line:
238
+ return False
239
+ if action == "submit" and self.multi_line:
240
+ return False
241
+ if action == "multiline_submit":
242
+ return self.multi_line
243
+ return True
244
+
245
+ def action_multiline_submit(self) -> None:
246
+ if not self.agent_ready:
247
+ self.app.bell()
248
+ self.post_message(
249
+ messages.Flash(
250
+ "Agent is not ready. Please wait while the agent connects…",
251
+ "error",
252
+ )
253
+ )
254
+ return
255
+ self.post_message(UserInputSubmitted(self.text, self.shell_mode))
256
+ self.clear()
257
+
258
+ def action_newline(self) -> None:
259
+ self.insert("\n")
260
+
261
+ def action_submit(self) -> None:
262
+ if not self.agent_ready and not self.shell_mode:
263
+ self.app.bell()
264
+ self.post_message(
265
+ messages.Flash(
266
+ "Agent is not ready. Please wait while the agent connects…",
267
+ "error",
268
+ )
269
+ )
270
+ return
271
+ if self.suggestion:
272
+ if " " not in self.text:
273
+ self.insert(self.suggestion + " ")
274
+ else:
275
+ prompt = self.query_ancestor(Prompt)
276
+ last_token = shlex.split(self.text + self.suggestion)[-1]
277
+ last_token_path = Path(prompt.working_directory) / last_token
278
+ if last_token_path.is_dir():
279
+ self.insert(self.suggestion)
280
+ else:
281
+ self.insert(self.suggestion + " ")
282
+ self.suggestion = ""
283
+ return
284
+ self.post_message(UserInputSubmitted(self.text, self.shell_mode))
285
+ self.clear()
286
+
287
+ def action_cursor_up(self, select: bool = False):
288
+ if self.selection.is_empty and not select:
289
+ row, _column = self.selection[0]
290
+ if row == 0:
291
+ self.post_message(messages.HistoryMove(-1, self.shell_mode, self.text))
292
+ return
293
+ super().action_cursor_up(select)
294
+
295
+ def action_cursor_down(self, select: bool = False):
296
+ if self.selection.is_empty and not select:
297
+ row, _column = self.selection[0]
298
+ if row == (self.wrapped_document.height - 1):
299
+ self.post_message(messages.HistoryMove(+1, self.shell_mode, self.text))
300
+ return
301
+ super().action_cursor_down(select)
302
+
303
+ def action_delete_left(self) -> None:
304
+ selection = self.selection
305
+ if selection.start == selection.end and self.selection.end == (0, 0):
306
+ self.post_message(self.CancelShell())
307
+ return
308
+ return super().action_delete_left()
309
+
310
+ def action_cursor_line_end(self, select: bool = False) -> None:
311
+ """Move the cursor to the end of the line."""
312
+ if not self._has_cursor:
313
+ self.scroll_end()
314
+ return
315
+ location = self.get_cursor_line_end_location()
316
+ if location == self.cursor_location:
317
+ # If the cursor is already at the end, then we assume the user wants to
318
+ # scroll the conversation to the end
319
+ from tui.widgets.conversation import Conversation
320
+
321
+ self.query_ancestor(Conversation).window.anchor()
322
+ else:
323
+ self.move_cursor(location, select=select)
324
+
325
+ async def action_tab_complete(self) -> None:
326
+ if not self.shell_mode:
327
+ return
328
+
329
+ import shlex
330
+
331
+ prompt = self.query_ancestor(Prompt)
332
+
333
+ if not self.cursor_at_end_of_text:
334
+ return
335
+
336
+ _cursor_row, cursor_column = prompt.prompt_text_area.selection.end
337
+ pre_complete = self.text[:cursor_column]
338
+ post_complete = self.text[cursor_column:]
339
+ shlex_tokens = shlex.split(pre_complete)
340
+ if not shlex_tokens:
341
+ return
342
+
343
+ command = shlex_tokens[0]
344
+
345
+ exclude_node_type: Literal["file"] | Literal["dir"] | None = None
346
+ if (
347
+ command
348
+ in self.app.settings.get("shell.directory_commands", str).splitlines()
349
+ ):
350
+ exclude_node_type = "file"
351
+ elif command in self.app.settings.get("shell.file_commands", str).splitlines():
352
+ exclude_node_type = "dir"
353
+
354
+ tab_complete, suggestions = await self.path_complete(
355
+ Path(prompt.working_directory),
356
+ shlex_tokens[-1],
357
+ exclude_type=exclude_node_type,
358
+ )
359
+
360
+ if tab_complete is not None:
361
+ shlex_tokens = shlex_tokens[:-1] + [shlex_tokens[-1] + tab_complete]
362
+ path_component = Path(prompt.working_directory) / shlex_tokens[-1]
363
+ if path_component.is_file():
364
+ spaces = " "
365
+ else:
366
+ spaces = ""
367
+
368
+ self.clear()
369
+ self.insert(
370
+ " ".join(token.replace(" ", "\\ ") for token in shlex_tokens)
371
+ + post_complete
372
+ + spaces
373
+ )
374
+ self.suggestions = None
375
+ else:
376
+ if suggestions != self.suggestions:
377
+ self.suggestions = suggestions or None
378
+ self.suggestions_index = 0
379
+ if suggestions:
380
+ self.suggestion = suggestions[0]
381
+ elif self.suggestions:
382
+ self.suggestions_index = (self.suggestions_index + 1) % len(
383
+ self.suggestions
384
+ )
385
+ self.suggestion = self.suggestions[self.suggestions_index]
386
+
387
+ async def watch_selection(
388
+ self, previous_selection: Selection, selection: Selection
389
+ ) -> None:
390
+ if previous_selection == selection:
391
+ return
392
+ if selection.start == selection.end:
393
+ previous_y, previous_x = previous_selection.end
394
+ y, x = selection.end
395
+ if y == previous_y:
396
+ direction = -1 if x < previous_x else +1
397
+ else:
398
+ direction = 0
399
+ line = self.document.get_line(y)
400
+
401
+ if (
402
+ not self.shell_mode
403
+ and y == 0
404
+ and x == 1
405
+ and direction == +1
406
+ and line
407
+ and line[0] == "/"
408
+ ):
409
+ self.post_message(InvokeSlashComplete())
410
+ return
411
+
412
+ if y == 0 and line and line[0] == "/" and direction == -1:
413
+ if line in self.slash_command_prefixes:
414
+ self.selection = Selection((0, 0), (0, len(line)))
415
+ return
416
+
417
+ for _path, start, end in extract_paths_from_prompt(line):
418
+ if x > start and x < end:
419
+ self.selection = Selection((y, start), (y, end))
420
+ break
421
+ if direction == -1 and x == end:
422
+ self.selection = Selection((y, start), (y, end))
423
+ break
424
+
425
+ if x > 0 and x <= len(line) and line[x - 1] == "@":
426
+ remaining_line = line[x + 1 :]
427
+ if not remaining_line or remaining_line[0].isspace():
428
+ self.post_message(InvokeFileSearch())
429
+
430
+
431
+ class Prompt(containers.VerticalGroup):
432
+
433
+ BINDINGS = [
434
+ Binding("escape", "dismiss", "Dismiss", show=False),
435
+ ]
436
+
437
+ PROMPT_NULL = " "
438
+ PROMPT_SHELL = Content.styled("$", "$text-primary")
439
+ PROMPT_AI = Content.styled("❯", "$text-secondary")
440
+ PROMPT_MULTILINE = Content.styled("☰", "$text-secondary")
441
+
442
+ prompt_container = getters.query_one("#prompt-container", Widget)
443
+ prompt_text_area = getters.query_one(PromptTextArea)
444
+ prompt_label = getters.query_one("#prompt", Label)
445
+ current_directory = getters.query_one(CondensedPath)
446
+ path_search = getters.query_one(PathSearch)
447
+ slash_complete = getters.query_one(SlashComplete)
448
+ question = getters.query_one(Question)
449
+ mode_switcher = getters.query_one(ModeSwitcher)
450
+
451
+ slash_commands: var[list[SlashCommand]] = var(list)
452
+ shell_mode = var(False)
453
+ multi_line = var(False)
454
+ show_path_search = var(False, toggle_class="-show-path-search", bindings=True)
455
+ show_slash_complete = var(False, toggle_class="-show-slash-complete", bindings=True)
456
+ project_path = var(Path, init=False)
457
+ working_directory = var("")
458
+ agent_info = var(Content(""))
459
+ _ask: var[Ask | None] = var(None)
460
+ plan: var[list[Plan.Entry]]
461
+ agent_ready: var[bool] = var(False)
462
+ current_mode: var[Mode | None] = var(None)
463
+ modes: var[dict[str, Mode] | None] = var(None)
464
+ status: var[str] = var("")
465
+
466
+ app = getters.app(A2TUIApp)
467
+
468
+ def __init__(
469
+ self,
470
+ *,
471
+ name: str | None = None,
472
+ id: str | None = None,
473
+ classes: str | None = None,
474
+ disabled: bool = False,
475
+ complete_callback: Callable[[str], list[str]] | None = None,
476
+ ):
477
+ super().__init__(name=name, id=id, classes=classes, disabled=disabled)
478
+ self.ask_queue: list[Ask] = []
479
+ self.complete_callback = complete_callback
480
+
481
+ @property
482
+ def text(self) -> str:
483
+ return self.prompt_text_area.text
484
+
485
+ @text.setter
486
+ def text(self, text: str) -> None:
487
+ self.prompt_text_area.text = text
488
+ self.prompt_text_area.selection = Selection.cursor(
489
+ self.prompt_text_area.get_cursor_line_end_location()
490
+ )
491
+
492
+ def watch_current_mode(self, mode: Mode | None) -> None:
493
+ self.set_class(mode is not None, "-has-mode")
494
+ if mode is not None:
495
+ tooltip = Content.from_markup(
496
+ "[b]$description[/]\n\n[dim](click to open mode switcher)",
497
+ description=mode.description,
498
+ )
499
+ self.query_one(ModeInfo).with_tooltip(tooltip).update(mode.name)
500
+ self.watch_modes(self.modes)
501
+
502
+ async def watch_project_path(self, old_path: Path, new_path: Path) -> None:
503
+ """Initial refresh of paths."""
504
+ if old_path != new_path:
505
+ self.call_later(self.path_search.refresh_paths)
506
+
507
+ def ask(self, ask: Ask) -> None:
508
+ """Replace the textarea prompt with a menu of options.
509
+
510
+ Args:
511
+ ask: An `Ask` instance which contains a question and responses.
512
+ """
513
+ self.ask_queue.append(ask)
514
+ self.app.terminal_alert()
515
+ if self._ask is None:
516
+ self._ask = self.ask_queue.pop(0)
517
+
518
+ @on(events.Click, "ModeInfo")
519
+ def on_click(self):
520
+ self.mode_switcher.focus()
521
+
522
+ def watch_modes(self, modes: dict[str, Mode] | None) -> None:
523
+ from tui.visuals.columns import Columns
524
+
525
+ columns = Columns("auto", "auto", "flex")
526
+ if modes is not None:
527
+ mode_list = sorted(modes.values(), key=lambda mode: mode.name.lower())
528
+ for mode in mode_list:
529
+ columns.add_row(
530
+ (
531
+ Content.styled("✔", "$text-success")
532
+ if self.current_mode and mode.id == self.current_mode.id
533
+ else ""
534
+ ),
535
+ Content.from_markup("[bold]$mode[/]", mode=mode.name),
536
+ Content.styled(mode.description or "", "dim"),
537
+ )
538
+ else:
539
+ mode_list = []
540
+
541
+ self.mode_switcher.set_options(
542
+ [Option(row, id=mode.id) for row, mode in zip(columns, mode_list)]
543
+ )
544
+ if self.current_mode is not None:
545
+ self.mode_switcher.highlighted = self.mode_switcher.get_option_index(
546
+ self.current_mode.id
547
+ )
548
+
549
+ def watch_agent_ready(self, ready: bool) -> None:
550
+ self.set_class(not ready, "-not-ready")
551
+ if ready:
552
+ self.query_one(AgentInfo).update(self.agent_info)
553
+
554
+ def watch_agent_info(self, agent_info: Content) -> None:
555
+ if self.agent_ready:
556
+ self.query_one(AgentInfo).update(agent_info)
557
+ else:
558
+ self.query_one(AgentInfo).update("Initializing…")
559
+
560
+ def watch_multiline(self) -> None:
561
+ self.update_prompt()
562
+
563
+ def watch_shell_mode(self) -> None:
564
+ self.update_prompt()
565
+
566
+ def watch_working_directory(self, working_directory: str) -> None:
567
+ if not working_directory:
568
+ return
569
+ out_of_bounds = not Path(working_directory).is_relative_to(self.project_path)
570
+ if out_of_bounds and not self.has_class("-working-directory-out-of-bounds"):
571
+ self.post_message(
572
+ messages.Flash(
573
+ "You have navigated away from the project directory",
574
+ style="error",
575
+ duration=5,
576
+ )
577
+ )
578
+ self.set_class(
579
+ out_of_bounds,
580
+ "-working-directory-out-of-bounds",
581
+ )
582
+
583
+ def watch__ask(self, ask: Ask | None) -> None:
584
+ self.set_class(ask is not None, "-mode-ask")
585
+ if ask is None:
586
+ self.prompt_text_area.focus()
587
+ else:
588
+ self.question.update(ask)
589
+ self.question.focus()
590
+
591
+ def update_prompt(self):
592
+ """Update the prompt according to the current mode."""
593
+ if self.shell_mode:
594
+ self.prompt_label.update(self.PROMPT_SHELL, layout=False)
595
+ self.add_class("-shell-mode")
596
+ self.prompt_text_area.placeholder = Content.from_markup(
597
+ "Enter shell command\t[r]▌esc▐[/r] prompt mode"
598
+ ).expand_tabs(8)
599
+ self.prompt_text_area.highlight_language = "shell"
600
+ else:
601
+ self.prompt_label.update(
602
+ self.PROMPT_MULTILINE if self.multi_line else self.PROMPT_AI,
603
+ layout=False,
604
+ )
605
+ self.remove_class("-shell-mode")
606
+
607
+ prompt_message = self.app.settings.get("ui.prompt_message", str)
608
+ self.prompt_text_area.placeholder = Content.assemble(
609
+ f"{prompt_message}\t".expandtabs(8),
610
+ ("▌!▐", "r"),
611
+ " shell ",
612
+ ("▌/▐", "r"),
613
+ " commands ",
614
+ ("▌@▐", "r"),
615
+ " files",
616
+ )
617
+ self.prompt_text_area.highlight_language = "markdown"
618
+
619
+ @property
620
+ def likely_shell(self) -> bool:
621
+ text = self.prompt_text_area.text
622
+ if "\n" in text or " " in text or not text.strip():
623
+ return False
624
+
625
+ shell_commands = {
626
+ command.strip()
627
+ for command in self.app.settings.get(
628
+ "shell.allow_commands", expect_type=str
629
+ ).split()
630
+ }
631
+ if text.split(" ", 1)[0] in shell_commands:
632
+ return True
633
+ return False
634
+
635
+ @property
636
+ def is_shell_mode(self) -> bool:
637
+ return self.shell_mode or self.likely_shell
638
+
639
+ def focus(self, scroll_visible: bool = True) -> Self:
640
+ if self._ask is not None:
641
+ self.question.focus()
642
+ else:
643
+ self.query(HighlightedTextArea).focus()
644
+ return self
645
+
646
+ def append(self, text: str) -> None:
647
+ self.query_one(HighlightedTextArea).insert(
648
+ text, maintain_selection_offset=False
649
+ )
650
+
651
+ def watch_show_path_search(self, show: bool) -> None:
652
+ self.prompt_text_area.suggestion = ""
653
+
654
+ def watch_show_slash_complete(self, show: bool) -> None:
655
+ if show:
656
+ self.slash_complete.focus()
657
+
658
+ def project_directory_updated(self) -> None:
659
+ """Called when there is may be new files"""
660
+ self.path_search.refresh_paths()
661
+
662
+ @on(PromptTextArea.RequestShellMode)
663
+ def on_request_shell_mode(self, event: PromptTextArea.RequestShellMode):
664
+ self.shell_mode = True
665
+ self.update_prompt()
666
+
667
+ @on(TextArea.Changed)
668
+ def on_text_area_changed(self, event: TextArea.Changed) -> None:
669
+ text = event.text_area.text
670
+
671
+ self.multi_line = "```" in text
672
+
673
+ if not self.multi_line and self.likely_shell:
674
+ self.shell_mode = True
675
+
676
+ self.update_prompt()
677
+
678
+ @on(PromptTextArea.CancelShell)
679
+ def on_cancel_shell(self, event: PromptTextArea.CancelShell):
680
+ self.shell_mode = False
681
+
682
+ @on(InvokeFileSearch)
683
+ def on_invoke_file_search(self, event: InvokeFileSearch) -> None:
684
+ event.stop()
685
+ if not self.shell_mode:
686
+ self.show_path_search = True
687
+ self.path_search.reset()
688
+
689
+ @on(InvokeSlashComplete)
690
+ def on_invoke_slash_complete(self, event: InvokeSlashComplete) -> None:
691
+ event.stop()
692
+ self.show_slash_complete = True
693
+
694
+ @on(messages.PromptSuggestion)
695
+ def on_prompt_suggestion(self, event: messages.PromptSuggestion) -> None:
696
+ event.stop()
697
+ self.prompt_text_area.suggestion = event.suggestion
698
+
699
+ @on(SlashComplete.Completed)
700
+ def on_slash_complete_completed(self, event: SlashComplete.Completed) -> None:
701
+ self.prompt_text_area.clear()
702
+ self.prompt_text_area.insert(f"{event.command} ")
703
+ self.prompt_text_area.suggestion = ""
704
+ self.focus()
705
+
706
+ @on(messages.Dismiss)
707
+ def on_dismiss(self, event: messages.Dismiss) -> None:
708
+ event.stop()
709
+ if event.widget is self.slash_complete and self.show_slash_complete:
710
+ self.show_slash_complete = False
711
+ self.prompt_text_area.suggestion = ""
712
+ self.focus()
713
+ elif event.widget is self.path_search and self.show_path_search:
714
+ self.show_path_search = False
715
+ self.focus()
716
+
717
+ @on(messages.InsertPath)
718
+ def on_insert_path(self, event: messages.InsertPath) -> None:
719
+ event.stop()
720
+ if " " in event.path:
721
+ path = f'"{event.path}"'
722
+ else:
723
+ path = event.path
724
+ if (
725
+ self.prompt_text_area.get_text_range(*self.prompt_text_area.selection)
726
+ != " "
727
+ ):
728
+ path += " "
729
+ self.prompt_text_area.insert(path)
730
+
731
+ @on(Question.Answer)
732
+ def on_question_answer(self, event: Question.Answer) -> None:
733
+ """Question has been answered."""
734
+ event.stop()
735
+
736
+ def remove_question() -> None:
737
+ """Remove the question and restore the text prompt."""
738
+ if self.ask_queue:
739
+ self._ask = self.ask_queue.pop(0)
740
+ else:
741
+ self._ask = None
742
+ self.app.terminal_alert(False)
743
+
744
+ if self._ask is not None and (callback := self._ask.callback) is not None:
745
+ callback(event.answer)
746
+
747
+ self.set_timer(0.3, remove_question)
748
+
749
+ def suggest(self, suggestion: str) -> None:
750
+ if suggestion.startswith(self.text) and self.text != suggestion:
751
+ self.prompt_text_area.suggestion = suggestion[len(self.text) :]
752
+
753
+ def compose(self) -> ComposeResult:
754
+ yield PathSearch(self.project_path).data_bind(root=Prompt.project_path)
755
+ yield SlashComplete().data_bind(slash_commands=Prompt.slash_commands)
756
+ with PromptContainer(id="prompt-container"):
757
+ yield Question()
758
+ with containers.HorizontalGroup(id="text-prompt"):
759
+ yield Label(self.PROMPT_AI, id="prompt", markup=False)
760
+ yield PromptTextArea().data_bind(
761
+ multi_line=Prompt.multi_line,
762
+ shell_mode=Prompt.shell_mode,
763
+ agent_ready=Prompt.agent_ready,
764
+ project_path=Prompt.project_path,
765
+ working_directory=Prompt.working_directory,
766
+ slash_commands=Prompt.slash_commands,
767
+ )
768
+ with containers.HorizontalGroup(id="info-container"):
769
+ yield AgentInfo()
770
+ yield CondensedPath().data_bind(path=Prompt.working_directory)
771
+ yield StatusLine(markup=False).data_bind(status=Prompt.status)
772
+ yield ModeSwitcher()
773
+ yield ModeInfo("mode")
774
+
775
+ def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
776
+ return True
777
+
778
+ def action_dismiss(self) -> None:
779
+ if self._ask is not None:
780
+ ask = self._ask
781
+ if (callback := ask.callback) is not None:
782
+ callback(Answer("Cancelled", "reject_once", "reject_once"))
783
+ self.ask_queue.clear()
784
+ self._ask = None
785
+ self.app.terminal_alert(False)
786
+ return
787
+ if self.prompt_text_area.suggestion:
788
+ self.prompt_text_area.suggestion = ""
789
+ return
790
+ if self.shell_mode:
791
+ self.shell_mode = False
792
+ elif self.show_slash_complete:
793
+ self.show_slash_complete = False
794
+ else:
795
+ raise SkipAction()