@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,319 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from time import monotonic
5
+
6
+ from textual.app import App, ComposeResult
7
+ from textual.containers import Horizontal, Vertical
8
+ from textual.reactive import var
9
+ from textual.widget import Widget
10
+ from textual.widgets import Button, Label, Static
11
+
12
+ from onecode.config import CLOUD_DEV_HARNESS_DIR, load_config, save_config
13
+ from onecode.agent.cdh_loader import CdhProjectLoader
14
+ from onecode.config_screen import EditFieldScreen
15
+
16
+ import yaml
17
+
18
+ CONFIRM_TIMEOUT = 5.0
19
+
20
+
21
+ class ProjectItem(Static, can_focus=True):
22
+ """A single project row in the list."""
23
+
24
+ def __init__(self, name: str, path: str) -> None:
25
+ super().__init__()
26
+ self._name = name
27
+ self._path = path
28
+
29
+ def render(self) -> str:
30
+ return f" {self._name:<20} {self._path}"
31
+
32
+
33
+ class ProjectsApp(App):
34
+ """Standalone project management TUI."""
35
+
36
+ CSS = """
37
+ Screen {
38
+ align: center middle;
39
+ }
40
+ ProjectsApp {
41
+ background: #000;
42
+ }
43
+ #dialog {
44
+ width: 64;
45
+ height: 20;
46
+ background: #000;
47
+ border: solid #555;
48
+ }
49
+ #header {
50
+ height: 2;
51
+ background: #333;
52
+ content-align: center middle;
53
+ color: #fff;
54
+ text-style: bold;
55
+ }
56
+ #content {
57
+ width: 100%;
58
+ height: 1fr;
59
+ background: #000;
60
+ }
61
+ ProjectItem {
62
+ width: 100%;
63
+ height: 1;
64
+ padding: 0 1;
65
+ color: #fff;
66
+ background: #000;
67
+ }
68
+ ProjectItem:hover, ProjectItem.-focus {
69
+ background: #444;
70
+ }
71
+ #shortcuts {
72
+ height: 1;
73
+ background: #222;
74
+ color: #888;
75
+ padding: 0 1;
76
+ content-align: left middle;
77
+ }
78
+ #button-row {
79
+ height: 3;
80
+ background: #333;
81
+ align: center middle;
82
+ }
83
+ Button {
84
+ margin: 0 1;
85
+ background: #444;
86
+ color: #fff;
87
+ }
88
+ Button:hover {
89
+ background: #666;
90
+ }
91
+ Button:focus {
92
+ background: #555;
93
+ }
94
+ #empty-label {
95
+ width: 100%;
96
+ height: 100%;
97
+ content-align: center middle;
98
+ color: #666;
99
+ }
100
+ """
101
+
102
+ BINDINGS = [
103
+ ("up", "cursor_up", "Up"),
104
+ ("down", "cursor_down", "Down"),
105
+ ("enter", "confirm", "Select"),
106
+ ("n", "new_project", "New"),
107
+ ("i", "init_project", "Init"),
108
+ ("d", "delete_project", "Delete"),
109
+ ("ctrl+q", "quit", "Quit"),
110
+ ("escape", "dismiss_modal", "Cancel"),
111
+ ]
112
+
113
+ cursor: var[int] = var(0)
114
+ _delete_confirm_time: float = 0.0
115
+
116
+ def __init__(self) -> None:
117
+ super().__init__()
118
+ self.projects: list[tuple[str, str]] = []
119
+
120
+ def compose(self) -> ComposeResult:
121
+ with Vertical(id="dialog"):
122
+ yield Static("Project Management", id="header")
123
+ with Widget(id="content"):
124
+ pass
125
+ yield Label("↑↓ Select ↵ Load n New i Init d Delete Ctrl+Q Quit", id="shortcuts")
126
+ with Horizontal(id="button-row"):
127
+ yield Button("Load (↵)", id="load")
128
+ yield Button("New (n)", id="new", variant="primary")
129
+ yield Button("Init .cdh (i)", id="init")
130
+ yield Button("Delete (d)", id="delete")
131
+
132
+ def _rebuild_items(self) -> None:
133
+ content = self.query_one("#content")
134
+ content.remove_children()
135
+ if not self.projects:
136
+ content.mount(Static("No projects found", id="empty-label"))
137
+ return
138
+ for name, path in self.projects:
139
+ content.mount(ProjectItem(name, path))
140
+ self.cursor = 0
141
+
142
+ def _clamp_cursor(self) -> None:
143
+ if not self.projects:
144
+ return
145
+ self.cursor = max(0, min(self.cursor, len(self.projects) - 1))
146
+
147
+ def watch_cursor(self, old: int, new: int) -> None:
148
+ if not self.projects:
149
+ return
150
+ items = list(self.query(ProjectItem))
151
+ for i, item in enumerate(items):
152
+ item.set_class(i == new, "-focus")
153
+
154
+ @property
155
+ def _current_item(self) -> ProjectItem | None:
156
+ items = list(self.query(ProjectItem))
157
+ if 0 <= self.cursor < len(items):
158
+ return items[self.cursor]
159
+ return None
160
+
161
+ def on_mount(self) -> None:
162
+ self._refresh()
163
+
164
+ def _refresh(self) -> None:
165
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
166
+ projects_dir.mkdir(parents=True, exist_ok=True)
167
+ pf_list = sorted(
168
+ list(projects_dir.glob("*.yaml")) + list(projects_dir.glob("*.json"))
169
+ )
170
+ self.projects = []
171
+ for pf in pf_list:
172
+ try:
173
+ data = yaml.safe_load(pf.read_text()) or {}
174
+ p = data.get("path", ".")
175
+ except Exception:
176
+ p = "."
177
+ self.projects.append((pf.stem, p))
178
+ self._rebuild_items()
179
+
180
+ def action_cursor_up(self) -> None:
181
+ self.cursor -= 1
182
+ self._clamp_cursor()
183
+
184
+ def action_cursor_down(self) -> None:
185
+ self.cursor += 1
186
+ self._clamp_cursor()
187
+
188
+ def action_confirm(self) -> None:
189
+ self._load_project()
190
+
191
+ def action_new_project(self) -> None:
192
+ self._new_project()
193
+
194
+ def action_init_project(self) -> None:
195
+ self._init_project()
196
+
197
+ def action_delete_project(self) -> None:
198
+ self._delete_project()
199
+
200
+ def action_quit(self) -> None:
201
+ self.exit()
202
+
203
+ def action_dismiss_modal(self) -> None:
204
+ pass
205
+
206
+ def on_button_pressed(self, event: Button.Pressed) -> None:
207
+ btn_id = event.button.id
208
+ if btn_id == "new":
209
+ self._new_project()
210
+ elif btn_id == "load":
211
+ self._load_project()
212
+ elif btn_id == "init":
213
+ self._init_project()
214
+ elif btn_id == "delete":
215
+ self._delete_project()
216
+
217
+ def _new_project(self) -> None:
218
+ self.push_screen(
219
+ EditFieldScreen("Project path", str(Path.cwd().resolve())),
220
+ self._on_new_project_path,
221
+ )
222
+
223
+ def _on_new_project_path(self, path_str: str | None) -> None:
224
+ try:
225
+ ws = Path(path_str).expanduser().resolve() if path_str else Path.cwd().resolve()
226
+ except Exception:
227
+ ws = Path.cwd().resolve()
228
+ name = ws.name
229
+ from cdh.scaffold import scaffold_dlc_project
230
+ scaffold_dlc_project(ws, name)
231
+ CdhProjectLoader.init_project(ws, name)
232
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
233
+ projects_dir.mkdir(parents=True, exist_ok=True)
234
+ proj_data = {"name": name, "path": str(ws), "description": ""}
235
+ (projects_dir / f"{name}.yaml").write_text(yaml.dump(proj_data))
236
+ cfg = load_config()
237
+ cfg.current_project = name
238
+ cfg.current_project_path = str(ws)
239
+ save_config(cfg)
240
+ self.notify(f"Created project '{name}' at {ws}")
241
+ self._refresh()
242
+
243
+ def _init_project(self) -> None:
244
+ self.push_screen(
245
+ EditFieldScreen("Directory to initialize .cdh", str(Path.cwd().resolve())),
246
+ self._on_init_project_path,
247
+ )
248
+
249
+ def _on_init_project_path(self, path_str: str | None) -> None:
250
+ if not path_str:
251
+ return
252
+ try:
253
+ target = Path(path_str).expanduser().resolve()
254
+ if not target.is_dir():
255
+ self.notify(f"Not a directory: {target}", severity="error")
256
+ return
257
+ except Exception:
258
+ self.notify("Invalid path", severity="error")
259
+ return
260
+ existing = CdhProjectLoader.find_cdh_dir(target)
261
+ if existing is not None:
262
+ self.notify(f".cdh already exists at {existing}", severity="warning")
263
+ return
264
+ name = target.name
265
+ from cdh.scaffold import init_dlc_project
266
+ init_dlc_project(target, name)
267
+ CdhProjectLoader.init_project(target, name)
268
+ self.notify(f"Initialized .cdh in {target}")
269
+
270
+ def _load_project(self) -> None:
271
+ item = self._current_item
272
+ if item is None:
273
+ self.notify("No project selected", severity="warning")
274
+ return
275
+ name = item._name
276
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
277
+ for ext in ["yaml", "yml", "json"]:
278
+ pf = projects_dir / f"{name}.{ext}"
279
+ if pf.exists():
280
+ proj_data = yaml.safe_load(pf.read_text()) if ext in ["yaml", "yml"] else __import__("json").loads(pf.read_text())
281
+ project_path = proj_data.get("path", ".")
282
+ cfg = load_config()
283
+ cfg.current_project = name
284
+ cfg.current_project_path = project_path
285
+ save_config(cfg)
286
+ self.notify(f"Loaded project '{name}' ({project_path})")
287
+ self.exit("loaded")
288
+ return
289
+ self.notify(f"Project '{name}' not found", severity="error")
290
+
291
+ def _delete_project(self) -> None:
292
+ now = monotonic()
293
+ if now - self._delete_confirm_time > CONFIRM_TIMEOUT:
294
+ self._delete_confirm_time = now
295
+ self.notify(
296
+ "Press [b]d[/b] again to confirm deletion",
297
+ title="Delete project",
298
+ timeout=CONFIRM_TIMEOUT,
299
+ )
300
+ return
301
+ self._delete_confirm_time = 0.0
302
+ item = self._current_item
303
+ if item is None:
304
+ self.notify("No project selected", severity="warning")
305
+ return
306
+ name = item._name
307
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
308
+ for ext in ["yaml", "yml", "json"]:
309
+ pf = projects_dir / f"{name}.{ext}"
310
+ if pf.exists():
311
+ pf.unlink()
312
+ self.notify(f"Deleted project '{name}'")
313
+ self._refresh()
314
+ return
315
+ self.notify(f"Project '{name}' not found", severity="error")
316
+
317
+
318
+ def main():
319
+ return ProjectsApp().run()
@@ -0,0 +1,142 @@
1
+ from time import monotonic
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.binding import Binding
5
+ from textual.events import ScreenResume
6
+ from textual.screen import ModalScreen
7
+ from textual import getters
8
+ from textual.widget import Widget
9
+ from textual import widgets
10
+ from textual import containers
11
+ from textual import on
12
+
13
+
14
+ from onecode.config import CLOUD_DEV_HARNESS_DIR
15
+
16
+ from tui.app import A2TUIApp
17
+ from tui.widgets.grid_select import GridSelect
18
+ from tui.widgets.project_grid_select import ProjectGridSelect
19
+ from tui.widgets.project_summary import ProjectSummary
20
+
21
+
22
+ CONFIRM_TIMEOUT = 5.0
23
+ INSTRUCTIONS_NO_PROJECTS = "Your projects will be shown here."
24
+
25
+
26
+ class ProjectsScreen(ModalScreen[str]):
27
+ CSS_PATH = "projects.tcss"
28
+ BINDINGS = [
29
+ Binding("escape", "dismiss", "Dismiss"),
30
+ Binding("d", "delete_project", "Delete"),
31
+ Binding("n", "new_project", "New"),
32
+ Binding("i", "init_project", "Init"),
33
+ ]
34
+
35
+ app: getters.app[A2TUIApp] = getters.app(A2TUIApp)
36
+ project_grid_select = getters.query_one(ProjectGridSelect)
37
+ _delete_confirm_time: float = 0.0
38
+
39
+ def compose(self) -> ComposeResult:
40
+ with containers.Center(id="title-container"):
41
+ yield widgets.Label("Projects")
42
+ yield widgets.Static(INSTRUCTIONS_NO_PROJECTS, classes="instructions")
43
+ yield ProjectGridSelect()
44
+ with containers.Center():
45
+ yield widgets.Button("+ New Project", id="new-project", variant="primary")
46
+ yield widgets.Button("Init .cdh (i)", id="init-project")
47
+ yield widgets.Footer()
48
+
49
+ @property
50
+ def focus_chain(self) -> list[Widget]:
51
+ return [self.project_grid_select]
52
+
53
+ def action_delete_project(self) -> None:
54
+ now = monotonic()
55
+ if now - self._delete_confirm_time > CONFIRM_TIMEOUT:
56
+ self._delete_confirm_time = now
57
+ self.notify(
58
+ "Press [b]d[/b] again to confirm deletion",
59
+ title="Delete project",
60
+ timeout=CONFIRM_TIMEOUT,
61
+ )
62
+ return
63
+
64
+ self._delete_confirm_time = 0.0
65
+ highlighted = self.project_grid_select.highlighted
66
+ if highlighted is None:
67
+ return
68
+ try:
69
+ widget = self.project_grid_select.children[highlighted]
70
+ except IndexError:
71
+ return
72
+ if not isinstance(widget, ProjectSummary):
73
+ return
74
+ project_name = widget.id
75
+ if project_name is None:
76
+ return
77
+ projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
78
+ deleted = False
79
+ for ext in ["yaml", "yml", "json"]:
80
+ pf = projects_dir / f"{project_name}.{ext}"
81
+ if pf.exists():
82
+ pf.unlink()
83
+ deleted = True
84
+ break
85
+ if not deleted:
86
+ self.notify(f"Project file not found", severity="error")
87
+ return
88
+ widget.remove()
89
+ self.notify(f"Deleted project: {project_name}")
90
+
91
+ def action_new_project(self) -> None:
92
+ self.dismiss("__new__")
93
+
94
+ def action_init_project(self) -> None:
95
+ from pathlib import Path
96
+ from onecode.config_screen import EditFieldScreen
97
+ from onecode.agent.cdh_loader import CdhProjectLoader
98
+
99
+ default_path = str(Path.cwd().resolve())
100
+ self.app.push_screen(
101
+ EditFieldScreen("Directory to initialize .cdh", default_path),
102
+ self._on_init_path,
103
+ )
104
+
105
+ def _on_init_path(self, path_str: str | None) -> None:
106
+ from pathlib import Path
107
+ from onecode.agent.cdh_loader import CdhProjectLoader
108
+
109
+ if not path_str:
110
+ return
111
+ try:
112
+ target = Path(path_str).expanduser().resolve()
113
+ if not target.is_dir():
114
+ self.notify(f"Not a directory: {target}", severity="error")
115
+ return
116
+ except Exception:
117
+ self.notify("Invalid path", severity="error")
118
+ return
119
+ existing = CdhProjectLoader.find_cdh_dir(target)
120
+ if existing is not None:
121
+ self.notify(f".cdh already exists at {existing}", severity="warning")
122
+ return
123
+ name = target.name
124
+ from cdh.scaffold import scaffold_dlc_project
125
+ scaffold_dlc_project(target, name)
126
+ CdhProjectLoader.init_project(target, name)
127
+ self.notify(f"Initialized .cdh in {target}")
128
+
129
+ @on(GridSelect.Selected)
130
+ def on_selected(self, event: GridSelect.Selected) -> None:
131
+ if (
132
+ isinstance(event.widget, ProjectSummary)
133
+ ):
134
+ self.dismiss(event.widget.id)
135
+
136
+ @on(widgets.Button.Pressed, "#new-project")
137
+ def on_new_project(self) -> None:
138
+ self.dismiss("__new__")
139
+
140
+ @on(widgets.Button.Pressed, "#init-project")
141
+ def on_init_project(self) -> None:
142
+ self.action_init_project()
@@ -0,0 +1,172 @@
1
+ from datetime import datetime, timezone
2
+ import json
3
+
4
+ from textual import on
5
+ from textual.app import ComposeResult
6
+ from textual import getters
7
+ from textual import widgets
8
+ from textual import containers
9
+ from textual.screen import ModalScreen
10
+
11
+ from tui.db import DB, Session
12
+
13
+ HELP = """\
14
+ # Session Resume
15
+
16
+ Pick a session to resume.
17
+ """
18
+
19
+
20
+ class SessionResumeModal(ModalScreen[Session]):
21
+ """Dialog to select a session to resume."""
22
+
23
+ CSS_PATH = "session_resume_modal.tcss"
24
+
25
+ BINDINGS = [("escape", "dismiss", "Dismiss")]
26
+
27
+ session_table = getters.query_one("#sessions", widgets.DataTable)
28
+
29
+ def compose(self) -> ComposeResult:
30
+ with containers.VerticalGroup(id="container"):
31
+ yield widgets.Markdown(HELP)
32
+ yield widgets.Static(
33
+ "⚠ Not all ACP agents currently support resume",
34
+ classes="warning",
35
+ )
36
+ with containers.Center(id="table-container"):
37
+ yield widgets.DataTable(id="sessions", cursor_type="row")
38
+ with containers.HorizontalGroup(id="buttons"):
39
+ yield widgets.Button(
40
+ "Resume", id="resume", variant="primary", disabled=True
41
+ )
42
+ yield widgets.Button("Cancel", id="cancel")
43
+
44
+ @classmethod
45
+ def friendly_time_ago(cls, iso_timestamp: str) -> str:
46
+ """
47
+ Convert ISO timestamp to friendly time description.
48
+
49
+ Args:
50
+ iso_timestamp: ISO format timestamp string (e.g., '2024-01-30T15:30:00+00:00')
51
+
52
+ Returns:
53
+ - "just now" if < 1 minute ago
54
+ - "X minute(s) ago" if < 1 hour ago
55
+ - "X hour(s) ago" if < 24 hours ago
56
+ - Local datetime string if >= 24 hours ago (format: 'YYYY-MM-DD HH:MM AM/PM')
57
+
58
+ Examples:
59
+ >>> friendly_time_ago('2024-01-30T15:30:00+00:00') # 30 seconds ago
60
+ 'just now'
61
+ >>> friendly_time_ago('2024-01-30T15:00:00+00:00') # 5 minutes ago
62
+ '5 minutes ago'
63
+ >>> friendly_time_ago('2024-01-30T13:00:00+00:00') # 3 hours ago
64
+ '3 hours ago'
65
+ >>> friendly_time_ago('2024-01-28T15:30:00+00:00') # 2 days ago
66
+ '2024-01-28 10:30 AM' # (in local time)
67
+ """
68
+ # Parse the timestamp
69
+ past_dt = datetime.fromisoformat(iso_timestamp)
70
+
71
+ # Get current time in appropriate timezone
72
+ if past_dt.tzinfo is not None:
73
+ # Timezone-aware: use UTC for comparison
74
+ now = datetime.now(timezone.utc)
75
+ else:
76
+ # Naive datetime: use naive now
77
+ now = datetime.now()
78
+
79
+ # Calculate time difference
80
+ diff = now - past_dt
81
+ total_seconds = diff.total_seconds()
82
+
83
+ # Less than 1 minute
84
+ if total_seconds < 60:
85
+ return "just now"
86
+
87
+ # Less than 1 hour (3600 seconds)
88
+ if total_seconds < 3600:
89
+ minutes = int(total_seconds // 60)
90
+ return f"{minutes} minute{'s' if minutes != 1 else ''} ago"
91
+
92
+ # Less than 24 hours (86400 seconds)
93
+ if total_seconds < 86400:
94
+ hours = int(total_seconds // 3600)
95
+ return f"{hours} hour{'s' if hours != 1 else ''} ago"
96
+
97
+ # 24 hours or more - return as local time
98
+ if past_dt.tzinfo is not None:
99
+ local_dt = past_dt.astimezone() # Convert to local timezone
100
+ else:
101
+ local_dt = past_dt # Already naive, assume local
102
+
103
+ return local_dt.strftime("%c")
104
+
105
+ async def on_mount(self) -> None:
106
+ table = self.session_table
107
+ table.add_columns("Agent", "Session", "Created", "Last Used", "Path")
108
+ db = DB()
109
+ sessions = await db.session_get_recent()
110
+ if sessions is None:
111
+ return
112
+
113
+ for session in sessions:
114
+ cwd = ""
115
+ if meta_json := session["meta_json"]:
116
+ try:
117
+ cwd = json.loads(meta_json).get("cwd", None)
118
+ except Exception:
119
+ pass
120
+
121
+ table.add_row(
122
+ session["agent"],
123
+ session["title"],
124
+ self.friendly_time_ago(session["created_at"]),
125
+ self.friendly_time_ago(session["last_used"]),
126
+ cwd,
127
+ key=str(session["id"]),
128
+ )
129
+
130
+ async def dissmiss_with_session(self, row_key_value: str) -> Session | None:
131
+ try:
132
+ session_id = int(row_key_value)
133
+ except ValueError:
134
+ return
135
+ db = DB()
136
+ session = await db.session_get(session_id)
137
+ self.dismiss(session)
138
+
139
+ @on(widgets.Button.Pressed, "#resume")
140
+ async def on_resume_button(self) -> None:
141
+ table = self.session_table
142
+ row_key, _ = table.coordinate_to_cell_key(table.cursor_coordinate)
143
+ if row_key is None or row_key.value is None:
144
+ return
145
+ await self.dissmiss_with_session(row_key.value)
146
+
147
+ @on(widgets.Button.Pressed, "#cancel")
148
+ def on_cancel_button(self) -> None:
149
+ self.dismiss()
150
+
151
+ @on(widgets.DataTable.RowHighlighted)
152
+ def on_data_table_row_highlighted(self) -> None:
153
+ self.query_one("#resume").disabled = False
154
+
155
+ @on(widgets.DataTable.RowSelected)
156
+ async def on_data_table_row_selected(
157
+ self, event: widgets.DataTable.RowSelected
158
+ ) -> None:
159
+ if event.row_key is None or event.row_key.value is None:
160
+ return
161
+ await self.dissmiss_with_session(event.row_key.value)
162
+
163
+
164
+ if __name__ == "__main__":
165
+ from textual.app import App, ComposeResult
166
+
167
+ class TApp(App):
168
+ def on_mount(self):
169
+ self.push_screen(SessionResumeModal())
170
+
171
+ app = TApp()
172
+ app.run()
@@ -0,0 +1,35 @@
1
+ SessionResumeModal {
2
+ align: center middle;
3
+ .warning {
4
+ border: round $warning;
5
+ padding: 0 1;
6
+ color: $text-warning;
7
+ margin: 0 0 1 1;
8
+ }
9
+ #container {
10
+ margin: 2 4 1 4;
11
+ padding: 0 1 0 0;
12
+ max-width: 100;
13
+ height: auto;
14
+ border: thick $primary 20%;
15
+
16
+ Markdown {
17
+ MarkdownH1 {
18
+ margin: 1 0;
19
+ }
20
+ }
21
+
22
+ #table-container {
23
+ height: auto;
24
+ padding: 0 0 0 1;
25
+ DataTable {
26
+ width: 1fr;
27
+ height: 12;
28
+ }
29
+ }
30
+ #buttons {
31
+ margin-top: 1;
32
+ align: right top;
33
+ }
34
+ }
35
+ }