@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,87 @@
1
+ from __future__ import annotations
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.reactive import var
5
+ from textual import containers
6
+ from textual.widgets import Static, Markdown
7
+
8
+ from tui.pill import pill
9
+
10
+
11
+ class SubAgentHeader(Static):
12
+ ALLOW_SELECT = False
13
+ DEFAULT_CSS = """
14
+ SubAgentHeader {
15
+ width: auto;
16
+ max-width: 1fr;
17
+ }
18
+ """
19
+
20
+
21
+ class SubAgentContent(Markdown):
22
+ DEFAULT_CSS = """
23
+ SubAgentContent {
24
+ margin: 0 1 0 2;
25
+ }
26
+ """
27
+
28
+
29
+ class SubAgent(containers.VerticalGroup):
30
+ DEFAULT_CLASSES = "block"
31
+ DEFAULT_CSS = """
32
+ SubAgent {
33
+ margin: 0 0 0 1;
34
+ height: auto;
35
+ SubAgentHeader {
36
+ color: $text-secondary;
37
+ }
38
+ &.-status-running SubAgentHeader {
39
+ color: $text-warning;
40
+ text-style: italic;
41
+ }
42
+ &.-status-completed SubAgentHeader {
43
+ color: $text-success;
44
+ }
45
+ }
46
+ """
47
+
48
+ agent_type: var[str] = var("")
49
+ status: var[str] = var("running")
50
+
51
+ def __init__(
52
+ self,
53
+ agent_type: str,
54
+ tool_id: str | None = None,
55
+ ) -> None:
56
+ super().__init__(id=tool_id)
57
+ self.agent_type = agent_type
58
+ self.status = "running"
59
+ self._chunks: list[str] = []
60
+
61
+ def compose(self) -> ComposeResult:
62
+ self.set_class(True, f"-status-{self.status}")
63
+ label = pill(self.agent_type, "$warning-muted", "$text-warning")
64
+ yield SubAgentHeader(
65
+ f"🧠 Sub‑agent: {self.agent_type} {label}",
66
+ markup=False,
67
+ )
68
+ yield SubAgentContent("")
69
+
70
+ def append_chunk(self, text: str) -> None:
71
+ self._chunks.append(text)
72
+ try:
73
+ content = self.query_one(SubAgentContent)
74
+ content.load("".join(self._chunks))
75
+ except Exception:
76
+ pass
77
+
78
+ def complete(self) -> None:
79
+ self.status = "completed"
80
+ self.set_class(True, "-status-completed")
81
+ self.set_class(False, "-status-running")
82
+ try:
83
+ self.query_one(SubAgentHeader).update(
84
+ f"🧠 Sub‑agent: {self.agent_type} — completed ✅"
85
+ )
86
+ except Exception:
87
+ pass
@@ -0,0 +1,577 @@
1
+ from dataclasses import dataclass
2
+
3
+ from time import monotonic
4
+ from typing import Awaitable, Callable, Iterable
5
+
6
+ from textual.cache import LRUCache
7
+
8
+ from textual import on
9
+ from textual import events
10
+ from textual.css.query import NoMatches
11
+ from textual.message import Message
12
+ from textual.reactive import reactive
13
+ from textual.selection import Selection
14
+ from textual.style import Style
15
+ from textual.geometry import Region, Size
16
+ from textual.scroll_view import ScrollView
17
+ from textual.strip import Strip
18
+ from textual.timer import Timer
19
+
20
+ from tui import ansi
21
+ from tui.menus import MenuItem
22
+
23
+
24
+ # Time required to double tab escape
25
+ ESCAPE_TAP_DURATION = 400 / 1000
26
+
27
+
28
+ class Terminal(ScrollView, can_focus=True):
29
+ BINDING_GROUP_TITLE = "Terminal"
30
+ HELP = """\
31
+ ## Terminal
32
+
33
+ An embedded terminal running within A2TUI.
34
+ When the terminal has focus, it will take over the handling of keys.
35
+
36
+ Tap escape *twice* to exit.
37
+
38
+ """
39
+
40
+ CURSOR_STYLE = Style.parse("reverse")
41
+
42
+ hide_cursor = reactive(False)
43
+
44
+ @dataclass
45
+ class Finalized(Message):
46
+ """Terminal was finalized."""
47
+
48
+ terminal: Terminal
49
+
50
+ @property
51
+ def control(self) -> Terminal:
52
+ return self.terminal
53
+
54
+ @dataclass
55
+ class AlternateScreenChanged(Message):
56
+ """Terminal enabled or disabled alternate screen."""
57
+
58
+ terminal: Terminal
59
+ enabled: bool
60
+
61
+ @property
62
+ def control(self) -> Terminal:
63
+ return self.terminal
64
+
65
+ @dataclass
66
+ class LongRunning(Message):
67
+ """Terminal enabled or disabled alternate screen."""
68
+
69
+ terminal: Terminal
70
+
71
+ @property
72
+ def control(self) -> Terminal:
73
+ return self.terminal
74
+
75
+ @dataclass
76
+ class CancelRequested(Message):
77
+ """User double-pressed escape to cancel the agent."""
78
+
79
+ terminal: Terminal
80
+
81
+ @property
82
+ def control(self) -> Terminal:
83
+ return self.terminal
84
+
85
+ def __init__(
86
+ self,
87
+ name: str | None = None,
88
+ id: str | None = None,
89
+ classes: str | None = None,
90
+ disabled: bool = False,
91
+ minimum_terminal_width: int = 0,
92
+ size: tuple[int, int] | None = None,
93
+ get_terminal_dimensions: Callable[[], tuple[int, int]] | None = None,
94
+ ):
95
+ super().__init__(
96
+ name=name,
97
+ id=id,
98
+ classes=classes,
99
+ disabled=disabled,
100
+ )
101
+ self.set_reactive(Terminal.auto_links, False)
102
+ self.minimum_terminal_width = minimum_terminal_width
103
+ self._get_terminal_dimensions = get_terminal_dimensions
104
+
105
+ self.state = ansi.TerminalState(self.write_process_stdin)
106
+
107
+ if size is None:
108
+ self._width = minimum_terminal_width or 80
109
+ self._height: int = 24
110
+ else:
111
+ width, height = size
112
+ self._width = width
113
+ self._height = height
114
+
115
+ self.minimum_terminal_width = self._width
116
+
117
+ self.max_window_width = 0
118
+ self._escape_time = monotonic()
119
+ self._escaping = False
120
+ self._escape_reset_timer: Timer | None = None
121
+ self._finalized: bool = False
122
+ self.current_directory: str | None = None
123
+ self._alternate_screen: bool = False
124
+ self._terminal_render_cache: LRUCache[tuple, Strip] = LRUCache(1024)
125
+ self._write_to_stdin: Callable[[str], Awaitable] | None = None
126
+ self._write_count = 0
127
+ self._long_running_timer: Timer | None = None
128
+
129
+ @property
130
+ def is_finalized(self) -> bool:
131
+ """Finalized terminals will not accept writes or receive input."""
132
+ return self._finalized
133
+
134
+ @property
135
+ def width(self) -> int:
136
+ """Width of the terminal."""
137
+ return self._width
138
+
139
+ @property
140
+ def height(self) -> int:
141
+ """Height of the terminal."""
142
+ height = self._height
143
+ return height
144
+
145
+ @property
146
+ def size(self) -> Size:
147
+ return Size(self.width, self.height)
148
+
149
+ @property
150
+ def alternate_screen(self) -> bool:
151
+ return self._alternate_screen
152
+
153
+ def notify_style_update(self) -> None:
154
+ """Clear cache when theme chages."""
155
+ self._terminal_render_cache.clear()
156
+ super().notify_style_update()
157
+
158
+ def set_state(self, state: ansi.TerminalState) -> None:
159
+ """Set the terminal state, if this terminal is to inherit an existing state.
160
+
161
+ Args:
162
+ state: Terminal state object.
163
+ """
164
+ self.state = state
165
+
166
+ def set_write_to_stdin(self, write_to_stdin: Callable[[str], Awaitable]) -> None:
167
+ """Set a callable which is invoked with input, to be sent to stdin.
168
+
169
+ Args:
170
+ write_to_stdin: Callable which takes a string.
171
+ """
172
+ self._write_to_stdin = write_to_stdin
173
+
174
+ def finalize(self) -> None:
175
+ """FInalize the terminal.
176
+
177
+ The finalized terminal will reject new writes.
178
+ Adds the TCSS class `-finalize`
179
+ """
180
+ if not self._finalized:
181
+ if self._long_running_timer is not None:
182
+ self._long_running_timer.stop()
183
+ self._finalized = True
184
+ self.state.show_cursor = False
185
+ self.add_class("-finalized")
186
+ self._terminal_render_cache.clear()
187
+ self.refresh()
188
+ self.blur()
189
+ self.post_message(self.Finalized(self))
190
+ if not self.state.buffer.height:
191
+ self.display = False
192
+
193
+ def allow_focus(self) -> bool:
194
+ """Prohibit focus when the terminal is finalized and couldn't accept input."""
195
+ return not self.is_finalized
196
+
197
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
198
+ """Get the text under the selection.
199
+
200
+ Args:
201
+ selection: Selection information.
202
+
203
+ Returns:
204
+ Tuple of extracted text and ending (typically "\n" or " "), or `None` if no text could be extracted.
205
+ """
206
+ text = "\n".join(
207
+ line_record.content.plain for line_record in self.state.buffer.lines
208
+ )
209
+ return selection.extract(text), "\n"
210
+
211
+ def _on_resize(self, event: events.Resize) -> None:
212
+ if self._get_terminal_dimensions is None:
213
+ width, height = self.scrollable_content_region.size
214
+ else:
215
+ width, height = self._get_terminal_dimensions()
216
+ self.update_size(width, height)
217
+
218
+ def update_size(self, width: int, height: int) -> None:
219
+ old_width = self._width
220
+ old_height = self._height
221
+
222
+ self._terminal_render_cache.grow(height * 2)
223
+ self._width = width or 80
224
+ self._height = height or 24
225
+ self._width = max(self._width, self.minimum_terminal_width)
226
+
227
+ if (
228
+ old_width != self._width
229
+ or old_height != self._height
230
+ and not self.is_finalized
231
+ ):
232
+ from tui.widgets.conversation import Conversation
233
+
234
+ try:
235
+ conversation = self.query_ancestor(Conversation)
236
+ except NoMatches:
237
+ pass
238
+ else:
239
+ conversation.shell.update_size(self._width, self._height)
240
+
241
+ self.state.update_size(self._width, height)
242
+ self._terminal_render_cache.clear()
243
+ self.refresh()
244
+
245
+ def on_mount(self) -> None:
246
+ self.anchor()
247
+ if self._get_terminal_dimensions is None:
248
+ width, height = self.scrollable_content_region.size
249
+ else:
250
+ width, height = self._get_terminal_dimensions()
251
+ self.update_size(width, height)
252
+
253
+ async def write(self, text: str, hide_output: bool = False) -> bool:
254
+ """Write sequences to the terminal.
255
+
256
+ Args:
257
+ text: Text with ANSI escape sequences.
258
+ hide_output: Do not update the buffers with visible text.
259
+
260
+ Returns:
261
+ `True` if the state visuals changed, `False` if no visual change.
262
+ """
263
+ if self._write_count and self._long_running_timer is None:
264
+
265
+ def warn_long_run():
266
+ """Warn about a long running command."""
267
+ self.post_message(self.LongRunning(self))
268
+
269
+ self._long_running_timer = self.set_timer(2, warn_long_run)
270
+ self._write_count += 1
271
+
272
+ scrollback_delta, alternate_delta = await self.state.write(
273
+ text, hide_output=hide_output
274
+ )
275
+ self._update_from_state(scrollback_delta, alternate_delta)
276
+ scrollback_changed = bool(scrollback_delta is None or scrollback_delta)
277
+ alternate_changed = bool(alternate_delta is None or alternate_delta)
278
+
279
+ if self._alternate_screen != self.state.alternate_screen:
280
+ self.post_message(
281
+ self.AlternateScreenChanged(self, enabled=self.state.alternate_screen)
282
+ )
283
+ self._alternate_screen = self.state.alternate_screen
284
+ return scrollback_changed or alternate_changed
285
+
286
+ def on_click(self, event: events.Click) -> None:
287
+ self.focus()
288
+ event.stop()
289
+
290
+ def _update_from_state(
291
+ self, scrollback_delta: set[int] | None, alternate_delta: set[int] | None
292
+ ) -> None:
293
+ if self.state.current_directory:
294
+ self.current_directory = self.state.current_directory
295
+ self.finalize()
296
+ width = self.state.width
297
+ height = self.state.scrollback_buffer.height
298
+
299
+ if self.state.alternate_screen:
300
+ height += self.state.alternate_buffer.height
301
+ self.virtual_size = Size(min(self.state.buffer.max_line_width, width), height)
302
+ was_at_bottom = self.scroll_y >= self.max_scroll_y
303
+ if was_at_bottom:
304
+ self.anchor()
305
+ self.scroll_y = self.max_scroll_y
306
+ elif self._anchored:
307
+ self.scroll_y = self.max_scroll_y
308
+
309
+ scroll_y = int(self.scroll_y)
310
+ visible_lines = frozenset(range(scroll_y, scroll_y + height))
311
+
312
+ if scrollback_delta is None and alternate_delta is None:
313
+ self.refresh()
314
+ else:
315
+ window_width = self.region.width
316
+ scrollback_height = self.state.scrollback_buffer.height
317
+ if scrollback_delta is None:
318
+ self.refresh(Region(0, 0, window_width, scrollback_height))
319
+ else:
320
+ refresh_lines = [
321
+ Region(0, y - scroll_y, window_width, 1)
322
+ for y in sorted(scrollback_delta & visible_lines)
323
+ ]
324
+ if refresh_lines:
325
+ self.refresh(*refresh_lines)
326
+ if alternate_delta is None:
327
+ self.refresh()
328
+ else:
329
+ alternate_delta = {
330
+ line_no + scrollback_height for line_no in alternate_delta
331
+ }
332
+ refresh_lines = [
333
+ Region(0, y - scroll_y, window_width, 1)
334
+ for y in sorted(alternate_delta & visible_lines)
335
+ ]
336
+ if refresh_lines:
337
+ self.refresh(*refresh_lines)
338
+
339
+ def render_line(self, y: int) -> Strip:
340
+ scroll_x, scroll_y = self.scroll_offset
341
+ strip = self._render_line(scroll_x, scroll_y + y, self._width)
342
+ return strip
343
+
344
+ def on_focus(self) -> None:
345
+ self.border_subtitle = "Tap [b]esc[/b] [i]twice[/i] to exit"
346
+
347
+ def on_blur(self) -> None:
348
+ self.border_subtitle = "Click to focus"
349
+
350
+ def _render_line(self, x: int, y: int, width: int) -> Strip:
351
+ selection = self.text_selection
352
+ visual_style = self.visual_style
353
+ rich_style = visual_style.rich_style
354
+
355
+ state = self.state
356
+ buffer = state.scrollback_buffer
357
+ buffer_offset = 0
358
+ # If alternate screen is active place it (virtually) at the end
359
+ if y >= buffer.height and state.alternate_screen:
360
+ buffer_offset = buffer.height
361
+ buffer = state.alternate_buffer
362
+ # Get the folded line, which as a one to one relationship with y
363
+ try:
364
+ folded_line_ = buffer.folded_lines[y - buffer_offset]
365
+ line_no, line_offset, offset, line, updates = folded_line_
366
+ except IndexError:
367
+ return Strip.blank(width, rich_style)
368
+
369
+ line_record = buffer.lines[line_no]
370
+ cache_key: tuple | None = (
371
+ self.state.alternate_screen,
372
+ y,
373
+ line_record.updates,
374
+ updates,
375
+ )
376
+
377
+ # Add in cursor
378
+ if (
379
+ not self.hide_cursor
380
+ and state.show_cursor
381
+ and buffer.cursor_line == y - buffer_offset
382
+ ):
383
+ if buffer.cursor_offset >= len(line):
384
+ line = line.pad_right(buffer.cursor_offset - len(line) + 1)
385
+ line_cursor_offset = buffer.cursor_offset
386
+ line = line.stylize(
387
+ self.CURSOR_STYLE, line_cursor_offset, line_cursor_offset + 1
388
+ )
389
+ cache_key = None
390
+
391
+ # get cached strip if there is no selection
392
+ if (
393
+ not selection
394
+ and cache_key is not None
395
+ and (strip := self._terminal_render_cache.get(cache_key))
396
+ ):
397
+ strip = strip.crop(x, x + width)
398
+ strip = strip.adjust_cell_length(
399
+ width, (visual_style + line_record.style).rich_style
400
+ )
401
+ strip = strip.apply_offsets(x + offset, line_no)
402
+ return strip
403
+
404
+ # Apply selection
405
+ if selection is not None and (select_span := selection.get_span(line_no)):
406
+ unfolded_content = line_record.content.expand_tabs(8)
407
+ start, end = select_span
408
+ if end == -1:
409
+ end = len(unfolded_content)
410
+ selection_style = self.screen.get_visual_style("screen--selection")
411
+ unfolded_content = unfolded_content.stylize(selection_style, start, end)
412
+ try:
413
+ folded_lines = self.state._fold_line(line_no, unfolded_content, width)
414
+ line = folded_lines[line_offset].content
415
+ cache_key = None
416
+ except IndexError:
417
+ pass
418
+
419
+ try:
420
+ strip = Strip(
421
+ line.render_segments(visual_style), cell_length=line.cell_length
422
+ )
423
+ except Exception:
424
+ # TODO: Is this neccesary?
425
+ strip = Strip.blank(line.cell_length)
426
+
427
+ if cache_key is not None:
428
+ self._terminal_render_cache[cache_key] = strip
429
+
430
+ strip = strip.crop(x, x + width)
431
+ strip = strip.adjust_cell_length(
432
+ width, (visual_style + line_record.style).rich_style
433
+ )
434
+ strip = strip.apply_offsets(x + offset, line_no)
435
+
436
+ return strip
437
+
438
+ async def _reset_escaping(self) -> None:
439
+ if self._escaping:
440
+ await self.write_process_stdin(self.state.key_escape())
441
+ self._escaping = False
442
+
443
+ async def on_key(self, event: events.Key):
444
+ event.prevent_default()
445
+ event.stop()
446
+
447
+ if event.key == "escape":
448
+ if self._alternate_screen:
449
+ await self.write_process_stdin("\x1b")
450
+ return
451
+ if self._escaping:
452
+ if monotonic() < self._escape_time + ESCAPE_TAP_DURATION:
453
+ self.blur()
454
+ self._escaping = False
455
+ self.post_message(self.CancelRequested(self))
456
+ return
457
+ else:
458
+ await self.write_process_stdin(self.state.key_escape())
459
+ else:
460
+ self._escaping = True
461
+ self._escape_time = monotonic()
462
+ self._escape_reset_timer = self.set_timer(
463
+ ESCAPE_TAP_DURATION, self._reset_escaping
464
+ )
465
+ return
466
+ else:
467
+ await self._reset_escaping()
468
+ if self._escape_reset_timer is not None:
469
+ self._escape_reset_timer.stop()
470
+
471
+ if (stdin := self.state.key_event_to_stdin(event)) is not None:
472
+ await self.write_process_stdin(stdin)
473
+
474
+ @property
475
+ def allow_select(self) -> bool:
476
+ return self.is_finalized or not self._alternate_screen
477
+
478
+ def _encode_mouse_event_sgr(self, event: events.MouseEvent) -> str:
479
+ x = int(event.x)
480
+ y = int(event.y)
481
+
482
+ if isinstance(event, events.MouseMove):
483
+ button = event.button + 32 if event.button else 35
484
+ else:
485
+ button = event.button - 1
486
+ if button >= 4:
487
+ button = button - 4 + 128
488
+ if event.shift:
489
+ button += 4
490
+ if event.meta:
491
+ button += 8
492
+ if event.ctrl:
493
+ button += 16
494
+
495
+ if isinstance(event, events.MouseDown):
496
+ final_character = "M"
497
+ elif isinstance(event, events.MouseUp):
498
+ button = 0
499
+ final_character = "m"
500
+ else:
501
+ final_character = "M"
502
+ mouse_stdin = f"\x1b[<{button};{x + 1};{y + 1}{final_character}"
503
+ return mouse_stdin
504
+
505
+ @on(events.MouseMove)
506
+ async def on_mouse_move(self, event: events.MouseMove) -> None:
507
+ if self.is_finalized:
508
+ return
509
+ if (mouse_tracking := self.state.mouse_tracking) is None:
510
+ return
511
+ if mouse_tracking.tracking == "all" or (
512
+ event.button and mouse_tracking.tracking == "drag"
513
+ ):
514
+ await self._handle_mouse_event(event)
515
+ event.prevent_default()
516
+ event.stop()
517
+
518
+ @on(events.MouseDown)
519
+ @on(events.MouseUp)
520
+ async def on_mouse_button(self, event: events.MouseUp | events.MouseDown) -> None:
521
+ if self.is_finalized:
522
+ return
523
+ if self.state.mouse_tracking is None:
524
+ return
525
+ await self._handle_mouse_event(event)
526
+ event.prevent_default()
527
+ event.stop()
528
+
529
+ async def _handle_mouse_event(self, event: events.MouseEvent) -> None:
530
+ if (mouse_tracking := self.state.mouse_tracking) is None:
531
+ return
532
+ # TODO: Other mouse tracking formats
533
+ match mouse_tracking.format:
534
+ case "sgr":
535
+ await self.write_process_stdin(self._encode_mouse_event_sgr(event))
536
+
537
+ async def on_paste(self, event: events.Paste) -> None:
538
+ await self.paste(event.text)
539
+
540
+ async def paste(self, text: str) -> None:
541
+ if self.state.bracketed_paste:
542
+ await self.write_process_stdin(f"\x1b[200~{text}\x1b[201~")
543
+ else:
544
+ await self.write_process_stdin(text)
545
+
546
+ async def write_process_stdin(self, input: str) -> None:
547
+ if self._write_to_stdin is not None:
548
+ await self._write_to_stdin(input)
549
+
550
+
551
+ if __name__ == "__main__":
552
+ from textual.app import App, ComposeResult
553
+
554
+ TEST = (
555
+ "\033[31mThis is red text\033[0m\n"
556
+ "\033[32mThis is green text\033[0m\n"
557
+ "\033[33mThis is yellow text\033[0m\n"
558
+ "\033[34mThis is blue text\033[0m\n"
559
+ "\033[35mThis is magenta text\033[0m\n"
560
+ "\033[36mThis is cyan text\033[0m\n"
561
+ "\033[1mThis is bold text\033[0m\n"
562
+ "\033[4mThis is underlined text\033[0m\n"
563
+ "\033[1;31mThis is bold red text\033[0m\n"
564
+ "\033[42mThis has a green background\033[0m\n"
565
+ "\033[97;44mWhite text on blue background\033[0m"
566
+ )
567
+
568
+ class TApp(App):
569
+ def compose(self) -> ComposeResult:
570
+ yield Terminal()
571
+
572
+ def on_mount(self) -> None:
573
+ terminal = self.query_one(Terminal)
574
+ terminal.write(TEST)
575
+
576
+ app = TApp()
577
+ app.run()