one-code-extension 0.4.1 → 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 +16 -5
  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 +58 -31
  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 +18 -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
@@ -10,10 +10,11 @@
10
10
  */
11
11
 
12
12
  import type { HttpServer } from "../config.ts";
13
- import { beginInteractiveAuth, type Connection, connect } from "../client.ts";
13
+ import { beginInteractiveAuth, type Connection, connect, finishInteractiveAuth } from "../client.ts";
14
14
  import { openBrowser } from "./browser.ts";
15
15
  import { startCallbackServer } from "./callback.ts";
16
16
  import { McpOAuthProvider } from "./provider.ts";
17
+ import type { OAuthServer } from "./store.ts";
17
18
 
18
19
  export interface AuthenticateOptions {
19
20
  server: HttpServer;
@@ -25,8 +26,8 @@ export interface AuthenticateOptions {
25
26
  }
26
27
 
27
28
  /** A provider for a silent (non-interactive) connect using already-stored tokens. */
28
- export function silentProvider(serverName: string, home?: string, env?: NodeJS.ProcessEnv): McpOAuthProvider {
29
- return new McpOAuthProvider({ serverName, home, env });
29
+ export function silentProvider(server: OAuthServer, home?: string, env?: NodeJS.ProcessEnv): McpOAuthProvider {
30
+ return new McpOAuthProvider({ server, home, env });
30
31
  }
31
32
 
32
33
  export async function authenticate(options: AuthenticateOptions): Promise<Connection> {
@@ -34,14 +35,17 @@ export async function authenticate(options: AuthenticateOptions): Promise<Connec
34
35
  const callback = await startCallbackServer();
35
36
  try {
36
37
  const provider = new McpOAuthProvider({
37
- serverName: server.name,
38
+ server,
38
39
  redirectUrl: callback.redirectUrl,
39
40
  openAuthorization: (url) => {
40
- const opened = openBrowser(url.toString());
41
- onPrompt?.(
42
- opened
43
- ? `Opened your browser to authorize "${server.name}". If it didn't open, visit:\n${url}`
44
- : `Open this URL to authorize "${server.name}":\n${url}`,
41
+ // A refused URL throws here, before any launch; the prompt waits
42
+ // for the opener to report whether it started.
43
+ void openBrowser(url.toString()).then((opened) =>
44
+ onPrompt?.(
45
+ opened
46
+ ? `Opened your browser to authorize "${server.name}". If it didn't open, visit:\n${url}`
47
+ : `Open this URL to authorize "${server.name}":\n${url}`,
48
+ ),
45
49
  );
46
50
  },
47
51
  home,
@@ -51,17 +55,21 @@ export async function authenticate(options: AuthenticateOptions): Promise<Connec
51
55
  const started = await beginInteractiveAuth(server, provider);
52
56
  if ("connection" in started) return started.connection; // valid tokens already present
53
57
 
54
- const { code, state } = await callback.waitForCode(timeoutMs);
55
- // Login-CSRF guard (RFC 8252 §8.9): the redirect must echo the state this
56
- // provider issued, or an attacker-planted code could bind their account.
57
- const expected = provider.expectedState();
58
- if (expected !== undefined && state !== expected) {
59
- throw new Error(`authorization response state mismatch for "${server.name}" — the redirect did not come from this authorization request`);
58
+ try {
59
+ const { code, state } = await callback.waitForCode(timeoutMs);
60
+ // Login-CSRF guard (RFC 8252 §8.9): the redirect must echo the state this
61
+ // provider issued, or an attacker-planted code could bind their account.
62
+ const expected = provider.expectedState();
63
+ if (expected !== undefined && state !== expected) {
64
+ throw new Error(`authorization response state mismatch for "${server.name}" — the redirect did not come from this authorization request`);
65
+ }
66
+ await finishInteractiveAuth(started.transport, code);
67
+ } finally {
68
+ await started.transport.close().catch(() => {});
60
69
  }
61
- await started.transport.finishAuth(code);
62
70
 
63
71
  // Reconnect fresh: the provider now has stored tokens, so this is silent.
64
- return await connect(server, silentProvider(server.name, home, env));
72
+ return await connect(server, silentProvider(server, home, env));
65
73
  } finally {
66
74
  callback.close();
67
75
  }
@@ -20,10 +20,13 @@ import type {
20
20
  } from "@modelcontextprotocol/sdk/shared/auth.js";
21
21
  import { randomBytes } from "node:crypto";
22
22
  import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js";
23
- import { readAuth, type StoredAuth, writeAuth } from "./store.ts";
23
+ import { type OAuthServer, readAuth, type StoredAuth, writeAuth } from "./store.ts";
24
+
25
+ export class McpAuthenticationRequiredError extends Error {}
24
26
 
25
27
  export interface McpOAuthProviderOptions {
26
- serverName: string;
28
+ /** The server the credentials are bound to (name, URL and headers). */
29
+ server: OAuthServer;
27
30
  /** Loopback redirect_uri; required for an interactive flow, omitted for silent reconnect. */
28
31
  redirectUrl?: string;
29
32
  /** Sends the user to the authorization URL (opens a browser). Interactive flows only. */
@@ -33,7 +36,7 @@ export interface McpOAuthProviderOptions {
33
36
  }
34
37
 
35
38
  export class McpOAuthProvider implements OAuthClientProvider {
36
- private readonly serverName: string;
39
+ private readonly server: OAuthServer;
37
40
  private readonly _redirectUrl?: string;
38
41
  private readonly openAuthorization?: (url: URL) => void;
39
42
  private readonly home?: string;
@@ -47,7 +50,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
47
50
  private issuedState?: string;
48
51
 
49
52
  constructor(options: McpOAuthProviderOptions) {
50
- this.serverName = options.serverName;
53
+ this.server = options.server;
51
54
  this._redirectUrl = options.redirectUrl;
52
55
  this.openAuthorization = options.openAuthorization;
53
56
  this.home = options.home;
@@ -55,17 +58,20 @@ export class McpOAuthProvider implements OAuthClientProvider {
55
58
  }
56
59
 
57
60
  private load(): StoredAuth {
58
- return (this.cache ??= readAuth(this.serverName, this.home, this.env));
61
+ return (this.cache ??= readAuth(this.server, this.home, this.env));
59
62
  }
60
63
 
61
64
  /** Merge a patch into the cached store and persist it. */
62
65
  private save(patch: Partial<StoredAuth>): void {
63
66
  this.cache = { ...this.load(), ...patch };
64
- writeAuth(this.serverName, this.cache, this.home, this.env);
67
+ writeAuth(this.server, this.cache, this.home, this.env);
65
68
  }
66
69
 
67
70
  get redirectUrl(): string | URL | undefined {
68
- return this._redirectUrl;
71
+ // The SDK uses an absent redirect URI to select a non-interactive grant,
72
+ // which skips refresh tokens. Silent reconnects still use the registered
73
+ // authorization-code client; only opening a browser is disallowed.
74
+ return this._redirectUrl ?? this.load().clientMetadata?.redirect_uris[0];
69
75
  }
70
76
 
71
77
  get clientMetadata(): OAuthClientMetadata {
@@ -113,7 +119,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
113
119
 
114
120
  redirectToAuthorization(authorizationUrl: URL): void {
115
121
  if (!this.openAuthorization) {
116
- throw new Error(`MCP server "${this.serverName}" needs authentication; run /mcp and choose Authenticate.`);
122
+ throw new McpAuthenticationRequiredError(`MCP server "${this.server.name}" needs authentication; run /mcp and choose Authenticate.`);
117
123
  }
118
124
  this.openAuthorization(authorizationUrl);
119
125
  }
@@ -10,8 +10,16 @@
10
10
  *
11
11
  * Kept in ~/.onecode (never ~/.claude) like all One Code state, and separate
12
12
  * from settings.json because tokens are secrets, not configuration.
13
+ *
14
+ * A record belongs to a server, not a name: the file is keyed by the name
15
+ * plus a sha256 of the server's URL and headers, and records the URL it was
16
+ * issued for. Until 2026-09-27 it was keyed by name alone, so a project
17
+ * `.mcp.json` server named like an authenticated user server received that
18
+ * server's bearer token at its own URL. A record whose URL differs is never
19
+ * offered, so a server whose URL or headers change signs in again.
13
20
  */
14
21
 
22
+ import { createHash } from "node:crypto";
15
23
  import { mkdirSync, chmodSync } from "node:fs";
16
24
  import os from "node:os";
17
25
  import { join } from "node:path";
@@ -20,7 +28,16 @@ import type { OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.
20
28
  import { readJsonFile, writeJsonAtomic } from "../../lib/atomic-write.ts";
21
29
  import { oneCodeStateDir } from "../../lib/paths.ts";
22
30
 
31
+ /** The part of an http server's config its credentials are bound to. */
32
+ export interface OAuthServer {
33
+ name: string;
34
+ url: string;
35
+ headers?: Record<string, string>;
36
+ }
37
+
23
38
  export interface StoredAuth {
39
+ /** The server URL the record was issued for; a record for another URL is never read. */
40
+ serverUrl?: string;
24
41
  clientInformation?: OAuthClientInformationFull;
25
42
  clientMetadata?: OAuthClientMetadata;
26
43
  tokens?: OAuthTokens;
@@ -33,27 +50,28 @@ export interface StoredAuth {
33
50
  * stem plus a short hash of the raw name, so two names that sanitize alike
34
51
  * (e.g. "a/b" and "a_b") still get distinct files.
35
52
  */
36
- function slug(serverName: string): string {
37
- let hash = 5381;
38
- for (let i = 0; i < serverName.length; i++) hash = ((hash << 5) + hash + serverName.charCodeAt(i)) >>> 0;
39
- const safe = serverName.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
40
- return `${safe || "server"}-${hash.toString(16).padStart(8, "0")}.json`;
53
+ function slug(server: OAuthServer): string {
54
+ const headers = Object.entries(server.headers ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
55
+ const hash = createHash("sha256").update(JSON.stringify({ name: server.name, type: "http", url: server.url, headers })).digest("hex").slice(0, 16);
56
+ const safe = server.name.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
57
+ return `${safe || "server"}-${hash}.json`;
41
58
  }
42
59
 
43
60
  export function authStoreDir(home: string = os.homedir(), env: NodeJS.ProcessEnv = process.env): string {
44
61
  return join(oneCodeStateDir(env, home), "mcp-auth");
45
62
  }
46
63
 
47
- function authFilePath(serverName: string, home: string, env: NodeJS.ProcessEnv): string {
48
- return join(authStoreDir(home, env), slug(serverName));
64
+ function authFilePath(server: OAuthServer, home: string, env: NodeJS.ProcessEnv): string {
65
+ return join(authStoreDir(home, env), slug(server));
49
66
  }
50
67
 
51
- export function readAuth(serverName: string, home: string = os.homedir(), env: NodeJS.ProcessEnv = process.env): StoredAuth {
52
- return readJsonFile<StoredAuth>(authFilePath(serverName, home, env)) ?? {};
68
+ export function readAuth(server: OAuthServer, home: string = os.homedir(), env: NodeJS.ProcessEnv = process.env): StoredAuth {
69
+ const stored = readJsonFile<StoredAuth>(authFilePath(server, home, env));
70
+ return stored && stored.serverUrl === server.url ? stored : {};
53
71
  }
54
72
 
55
73
  export function writeAuth(
56
- serverName: string,
74
+ server: OAuthServer,
57
75
  auth: StoredAuth,
58
76
  home: string = os.homedir(),
59
77
  env: NodeJS.ProcessEnv = process.env,
@@ -67,20 +85,20 @@ export function writeAuth(
67
85
  } catch {
68
86
  // Not fatal (a filesystem without modes); the file mode below still applies where it can.
69
87
  }
70
- writeJsonAtomic(authFilePath(serverName, home, env), auth, { mode: 0o600 });
88
+ writeJsonAtomic(authFilePath(server, home, env), { ...auth, serverUrl: server.url }, { mode: 0o600 });
71
89
  }
72
90
 
73
91
  /** Merge a partial update into the stored record. */
74
92
  export function updateAuth(
75
- serverName: string,
93
+ server: OAuthServer,
76
94
  patch: Partial<StoredAuth>,
77
95
  home: string = os.homedir(),
78
96
  env: NodeJS.ProcessEnv = process.env,
79
97
  ): void {
80
- writeAuth(serverName, { ...readAuth(serverName, home, env), ...patch }, home, env);
98
+ writeAuth(server, { ...readAuth(server, home, env), ...patch }, home, env);
81
99
  }
82
100
 
83
- /** True when a usable (non-empty) access token is stored. */
84
- export function hasStoredTokens(serverName: string, home: string = os.homedir(), env: NodeJS.ProcessEnv = process.env): boolean {
85
- return Boolean(readAuth(serverName, home, env).tokens?.access_token);
101
+ /** True when a usable (non-empty) access token is stored for this server. */
102
+ export function hasStoredTokens(server: OAuthServer, home: string = os.homedir(), env: NodeJS.ProcessEnv = process.env): boolean {
103
+ return Boolean(readAuth(server, home, env).tokens?.access_token);
86
104
  }
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Raw-byte key decoding for the /mcp panel (pure).
2
+ * Key decoding for the /mcp panel (pure), named through `lib/key-input.ts`
3
+ * so every terminal encoding of a key decodes alike.
3
4
  *
4
5
  * The panel is a two-level menu (server list → server detail with a numbered
5
6
  * action list), navigated with the arrows and Enter, Esc to go back/close. A
@@ -7,28 +8,28 @@
7
8
  * other printable input is ignored.
8
9
  */
9
10
 
11
+ import { keyId } from "../../lib/key-input.ts";
12
+
10
13
  export type McpKey =
11
14
  | { kind: "up" | "down" | "enter" | "back" | "close" }
12
15
  | { kind: "digit"; value: number };
13
16
 
14
17
  export function decodeMcpKey(data: string): McpKey | undefined {
15
- switch (data) {
16
- case "\x1b[A":
17
- case "\x1bOA":
18
+ const id = keyId(data);
19
+ switch (id) {
20
+ case "up":
18
21
  return { kind: "up" };
19
- case "\x1b[B":
20
- case "\x1bOB":
22
+ case "down":
21
23
  return { kind: "down" };
22
- case "\r":
23
- case "\n":
24
+ case "enter":
24
25
  return { kind: "enter" };
25
- case "\x1b":
26
+ case "escape":
26
27
  return { kind: "back" };
27
- case "\x03": // ctrl+c
28
+ case "ctrl+c":
28
29
  return { kind: "close" };
29
30
  default:
30
31
  break;
31
32
  }
32
- if (data.length === 1 && data >= "1" && data <= "9") return { kind: "digit", value: Number(data) };
33
+ if (id && id.length === 1 && id >= "1" && id <= "9") return { kind: "digit", value: Number(id) };
33
34
  return undefined;
34
35
  }
@@ -8,7 +8,7 @@
8
8
  * Only width-1 glyphs are used so cutPlainText's code-point count stays exact.
9
9
  */
10
10
 
11
- import { cutPlainText, padPlainText, panelTopRule, type RenderBlock as Block, windowBlocks } from "../../lib/tui-render.ts";
11
+ import { cutPlainText, hintLines, padPlainText, panelTopRule, type RenderBlock as Block, windowBlocks } from "../../lib/tui-render.ts";
12
12
  import { actionsFor, type McpEntry, statusText } from "./model.ts";
13
13
  import { clampMcpCursor, detailEntry, type McpPanelState } from "./state.ts";
14
14
 
@@ -106,19 +106,23 @@ export function renderMcpPanel(input: McpRenderInput, paint: McpPaint): string[]
106
106
  clampMcpCursor(state, entries);
107
107
 
108
108
  const out: string[] = [panelTopRule(paint.fg, width)];
109
- let footer: string;
109
+ // Hints wrap at their separators (hintLines); the list budget counts the rows.
110
+ const wrapFooter = (text: string) => hintLines(text, width - 1, " ");
111
+ let footer: string[];
110
112
 
111
113
  if (state.detail) {
112
114
  out.push("");
113
115
  const detail = detailLines(input, paint);
114
116
  out.push(...detail.lines);
115
- footer = detail.footer;
117
+ footer = wrapFooter(detail.footer);
116
118
  } else {
117
119
  out.push(` ${paint.bold("Manage MCP servers")}`);
118
120
  const count = `${entries.length} server${entries.length === 1 ? "" : "s"}${input.settled ? "" : " · connecting…"}`;
119
121
  out.push(paint.fg("dim", cutPlainText(` ${count}`, width - 1)));
120
122
 
121
- const footerReserve = 3 + input.notices.length + 1;
123
+ footer = wrapFooter("↑/↓ to navigate · Enter to confirm · Esc to cancel");
124
+ const noticeLines = input.notices.reduce((sum, notice) => sum + notice.split("\n").length, 0);
125
+ const footerReserve = 2 + footer.length + noticeLines + 1;
122
126
  const budget = Math.max(1, height - out.length - footerReserve);
123
127
  if (entries.length === 0) {
124
128
  out.push("", paint.fg("dim", " No MCP servers configured. Add them to .mcp.json or ~/.claude.json."));
@@ -129,13 +133,12 @@ export function renderMcpPanel(input: McpRenderInput, paint: McpPaint): string[]
129
133
  }
130
134
  out.push("");
131
135
  out.push(paint.fg("dim", cutPlainText(" Servers are configured in .mcp.json or ~/.claude.json.", width - 1)));
132
- footer = "↑/↓ to navigate · Enter to confirm · Esc to cancel";
133
136
  }
134
137
 
135
138
  for (const notice of input.notices) {
136
139
  for (const line of notice.split("\n")) out.push(paint.fg("warning", cutPlainText(` ${line}`, width - 1)));
137
140
  }
138
141
  out.push("");
139
- out.push(paint.fg("dim", cutPlainText(` ${footer}`, width - 1)));
142
+ for (const line of footer) out.push(paint.fg("dim", line));
140
143
  return out;
141
144
  }
@@ -7,6 +7,7 @@
7
7
  * anything exotic degrades to a permissive object that passes arguments through.
8
8
  */
9
9
 
10
+ import { crc32 } from "node:zlib";
10
11
  import { type TSchema, Type } from "typebox";
11
12
 
12
13
  /**
@@ -67,9 +68,8 @@ function descriptionOptions(schema: JsonSchema): Record<string, unknown> {
67
68
 
68
69
  function convertLeaf(schema: JsonSchema): TSchema | undefined {
69
70
  const options = descriptionOptions(schema);
70
- const type = Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") : schema.type;
71
71
 
72
- switch (type) {
72
+ switch (schema.type) {
73
73
  case "string":
74
74
  return Type.String(options);
75
75
  case "number":
@@ -78,10 +78,10 @@ function convertLeaf(schema: JsonSchema): TSchema | undefined {
78
78
  return Type.Integer(options);
79
79
  case "boolean":
80
80
  return Type.Boolean(options);
81
+ case "null":
82
+ return Type.Null(options);
81
83
  case "array":
82
84
  return Type.Array(schema.items ? jsonSchemaToTypeBox(schema.items) : Type.Unknown(), options);
83
- case "object":
84
- return jsonSchemaToTypeBox(schema);
85
85
  default:
86
86
  return undefined;
87
87
  }
@@ -108,7 +108,17 @@ export function jsonSchemaToTypeBox(schema: JsonSchema | undefined): TSchema {
108
108
  return Type.Unknown();
109
109
  }
110
110
 
111
- if (schema.properties || schema.type === "object") {
111
+ if (Array.isArray(schema.type) && schema.type.length > 0) {
112
+ if (schema.type.length === 1) return jsonSchemaToTypeBox({ ...schema, type: schema.type[0] });
113
+ // TypeBox emits anyOf, which pi's provider converters understand. Keep
114
+ // the description on the union, not on each of its generated branches.
115
+ return Type.Union(
116
+ schema.type.map((type) => jsonSchemaToTypeBox({ ...schema, type, description: undefined })),
117
+ options,
118
+ );
119
+ }
120
+
121
+ if (schema.type === "object" || (schema.type === undefined && schema.properties)) {
112
122
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
113
123
  const properties: Record<string, TSchema> = {};
114
124
  for (const [key, value] of Object.entries(schema.properties ?? {})) {
@@ -176,6 +186,12 @@ const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
176
186
  * on every later one, since the block stays in the history. So the bytes are
177
187
  * checked against a small magic-number allow-list here and `mimeType` is
178
188
  * derived from them, never trusted; a block that fails becomes a text note.
189
+ * A valid signature is not enough: a PNG signature followed by garbage passed
190
+ * the sniff and still poisoned the session. So the container is walked to its
191
+ * end too (`wholeImageProblem`): PNG chunks with their CRCs from IHDR to IEND,
192
+ * JPEG segments to the scan and the end-of-image marker, the GIF trailer, the
193
+ * WebP RIFF length. It is not a pixel decode, so a crafted image with a valid
194
+ * container can still fail at the provider; a corrupt or truncated one cannot.
179
195
  */
180
196
  /** Base64 length that decodes to just over the byte ceiling (4 base64 chars per 3 bytes). */
181
197
  const MAX_IMAGE_BASE64_LENGTH = Math.ceil((MAX_IMAGE_BYTES * 4) / 3);
@@ -197,9 +213,92 @@ export function validateImageData(
197
213
  if (head.length === 0) return { ok: false, reason: "empty after base64 decode", bytes: 0 };
198
214
  const mime = sniffImageMime(head);
199
215
  if (!mime) return { ok: false, reason: `unrecognised image format (claimed ${claimedMime ?? "none"})`, bytes: approxBytes };
216
+ const bytes = Buffer.from(data, "base64");
217
+ const problem = wholeImageProblem(bytes, mime);
218
+ if (problem) return { ok: false, reason: problem, bytes: bytes.length };
200
219
  return { ok: true, mimeType: mime };
201
220
  }
202
221
 
222
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
223
+
224
+ /** A JPEG end-of-image marker. */
225
+ const JPEG_EOI = Buffer.from([0xff, 0xd9]);
226
+
227
+ /** The end of `buf` with trailing zero padding dropped (some encoders pad after the end marker). */
228
+ function paddedEnd(buf: Buffer): number {
229
+ let end = buf.length;
230
+ while (end > 0 && buf[end - 1] === 0x00) end--;
231
+ return end;
232
+ }
233
+
234
+ /** Why the image's container is not whole, or undefined when it is. Pure. */
235
+ export function wholeImageProblem(buf: Buffer, mime: string): string | undefined {
236
+ if (mime === "image/png") {
237
+ if (buf.length < 8 || !buf.subarray(0, 8).equals(PNG_SIGNATURE)) return "corrupt PNG signature";
238
+ let offset = 8;
239
+ let first = true;
240
+ let sawData = false;
241
+ for (;;) {
242
+ if (offset + 12 > buf.length) return "truncated PNG (no IEND chunk)";
243
+ const length = buf.readUInt32BE(offset);
244
+ const type = buf.toString("latin1", offset + 4, offset + 8);
245
+ if (offset + 12 + length > buf.length) return `truncated PNG (the ${type} chunk runs past the data)`;
246
+ if (crc32(buf.subarray(offset + 4, offset + 8 + length)) !== buf.readUInt32BE(offset + 8 + length)) return `corrupt PNG (bad ${type} chunk checksum)`;
247
+ if (first) {
248
+ if (type !== "IHDR" || length !== 13) return "corrupt PNG (IHDR is not the first chunk)";
249
+ if (buf.readUInt32BE(offset + 8) === 0 || buf.readUInt32BE(offset + 12) === 0) return "corrupt PNG (zero width or height)";
250
+ first = false;
251
+ }
252
+ if (type === "IDAT") sawData = true;
253
+ offset += 12 + length;
254
+ if (type === "IEND") return sawData ? undefined : "corrupt PNG (no image data)";
255
+ }
256
+ }
257
+ if (mime === "image/jpeg") {
258
+ let offset = 2;
259
+ for (;;) {
260
+ if (offset + 2 > buf.length) return "truncated JPEG (no image scan)";
261
+ if (buf[offset] !== 0xff) return "corrupt JPEG (bad segment marker)";
262
+ const marker = buf[offset + 1];
263
+ if (marker === 0xff) {
264
+ offset++; // fill byte
265
+ continue;
266
+ }
267
+ if (marker === 0xda) break; // start of scan: entropy-coded data follows
268
+ if (marker === 0xd9) return "corrupt JPEG (ends before its image scan)";
269
+ if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
270
+ offset += 2;
271
+ continue;
272
+ }
273
+ if (offset + 4 > buf.length) return "truncated JPEG";
274
+ const length = buf.readUInt16BE(offset + 2);
275
+ if (length < 2 || offset + 2 + length > buf.length) return "truncated JPEG (a segment runs past the data)";
276
+ offset += 2 + length;
277
+ }
278
+ // The EOI marker anywhere after the scan: scan data escapes every 0xFF, and
279
+ // bytes after EOI (a vendor trailer, a Motion Photo payload) are allowed.
280
+ return buf.indexOf(JPEG_EOI, offset + 2) !== -1 ? undefined : "truncated JPEG (no end-of-image marker)";
281
+ }
282
+ if (mime === "image/gif") {
283
+ const header = buf.toString("latin1", 0, 6);
284
+ if (header !== "GIF87a" && header !== "GIF89a") return "corrupt GIF header";
285
+ if (buf.length < 14) return "truncated GIF";
286
+ if (buf.readUInt16LE(6) === 0 || buf.readUInt16LE(8) === 0) return "corrupt GIF (zero width or height)";
287
+ const end = paddedEnd(buf);
288
+ return buf[end - 1] === 0x3b ? undefined : "truncated GIF (no trailer)";
289
+ }
290
+ if (mime === "image/webp") {
291
+ if (buf.length < 20) return "truncated WebP";
292
+ const riffEnd = buf.readUInt32LE(4) + 8;
293
+ if (riffEnd > buf.length) return "truncated WebP (shorter than its RIFF length)";
294
+ const chunk = buf.toString("latin1", 12, 16);
295
+ if (chunk !== "VP8 " && chunk !== "VP8L" && chunk !== "VP8X") return "corrupt WebP (no image chunk)";
296
+ if (20 + buf.readUInt32LE(16) > riffEnd) return "truncated WebP (its image chunk runs past the data)";
297
+ return undefined;
298
+ }
299
+ return `unsupported image type ${mime}`;
300
+ }
301
+
203
302
  /** Magic-number sniff for the formats Anthropic accepts. Returns undefined for anything else. */
204
303
  function sniffImageMime(buf: Buffer): string | undefined {
205
304
  if (buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return "image/png";