@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,553 @@
1
+ """Log Screen — collapsible per-message view of the JSON-RPC log (F4)."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from rich.text import Text
9
+ from textual import on
10
+ from textual.app import ComposeResult
11
+ from textual.binding import Binding
12
+ from textual.containers import Vertical, VerticalScroll
13
+ from textual.message import Message
14
+ from textual.reactive import reactive
15
+ from textual.screen import ModalScreen
16
+ from textual.widgets import Static
17
+
18
+ from tui.paths import get_log
19
+
20
+
21
+ _TAG_RE = re.compile(r"^\[(client|agent|error)\]\s*(.*)$", re.DOTALL)
22
+ _PAGE_SIZE = 50
23
+
24
+
25
+ def _find_log_file() -> Path | None:
26
+ """Return the most recent message log file, or None if empty."""
27
+ log_dir = get_log() / "messages"
28
+ if not log_dir.exists():
29
+ return None
30
+ candidates = [
31
+ p for p in log_dir.iterdir()
32
+ if p.is_file() and p.suffix == ".jsonl" and not p.name.startswith(".")
33
+ ]
34
+ if not candidates:
35
+ return None
36
+ return max(candidates, key=lambda p: p.stat().st_mtime)
37
+
38
+
39
+ # ── JSON syntax highlighting (ANSI) ───────────────────────────────
40
+
41
+ _ANSI_HIGHLIGHT = re.compile(
42
+ r'("(?:[^"\\]|\\.)*")(\s*:)?'
43
+ r'|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)'
44
+ r'|\b(true|false|null)\b',
45
+ )
46
+
47
+
48
+ def _ansi_repl(m: re.Match) -> str:
49
+ s, colon, num, lit = m.group(1), m.group(2), m.group(3), m.group(4)
50
+ if s is not None:
51
+ if colon:
52
+ return f"\x1b[36m{s}\x1b[0m{colon}"
53
+ return f"\x1b[33m{s}\x1b[0m"
54
+ if num is not None:
55
+ return f"\x1b[35m{num}\x1b[0m"
56
+ if lit is not None:
57
+ return f"\x1b[32m{lit}\x1b[0m"
58
+ return m.group(0)
59
+
60
+
61
+ def _highlight_json(raw: str) -> str:
62
+ """Return a pretty-printed JSON string with ANSI color escapes."""
63
+ try:
64
+ obj = json.loads(raw)
65
+ formatted = json.dumps(obj, indent=2, ensure_ascii=False)
66
+ except Exception:
67
+ return raw
68
+ return _ANSI_HIGHLIGHT.sub(_ansi_repl, formatted)
69
+
70
+
71
+ def _summarize_message_log(record: dict) -> str:
72
+ """Extract a one-line summary of a MessageLog event record."""
73
+ event = record.get("event", "")
74
+ turn = record.get("turn", "")
75
+ turn_str = f" [dim](turn {turn})[/dim]" if turn != "" else ""
76
+ if event == "user_input":
77
+ text = record.get("text", "")
78
+ return f"user_input [dim]\"{text[:60]}{'…' if len(text) > 60 else ''}\"[/dim]{turn_str}"
79
+ if event == "agent_output":
80
+ text = record.get("text", "")
81
+ return f"agent_output [dim]\"{text[:60]}{'…' if len(text) > 60 else ''}\"[/dim]{turn_str}"
82
+ if event == "agent_thought":
83
+ return f"agent_thought{turn_str}"
84
+ if event == "tool_call":
85
+ name = record.get("tool_name", "")
86
+ return f"tool_call [bold]{name}[/bold]{turn_str}"
87
+ if event == "tool_result":
88
+ name = record.get("tool_name", "")
89
+ status = record.get("status", "success")
90
+ return f"tool_result [bold]{name}[/bold] → {status}{turn_str}"
91
+ if event == "turn_end":
92
+ reason = record.get("stop_reason", "")
93
+ return f"turn_end stop={reason}{turn_str}"
94
+ if event == "error":
95
+ msg = record.get("message", "")
96
+ return f"error: {msg[:60]}{turn_str}"
97
+ return str(record)[:80]
98
+
99
+
100
+ def _summarize(tag: str, raw: str) -> str:
101
+ """Extract a one-line summary of a JSON-RPC or MessageLog message."""
102
+ try:
103
+ obj = json.loads(raw)
104
+ except Exception:
105
+ return raw[:80]
106
+ if not isinstance(obj, dict):
107
+ return str(obj)[:80]
108
+ # MessageLog event record (event-driven format)
109
+ if "event" in obj:
110
+ return _summarize_message_log(obj)
111
+ # Legacy JSON-RPC wire format
112
+ if tag == "agent" and "method" in obj:
113
+ method = obj.get("method", "")
114
+ params = obj.get("params", {})
115
+ if isinstance(params, dict):
116
+ update = params.get("update", {})
117
+ if isinstance(update, dict):
118
+ kind = update.get("sessionUpdate", "")
119
+ if kind:
120
+ return f"{method} → {kind}"
121
+ sid = params.get("sessionId", "")
122
+ if method == "session/update" and sid:
123
+ return f"{method} [dim]({sid[:8]}…)[/dim]"
124
+ return method
125
+ if tag == "client":
126
+ method = obj.get("method", "")
127
+ params = obj.get("params", {})
128
+ if isinstance(params, dict):
129
+ if method == "session/prompt":
130
+ prompt = params.get("prompt", [])
131
+ if isinstance(prompt, list) and prompt:
132
+ first = prompt[0]
133
+ if isinstance(first, dict):
134
+ text = first.get("text", "")
135
+ return f"{method} [dim]\"{text[:40]}{'…' if len(text) > 40 else ''}\"[/dim]"
136
+ return method
137
+ if "error" in obj:
138
+ err = obj.get("error", {})
139
+ return f"error: {err.get('message', '?')}" if isinstance(err, dict) else "error"
140
+ return obj.get("method", raw[:60])
141
+
142
+
143
+ # ── LogEntry: a single collapsible message ────────────────────────
144
+
145
+ class LogEntryClicked(Message):
146
+ """Posted when a LogEntry is clicked. Carries the entry reference."""
147
+
148
+ def __init__(self, entry: "LogEntry") -> None:
149
+ super().__init__()
150
+ self.entry = entry
151
+
152
+
153
+ class LogEntry(Static):
154
+ """One log line: collapsed = summary, expanded = full pretty JSON."""
155
+
156
+ DEFAULT_CSS = """
157
+ LogEntry {
158
+ height: auto;
159
+ padding: 0 1;
160
+ }
161
+ LogEntry > .log-summary {
162
+ height: 1;
163
+ }
164
+ LogEntry > .log-summary:hover {
165
+ background: $panel;
166
+ }
167
+ LogEntry.-selected > .log-summary {
168
+ background: $accent;
169
+ color: $text;
170
+ text-style: bold;
171
+ }
172
+ LogEntry > .log-body {
173
+ height: auto;
174
+ padding: 0 0 0 4;
175
+ }
176
+ LogEntry.-collapsed > .log-body {
177
+ display: none;
178
+ }
179
+ """
180
+
181
+ expanded: reactive[bool] = reactive(False)
182
+ selected: reactive[bool] = reactive(False)
183
+
184
+ def __init__(self, tag: str, raw: str) -> None:
185
+ super().__init__()
186
+ self.tag = tag
187
+ self.raw = raw
188
+
189
+ def compose(self) -> ComposeResult:
190
+ yield Static(self._summary_text(), classes="log-summary")
191
+ yield Static(self._body_text(), classes="log-body", markup=False)
192
+
193
+ def on_mount(self) -> None:
194
+ self.set_class(not self.expanded, "-collapsed")
195
+
196
+ def _summary_text(self) -> Text:
197
+ marker = "▼" if self.expanded else "▶"
198
+ if self.tag == "client":
199
+ style = "bold cyan"
200
+ tag_label = "[→]"
201
+ elif self.tag == "agent":
202
+ style = "bold green"
203
+ tag_label = "[←]"
204
+ else:
205
+ style = "bold red"
206
+ tag_label = "[!]"
207
+ t = Text()
208
+ t.append(f" {marker} ", style="dim")
209
+ t.append(tag_label, style=style)
210
+ t.append(" ")
211
+ # render the summary as markup
212
+ from rich.markup import render as render_markup
213
+ try:
214
+ rendered = render_markup(self._summary_markup())
215
+ t.append_text(rendered)
216
+ except Exception:
217
+ t.append(self._summary_markup())
218
+ return t
219
+
220
+ def _summary_markup(self) -> str:
221
+ return _summarize(self.tag, self.raw)
222
+
223
+ def _body_text(self) -> Text:
224
+ return Text.from_ansi(_highlight_json(self.raw))
225
+
226
+ def watch_expanded(self, expanded: bool) -> None:
227
+ self.set_class(not expanded, "-collapsed")
228
+ # Refresh the summary to update the ▼ / ▶ marker
229
+ try:
230
+ self.query_one(".log-summary", Static).update(self._summary_text())
231
+ except Exception:
232
+ pass
233
+
234
+ def on_click(self) -> None:
235
+ self.expanded = not self.expanded
236
+ # Notify the screen to update its current_index
237
+ self.post_message(LogEntryClicked(self))
238
+
239
+ def watch_selected(self, selected: bool) -> None:
240
+ self.set_class(selected, "-selected")
241
+ if selected:
242
+ self.scroll_visible()
243
+
244
+
245
+ # ── LogScreen ──────────────────────────────────────────────────────
246
+
247
+ class LogScreen(ModalScreen[None]):
248
+ """Page-based log viewer (F4). Shows all log entries with 50 per page."""
249
+
250
+ BINDINGS = [
251
+ Binding("escape,f4", "dismiss", "Close"),
252
+ # Entry navigation within current page
253
+ Binding("down,j", "next_entry", "Next entry"),
254
+ Binding("up,k", "prev_entry", "Prev entry"),
255
+ # Page navigation (log is newest-first, so PageDown = older entries)
256
+ Binding("pagedown,n", "prev_page", "Prev page", show=False),
257
+ Binding("pageup,p", "next_page", "Next page", show=False),
258
+ Binding("home,g", "first_page", "First page", show=False),
259
+ Binding("end,G", "last_page", "Last page", show=False),
260
+ # Toggle current
261
+ Binding("enter,space,l", "toggle_current", "Toggle", show=False),
262
+ Binding("o", "open_current", "Open", show=False),
263
+ Binding("O", "expand_all", "Open all"),
264
+ Binding("C", "collapse_all", "Close all"),
265
+ # Misc
266
+ Binding("c", "copy_all", "Copy"),
267
+ ]
268
+
269
+ DEFAULT_CSS = """
270
+ LogScreen {
271
+ align: center middle;
272
+ }
273
+
274
+ LogScreen > Vertical {
275
+ width: 95%;
276
+ height: 90%;
277
+ border: thick $primary;
278
+ background: $surface;
279
+ }
280
+
281
+ LogScreen #log-header {
282
+ height: 1;
283
+ background: $primary;
284
+ color: $text;
285
+ padding: 0 1;
286
+ }
287
+
288
+ LogScreen VerticalScroll {
289
+ height: 1fr;
290
+ background: $surface;
291
+ }
292
+ """
293
+
294
+ def __init__(self, log_path: Path | None = None) -> None:
295
+ super().__init__()
296
+ self._log_path = log_path
297
+ self._file_pos = 0
298
+ self._poll_timer: object = None
299
+ self._entries: list[tuple[str, str]] = []
300
+ self._current_page: int = 1
301
+ self._current_index: int = -1
302
+ self._file_was_present: bool = False
303
+
304
+ def compose(self) -> ComposeResult:
305
+ path = self._log_path or _find_log_file()
306
+ self._log_path = path
307
+ with Vertical():
308
+ yield Static(self._header_text(), id="log-header")
309
+ yield VerticalScroll(id="log-list")
310
+
311
+ def on_mount(self) -> None:
312
+ if self._log_path is None:
313
+ self._append_system("No log file yet. Start an agent to begin logging.")
314
+ return
315
+ if not self._log_path.exists():
316
+ self._append_system(
317
+ f"⌛ Waiting for log file to be created…\n {self._log_path}"
318
+ )
319
+ self._poll_timer = self.set_interval(0.2, self._poll_new_lines)
320
+ return
321
+ self._begin_tailing()
322
+
323
+ # ── Page helpers ───────────────────────────────────────────────
324
+
325
+ def _total_pages(self) -> int:
326
+ return max(1, (len(self._entries) + _PAGE_SIZE - 1) // _PAGE_SIZE)
327
+
328
+ def _page_range(self) -> tuple[int, int]:
329
+ start = (self._current_page - 1) * _PAGE_SIZE
330
+ end = min(start + _PAGE_SIZE, len(self._entries))
331
+ return start, end
332
+
333
+ def _render_page(self) -> None:
334
+ scroller = self.query_one("#log-list", VerticalScroll)
335
+ scroller.remove_children()
336
+ start, end = self._page_range()
337
+ page_entries = self._entries[start:end]
338
+ for tag, raw in page_entries:
339
+ scroller.mount(LogEntry(tag, raw))
340
+ self._current_index = 0 if page_entries else -1
341
+ self._apply_selection()
342
+ self._refresh_header()
343
+
344
+ def _navigate_to_page(self, page: int) -> None:
345
+ total = self._total_pages()
346
+ self._current_page = max(1, min(page, total))
347
+ self._render_page()
348
+
349
+ # ── File loading ───────────────────────────────────────────────
350
+
351
+ def _begin_tailing(self) -> None:
352
+ """Read entire log file and start polling."""
353
+ assert self._log_path is not None
354
+ try:
355
+ with self._log_path.open("r", encoding="utf-8", errors="replace") as f:
356
+ data = f.read()
357
+ self._file_pos = self._log_path.stat().st_size
358
+ except OSError as e:
359
+ self._append_system(f"Failed to open log: {e}")
360
+ return
361
+ self._file_was_present = True
362
+ self._entries = []
363
+ self._ingest(data)
364
+ self._current_page = self._total_pages()
365
+ self._render_page()
366
+ if self._poll_timer is None:
367
+ self._poll_timer = self.set_interval(0.2, self._poll_new_lines)
368
+
369
+ def on_unmount(self) -> None:
370
+ if self._poll_timer is not None:
371
+ stop = getattr(self._poll_timer, "stop", None)
372
+ if callable(stop):
373
+ stop()
374
+ self._poll_timer = None
375
+
376
+ def _header_text(self) -> str:
377
+ total = len(self._entries)
378
+ pages = self._total_pages()
379
+ start, end = self._page_range()
380
+ display_start = start + 1 if total > 0 else 0
381
+ return (
382
+ f"📋 Session log — Page {self._current_page}/{pages} · "
383
+ f"Showing {display_start}-{end} of {total} entries │ "
384
+ f"↑↓: entry · n/p: page · g/G: first/last · ⏎ toggle · F4/Esc close"
385
+ if self._log_path
386
+ else "📋 Session log — (no log file found)"
387
+ )
388
+
389
+ def _refresh_header(self) -> None:
390
+ try:
391
+ self.query_one("#log-header", Static).update(self._header_text())
392
+ except Exception:
393
+ pass
394
+
395
+ def _apply_selection(self) -> None:
396
+ scroller = self.query_one("#log-list", VerticalScroll)
397
+ for i, child in enumerate(scroller.children):
398
+ if isinstance(child, LogEntry):
399
+ child.selected = (i == self._current_index)
400
+
401
+ @on(LogEntryClicked)
402
+ def on_log_entry_clicked(self, event: LogEntryClicked) -> None:
403
+ scroller = self.query_one("#log-list", VerticalScroll)
404
+ for i, child in enumerate(scroller.children):
405
+ if child is event.entry:
406
+ self._current_index = i
407
+ self._apply_selection()
408
+ return
409
+
410
+ def _append_system(self, msg: str) -> None:
411
+ scroller = self.query_one("#log-list", VerticalScroll)
412
+ scroller.mount(Static(f"[dim]{msg}[/dim]"))
413
+
414
+ # ── Polling for new lines ──────────────────────────────────────
415
+
416
+ def _poll_new_lines(self) -> None:
417
+ if self._log_path is None:
418
+ return
419
+ if not self._log_path.exists():
420
+ return
421
+ if not self._file_was_present:
422
+ self._append_system(f"── log file appeared: {self._log_path} ──")
423
+ self._begin_tailing()
424
+ return
425
+ try:
426
+ size = self._log_path.stat().st_size
427
+ if size < self._file_pos:
428
+ self._file_pos = 0
429
+ if size == self._file_pos:
430
+ return
431
+ with self._log_path.open("r", encoding="utf-8", errors="replace") as f:
432
+ f.seek(self._file_pos)
433
+ new_text = f.read()
434
+ self._file_pos = size
435
+ if new_text:
436
+ self._ingest(new_text)
437
+ except OSError:
438
+ pass
439
+
440
+ def _ingest(self, chunk: str) -> None:
441
+ was_on_last_page = (
442
+ len(self._entries) == 0
443
+ or self._current_page == self._total_pages()
444
+ )
445
+ for line in chunk.splitlines():
446
+ if not line.strip():
447
+ continue
448
+ try:
449
+ record = json.loads(line)
450
+ if isinstance(record, dict) and "event" in record:
451
+ event = record.get("event", "")
452
+ if event in ("user_input",):
453
+ tag = "client"
454
+ elif event in ("agent_output", "agent_thought", "tool_call", "tool_result", "turn_end"):
455
+ tag = "agent"
456
+ else:
457
+ tag = "error"
458
+ self._entries.append((tag, line))
459
+ continue
460
+ except (json.JSONDecodeError, ValueError):
461
+ pass
462
+ m = _TAG_RE.match(line)
463
+ if m:
464
+ self._entries.append((m.group(1), m.group(2)))
465
+ else:
466
+ self._entries.append(("error", line))
467
+ if was_on_last_page:
468
+ self._current_page = self._total_pages()
469
+ self._render_page()
470
+ else:
471
+ self._refresh_header()
472
+
473
+ # ── Actions ────────────────────────────────────────────────────
474
+
475
+ def _current_entry(self) -> LogEntry | None:
476
+ scroller = self.query_one("#log-list", VerticalScroll)
477
+ for i, child in enumerate(scroller.children):
478
+ if isinstance(child, LogEntry) and i == self._current_index:
479
+ return child
480
+ return None
481
+
482
+ def action_next_page(self) -> None:
483
+ if self._current_page < self._total_pages():
484
+ self._navigate_to_page(self._current_page + 1)
485
+
486
+ def action_prev_page(self) -> None:
487
+ if self._current_page > 1:
488
+ self._navigate_to_page(self._current_page - 1)
489
+
490
+ def action_first_page(self) -> None:
491
+ self._navigate_to_page(1)
492
+
493
+ def action_last_page(self) -> None:
494
+ self._navigate_to_page(self._total_pages())
495
+
496
+ def action_next_entry(self) -> None:
497
+ scroller = self.query_one("#log-list", VerticalScroll)
498
+ count = sum(1 for c in scroller.children if isinstance(c, LogEntry))
499
+ if count == 0:
500
+ return
501
+ if self._current_index < 0:
502
+ self._current_index = 0
503
+ else:
504
+ self._current_index = min(count - 1, self._current_index + 1)
505
+ self._apply_selection()
506
+
507
+ def action_prev_entry(self) -> None:
508
+ scroller = self.query_one("#log-list", VerticalScroll)
509
+ count = sum(1 for c in scroller.children if isinstance(c, LogEntry))
510
+ if count == 0:
511
+ return
512
+ if self._current_index < 0:
513
+ self._current_index = count - 1
514
+ else:
515
+ self._current_index = max(0, self._current_index - 1)
516
+ self._apply_selection()
517
+
518
+ def action_toggle_current(self) -> None:
519
+ entry = self._current_entry()
520
+ if entry is not None:
521
+ entry.expanded = not entry.expanded
522
+
523
+ def action_open_current(self) -> None:
524
+ entry = self._current_entry()
525
+ if entry is not None:
526
+ entry.expanded = True
527
+
528
+ def action_expand_all(self) -> None:
529
+ scroller = self.query_one("#log-list", VerticalScroll)
530
+ for child in scroller.children:
531
+ if isinstance(child, LogEntry):
532
+ child.expanded = True
533
+
534
+ def action_collapse_all(self) -> None:
535
+ scroller = self.query_one("#log-list", VerticalScroll)
536
+ for child in scroller.children:
537
+ if isinstance(child, LogEntry):
538
+ child.expanded = False
539
+
540
+ def action_copy_all(self) -> None:
541
+ try:
542
+ import pyperclip
543
+ except ImportError:
544
+ self.app.notify("pyperclip not installed", title="Log", severity="warning")
545
+ return
546
+ if self._log_path is None:
547
+ return
548
+ try:
549
+ text = self._log_path.read_text(errors="replace")
550
+ pyperclip.copy(text)
551
+ self.app.notify("Log copied to clipboard", title="Log")
552
+ except Exception as e:
553
+ self.app.notify(f"Copy failed: {e}", title="Log", severity="error")