one-code-extension 0.4.0 → 0.5.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 (419) hide show
  1. package/README.md +219 -219
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +66 -34
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +123 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +52 -0
  63. package/extensions/background/wakeup.ts +159 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +88 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +19 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -92,7 +92,7 @@ export function isRealModel(id: string | undefined): id is string {
92
92
  /**
93
93
  * The key that cycles permission modes. Claude Code cycles on shift+tab and
94
94
  * says so in this footer; pi reserves shift+tab for the effort dial, so One Code
95
- * cycles on ctrl+q (see docs/decisions/modes.md) — alt+m on Windows and WSL,
95
+ * cycles on ctrl+q (see working-docs/decisions/modes.md) — alt+m on Windows and WSL,
96
96
  * where pi owns ctrl+q (lib/keys.ts) — and the hint names that key.
97
97
  */
98
98
  export const CYCLE_KEY = modeCycleKey();
@@ -108,16 +108,16 @@ export const CYCLE_KEY_HINT = `${CYCLE_KEY} to cycle`;
108
108
  */
109
109
  export function modeBadge(
110
110
  mode: PermissionMode,
111
- opts?: { paused?: boolean; classifierModel?: string; streaming?: boolean },
111
+ opts?: { paused?: boolean; classifier?: string; streaming?: boolean },
112
112
  ): string {
113
113
  const base = badgeBase(mode, opts);
114
114
  // Claude Code appends the interrupt hint to this line while the model works.
115
115
  return `${base} (${CYCLE_KEY_HINT})${opts?.streaming ? " · esc to interrupt" : ""}`;
116
116
  }
117
117
 
118
- function badgeBase(mode: PermissionMode, opts?: { paused?: boolean; classifierModel?: string }): string {
118
+ function badgeBase(mode: PermissionMode, opts?: { paused?: boolean; classifier?: string }): string {
119
119
  if (mode !== "auto") return MODE_BADGES[mode];
120
- const suffix = opts?.classifierModel ? ` · ${shortModelName(opts.classifierModel)}` : "";
120
+ const suffix = opts?.classifier ? ` · ${shortModelName(opts.classifier)}` : "";
121
121
  return opts?.paused ? `${PAUSE} auto mode paused${suffix}` : `${MODE_BADGES.auto}${suffix}`;
122
122
  }
123
123
 
@@ -131,6 +131,8 @@ export const PERMISSION_STATUS_CHANNEL = "one-code:permission-status";
131
131
  export interface PermissionStatus {
132
132
  mode: string;
133
133
  paused: boolean;
134
+ /** A tool-driven switch anchors its state/reminders to this result, not a parallel sibling's. */
135
+ toolCallId?: string;
134
136
  /** `provider/id` of the classifier screening calls (or about to). */
135
137
  classifier?: string;
136
138
  /** Whether `classifier` is pinned by a real call, or still the planned first candidate. */
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Mounts the /permissions panel (`ctx.ui.custom`) over the pure state machine
3
+ * and renderer, and runs its effects through the callbacks the permissions
4
+ * extension passes in: the panel owns no gate state. It resolves when the
5
+ * panel closes, with what the user approved and the changes they made; the
6
+ * caller tells the model.
7
+ *
8
+ * Editing the environment needs pi's multi-line editor, which cannot open over
9
+ * a custom component, so that effect closes the panel with `editEnvironment`
10
+ * set. The caller runs the editor and reopens the panel with the same state
11
+ * and change list, so approvals and the tab survive the round trip.
12
+ */
13
+
14
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
15
+ import { panelUi } from "../../lib/panel-focus.ts";
16
+ import { boundedDockHeight, safeThemeBold, safeThemeInverse, safeThemePaint } from "../../lib/tui-render.ts";
17
+ import { decodePanelKey } from "./keys.ts";
18
+ import { type PanelPaint, renderPanel } from "./render.ts";
19
+ import { type AutoSection, applyPanelKey, initialPanelState, type PanelEffect, type PanelState, type PanelView, type RuleTab } from "./state.ts";
20
+
21
+ const PANEL_MAX_HEIGHT = 24;
22
+
23
+ /** The panel's I/O. Each change callback returns its change line, or throws with the reason. */
24
+ export interface PermissionsPanelHost {
25
+ /** The current rules, denials and save destinations. Rebuilt after every effect. */
26
+ view: () => PanelView;
27
+ /** One dim status line under the tabs. */
28
+ status: () => string | undefined;
29
+ addRule: (behavior: RuleTab, rule: string, destination: string) => string;
30
+ /** Delete a rule by its row key. */
31
+ deleteRule: (behavior: RuleTab, key: string) => string;
32
+ addAutoRule: (section: AutoSection, text: string) => string;
33
+ /** Replace an auto-mode entry, by its row key. */
34
+ editAutoRule: (key: string, text: string) => string;
35
+ deleteAutoRule: (key: string) => string;
36
+ /** Add a validated directory, for this session or remembered. */
37
+ addDir: (path: string, remember: boolean) => string;
38
+ /** Remove a workspace directory, by its row key. */
39
+ removeDir: (key: string) => string;
40
+ }
41
+
42
+ export interface PermissionsPanelSession {
43
+ state: PanelState;
44
+ /** `Added allow rule X`, `Deleted deny rule Y`, in the order they happened. */
45
+ changes: string[];
46
+ /** Set when the panel closed so the caller can open the environment editor. */
47
+ editEnvironment?: boolean;
48
+ }
49
+
50
+ /**
51
+ * Open the panel. Pass the previous session to reopen it where it was, or
52
+ * `prepare` to adjust a fresh state first (`/add-dir` opens on its dialog).
53
+ */
54
+ export async function openPermissionsPanel(
55
+ ctx: ExtensionContext,
56
+ events: ExtensionAPI["events"],
57
+ host: PermissionsPanelHost,
58
+ previous?: PermissionsPanelSession,
59
+ prepare?: (state: PanelState) => void,
60
+ ): Promise<PermissionsPanelSession> {
61
+ const first = host.view();
62
+ const session: PermissionsPanelSession = { state: previous?.state ?? initialPanelState(first.denials.length > 0), changes: previous?.changes ?? [] };
63
+ if (!previous) prepare?.(session.state);
64
+ const { state, changes } = session;
65
+
66
+ // Reports its focus, so a permission prompt waits for it to close (lib/panel-focus.ts).
67
+ await panelUi(events, ctx.ui).custom<null>((tui, theme, _keybindings, done) => {
68
+ const paint: PanelPaint = { fg: safeThemePaint(theme), bold: safeThemeBold(theme), inverse: safeThemeInverse(theme) };
69
+ // Rendered lines are cached per width and cleared on every key; the view
70
+ // is rebuilt only when an effect changed the rules underneath it.
71
+ let cache: { width: number; lines: string[] } | undefined;
72
+ let view: PanelView = first;
73
+
74
+ const change = (effect: Exclude<PanelEffect, { kind: "close" } | { kind: "editEnvironment" }>): string => {
75
+ switch (effect.kind) {
76
+ case "addRule":
77
+ return host.addRule(effect.behavior, effect.rule, effect.destination);
78
+ case "deleteRule":
79
+ return host.deleteRule(effect.behavior, effect.key);
80
+ case "addAutoRule":
81
+ return host.addAutoRule(effect.section, effect.text);
82
+ case "editAutoRule":
83
+ return host.editAutoRule(effect.key, effect.text);
84
+ case "deleteAutoRule":
85
+ return host.deleteAutoRule(effect.key);
86
+ case "addDir":
87
+ return host.addDir(effect.path, effect.remember);
88
+ case "removeDir":
89
+ return host.removeDir(effect.key);
90
+ }
91
+ };
92
+
93
+ const runEffect = (effect: PanelEffect) => {
94
+ if (effect.kind === "close" || effect.kind === "editEnvironment") {
95
+ session.editEnvironment = effect.kind === "editEnvironment";
96
+ done(null);
97
+ return;
98
+ }
99
+ try {
100
+ changes.push(change(effect));
101
+ } catch (error) {
102
+ state.notice = (error as Error).message;
103
+ }
104
+ view = host.view();
105
+ };
106
+
107
+ return {
108
+ render: (width: number) => {
109
+ if (cache?.width === width) return cache.lines;
110
+ const rows = (tui as { terminal: { rows: number } }).terminal.rows;
111
+ const lines = renderPanel({ state, view, width, height: boundedDockHeight(rows, PANEL_MAX_HEIGHT), status: host.status() }, paint);
112
+ cache = { width, lines };
113
+ return lines;
114
+ },
115
+ handleInput: (data: string) => {
116
+ const key = decodePanelKey(data);
117
+ if (!key) return;
118
+ const effect = applyPanelKey(state, key, view);
119
+ if (effect) runEffect(effect);
120
+ cache = undefined;
121
+ tui.requestRender();
122
+ },
123
+ invalidate: () => {
124
+ cache = undefined;
125
+ },
126
+ };
127
+ });
128
+
129
+ return session;
130
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Key decoding for the /permissions panel (pure).
3
+ *
4
+ * Keys are named through `lib/key-input.ts`, so the legacy, kitty and
5
+ * modifyOtherKeys encodings of a key decode alike. Navigation keys are always
6
+ * controls; any other printable input is a `text` intent the state layer
7
+ * routes (a search box, a draft, or a single-letter action such as `r`
8
+ * retry). Unrecognized escape sequences are swallowed so they never leak into
9
+ * a draft.
10
+ */
11
+
12
+ import { keyId, keyText } from "../../lib/key-input.ts";
13
+
14
+ export type PanelKey =
15
+ | { kind: "up" | "down" | "pageUp" | "pageDown" | "nextTab" | "prevTab" | "enter" | "back" | "backspace" | "close" }
16
+ | { kind: "text"; text: string };
17
+
18
+ export function decodePanelKey(data: string): PanelKey | undefined {
19
+ switch (keyId(data)) {
20
+ case "up":
21
+ return { kind: "up" };
22
+ case "down":
23
+ return { kind: "down" };
24
+ case "pageUp":
25
+ return { kind: "pageUp" };
26
+ case "pageDown":
27
+ return { kind: "pageDown" };
28
+ case "right":
29
+ case "tab":
30
+ return { kind: "nextTab" };
31
+ case "left":
32
+ case "shift+tab":
33
+ return { kind: "prevTab" };
34
+ case "enter":
35
+ return { kind: "enter" };
36
+ case "escape":
37
+ return { kind: "back" };
38
+ case "backspace":
39
+ case "ctrl+backspace":
40
+ return { kind: "backspace" };
41
+ case "ctrl+c":
42
+ return { kind: "close" };
43
+ default:
44
+ break;
45
+ }
46
+ const text = keyText(data);
47
+ return text ? { kind: "text", text } : undefined;
48
+ }
@@ -0,0 +1,450 @@
1
+ /**
2
+ * /permissions panel rendering (pure).
3
+ *
4
+ * Returns lines already cut to terminal columns (pi-tui crashes on an
5
+ * overwide line). Wording follows Claude Code's panel (findings §33), naming
6
+ * One Code where Claude Code names itself.
7
+ */
8
+
9
+ import { cutPlainText, hintLines, panelTopRule, type RenderBlock as Block, searchBoxLines, truncateLine, visibleWidth, windowBlocks, wrapPlainText } from "../../lib/tui-render.ts";
10
+ import {
11
+ AUTO_SECTION_LABELS,
12
+ AUTO_SECTIONS,
13
+ type AutoEntryRow,
14
+ type AutoSection,
15
+ autoEntry,
16
+ autoListRows,
17
+ clampPanelState,
18
+ detailRule,
19
+ type Dialog,
20
+ isRuleTab,
21
+ type PanelState,
22
+ type PanelView,
23
+ ruleListRows,
24
+ type RuleTab,
25
+ TABS,
26
+ type Tab,
27
+ workspaceDir,
28
+ workspaceListRows,
29
+ } from "./state.ts";
30
+
31
+ export interface PanelPaint {
32
+ fg: (color: string, text: string) => string;
33
+ bold: (text: string) => string;
34
+ inverse: (text: string) => string;
35
+ }
36
+
37
+ export interface PanelRenderInput {
38
+ state: PanelState;
39
+ view: PanelView;
40
+ width: number;
41
+ height: number;
42
+ /** One dim line under the tabs: the mode and auto mode's state. */
43
+ status?: string;
44
+ }
45
+
46
+ const TAB_TITLES: Record<Tab, string> = {
47
+ recent: "Recently denied",
48
+ allow: "Allow",
49
+ ask: "Ask",
50
+ deny: "Deny",
51
+ automode: "Auto mode",
52
+ workspace: "Workspace",
53
+ };
54
+
55
+ const WORKSPACE_SUBTITLE = "One Code can read files in the workspace, and make edits when auto-accept edits is on.";
56
+ /** Claude Code's three answers when a directory is added. */
57
+ const REMEMBER_OPTIONS = ["Yes, for this session", "Yes, and remember this directory", "No"];
58
+
59
+ const SUBTITLES: Record<RuleTab | "automode", string> = {
60
+ allow: "One Code won't ask before using allowed tools.",
61
+ ask: "One Code will always ask for confirmation before using these tools.",
62
+ deny: "One Code will always reject requests to use denied tools.",
63
+ automode: "Extra rules for the auto mode classifier. Rules are plain sentences; new rules are saved to your user settings.",
64
+ };
65
+
66
+ const BEHAVIOR_LABEL: Record<RuleTab, string> = { allow: "allowed", ask: "ask", deny: "denied" };
67
+
68
+ const SECTION_LABEL = AUTO_SECTION_LABELS;
69
+ /** Claude Code's section colors. */
70
+ const SECTION_COLOR: Record<AutoSection, string> = { allow: "success", soft_deny: "warning", hard_deny: "error" };
71
+ /** Claude Code's section descriptions, from its "What kind of rule is this?" picker. */
72
+ const SECTION_DESCRIPTION: Record<AutoSection, string> = {
73
+ allow: "Actions the classifier lets run without blocking",
74
+ soft_deny: "Actions the classifier blocks, unless a soft allow rule or your explicit direction applies",
75
+ hard_deny: "Actions the classifier always blocks; soft allow rules cannot override",
76
+ };
77
+ /** Wide enough for the longest section label, "Environment". */
78
+ const LABEL_COLUMNS = 13;
79
+ /** Environment lines the Auto mode tab shows before `… (+N more lines)`. */
80
+ const ENVIRONMENT_PREVIEW = 4;
81
+
82
+ const EMPTY_DENIALS = "No recent denials. Commands denied by the auto mode classifier will appear here.";
83
+
84
+ function tabBar(state: PanelState, paint: PanelPaint): string {
85
+ const chips = TABS.map((tab) => (tab === state.tab ? paint.inverse(` ${TAB_TITLES[tab]} `) : ` ${TAB_TITLES[tab]} `));
86
+ return ` ${paint.bold("Permissions")} ${chips.join(" ")}`;
87
+ }
88
+
89
+ /** A cursor-marked list row: `❯ text` in the accent color, or indented. */
90
+ function listLine(text: string, isCursor: boolean, paint: PanelPaint, width: number): string {
91
+ const line = cutPlainText(`${isCursor ? "❯" : " "} ${text}`, width - 1);
92
+ return isCursor ? paint.fg("accent", line) : line;
93
+ }
94
+
95
+ /** A dim ` · <source>` tail naming where an uneditable entry lives, cut to what is left of the row. */
96
+ function sourceTail(entry: { editable: boolean; sourceLabel: string }, used: number, width: number): string {
97
+ return entry.editable ? "" : cutPlainText(` · ${entry.sourceLabel.replace(/^From /, "")}`, Math.max(0, width - 3 - used));
98
+ }
99
+
100
+ function recentBlocks(input: PanelRenderInput, paint: PanelPaint): Block[] {
101
+ const { state, view, width } = input;
102
+ return view.denials.map((denial, index) => {
103
+ const isCursor = index === state.cursor.recent;
104
+ const approved = state.approved.has(denial.id);
105
+ const mark = approved ? paint.fg("success", "✔") : paint.fg("error", "✗");
106
+ const retry = state.retry.has(denial.id) ? " (retry)" : "";
107
+ const rule = denial.rule ? ` [${denial.rule}]` : "";
108
+ // Measured without the paint, then painted: the mark and the dim tail are
109
+ // colored separately from the cursor highlight.
110
+ const room = Math.max(8, width - 5 - visibleWidth(retry) - visibleWidth(rule));
111
+ const body = cutPlainText(denial.display, room);
112
+ const text = isCursor ? paint.fg("accent", body) : body;
113
+ return {
114
+ lines: [`${isCursor ? paint.fg("accent", "❯") : " "} ${mark} ${text}${paint.fg("dim", `${retry}${rule}`)}`],
115
+ selectable: true,
116
+ };
117
+ });
118
+ }
119
+
120
+ function ruleBlocks(input: PanelRenderInput, paint: PanelPaint, tab: RuleTab): Block[] {
121
+ const { state, view, width } = input;
122
+ const rows = ruleListRows(state, view, tab);
123
+ const blocks: Block[] = rows.map((row, index) => {
124
+ const isCursor = index === state.cursor[tab];
125
+ if (row.kind === "add") return { lines: [listLine("Add a new rule…", isCursor, paint, width)], selectable: true };
126
+ const raw = cutPlainText(row.rule.raw, width - 3);
127
+ const tail = sourceTail(row.rule, visibleWidth(raw), width);
128
+ return { lines: [`${listLine(raw, isCursor, paint, width)}${tail ? paint.fg("dim", tail) : ""}`], selectable: true };
129
+ });
130
+ if (view.rules[tab].length === 0) blocks.push({ lines: [paint.fg("dim", ` No ${tab} rules`)], selectable: false });
131
+ else if (rows.length === 0) blocks.push({ lines: [paint.fg("dim", " No rules match the search")], selectable: false });
132
+ return blocks;
133
+ }
134
+
135
+ /** ` Soft deny ` in the section's color: the fixed-width label column of an Auto mode row. */
136
+ function sectionLabel(label: string, color: string | undefined, paint: PanelPaint): string {
137
+ const padded = label.padEnd(LABEL_COLUMNS);
138
+ return color ? paint.fg(color, padded) : padded;
139
+ }
140
+
141
+ function autoBlocks(input: PanelRenderInput, paint: PanelPaint): Block[] {
142
+ const { state, view, width } = input;
143
+ const rows = autoListRows(state, view);
144
+ const auto = view.autoMode;
145
+ const room = Math.max(8, width - 4 - LABEL_COLUMNS);
146
+ const marker = (isCursor: boolean) => (isCursor ? paint.fg("accent", "❯") : " ");
147
+ const body = (text: string, isCursor: boolean) => (isCursor ? paint.fg("accent", text) : text);
148
+ const blocks: Block[] = rows.map((row, index) => {
149
+ const isCursor = index === state.cursor.automode;
150
+ switch (row.kind) {
151
+ case "add":
152
+ return { lines: [listLine("Add a new rule…", isCursor, paint, width)], selectable: true };
153
+ case "builtins": {
154
+ const text = cutPlainText(`Built-in rules · ${auto.builtins[row.section]} · always in effect`, room);
155
+ return { lines: [`${marker(isCursor)} ${sectionLabel(SECTION_LABEL[row.section], SECTION_COLOR[row.section], paint)}${body(text, isCursor)}`], selectable: true };
156
+ }
157
+ case "entry": {
158
+ const text = cutPlainText(row.entry.text, room);
159
+ const tail = sourceTail(row.entry, LABEL_COLUMNS + visibleWidth(text), width);
160
+ const label = sectionLabel(SECTION_LABEL[row.entry.section], SECTION_COLOR[row.entry.section], paint);
161
+ return { lines: [`${marker(isCursor)} ${label}${body(text, isCursor)}${tail ? paint.fg("dim", tail) : ""}`], selectable: true };
162
+ }
163
+ case "environment": {
164
+ const env = auto.environment;
165
+ const head = cutPlainText(`${env.summary} · enter to edit`, room);
166
+ const indent = " ".repeat(2 + LABEL_COLUMNS);
167
+ const preview = env.lines.slice(0, ENVIRONMENT_PREVIEW).map((line) => paint.fg("dim", `${indent}${cutPlainText(line, room)}`));
168
+ const more = env.lines.length > ENVIRONMENT_PREVIEW ? [paint.fg("dim", `${indent}… (+${env.lines.length - ENVIRONMENT_PREVIEW} more lines)`)] : [];
169
+ return { lines: [`${marker(isCursor)} ${sectionLabel("Environment", undefined, paint)}${body(head, isCursor)}`, ...preview, ...more], selectable: true };
170
+ }
171
+ }
172
+ });
173
+ if (rows.length === 0) blocks.push({ lines: [paint.fg("dim", " No rules match the search")], selectable: false });
174
+ return blocks;
175
+ }
176
+
177
+ function workspaceBlocks(input: PanelRenderInput, paint: PanelPaint): Block[] {
178
+ const { state, view, width } = input;
179
+ return workspaceListRows(view).map((row, index) => {
180
+ const isCursor = index === state.cursor.workspace;
181
+ if (row.kind === "add") return { lines: [listLine("Add directory…", isCursor, paint, width)], selectable: true };
182
+ const path = cutPlainText(row.dir.path, width - 3);
183
+ const tail = sourceTail(row.dir, visibleWidth(path), width);
184
+ return { lines: [`${listLine(path, isCursor, paint, width)}${tail ? paint.fg("dim", tail) : ""}`], selectable: true };
185
+ });
186
+ }
187
+
188
+ function ruleSummary(raw: string, paint: PanelPaint, width: number): string[] {
189
+ const lines = [` ${paint.bold(cutPlainText(raw, width - 4))}`];
190
+ const description = describeRule(raw);
191
+ if (description) lines.push(paint.fg("dim", cutPlainText(` ${description}`, width - 1)));
192
+ return lines;
193
+ }
194
+
195
+ /** Yes/No or Edit/Delete: two cursor-marked options. */
196
+ function choiceLines(options: [string, string], cursor: number, paint: PanelPaint, width: number): string[] {
197
+ return options.map((option, index) => listLine(option, cursor === index, paint, width));
198
+ }
199
+
200
+ /** An auto-mode entry's text wrapped under a heading, with where it lives. */
201
+ function autoEntrySummary(entry: AutoEntryRow, paint: PanelPaint, width: number): string[] {
202
+ return [
203
+ paint.fg(SECTION_COLOR[entry.section], cutPlainText(` ${SECTION_LABEL[entry.section]}`, width - 1)),
204
+ ...wrapPlainText(entry.text, Math.max(10, width - 6)).map((line) => ` ${paint.bold(line)}`),
205
+ paint.fg("dim", cutPlainText(` ${entry.sourceLabel}`, width - 1)),
206
+ ];
207
+ }
208
+
209
+ /** The body and footer of an open dialog. */
210
+ function dialogLines(dialog: Dialog, input: PanelRenderInput, paint: PanelPaint): { lines: string[]; footer: string } {
211
+ const { view, width } = input;
212
+ const text = (line: string) => cutPlainText(` ${line}`, width - 1);
213
+ const wrapped = (line: string) => wrapPlainText(line, Math.max(10, width - 2)).map((part) => ` ${part}`);
214
+ const gone = { lines: [paint.fg("dim", text("(this entry is no longer in the list)"))], footer: "Esc to go back" };
215
+ const draftBox = (draft: string, placeholder: string) => searchBoxLines(draft, placeholder, paint.fg, width, "");
216
+
217
+ switch (dialog.kind) {
218
+ case "addRule":
219
+ return {
220
+ lines: [
221
+ paint.fg("accent", paint.bold(text(`Add ${dialog.behavior} permission rule`))),
222
+ "",
223
+ text("Permission rules are a tool name, optionally followed by a specifier in parentheses."),
224
+ text("e.g., WebFetch or Bash(ls:*)"),
225
+ "",
226
+ ...draftBox(dialog.draft, "Enter permission rule…"),
227
+ ],
228
+ footer: "Enter to submit · Esc to cancel",
229
+ };
230
+ case "saveRule": {
231
+ const lines = [
232
+ paint.fg("accent", paint.bold(text(`Add ${dialog.behavior} permission rule`))),
233
+ "",
234
+ ...ruleSummary(dialog.rule, paint, width),
235
+ "",
236
+ text("Where should this rule be saved?"),
237
+ ];
238
+ view.destinations.forEach((destination, index) => {
239
+ lines.push(listLine(`${index + 1}. ${destination.label}`, index === dialog.cursor, paint, width));
240
+ lines.push(paint.fg("dim", cutPlainText(` ${destination.description}`, width - 1)));
241
+ });
242
+ return { lines, footer: "↑↓ to choose · Enter to save · Esc to go back" };
243
+ }
244
+ case "ruleDetail": {
245
+ const rule = detailRule(dialog, view);
246
+ if (!rule) return gone;
247
+ const details = [...ruleSummary(rule.raw, paint, width), paint.fg("dim", cutPlainText(` ${rule.sourceLabel}`, width - 1))];
248
+ if (!rule.editable) {
249
+ return { lines: [paint.bold(text("Rule details")), "", ...details, "", ...(rule.readOnlyNote ? wrapped(rule.readOnlyNote) : [])], footer: "Enter or Esc to go back" };
250
+ }
251
+ return {
252
+ lines: [
253
+ paint.fg("error", paint.bold(text(`Delete ${BEHAVIOR_LABEL[dialog.behavior]} tool?`))),
254
+ "",
255
+ ...details,
256
+ "",
257
+ text("Are you sure you want to delete this permission rule?"),
258
+ ...choiceLines(["Yes", "No"], dialog.cursor, paint, width),
259
+ ],
260
+ footer: "↑↓ to choose · Enter to confirm · Esc to go back",
261
+ };
262
+ }
263
+ case "pickSection": {
264
+ const lines = [paint.fg("accent", paint.bold(text("Add auto mode rule"))), "", text("What kind of rule is this?")];
265
+ AUTO_SECTIONS.forEach((section, index) => {
266
+ lines.push(listLine(`${index + 1}. ${SECTION_LABEL[section]}`, index === dialog.cursor, paint, width));
267
+ lines.push(paint.fg("dim", cutPlainText(` ${SECTION_DESCRIPTION[section]}`, width - 1)));
268
+ });
269
+ return { lines, footer: "↑↓ to choose · Enter to select · Esc to cancel" };
270
+ }
271
+ case "autoRuleInput":
272
+ return {
273
+ lines: [
274
+ paint.fg("accent", paint.bold(text(`${dialog.editKey ? "Edit" : "Add"} ${SECTION_LABEL[dialog.section].toLowerCase()} rule`))),
275
+ "",
276
+ ...wrapped("Write the rule as a plain sentence. A short label up front helps, e.g., Database Writes: UPDATE statements against the local dev database."),
277
+ "",
278
+ ...draftBox(dialog.draft, "Enter rule…"),
279
+ paint.fg("dim", text("Saved to your One Code user settings, for every project.")),
280
+ ],
281
+ footer: "Enter to save · Esc to cancel",
282
+ };
283
+ case "autoRuleDetail": {
284
+ const entry = autoEntry(dialog.key, view);
285
+ if (!entry) return gone;
286
+ if (!entry.editable) {
287
+ return { lines: [paint.bold(text("Auto mode rule")), "", ...autoEntrySummary(entry, paint, width), "", ...(entry.readOnlyNote ? wrapped(entry.readOnlyNote) : [])], footer: "Enter or Esc to go back" };
288
+ }
289
+ return {
290
+ lines: [paint.bold(text("Auto mode rule")), "", ...autoEntrySummary(entry, paint, width), "", ...choiceLines(["Edit", "Delete"], dialog.cursor, paint, width)],
291
+ footer: "↑↓ to choose · Enter to confirm · e to edit · d to delete · Esc to go back",
292
+ };
293
+ }
294
+ case "autoRuleDelete": {
295
+ const entry = autoEntry(dialog.key, view);
296
+ if (!entry) return gone;
297
+ const last = view.autoMode.entries.filter((other) => other.section === entry.section && other.editable).length === 1;
298
+ return {
299
+ lines: [
300
+ paint.fg("error", paint.bold(text("Delete auto mode rule?"))),
301
+ "",
302
+ ...autoEntrySummary(entry, paint, width),
303
+ "",
304
+ ...wrapped(
305
+ `Are you sure you want to delete this rule? The classifier stops applying it on your next request.${last ? " This is your last rule in this section; the built-in rules still apply." : ""}`,
306
+ ),
307
+ ...choiceLines(["Yes", "No"], dialog.cursor, paint, width),
308
+ ],
309
+ footer: "↑↓ to choose · Enter to confirm · Esc to go back",
310
+ };
311
+ }
312
+ case "builtinsInfo":
313
+ return {
314
+ lines: [
315
+ paint.fg(SECTION_COLOR[dialog.section], paint.bold(text(`${SECTION_LABEL[dialog.section]} · built-in rules`))),
316
+ "",
317
+ ...wrapped(
318
+ `The ${view.autoMode.builtins[dialog.section]} built-in ${SECTION_LABEL[dialog.section].toLowerCase()} rules are always in effect in One Code. Your own rules are added after them and never replace them, so a rule can tighten or carve out, but not switch the built-ins off.`,
319
+ ),
320
+ "",
321
+ ...wrapped("They are Claude Code's own classifier rules, embedded verbatim."),
322
+ ],
323
+ footer: "Enter or Esc to go back",
324
+ };
325
+ case "envConfirm":
326
+ return {
327
+ lines: [
328
+ paint.fg("warning", paint.bold(text("Replace the built-in environment?"))),
329
+ "",
330
+ ...wrapped(
331
+ "Writing your own environment replaces the built-in default document: the classifier context that defines trusted hosts, sensitive targets, and repository scope. The editor starts from the full default text so you can edit rather than rewrite; deleting all your environment entries later restores the default.",
332
+ ),
333
+ "",
334
+ ...choiceLines(["Yes, edit the environment", "No"], dialog.cursor, paint, width),
335
+ ],
336
+ footer: "↑↓ to choose · Enter to confirm · Esc to go back",
337
+ };
338
+ case "addDir":
339
+ return {
340
+ lines: [paint.fg("accent", paint.bold(text("Add directory to workspace"))), "", text("Enter the path to the directory:"), ...draftBox(dialog.draft, "Directory path…")],
341
+ footer: "Enter to submit · Esc to cancel",
342
+ };
343
+ case "rememberDir":
344
+ return {
345
+ lines: [
346
+ paint.fg("accent", paint.bold(text("Add directory to workspace"))),
347
+ "",
348
+ ` ${paint.fg("accent", cutPlainText(dialog.path, width - 4))}`,
349
+ ...wrapped("One Code will be able to read files in this directory and make edits when auto-accept edits is on. Auto mode still judges every write there."),
350
+ "",
351
+ ...REMEMBER_OPTIONS.map((option, index) => listLine(option, index === dialog.cursor, paint, width)),
352
+ paint.fg("dim", text("Remembered directories are saved to One Code's project settings.")),
353
+ ],
354
+ footer: "↑↓ to choose · Enter to confirm · Esc to go back",
355
+ };
356
+ case "removeDir": {
357
+ const dir = workspaceDir(dialog.key, view);
358
+ if (!dir) return gone;
359
+ const details = [` ${paint.bold(cutPlainText(dir.path, width - 4))}`, paint.fg("dim", cutPlainText(` ${dir.sourceLabel}`, width - 1))];
360
+ if (!dir.editable) {
361
+ return { lines: [paint.bold(text("Workspace directory")), "", ...details, "", ...(dir.readOnlyNote ? wrapped(dir.readOnlyNote) : [])], footer: "Enter or Esc to go back" };
362
+ }
363
+ return {
364
+ lines: [
365
+ paint.fg("error", paint.bold(text("Remove directory from workspace?"))),
366
+ "",
367
+ ...details,
368
+ "",
369
+ text("One Code will no longer have access to files in this directory."),
370
+ ...choiceLines(["Yes", "No"], dialog.cursor, paint, width),
371
+ ],
372
+ footer: "↑↓ to choose · Enter to confirm · Esc to go back",
373
+ };
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Claude Code's rule descriptions (`PermissionRuleDescription`): what a Bash
380
+ * rule matches, or that a bare tool name covers every use of the tool.
381
+ */
382
+ export function describeRule(raw: string): string | undefined {
383
+ const match = /^([^()]+?)(?:\((.*)\))?$/s.exec(raw.trim());
384
+ if (!match) return undefined;
385
+ const [, tool, content] = match;
386
+ if (/^bash$/i.test(tool)) {
387
+ if (!content) return "Any Bash command";
388
+ if (content.endsWith(":*")) return `Any Bash command starting with ${content.slice(0, -2)}`;
389
+ return `The Bash command ${content}`;
390
+ }
391
+ return content ? undefined : `Any use of the ${tool} tool`;
392
+ }
393
+
394
+ function footerFor(state: PanelState, view: PanelView): string[] {
395
+ if (state.tab === "recent") {
396
+ if (view.denials.length === 0) return ["←/→ to switch tabs · Esc to close"];
397
+ const pending = state.approved.size > 0 ? ["Approvals apply when you close the panel."] : [];
398
+ return ["Enter to approve · r to retry · ↑↓ to navigate · ←/→ to switch tabs · Esc to close", ...pending];
399
+ }
400
+ if (state.tab === "workspace") return ["↑↓ to navigate · Enter to select · ←/→ to switch tabs · Esc to close"];
401
+ if (state.searching) return ["Type to filter · Enter to select · Backspace to edit · Esc to clear"];
402
+ return ["↑↓ to navigate · Enter to select · Type to search · ←/→ to switch tabs · Esc to close"];
403
+ }
404
+
405
+ export function renderPanel(input: PanelRenderInput, paint: PanelPaint): string[] {
406
+ const { state, view, width, height } = input;
407
+ clampPanelState(state, view);
408
+
409
+ const out: string[] = [panelTopRule(paint.fg, width), tabBar(state, paint)];
410
+ if (input.status) out.push(paint.fg("dim", cutPlainText(` ${input.status}`, width - 1)));
411
+ out.push("");
412
+
413
+ // Hints wrap at their separators (hintLines); the list budget counts the rows.
414
+ const wrapFooter = (lines: string[]) => hintLines(lines, width - 1, " ");
415
+ let footer: string[];
416
+ if (state.dialog) {
417
+ const dialog = dialogLines(state.dialog, input, paint);
418
+ out.push(...dialog.lines);
419
+ footer = wrapFooter([dialog.footer]);
420
+ } else {
421
+ let blocks: Block[];
422
+ if (state.tab === "recent") {
423
+ if (view.denials.length === 0) blocks = [{ lines: [paint.fg("dim", cutPlainText(` ${EMPTY_DENIALS}`, width - 1))], selectable: false }];
424
+ else {
425
+ out.push(cutPlainText(" Commands recently denied by the auto mode classifier.", width - 1), "");
426
+ blocks = recentBlocks(input, paint);
427
+ }
428
+ } else if (state.tab === "workspace") {
429
+ out.push(...wrapPlainText(WORKSPACE_SUBTITLE, Math.max(10, width - 2)).map((line) => ` ${line}`), "");
430
+ out.push(` - ${cutPlainText(view.workspace.cwd, Math.max(8, width - 32))}${paint.fg("dim", " (Original working directory)")}`);
431
+ blocks = workspaceBlocks(input, paint);
432
+ } else {
433
+ out.push(...wrapPlainText(SUBTITLES[state.tab], Math.max(10, width - 2)).map((line) => ` ${line}`));
434
+ const placeholder = state.searching ? "Search…" : "Type to search…";
435
+ out.push(...searchBoxLines(state.search[state.tab], placeholder, paint.fg, width));
436
+ blocks = isRuleTab(state.tab) ? ruleBlocks(input, paint, state.tab) : autoBlocks(input, paint);
437
+ }
438
+ footer = wrapFooter(footerFor(state, view));
439
+ const budget = Math.max(3, height - out.length - 2 - footer.length - (state.notice ? 1 : 0));
440
+ const windowed = windowBlocks(blocks, state.cursor[state.tab], budget);
441
+ out.push(...windowed.lines);
442
+ if (windowed.more > 0) out.push(paint.fg("dim", ` ↓ more below (${windowed.more})`));
443
+ }
444
+
445
+ if (state.notice) out.push(paint.fg("warning", cutPlainText(` ${state.notice}`, width - 1)));
446
+ out.push("", ...footer.map((line) => paint.fg("dim", line)));
447
+ // Painted rows (the tab bar, a denial's mark and tail) are measured after
448
+ // painting, so every line gets a final column-aware cut.
449
+ return out.map((line) => truncateLine(line, width));
450
+ }