@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,106 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import subprocess
5
+
6
+ from textual.app import ComposeResult
7
+ from textual.reactive import reactive
8
+ from textual.widgets import Static
9
+ from textual import containers
10
+
11
+
12
+ class ModifiedFiles(containers.Vertical):
13
+ DEFAULT_CSS = """
14
+ ModifiedFiles {
15
+ height: auto;
16
+ max-height: 15;
17
+ overflow-y: auto;
18
+
19
+ Static {
20
+ padding: 0 1;
21
+ }
22
+
23
+ .file-added {
24
+ color: $text-success;
25
+ }
26
+
27
+ .file-modified {
28
+ color: $text-warning;
29
+ }
30
+
31
+ .file-deleted {
32
+ color: $text-error;
33
+ }
34
+
35
+ .file-renamed {
36
+ color: $text-secondary;
37
+ }
38
+
39
+ .no-changes {
40
+ color: $text-secondary;
41
+ text-style: italic;
42
+ }
43
+ }
44
+ """
45
+
46
+ path: reactive[Path | None] = reactive(None)
47
+ files: reactive[list[str]] = reactive([], recompose=True)
48
+
49
+ def __init__(
50
+ self,
51
+ path: Path | None = None,
52
+ name: str | None = None,
53
+ id: str | None = None,
54
+ classes: str | None = None,
55
+ ) -> None:
56
+ super().__init__(name=name, id=id, classes=classes)
57
+ # Initial value is set via data_bind in main.py on_mount.
58
+ # Don't use set_reactive here — let _initialize_reactive trigger
59
+ # watch_path properly on first reactive access.
60
+
61
+ def watch_path(self, path: Path | None) -> None:
62
+ """Run git status when path changes."""
63
+ if path is None:
64
+ self.files = []
65
+ return
66
+ try:
67
+ result = subprocess.run(
68
+ ["git", "-C", str(path), "status", "--porcelain"],
69
+ capture_output=True,
70
+ text=True,
71
+ timeout=5,
72
+ )
73
+ if result.returncode == 0:
74
+ self.files = [l for l in result.stdout.strip().split("\n") if l.strip()]
75
+ else:
76
+ self.files = []
77
+ except (subprocess.TimeoutExpired, FileNotFoundError):
78
+ self.files = []
79
+
80
+ def refresh_files(self) -> None:
81
+ """Re-run git status manually (e.g. after file system changes)."""
82
+ self.watch_path(self.path)
83
+
84
+ def compose(self) -> ComposeResult:
85
+ if not self.files:
86
+ yield Static("No modified files", classes="no-changes")
87
+ return
88
+ for line in self.files:
89
+ status = line[:2]
90
+ filepath = line[3:]
91
+ cls = self._class_for_status(status)
92
+ yield Static(f" {filepath}", classes=cls)
93
+
94
+ @staticmethod
95
+ def _class_for_status(status: str) -> str:
96
+ if "?" in status:
97
+ return "file-added"
98
+ if "D" in status:
99
+ return "file-deleted"
100
+ if "A" in status or "C" in status:
101
+ return "file-added"
102
+ if "R" in status:
103
+ return "file-renamed"
104
+ if "M" in status:
105
+ return "file-modified"
106
+ return "file-modified"
@@ -0,0 +1,5 @@
1
+ from textual.widgets import Label
2
+
3
+
4
+ class NonSelectableLabel(Label):
5
+ ALLOW_SELECT = False
@@ -0,0 +1,18 @@
1
+ from typing import Iterable
2
+ from textual.widgets import Static
3
+
4
+ from tui.menus import MenuItem
5
+
6
+
7
+ class Note(Static):
8
+ DEFAULT_CLASSES = "block"
9
+
10
+ def get_block_menu(self) -> Iterable[MenuItem]:
11
+ return
12
+ yield
13
+
14
+ def get_block_content(self, destination: str) -> str | None:
15
+ return str(self.render())
16
+
17
+ def action_hello(self, message: str) -> None:
18
+ self.notify(message, severity="warning")
@@ -0,0 +1,492 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ import asyncio
5
+ import concurrent.futures
6
+
7
+ from operator import itemgetter
8
+ import os
9
+ from pathlib import Path
10
+
11
+ from typing import Sequence
12
+
13
+
14
+ from textual import on
15
+ from textual.app import ComposeResult
16
+ from textual.binding import Binding
17
+ from textual import work
18
+ from textual import getters
19
+ from textual import containers
20
+ from textual import events
21
+ from textual.actions import SkipAction
22
+
23
+ from textual.cache import LRUCache
24
+ from textual.reactive import var, Initialize
25
+ from textual.content import Content, Span
26
+ from textual.strip import Strip
27
+ from textual.style import Style
28
+ from textual.widget import Widget
29
+ from textual import widgets
30
+ from textual.visual import RenderOptions
31
+ from textual.widgets import OptionList, Input, DirectoryTree
32
+ from textual.widgets.option_list import Option
33
+
34
+ from tui import directory
35
+ from tui.fuzzy_index import FuzzyIndex
36
+ from tui.messages import Dismiss, InsertPath, PromptSuggestion
37
+ from tui.path_filter import PathFilter
38
+ from tui.widgets.project_directory_tree import ProjectDirectoryTree
39
+ from tui._path_fuzzy_search import PathFuzzySearch
40
+ from tui._path_match import match_path
41
+
42
+
43
+ class PathContent(Content):
44
+
45
+ def render_strips(
46
+ self, width: int, height: int | None, style: Style, options: RenderOptions
47
+ ) -> list[Strip]:
48
+ """Render the Visual into an iterable of strips. Part of the Visual protocol.
49
+
50
+ Args:
51
+ width: Width of desired render.
52
+ height: Height of desired render or `None` for any height.
53
+ style: The base style to render on top of.
54
+ options: Additional render options.
55
+
56
+ Returns:
57
+ An list of Strips.
58
+ """
59
+ if not width:
60
+ return []
61
+
62
+ line = self
63
+ if line.cell_length > width:
64
+ while line.cell_length >= width - 3 and "/" in line.plain:
65
+ line = line[line.plain.find("/") + 1 :]
66
+ line = Content.assemble(("⋯ ", "$text-error"), line)
67
+
68
+ lines = line._wrap_and_format(
69
+ width,
70
+ tab_size=8,
71
+ overflow="clip",
72
+ no_wrap=True,
73
+ selection=options.selection,
74
+ selection_style=options.selection_style,
75
+ post_style=options.post_style,
76
+ get_style=options.get_style,
77
+ )
78
+
79
+ if height is not None:
80
+ lines = lines[:height]
81
+
82
+ strip_lines = [Strip(*line.to_strip(style)) for line in lines]
83
+ return strip_lines
84
+
85
+
86
+ class FuzzyPathOptionList(OptionList):
87
+ """Option list with loading indicator override."""
88
+
89
+ def get_loading_widget(self) -> Widget:
90
+ from textual.widgets import LoadingIndicator
91
+
92
+ return LoadingIndicator()
93
+
94
+
95
+ class FuzzyInput(Input):
96
+ """Adds a Content placeholder to fuzzy input.
97
+
98
+ TODO: Add this ability to Textual.
99
+ """
100
+
101
+ HELP = """\
102
+ ## Fuzzy search
103
+
104
+ Type a few characters from the file you are searching for.
105
+
106
+ The search is *fuzzy*, and will match characters that aren't neccesarily next to each other—only the order matters.
107
+ """
108
+
109
+ def render_line(self, y: int) -> Strip:
110
+ if y == 0 and not self.value:
111
+ placeholder = Content.from_markup(self.placeholder).expand_tabs()
112
+ placeholder = placeholder.stylize(self.visual_style)
113
+ placeholder = placeholder.stylize(
114
+ self.get_visual_style("input--placeholder")
115
+ )
116
+ if self.has_focus:
117
+ cursor_style = self.get_visual_style("input--cursor")
118
+ if self._cursor_visible:
119
+ # If the placeholder is empty, there's no characters to stylise
120
+ # to make the cursor flash, so use a single space character
121
+ if len(placeholder) == 0:
122
+ placeholder = Content(" ")
123
+ placeholder = placeholder.stylize(cursor_style, 0, 1)
124
+
125
+ strip = Strip(placeholder.render_segments())
126
+ return strip
127
+
128
+ return super().render_line(y)
129
+
130
+
131
+ class PathSearch(containers.VerticalGroup):
132
+
133
+ BINDING_GROUP_TITLE = "Path search"
134
+
135
+ CURSOR_BINDING_GROUP = Binding.Group(description="Move selection")
136
+ BINDINGS = [
137
+ Binding(
138
+ "up", "cursor_up", "Cursor up", group=CURSOR_BINDING_GROUP, priority=True
139
+ ),
140
+ Binding(
141
+ "down",
142
+ "cursor_down",
143
+ "Cursor down",
144
+ group=CURSOR_BINDING_GROUP,
145
+ priority=True,
146
+ ),
147
+ Binding("enter", "submit", "Insert path", priority=True, show=False),
148
+ Binding("escape", "dismiss", "Dismiss", priority=True, show=False),
149
+ Binding("tab", "switch_picker", "Switch picker", priority=True, show=False),
150
+ ]
151
+
152
+ def get_fuzzy_search(self) -> PathFuzzySearch:
153
+ return PathFuzzySearch(case_sensitive=False)
154
+
155
+ root: var[Path] = var(Path("./"))
156
+ paths: var[list[Path]] = var(list)
157
+ display_paths: var[list[str]] = var(list)
158
+ filtered_path_indices: var[list[int]] = var(list)
159
+ loaded = var(False)
160
+ filter = var("")
161
+ fuzzy_search: var[PathFuzzySearch] = var(Initialize(get_fuzzy_search))
162
+ show_tree_picker: var[bool] = var(False)
163
+
164
+ option_list = getters.query_one(FuzzyPathOptionList)
165
+ tree_view = getters.query_one(ProjectDirectoryTree)
166
+ input = getters.query_one(Input)
167
+
168
+ def __init__(self, root: Path) -> None:
169
+ super().__init__()
170
+ self.set_reactive(PathSearch.root, root)
171
+ self.root = root
172
+ self.fuzzy_index = FuzzyIndex()
173
+ self.pool = concurrent.futures.InterpreterPoolExecutor(
174
+ thread_name_prefix=f"fuzzy-path-search-{root}"
175
+ )
176
+ self.search_cache: LRUCache[str, list[tuple[float, Sequence[int], str]]] = (
177
+ LRUCache(1024)
178
+ )
179
+
180
+ def compose(self) -> ComposeResult:
181
+ with widgets.ContentSwitcher(initial="path-search-fuzzy"):
182
+ with containers.VerticalGroup(id="path-search-fuzzy"):
183
+ yield FuzzyInput(
184
+ compact=True, placeholder="fuzzy search \t[r]▌tab▐[/r] tree view"
185
+ )
186
+ yield FuzzyPathOptionList()
187
+ with containers.VerticalGroup(id="path-search-tree"):
188
+ yield widgets.Static(
189
+ Content.from_markup(
190
+ "tree view \t[r]▌tab▐[/] fuzzy search"
191
+ ).expand_tabs(),
192
+ classes="message",
193
+ )
194
+ yield ProjectDirectoryTree(self.root).data_bind(path=PathSearch.root)
195
+
196
+ def on_mount(self) -> None:
197
+ tree = self.tree_view
198
+ tree.guide_depth = 2
199
+ tree.center_scroll = True
200
+
201
+ def watch_show_tree_picker(self, show_tree_picker: bool) -> None:
202
+ content_switcher = self.query_one(widgets.ContentSwitcher)
203
+ content_switcher.current = (
204
+ "path-search-tree" if show_tree_picker else "path-search-fuzzy"
205
+ )
206
+ if show_tree_picker:
207
+ self.tree_view.focus()
208
+
209
+ else:
210
+ self.input.focus()
211
+
212
+ def action_switch_picker(self) -> None:
213
+ self.show_tree_picker = not self.show_tree_picker
214
+
215
+ def fuzzy_match_paths(
216
+ self, search: str, paths: list[str]
217
+ ) -> list[tuple[float, Sequence[int], str]]:
218
+
219
+ scores = list(
220
+ self.pool.map(
221
+ match_path,
222
+ [(search, path) for path in paths],
223
+ chunksize=10,
224
+ )
225
+ )
226
+ return scores
227
+
228
+ async def search(self, search: str) -> None:
229
+ if not search:
230
+ self.option_list.set_options(
231
+ [
232
+ Option(self.highlight_path(path), path)
233
+ for path in self.display_paths[:100]
234
+ ],
235
+ )
236
+ return
237
+
238
+ display_paths = await self.fuzzy_index.search(search)
239
+
240
+ if len(display_paths) > 20:
241
+ if (scored_paths := self.search_cache.get(search)) is None:
242
+ scored_paths = await asyncio.to_thread(
243
+ self.fuzzy_match_paths, search, display_paths
244
+ )
245
+ self.search_cache[search] = scored_paths
246
+ else:
247
+ fuzzy_search = self.fuzzy_search
248
+ scored_paths: list[tuple[float, Sequence[int], str]] = [
249
+ (
250
+ *fuzzy_search.match(search, path),
251
+ path,
252
+ )
253
+ for path in display_paths
254
+ ]
255
+
256
+ scored_paths = sorted(
257
+ [score for score in scored_paths if score[0]],
258
+ key=itemgetter(0),
259
+ reverse=True,
260
+ )
261
+
262
+ scores = [
263
+ (score, highlights, self.highlight_path(path))
264
+ for score, highlights, path in scored_paths[:30]
265
+ ]
266
+
267
+ def highlight_offsets(path: Content, offsets: Sequence[int]) -> Content:
268
+ highlighted_path = path.add_spans(
269
+ [Span(offset, offset + 1, "underline") for offset in offsets]
270
+ )
271
+ return PathContent(
272
+ highlighted_path.plain,
273
+ list(highlighted_path.spans),
274
+ highlighted_path.cell_length,
275
+ )
276
+
277
+ self.option_list.set_options(
278
+ [
279
+ Option(highlight_offsets(path, offsets), id=path.plain)
280
+ for index, (score, offsets, path) in enumerate(scores)
281
+ ]
282
+ )
283
+ with self.option_list.prevent(OptionList.OptionHighlighted):
284
+ self.option_list.highlighted = 0
285
+ self.post_message(PromptSuggestion(""))
286
+
287
+ def action_cursor_down(self) -> None:
288
+ if self.show_tree_picker:
289
+ self.tree_view.action_cursor_down()
290
+ else:
291
+ self.option_list.action_cursor_down()
292
+
293
+ def action_cursor_up(self) -> None:
294
+ if self.show_tree_picker:
295
+ self.tree_view.action_cursor_up()
296
+ else:
297
+ self.option_list.action_cursor_up()
298
+
299
+ def action_dismiss(self) -> None:
300
+ self.post_message(Dismiss(self))
301
+ self.filter = ""
302
+
303
+ def on_show(self) -> None:
304
+ self.focus()
305
+
306
+ def focus(self, scroll_visible: bool = False) -> Self:
307
+ if self.show_tree_picker:
308
+ return self.tree_view.focus(scroll_visible=scroll_visible)
309
+ else:
310
+ return self.input.focus(scroll_visible=scroll_visible)
311
+
312
+ def on_descendant_blur(self, event: events.DescendantBlur) -> None:
313
+ if self.show_tree_picker:
314
+ if event.widget == self.tree_view:
315
+ self.post_message(Dismiss(self))
316
+ else:
317
+ if event.widget == self.input:
318
+ self.post_message(Dismiss(self))
319
+
320
+ @classmethod
321
+ def make_relative(cls, path: Path, root: Path) -> Path:
322
+ """Make a path relative from the root.
323
+
324
+ Args:
325
+ path: Path to consider.
326
+ root: Root path.
327
+
328
+ Returns:
329
+ A relative path.
330
+ """
331
+ return path.resolve().relative_to(root.resolve())
332
+
333
+ @on(DirectoryTree.NodeHighlighted)
334
+ async def on_node_highlighted(self, event: DirectoryTree.NodeHighlighted) -> None:
335
+ event.stop()
336
+
337
+ dir_entry = event.node.data
338
+ if dir_entry is not None:
339
+ try:
340
+ path = await asyncio.to_thread(
341
+ self.make_relative, dir_entry.path, self.root
342
+ )
343
+ except ValueError:
344
+ # Being defensive here, shouldn't occur
345
+ return
346
+ tree_path = str(path)
347
+ self.post_message(PromptSuggestion(tree_path))
348
+
349
+ @on(DirectoryTree.FileSelected)
350
+ async def on_file_selected(self, event: DirectoryTree.FileSelected) -> None:
351
+ event.stop()
352
+
353
+ dir_entry = event.node.data
354
+ if dir_entry is not None:
355
+ try:
356
+ path = await asyncio.to_thread(
357
+ self.make_relative, dir_entry.path, self.root
358
+ )
359
+ except ValueError:
360
+ return
361
+ tree_path = str(path)
362
+ self.post_message(InsertPath(tree_path))
363
+ self.post_message(Dismiss(self))
364
+
365
+ @on(Input.Changed)
366
+ async def on_input_changed(self, event: Input.Changed):
367
+ await self.search(event.value)
368
+
369
+ @on(OptionList.OptionHighlighted)
370
+ async def on_option_list_changed(self, event: OptionList.OptionHighlighted):
371
+ event.stop()
372
+ if event.option:
373
+ self.post_message(PromptSuggestion(event.option.id))
374
+
375
+ @on(OptionList.OptionSelected)
376
+ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
377
+ self.action_submit()
378
+
379
+ def action_submit(self):
380
+ if self.show_tree_picker:
381
+ raise SkipAction()
382
+
383
+ elif (highlighted := self.option_list.highlighted) is not None:
384
+ option = self.option_list.options[highlighted]
385
+ if option.id:
386
+ self.post_message(InsertPath(option.id))
387
+ self.post_message(Dismiss(self))
388
+
389
+ def get_path_filter(self, project_path: Path) -> PathFilter:
390
+ """Get a PathFilter insance for the give project path.
391
+
392
+ Args:
393
+ project_path: Project path.
394
+
395
+ Returns:
396
+ `PathFilter` object.
397
+ """
398
+ path_filter = PathFilter.from_git_root(project_path)
399
+ return path_filter
400
+
401
+ def reset(self) -> None:
402
+ """Reset and focus input."""
403
+ self.input.clear()
404
+ self.input.focus()
405
+
406
+ @work(exclusive=True)
407
+ async def refresh_paths(self):
408
+ self.option_list.set_loading(True)
409
+ root = self.root
410
+ try:
411
+ path_filter = await asyncio.to_thread(self.get_path_filter, root)
412
+ self.tree_view.path_filter = path_filter
413
+ self.tree_view.clear()
414
+ self.tree_view.reload()
415
+ paths = await directory.scan(
416
+ root, path_filter=path_filter, add_directories=True
417
+ )
418
+
419
+ def make_absolute(paths: list[Path]) -> list[Path]:
420
+ """Make all paths absolute.
421
+
422
+ Args:
423
+ paths: A list of paths.
424
+
425
+ Returns:
426
+ List of absolute paths,
427
+
428
+ """
429
+ return [path.absolute() for path in paths]
430
+
431
+ paths = await asyncio.to_thread(make_absolute, paths)
432
+ self.root = root
433
+ self.paths = paths
434
+ except Exception:
435
+ self.option_list.set_loading(False)
436
+ raise
437
+
438
+ def highlight_path(self, path: str) -> PathContent:
439
+ content = Content.styled(path, "$text 50%")
440
+ if os.path.split(path)[-1].startswith("."):
441
+ return PathContent(
442
+ content.plain, list(content.spans), cell_length=content.cell_length
443
+ )
444
+ content = content.highlight_regex("[^/]*?$", style="$text-primary")
445
+ content = content.highlight_regex(r"\.[^/]*$", style="italic")
446
+ return PathContent(
447
+ content.plain, list(content.spans), cell_length=content.cell_length
448
+ )
449
+
450
+ @work(description="watch_paths")
451
+ async def watch_paths(self, paths: list[Path]) -> None:
452
+
453
+ def path_display(path: Path) -> str:
454
+ try:
455
+ is_directory = path.is_dir()
456
+ except OSError:
457
+ is_directory = False
458
+ if is_directory:
459
+ return str(path.relative_to(self.root)) + "/"
460
+ else:
461
+ return str(path.relative_to(self.root))
462
+
463
+ def make_display_paths() -> list[str]:
464
+ display_paths = sorted(map(path_display, paths), key=str.lower)
465
+ display_paths.sort(key=lambda path: path.count("/"))
466
+ return display_paths
467
+
468
+ self.display_paths = await asyncio.to_thread(make_display_paths)
469
+
470
+ self.option_list.highlighted = None
471
+ self._update_paths(self.display_paths)
472
+
473
+ self.option_list.set_options(
474
+ [
475
+ Option(self.highlight_path(path), id=path)
476
+ for path in self.display_paths[:100]
477
+ ]
478
+ )
479
+ with self.option_list.prevent(OptionList.OptionHighlighted):
480
+ self.option_list.highlighted = 0
481
+
482
+ self.post_message(PromptSuggestion(""))
483
+
484
+ @work(description="update_paths")
485
+ async def _update_paths(self, paths: list[str]) -> None:
486
+ """Update the paths index.
487
+
488
+ Args:
489
+ paths: A list of paths.
490
+ """
491
+ await self.fuzzy_index.update_paths(paths)
492
+ self.call_after_refresh(self.option_list.set_loading, False)