@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,188 @@
1
+ import asyncio
2
+ from functools import partial
3
+ from rich.style import Style as RichStyle
4
+
5
+ from textual import work
6
+ from textual.app import ComposeResult, RenderResult
7
+
8
+ from textual import events
9
+ from textual.content import Content
10
+ from textual.geometry import Offset
11
+ from textual.reactive import reactive
12
+ from textual.renderables.bar import Bar
13
+ from textual.widget import Widget
14
+ from textual import containers
15
+ from textual import widgets
16
+ from textual import getters
17
+ from textual.message import Message
18
+
19
+ from tui.app import A2TUIApp
20
+ from tui.session_tracker import SessionDetails
21
+ from tui import messages
22
+
23
+
24
+ class SessionLabel(widgets.Label):
25
+ ALLOW_SELECT = False
26
+
27
+ def on_click(self) -> None:
28
+ if self.id is not None:
29
+ self.post_message(messages.SessionSwitch(self.id))
30
+
31
+
32
+ class Underline(Widget):
33
+ """The animated underline beneath tabs."""
34
+
35
+ COMPONENT_CLASSES = {"underline--bar"}
36
+ """
37
+ | Class | Description |
38
+ | :- | :- |
39
+ | `underline--bar` | Style of the bar (may be used to change the color). |
40
+ """
41
+
42
+ highlight_start = reactive(0)
43
+ """First cell in highlight."""
44
+ highlight_end = reactive(0)
45
+ """Last cell (inclusive) in highlight."""
46
+ show_highlight: reactive[bool] = reactive(True)
47
+ """Flag to indicate if a highlight should be shown at all."""
48
+
49
+ class Clicked(Message):
50
+ """Inform ancestors the underline was clicked."""
51
+
52
+ offset: Offset
53
+ """The offset of the click, relative to the origin of the bar."""
54
+
55
+ def __init__(self, offset: Offset) -> None:
56
+ self.offset = offset
57
+ super().__init__()
58
+
59
+ @property
60
+ def _highlight_range(self) -> tuple[int, int]:
61
+ """Highlighted range for underline bar."""
62
+ return (
63
+ (self.highlight_start, self.highlight_end)
64
+ if self.show_highlight
65
+ else (0, 0)
66
+ )
67
+
68
+ def render(self) -> RenderResult:
69
+ """Render the bar."""
70
+ bar_style = self.get_component_rich_style("underline--bar")
71
+ return Bar(
72
+ highlight_range=self._highlight_range,
73
+ highlight_style=RichStyle.from_color(bar_style.color),
74
+ background_style=RichStyle.from_color(bar_style.bgcolor),
75
+ )
76
+
77
+ def _on_click(self, event: events.Click):
78
+ """Catch clicks, so that the underline can activate the tabs."""
79
+ event.stop()
80
+ self.post_message(self.Clicked(event.screen_offset))
81
+
82
+
83
+ class SessionsTabs(Widget):
84
+
85
+ ALLOW_SELECT = False
86
+ app: getters.app[A2TUIApp] = getters.app(A2TUIApp)
87
+
88
+ title_container = getters.query_one("#title-container", Widget)
89
+
90
+ current_session = reactive("", init=False)
91
+
92
+ def on_mount(self) -> None:
93
+ self.current_session = self.app.current_mode
94
+ self.app.mode_change_signal.subscribe(self, self.handle_mode_change)
95
+ self.app.session_update_signal.subscribe(
96
+ self, self.handle_session_update_signal, immediate=True
97
+ )
98
+ self.update_underline(self.current_session, animate=False)
99
+ self.call_after_refresh(self.update_underline, self.current_session)
100
+
101
+ def handle_mode_change(self, mode: str) -> None:
102
+ self.current_session = mode
103
+
104
+ def watch_current_session(self, old_session: str, new_session: str) -> None:
105
+ self.query(".-current").remove_class("-current")
106
+ self.query(f"#{new_session}").add_class("-current")
107
+ if old_session:
108
+ self.update_underline(old_session, animate=False)
109
+
110
+ self.update_underline(new_session, animate=True)
111
+
112
+ def update_underline(self, session: str | None = None, animate: bool = True):
113
+ if not self.is_mounted or not self.is_attached:
114
+ return
115
+ if session is None:
116
+ session = self.current_session
117
+ if not session:
118
+ return
119
+ if current_label := self.query_one_optional(f"#{session}", SessionLabel):
120
+ tab_region = current_label.virtual_region.shrink((0, 1, 0, 1))
121
+ if not tab_region:
122
+ return
123
+ start, end = tab_region.column_span
124
+ underline = self.query_one(Underline)
125
+ if animate:
126
+ underline.animate("highlight_start", start, duration=0.3)
127
+ underline.animate(
128
+ "highlight_end",
129
+ end,
130
+ duration=0.3,
131
+ on_complete=partial(self.scroll_to_center, current_label),
132
+ )
133
+ else:
134
+ underline.highlight_start = start
135
+ underline.highlight_end = end
136
+ self.scroll_to_center(current_label, animate=False)
137
+
138
+ def render_session_label(self, session: SessionDetails) -> Content:
139
+ match session.state:
140
+ case "asking":
141
+ return Content.assemble(
142
+ ("❯ ", "not dim $text-secondary"), session.title
143
+ )
144
+ case "busy":
145
+ return Content(f"⌛ {session.title}")
146
+ return Content(session.title)
147
+
148
+ def compose(self) -> ComposeResult:
149
+ with containers.HorizontalGroup(id="title-container"):
150
+ for session in self.app.session_tracker.ordered_sessions:
151
+ yield SessionLabel(
152
+ self.render_session_label(session),
153
+ id=session.mode_name,
154
+ classes="-current" if session.mode_name == self.screen.id else "",
155
+ )
156
+ yield Underline()
157
+
158
+ @work(exit_on_error=False)
159
+ async def handle_session_update_signal(
160
+ self, update: tuple[str, SessionDetails | None]
161
+ ) -> None:
162
+ mode, details = update
163
+ if details is None:
164
+ removed_mode = mode
165
+ await self.query(f"#{mode}").remove()
166
+ if self.current_session == removed_mode:
167
+ remaining = list(self.query(SessionLabel))
168
+ if remaining:
169
+ new_current = remaining[0].id or ""
170
+ else:
171
+ new_current = ""
172
+ self.current_session = new_current
173
+ else:
174
+ if tab_label := self.query_one_optional(f"#{mode}", SessionLabel):
175
+ tab_label.update(self.render_session_label(details))
176
+ else:
177
+ self.query(SessionLabel).remove_class("-current")
178
+ await self.title_container.mount(
179
+ SessionLabel(
180
+ self.render_session_label(details),
181
+ id=details.mode_name,
182
+ classes=(
183
+ "-current" if details.mode_name == self.screen.id else ""
184
+ ),
185
+ )
186
+ )
187
+ await asyncio.sleep(0.05)
188
+ self.call_after_refresh(self.update_underline, self.current_session)
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+ from typing import Iterable
3
+
4
+ from textual.app import ComposeResult
5
+ from textual import containers
6
+ from textual.highlight import highlight
7
+ from textual.widgets import Static
8
+
9
+
10
+ from tui.menus import MenuItem
11
+ from tui.widgets.non_selectable_label import NonSelectableLabel
12
+
13
+
14
+ class ShellResult(containers.HorizontalGroup):
15
+ def __init__(
16
+ self,
17
+ command: str,
18
+ *,
19
+ name: str | None = None,
20
+ id: str | None = None,
21
+ classes: str | None = None,
22
+ disabled: bool = False,
23
+ ) -> None:
24
+ self._command = command
25
+ super().__init__(name=name, id=id, classes=classes, disabled=disabled)
26
+
27
+ def compose(self) -> ComposeResult:
28
+ yield NonSelectableLabel("$", id="prompt", markup=False)
29
+ yield Static(highlight(self._command, language="sh"))
30
+
31
+ def get_block_menu(self) -> Iterable[MenuItem]:
32
+ yield from ()
33
+
34
+ def get_block_content(self, destination: str) -> str | None:
35
+ return self._command
@@ -0,0 +1,40 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable
3
+
4
+ from textual.content import Content
5
+ from textual.dom import DOMNode
6
+ from textual.message import Message
7
+
8
+ from tui.menus import MenuItem
9
+ from tui.widgets.terminal import Terminal
10
+
11
+
12
+ class ShellTerminal(Terminal):
13
+ """Subclass of Terminal used in the Shell view."""
14
+
15
+ @dataclass
16
+ class Terminate(Message):
17
+ teminal: ShellTerminal
18
+
19
+ @property
20
+ def control(self) -> DOMNode:
21
+ return self.terminal
22
+
23
+ @dataclass
24
+ class Interrupt(Message):
25
+ teminal: ShellTerminal
26
+
27
+ @property
28
+ def control(self) -> DOMNode:
29
+ return self.terminal
30
+
31
+ def get_block_menu(self) -> Iterable[MenuItem]:
32
+ if not self.is_finalized:
33
+ yield MenuItem("Interrupt", "interrupt", "i")
34
+ yield MenuItem("Focus", f"focus_block({self.id!r})", "f")
35
+
36
+ def get_block_content(self, destination: str) -> str | None:
37
+ return "\n".join(line.content.plain for line in self.state.buffer.lines)
38
+
39
+ def on_mount(self) -> None:
40
+ self.border_title = Content(self.name)
@@ -0,0 +1,114 @@
1
+ from dataclasses import dataclass
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.widget import Widget
5
+ from textual import containers
6
+ from textual import widgets
7
+ from textual.message import Message
8
+
9
+
10
+ class SideBarCollapsible(widgets.Collapsible):
11
+ BINDING_GROUP_TITLE = "Sidebar collapsible"
12
+ HELP = """\
13
+ ## Sidebar
14
+
15
+ This is your sidebar.
16
+
17
+ The Sidebar contains additonal information associated with the conversation.
18
+
19
+ - **tab / shift+tab** Navigate sections
20
+ - **enter** expand or collapse secions
21
+ """
22
+
23
+
24
+ class SideBar(containers.Vertical):
25
+ BINDING_GROUP_TITLE = "Sidebar"
26
+ BINDINGS = [("escape", "dismiss", "Dismiss sidebar")]
27
+
28
+ class Dismiss(Message):
29
+ pass
30
+
31
+ @dataclass(frozen=True)
32
+ class Panel:
33
+ title: str
34
+ widget: Widget
35
+ flex: bool = False
36
+ collapsed: bool = False
37
+ id: str | None = None
38
+
39
+ def __init__(
40
+ self,
41
+ *panels: Panel,
42
+ name: str | None = None,
43
+ id: str | None = None,
44
+ classes: str | None = None,
45
+ disabled: bool = False,
46
+ hide: bool = False,
47
+ ) -> None:
48
+ super().__init__(name=name, id=id, classes=classes, disabled=disabled)
49
+ self.panels: list[SideBar.Panel] = [*panels]
50
+ self.hide = hide
51
+
52
+ def on_mount(self) -> None:
53
+ self.trap_focus()
54
+
55
+ def compose(self) -> ComposeResult:
56
+ for panel in self.panels:
57
+ yield SideBarCollapsible(
58
+ panel.widget,
59
+ title=panel.title,
60
+ collapsed=panel.collapsed,
61
+ classes="-flex" if panel.flex else "-fixed",
62
+ id=panel.id,
63
+ )
64
+
65
+ def action_dismiss(self) -> None:
66
+ self.post_message(self.Dismiss())
67
+
68
+ def has_panel(self, panel_id: str) -> bool:
69
+ return self.query_one_optional(f"#{panel_id}") is not None
70
+
71
+ async def add_panel(self, panel: Panel) -> SideBarCollapsible:
72
+ collapsible = SideBarCollapsible(
73
+ panel.widget,
74
+ title=panel.title,
75
+ collapsed=panel.collapsed,
76
+ classes="-flex" if panel.flex else "-fixed",
77
+ id=panel.id,
78
+ )
79
+ await self.mount(collapsible)
80
+ self.panels.append(panel)
81
+ return collapsible
82
+
83
+ def remove_panel(self, panel_id: str) -> bool:
84
+ for i, p in enumerate(self.panels):
85
+ if p.id == panel_id:
86
+ widget = self.query_one_optional(f"#{panel_id}")
87
+ if widget is not None:
88
+ widget.remove()
89
+ self.panels.pop(i)
90
+ return True
91
+ return False
92
+
93
+
94
+ if __name__ == "__main__":
95
+ from textual.app import App, ComposeResult
96
+
97
+ class SApp(App):
98
+ def compose(self) -> ComposeResult:
99
+ yield SideBar(
100
+ SideBar.Panel("Hello", widgets.Label("Hello, World!")),
101
+ SideBar.Panel(
102
+ "Files",
103
+ widgets.DirectoryTree(
104
+ "~/",
105
+ ),
106
+ flex=True,
107
+ ),
108
+ SideBar.Panel(
109
+ "Hello",
110
+ widgets.Static("Where there is a Will! " * 10),
111
+ ),
112
+ )
113
+
114
+ SApp().run()
@@ -0,0 +1,235 @@
1
+ from dataclasses import dataclass
2
+ from operator import itemgetter
3
+ from typing import Iterable, Self, Sequence
4
+
5
+ from textual import on
6
+ from textual.app import ComposeResult
7
+ from textual.binding import Binding
8
+ from textual.content import Content, Span
9
+
10
+ from textual import getters
11
+ from textual.message import Message
12
+ from textual.reactive import var
13
+ from textual import containers
14
+ from textual import widgets
15
+ from textual.widgets.option_list import Option
16
+
17
+ from tui.fuzzy import FuzzySearch
18
+ from tui.messages import Dismiss
19
+ from tui.slash_command import SlashCommand
20
+ from tui.visuals.columns import Columns
21
+
22
+
23
+ class SlashCompleteInput(widgets.Input):
24
+ BINDING_GROUP_TITLE = "Fuzzy search slash commands"
25
+ HELP = """\
26
+ ## Slash command fuzzy search
27
+
28
+ Search for slash commands by typing a few characters from the command.
29
+
30
+ - **cursor keys** Navigate list
31
+ - **enter** Add command to prompt
32
+ - **escape** Dismiss fuzzy search
33
+ """
34
+
35
+
36
+ class SlashComplete(containers.VerticalGroup):
37
+ """A widget to auto-complete slash commands."""
38
+
39
+ CURSOR_BINDING_GROUP = Binding.Group(description="Select")
40
+ BINDINGS = [
41
+ Binding(
42
+ "up",
43
+ "cursor_up",
44
+ "Cursor up",
45
+ group=CURSOR_BINDING_GROUP,
46
+ priority=True,
47
+ ),
48
+ Binding(
49
+ "down",
50
+ "cursor_down",
51
+ "Cursor down",
52
+ group=CURSOR_BINDING_GROUP,
53
+ priority=True,
54
+ ),
55
+ Binding("enter", "submit", "Insert /command", priority=True),
56
+ Binding("escape", "dismiss", "Dismiss", priority=True),
57
+ ]
58
+
59
+ DEFAULT_CSS = """
60
+ SlashComplete {
61
+ OptionList {
62
+ height: auto;
63
+ }
64
+ }
65
+ """
66
+
67
+ input = getters.query_one(widgets.Input)
68
+ option_list = getters.query_one(widgets.OptionList)
69
+
70
+ slash_commands: var[list[SlashCommand]] = var(list)
71
+
72
+ @dataclass
73
+ class Completed(Message):
74
+ command: str
75
+
76
+ def __init__(
77
+ self,
78
+ slash_commands: Iterable[SlashCommand] | None = None,
79
+ id: str | None = None,
80
+ classes: str | None = None,
81
+ ) -> None:
82
+ super().__init__(id=id, classes=classes)
83
+ self.slash_commands = list(slash_commands) if slash_commands else []
84
+ self.hints = {
85
+ slash_command.command: slash_command.hint
86
+ for slash_command in self.slash_commands
87
+ if slash_command.hint
88
+ }
89
+
90
+ self.fuzzy_search = FuzzySearch(case_sensitive=False)
91
+
92
+ def compose(self) -> ComposeResult:
93
+ yield SlashCompleteInput(compact=True, placeholder="fuzzy search")
94
+ yield widgets.OptionList()
95
+
96
+ def focus(self, scroll_visible: bool = False) -> Self:
97
+ self.filter_slash_commands("")
98
+ self.input.focus(scroll_visible)
99
+ return self
100
+
101
+ def on_mount(self) -> None:
102
+ self.filter_slash_commands("")
103
+
104
+ def on_descendant_blur(self) -> None:
105
+ self.post_message(Dismiss(self))
106
+
107
+ @on(widgets.Input.Changed)
108
+ def on_input_changed(self, event: widgets.Input.Changed) -> None:
109
+ event.stop()
110
+ self.filter_slash_commands(event.value)
111
+
112
+ async def watch_slash_commands(self, slash_commands: list[SlashCommand]) -> None:
113
+ self.hints = {
114
+ slash_command.command: slash_command.hint
115
+ for slash_command in slash_commands
116
+ if slash_command.hint
117
+ }
118
+ self.filter_slash_commands(self.input.value)
119
+
120
+ def filter_slash_commands(self, prompt: str) -> None:
121
+ """Filter slash commands by the given prompt.
122
+
123
+ Args:
124
+ prompt: Text prompt.
125
+ """
126
+ prompt = prompt.lstrip("/").casefold().rstrip()
127
+ columns = self.columns = Columns("auto", "flex")
128
+
129
+ slash_commands = sorted(
130
+ self.slash_commands,
131
+ key=lambda slash_command: slash_command.command.casefold(),
132
+ )
133
+ deduplicated_slash_commands = {
134
+ slash_command.command: slash_command for slash_command in slash_commands
135
+ }
136
+ self.fuzzy_search.cache.grow(len(deduplicated_slash_commands))
137
+
138
+ if prompt:
139
+ slash_prompt = f"/{prompt}"
140
+ scores: list[tuple[float, Sequence[int], SlashCommand]] = [
141
+ (
142
+ *self.fuzzy_search.match(prompt, slash_command.command[1:]),
143
+ slash_command,
144
+ )
145
+ for slash_command in slash_commands
146
+ ]
147
+
148
+ scores = sorted(
149
+ [
150
+ (
151
+ (
152
+ score * 2
153
+ if slash_command.command.casefold().startswith(slash_prompt)
154
+ else score
155
+ ),
156
+ highlights,
157
+ slash_command,
158
+ )
159
+ for score, highlights, slash_command in scores
160
+ if score
161
+ ],
162
+ key=itemgetter(0),
163
+ reverse=True,
164
+ )
165
+ else:
166
+ scores = [(1.0, [], slash_command) for slash_command in slash_commands]
167
+
168
+ def make_row(
169
+ slash_command: SlashCommand, indices: Iterable[int]
170
+ ) -> tuple[Content, ...]:
171
+ """Make a row for the Columns display.
172
+
173
+ Args:
174
+ slash_command: The slash command instance.
175
+ indices: Indices of matching characters.
176
+
177
+ Returns:
178
+ A tuple of `Content` instances for use as a column row.
179
+ """
180
+ command = Content.styled(slash_command.command, "$text-success")
181
+ command = command.add_spans(
182
+ [Span(index + 1, index + 2, "underline not dim") for index in indices]
183
+ )
184
+ return (command, Content.styled(slash_command.help, "dim"))
185
+
186
+ rows = [
187
+ (
188
+ columns.add_row(
189
+ *make_row(slash_command, indices),
190
+ ),
191
+ slash_command.command,
192
+ )
193
+ for _, indices, slash_command in scores
194
+ ]
195
+ self.option_list.set_options(
196
+ Option(row, id=command_name) for row, command_name in rows
197
+ )
198
+ if self.display:
199
+ self.option_list.highlighted = 0
200
+ else:
201
+ with self.option_list.prevent(widgets.OptionList.OptionHighlighted):
202
+ self.option_list.highlighted = 0
203
+
204
+ def action_cursor_down(self) -> None:
205
+ self.option_list.action_cursor_down()
206
+
207
+ def action_cursor_up(self) -> None:
208
+ self.option_list.action_cursor_up()
209
+
210
+ def action_dismiss(self) -> None:
211
+ self.post_message(Dismiss(self))
212
+
213
+ def action_submit(self) -> None:
214
+ option_list = self.option_list
215
+ if (option := option_list.highlighted_option) is not None:
216
+ with self.input.prevent(widgets.Input.Changed):
217
+ self.input.clear()
218
+ self.post_message(self.Completed(option.id or ""))
219
+
220
+
221
+ if __name__ == "__main__":
222
+ from textual.app import App, ComposeResult
223
+
224
+ COMMANDS = [
225
+ SlashCommand("/help", "Help with slash commands"),
226
+ SlashCommand("/foo", "This is FOO"),
227
+ SlashCommand("/bar", "This is BAR"),
228
+ SlashCommand("/baz", "This is BAZ"),
229
+ ]
230
+
231
+ class SlashApp(App):
232
+ def compose(self) -> ComposeResult:
233
+ yield SlashComplete(COMMANDS)
234
+
235
+ SlashApp().run()
@@ -0,0 +1,70 @@
1
+ from time import monotonic
2
+
3
+ from textual.widget import Widget
4
+
5
+ from textual.content import Content
6
+ from textual.reactive import reactive
7
+
8
+
9
+ class StrikeText(Widget):
10
+ DEFAULT_CSS = """
11
+ StrikeText {
12
+ height: auto;
13
+ &.-complete {
14
+ text-style: strike;
15
+ color: $text-primary;
16
+ }
17
+ }
18
+ """
19
+
20
+ strike_time: reactive[float | None] = reactive(None)
21
+
22
+ def __init__(
23
+ self,
24
+ content: Content,
25
+ name: str | None = None,
26
+ id: str | None = None,
27
+ classes: str | None = None,
28
+ ):
29
+ self.content = content
30
+ super().__init__(name=name, id=id, classes=classes)
31
+
32
+ def strike(self) -> None:
33
+ self.strike_time = monotonic()
34
+ self.auto_refresh = 1 / 30
35
+
36
+ def render(self) -> Content:
37
+ content = self.content
38
+ if self.strike_time is not None:
39
+ position = int((monotonic() - self.strike_time) * 70)
40
+ content = content.stylize("strike $text-primary", 0, position)
41
+ if position > len(content):
42
+ self.auto_refresh = None
43
+ return content
44
+
45
+
46
+ if __name__ == "__main__":
47
+ from textual.app import App, ComposeResult
48
+ from textual.widgets import Static
49
+
50
+ class StrikeApp(App):
51
+ CSS = """
52
+ Screen {
53
+ overflow: auto;
54
+ }
55
+
56
+ """
57
+ BINDINGS = [("space", "strike", "Strike")]
58
+
59
+ def compose(self) -> ComposeResult:
60
+ for n in range(20):
61
+ yield Static("HELLO")
62
+ yield StrikeText(Content("Where there is a Will, there is a way"))
63
+ for n in range(200):
64
+ yield Static("World")
65
+
66
+ def action_strike(self):
67
+ self.query_one(StrikeText).strike()
68
+
69
+ app = StrikeApp()
70
+ app.run()