@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.0-rc.2

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 (387) hide show
  1. package/README.md +13 -10
  2. package/dist/agent/agent-definition-registry.d.ts +3 -0
  3. package/dist/agent/agent-definition-registry.js +3 -1
  4. package/dist/agent/agent-definition.d.ts +15 -0
  5. package/dist/agent/agent-definition.js +27 -3
  6. package/dist/arena/context/context-tools.js +47 -4
  7. package/dist/arena/ledger.js +9 -1
  8. package/dist/arena/strategies/utils.d.ts +1 -4
  9. package/dist/arena/strategies/utils.js +7 -84
  10. package/dist/automation/cron-expr.d.ts +10 -0
  11. package/dist/automation/cron-expr.js +23 -5
  12. package/dist/automation/runner.d.ts +21 -4
  13. package/dist/automation/runner.js +48 -12
  14. package/dist/automation/scheduler.d.ts +38 -0
  15. package/dist/automation/scheduler.js +89 -12
  16. package/dist/automation/store.js +5 -2
  17. package/dist/capability-control/disabled-lists.d.ts +25 -0
  18. package/dist/capability-control/disabled-lists.js +57 -0
  19. package/dist/capability-control/overlay.d.ts +15 -0
  20. package/dist/capability-control/overlay.js +27 -0
  21. package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
  22. package/dist/cc-orchestrator/agent-adapter.js +133 -0
  23. package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
  24. package/dist/cc-orchestrator/cc-capability.js +53 -0
  25. package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
  26. package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
  27. package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
  28. package/dist/cc-orchestrator/codex-session-history.js +187 -0
  29. package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
  30. package/dist/cc-orchestrator/external-agent-changes.js +214 -0
  31. package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
  32. package/dist/cc-orchestrator/external-agent-driver.js +69 -0
  33. package/dist/cc-orchestrator/index.d.ts +8 -0
  34. package/dist/cc-orchestrator/index.js +8 -0
  35. package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
  36. package/dist/cc-orchestrator/relevance-judge.js +29 -0
  37. package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
  38. package/dist/cc-orchestrator/session-discovery.js +125 -0
  39. package/dist/cc-orchestrator/session-history.d.ts +19 -0
  40. package/dist/cc-orchestrator/session-history.js +67 -0
  41. package/dist/cli/agent-server-stdio.d.ts +15 -1
  42. package/dist/cli/agent-server-stdio.js +115 -17
  43. package/dist/cli/agent-server-tcp.js +40 -26
  44. package/dist/context/compaction.d.ts +56 -0
  45. package/dist/context/compaction.js +186 -0
  46. package/dist/context/manager.js +25 -4
  47. package/dist/cost-tracker.js +5 -61
  48. package/dist/credentials/cipher.d.ts +49 -0
  49. package/dist/credentials/cipher.js +45 -0
  50. package/dist/credentials/cookie-jar.d.ts +24 -0
  51. package/dist/credentials/cookie-jar.js +40 -0
  52. package/dist/credentials/index.d.ts +7 -0
  53. package/dist/credentials/index.js +5 -0
  54. package/dist/credentials/inject-credential-tool.d.ts +20 -0
  55. package/dist/credentials/inject-credential-tool.js +130 -0
  56. package/dist/credentials/store.d.ts +72 -0
  57. package/dist/credentials/store.js +184 -0
  58. package/dist/credentials/types.d.ts +56 -0
  59. package/dist/credentials/use-credential-tool.d.ts +29 -0
  60. package/dist/credentials/use-credential-tool.js +205 -0
  61. package/dist/credentials/use-gate.d.ts +56 -0
  62. package/dist/credentials/use-gate.js +52 -0
  63. package/dist/data/model-metadata.d.ts +77 -0
  64. package/dist/data/model-metadata.js +56 -0
  65. package/dist/data/model-metadata.json +216 -0
  66. package/dist/data/openrouter-models.d.ts +18 -7
  67. package/dist/data/openrouter-models.js +35 -8
  68. package/dist/engine/aux-key.d.ts +10 -0
  69. package/dist/engine/aux-key.js +11 -0
  70. package/dist/engine/dynamic-tool-defs.d.ts +19 -0
  71. package/dist/engine/dynamic-tool-defs.js +36 -0
  72. package/dist/engine/engine.d.ts +267 -138
  73. package/dist/engine/engine.js +957 -191
  74. package/dist/engine/friendly-error.d.ts +18 -0
  75. package/dist/engine/friendly-error.js +63 -0
  76. package/dist/engine/goal.d.ts +145 -0
  77. package/dist/engine/goal.js +144 -0
  78. package/dist/engine/image-policy.d.ts +13 -0
  79. package/dist/engine/image-policy.js +24 -0
  80. package/dist/engine/model-connections-pool.d.ts +17 -0
  81. package/dist/engine/model-connections-pool.js +66 -0
  82. package/dist/engine/model-facade.d.ts +10 -0
  83. package/dist/engine/model-facade.js +25 -0
  84. package/dist/engine/patch-orphaned-tools.js +3 -0
  85. package/dist/engine/resolve-llm-config.d.ts +16 -0
  86. package/dist/engine/resolve-llm-config.js +44 -0
  87. package/dist/engine/runtime.js +2 -1
  88. package/dist/engine/sandbox-cache-key.d.ts +10 -0
  89. package/dist/engine/sandbox-cache-key.js +9 -0
  90. package/dist/engine/sandbox-config.d.ts +31 -0
  91. package/dist/engine/sandbox-config.js +38 -0
  92. package/dist/engine/session-usage.d.ts +19 -0
  93. package/dist/engine/session-usage.js +25 -0
  94. package/dist/engine/steer-queue.d.ts +32 -0
  95. package/dist/engine/steer-queue.js +26 -0
  96. package/dist/engine/streaming-tool-queue.d.ts +12 -0
  97. package/dist/engine/streaming-tool-queue.js +50 -10
  98. package/dist/engine/turn-loop.d.ts +67 -3
  99. package/dist/engine/turn-loop.js +292 -41
  100. package/dist/engine/types.d.ts +175 -0
  101. package/dist/engine/types.js +13 -0
  102. package/dist/external-agents/config.d.ts +2 -0
  103. package/dist/external-agents/config.js +15 -0
  104. package/dist/external-agents/types.d.ts +31 -0
  105. package/dist/external-agents/types.js +1 -0
  106. package/dist/git/utils.js +11 -6
  107. package/dist/git/worktree.d.ts +48 -0
  108. package/dist/git/worktree.js +86 -10
  109. package/dist/hooks/goal-stop-hook.d.ts +28 -0
  110. package/dist/hooks/goal-stop-hook.js +186 -9
  111. package/dist/hooks/registry.d.ts +8 -0
  112. package/dist/hooks/registry.js +19 -0
  113. package/dist/hooks/shell-runner.js +10 -21
  114. package/dist/index.d.ts +49 -6
  115. package/dist/index.js +60 -4
  116. package/dist/llm/capabilities/param-specs.d.ts +14 -0
  117. package/dist/llm/capabilities/param-specs.js +62 -0
  118. package/dist/llm/capabilities/rules.js +4 -0
  119. package/dist/llm/capabilities/types.d.ts +10 -0
  120. package/dist/llm/client-base.d.ts +28 -1
  121. package/dist/llm/client-base.js +119 -13
  122. package/dist/llm/model-cache.js +4 -2
  123. package/dist/llm/model-pool.d.ts +14 -0
  124. package/dist/llm/model-pool.js +13 -0
  125. package/dist/llm/provider-auth.d.ts +41 -0
  126. package/dist/llm/provider-auth.js +76 -0
  127. package/dist/llm/provider-catalog.d.ts +4 -0
  128. package/dist/llm/providers/anthropic.js +64 -14
  129. package/dist/llm/providers/openai.d.ts +36 -0
  130. package/dist/llm/providers/openai.js +257 -51
  131. package/dist/llm/reasoning-setting.d.ts +3 -3
  132. package/dist/llm/reasoning-setting.js +9 -1
  133. package/dist/llm/stream-watchdog.js +5 -1
  134. package/dist/llm/token-counter.js +9 -2
  135. package/dist/llm/types.d.ts +4 -0
  136. package/dist/logging/sanitize-messages.js +22 -0
  137. package/dist/lsp/manager.d.ts +1 -1
  138. package/dist/lsp/manager.js +40 -10
  139. package/dist/model-catalog/builtin.d.ts +12 -0
  140. package/dist/model-catalog/builtin.js +407 -0
  141. package/dist/model-catalog/gen-connections.d.ts +20 -0
  142. package/dist/model-catalog/gen-connections.js +28 -0
  143. package/dist/model-catalog/index.d.ts +41 -0
  144. package/dist/model-catalog/index.js +90 -0
  145. package/dist/model-catalog/params.d.ts +20 -0
  146. package/dist/model-catalog/params.js +45 -0
  147. package/dist/model-catalog/resolve.d.ts +48 -0
  148. package/dist/model-catalog/resolve.js +33 -0
  149. package/dist/model-catalog/save-entry.d.ts +32 -0
  150. package/dist/model-catalog/save-entry.js +104 -0
  151. package/dist/model-catalog/types.d.ts +561 -0
  152. package/dist/model-catalog/types.js +93 -0
  153. package/dist/model-catalog/upsert.d.ts +9 -0
  154. package/dist/model-catalog/upsert.js +8 -0
  155. package/dist/onboarding.d.ts +12 -82
  156. package/dist/onboarding.js +61 -326
  157. package/dist/plugins/gitOps.d.ts +19 -0
  158. package/dist/plugins/gitOps.js +73 -4
  159. package/dist/plugins/installer/checkUpdate.d.ts +16 -0
  160. package/dist/plugins/installer/checkUpdate.js +32 -0
  161. package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
  162. package/dist/plugins/installer/codex/convertCommands.js +46 -0
  163. package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
  164. package/dist/plugins/installer/codex/convertMcp.js +45 -5
  165. package/dist/plugins/installer/install.js +25 -1
  166. package/dist/plugins/installer/installFromArchive.d.ts +43 -0
  167. package/dist/plugins/installer/installFromArchive.js +134 -0
  168. package/dist/plugins/installer/installFromSource.js +8 -2
  169. package/dist/plugins/installer/loadPluginAgents.js +6 -2
  170. package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
  171. package/dist/plugins/installer/loadPluginMcp.js +35 -2
  172. package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
  173. package/dist/plugins/installer/pruneDisabled.js +73 -0
  174. package/dist/plugins/installer/types.d.ts +5 -2
  175. package/dist/plugins/installer/types.js +1 -0
  176. package/dist/plugins/installer/uninstall.js +4 -0
  177. package/dist/plugins/installer/unzip.d.ts +14 -0
  178. package/dist/plugins/installer/unzip.js +82 -0
  179. package/dist/plugins/installer/update.d.ts +14 -0
  180. package/dist/plugins/installer/update.js +53 -21
  181. package/dist/plugins/loadPluginHooks.d.ts +47 -1
  182. package/dist/plugins/loadPluginHooks.js +73 -1
  183. package/dist/plugins/marketplaceManager.d.ts +7 -0
  184. package/dist/plugins/marketplaceManager.js +20 -0
  185. package/dist/plugins/pluginCommandHook.js +4 -18
  186. package/dist/plugins/pluginContent.d.ts +30 -0
  187. package/dist/plugins/pluginContent.js +83 -0
  188. package/dist/plugins/pluginInstaller.d.ts +13 -0
  189. package/dist/plugins/pluginInstaller.js +42 -8
  190. package/dist/plugins/schemas.js +1 -0
  191. package/dist/plugins/types.d.ts +6 -0
  192. package/dist/preset/index.d.ts +7 -1
  193. package/dist/preset/index.js +96 -6
  194. package/dist/product/types.d.ts +1 -1
  195. package/dist/prompt/composer.d.ts +27 -0
  196. package/dist/prompt/composer.js +67 -21
  197. package/dist/prompt/instruction-scanner.js +5 -3
  198. package/dist/prompt/section-loader.js +1 -0
  199. package/dist/prompt/sections/base.md +1 -0
  200. package/dist/prompt/sections/browser.md +10 -0
  201. package/dist/prompt/sections/coding.md +4 -0
  202. package/dist/protocol/chat-session-manager.d.ts +8 -2
  203. package/dist/protocol/chat-session-manager.js +36 -0
  204. package/dist/protocol/chat-session.d.ts +65 -0
  205. package/dist/protocol/chat-session.js +94 -2
  206. package/dist/protocol/client.d.ts +27 -0
  207. package/dist/protocol/client.js +38 -0
  208. package/dist/protocol/server.d.ts +112 -0
  209. package/dist/protocol/server.js +586 -16
  210. package/dist/protocol/transport.js +3 -2
  211. package/dist/protocol/types.d.ts +46 -0
  212. package/dist/protocol/types.js +16 -0
  213. package/dist/quota/credentials.d.ts +3 -0
  214. package/dist/quota/credentials.js +80 -0
  215. package/dist/quota/index.d.ts +36 -0
  216. package/dist/quota/index.js +155 -0
  217. package/dist/quota/types.d.ts +48 -0
  218. package/dist/quota/types.js +13 -0
  219. package/dist/review/review-prompt.d.ts +28 -0
  220. package/dist/review/review-prompt.js +81 -0
  221. package/dist/run/FileRunStore.js +8 -3
  222. package/dist/run/RunApprovalBackend.js +25 -5
  223. package/dist/run/RunManager.d.ts +12 -0
  224. package/dist/run/RunManager.js +35 -0
  225. package/dist/run/factory.d.ts +1 -1
  226. package/dist/runtime/background-shell.d.ts +139 -0
  227. package/dist/runtime/background-shell.js +495 -0
  228. package/dist/runtime/output-clean.d.ts +24 -0
  229. package/dist/runtime/output-clean.js +41 -0
  230. package/dist/runtime/ring-file.d.ts +64 -0
  231. package/dist/runtime/ring-file.js +174 -0
  232. package/dist/runtime/safe-spawn.js +15 -34
  233. package/dist/runtime/spawn-common.d.ts +148 -0
  234. package/dist/runtime/spawn-common.js +303 -0
  235. package/dist/runtime/truncate-output.d.ts +22 -0
  236. package/dist/runtime/truncate-output.js +49 -0
  237. package/dist/runtime/utf8-cut.d.ts +11 -0
  238. package/dist/runtime/utf8-cut.js +23 -0
  239. package/dist/services/auto-dream.d.ts +4 -0
  240. package/dist/services/auto-dream.js +26 -26
  241. package/dist/services/diagnostics.d.ts +1 -2
  242. package/dist/services/diagnostics.js +12 -7
  243. package/dist/services/extract-memories.d.ts +14 -1
  244. package/dist/services/extract-memories.js +45 -6
  245. package/dist/services/memory-orchestrator.d.ts +21 -0
  246. package/dist/services/memory-orchestrator.js +125 -50
  247. package/dist/services/session-memory.js +24 -12
  248. package/dist/session/file-history.d.ts +124 -1
  249. package/dist/session/file-history.js +222 -6
  250. package/dist/session/memory.d.ts +116 -2
  251. package/dist/session/memory.js +250 -28
  252. package/dist/session/session-manager.d.ts +40 -0
  253. package/dist/session/session-manager.js +113 -3
  254. package/dist/session/simple-diff.d.ts +23 -0
  255. package/dist/session/simple-diff.js +84 -0
  256. package/dist/session/transcript.d.ts +25 -1
  257. package/dist/session/transcript.js +35 -2
  258. package/dist/session/undo-target.d.ts +67 -0
  259. package/dist/session/undo-target.js +144 -0
  260. package/dist/settings/disk-defaults.d.ts +9 -2
  261. package/dist/settings/disk-defaults.js +11 -2
  262. package/dist/settings/feature-flags.d.ts +64 -0
  263. package/dist/settings/feature-flags.js +61 -0
  264. package/dist/settings/manager.d.ts +68 -1
  265. package/dist/settings/manager.js +266 -29
  266. package/dist/settings/migrate-config.d.ts +45 -0
  267. package/dist/settings/migrate-config.js +125 -0
  268. package/dist/settings/schema-export.d.ts +25 -0
  269. package/dist/settings/schema-export.js +38 -0
  270. package/dist/settings/schema.d.ts +1186 -771
  271. package/dist/settings/schema.js +268 -97
  272. package/dist/skills/scanner.d.ts +9 -0
  273. package/dist/skills/scanner.js +30 -2
  274. package/dist/stt/resolve-transcribe.d.ts +31 -0
  275. package/dist/stt/resolve-transcribe.js +108 -0
  276. package/dist/stt/transcribe.d.ts +51 -0
  277. package/dist/stt/transcribe.js +65 -0
  278. package/dist/tool-system/browser-bridge.d.ts +226 -0
  279. package/dist/tool-system/browser-bridge.js +163 -0
  280. package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
  281. package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
  282. package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
  283. package/dist/tool-system/builtin/agent-notifications.js +9 -3
  284. package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
  285. package/dist/tool-system/builtin/agent-output-file.js +72 -0
  286. package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
  287. package/dist/tool-system/builtin/agent-registry.js +8 -0
  288. package/dist/tool-system/builtin/agent.d.ts +21 -1
  289. package/dist/tool-system/builtin/agent.js +489 -42
  290. package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
  291. package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
  292. package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
  293. package/dist/tool-system/builtin/apply-patch/index.js +0 -15
  294. package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
  295. package/dist/tool-system/builtin/background-jobs.js +124 -0
  296. package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
  297. package/dist/tool-system/builtin/background-shell-tools.js +108 -0
  298. package/dist/tool-system/builtin/background-work.d.ts +67 -0
  299. package/dist/tool-system/builtin/background-work.js +86 -0
  300. package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
  301. package/dist/tool-system/builtin/bash-output-style.js +40 -0
  302. package/dist/tool-system/builtin/bash.d.ts +5 -2
  303. package/dist/tool-system/builtin/bash.js +98 -62
  304. package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
  305. package/dist/tool-system/builtin/browser-tools.js +312 -0
  306. package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
  307. package/dist/tool-system/builtin/cancel-goal.js +64 -0
  308. package/dist/tool-system/builtin/check-quota.d.ts +15 -0
  309. package/dist/tool-system/builtin/check-quota.js +34 -0
  310. package/dist/tool-system/builtin/config.js +7 -0
  311. package/dist/tool-system/builtin/cron.d.ts +7 -0
  312. package/dist/tool-system/builtin/cron.js +55 -3
  313. package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
  314. package/dist/tool-system/builtin/drive-claude-code.js +157 -0
  315. package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
  316. package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
  317. package/dist/tool-system/builtin/edit.js +20 -16
  318. package/dist/tool-system/builtin/eol.d.ts +29 -0
  319. package/dist/tool-system/builtin/eol.js +37 -0
  320. package/dist/tool-system/builtin/file-cache.d.ts +6 -0
  321. package/dist/tool-system/builtin/file-cache.js +8 -0
  322. package/dist/tool-system/builtin/generate-image.d.ts +35 -0
  323. package/dist/tool-system/builtin/generate-image.js +278 -50
  324. package/dist/tool-system/builtin/generate-video.d.ts +55 -0
  325. package/dist/tool-system/builtin/generate-video.js +361 -0
  326. package/dist/tool-system/builtin/glob.js +0 -7
  327. package/dist/tool-system/builtin/grep.js +6 -8
  328. package/dist/tool-system/builtin/image-providers.d.ts +86 -0
  329. package/dist/tool-system/builtin/image-providers.js +190 -0
  330. package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
  331. package/dist/tool-system/builtin/image-uploader.js +74 -0
  332. package/dist/tool-system/builtin/index.d.ts +7 -1
  333. package/dist/tool-system/builtin/index.js +249 -25
  334. package/dist/tool-system/builtin/mcp-tools.js +23 -3
  335. package/dist/tool-system/builtin/memory.js +45 -7
  336. package/dist/tool-system/builtin/notebook-edit.js +0 -7
  337. package/dist/tool-system/builtin/powershell.js +4 -1
  338. package/dist/tool-system/builtin/read.js +10 -10
  339. package/dist/tool-system/builtin/repl.js +4 -1
  340. package/dist/tool-system/builtin/skill.js +9 -0
  341. package/dist/tool-system/builtin/sleep.js +3 -2
  342. package/dist/tool-system/builtin/tool-search.js +25 -7
  343. package/dist/tool-system/builtin/video-providers.d.ts +154 -0
  344. package/dist/tool-system/builtin/video-providers.js +235 -0
  345. package/dist/tool-system/builtin/web-fetch.js +12 -2
  346. package/dist/tool-system/builtin/web-search.js +21 -7
  347. package/dist/tool-system/builtin/worktree.d.ts +2 -1
  348. package/dist/tool-system/builtin/worktree.js +31 -4
  349. package/dist/tool-system/builtin/write.js +0 -6
  350. package/dist/tool-system/context.d.ts +154 -6
  351. package/dist/tool-system/executor.d.ts +3 -1
  352. package/dist/tool-system/executor.js +172 -88
  353. package/dist/tool-system/investigation-guard.js +1 -1
  354. package/dist/tool-system/mcp-manager.d.ts +42 -4
  355. package/dist/tool-system/mcp-manager.js +186 -20
  356. package/dist/tool-system/path-policy.d.ts +5 -0
  357. package/dist/tool-system/path-policy.js +307 -8
  358. package/dist/tool-system/permission.d.ts +36 -1
  359. package/dist/tool-system/permission.js +281 -69
  360. package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
  361. package/dist/tool-system/plan-mode-allowlist.js +24 -2
  362. package/dist/tool-system/registry.d.ts +1 -0
  363. package/dist/tool-system/registry.js +16 -2
  364. package/dist/tool-system/sandbox/index.d.ts +8 -0
  365. package/dist/tool-system/sandbox/index.js +7 -2
  366. package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
  367. package/dist/tool-system/validate-tool-metadata.js +63 -0
  368. package/dist/types.d.ts +217 -6
  369. package/dist/updater.js +20 -9
  370. package/dist/utils/envUtils.d.ts +0 -9
  371. package/dist/utils/envUtils.js +3 -28
  372. package/dist/utils/exec.d.ts +40 -0
  373. package/dist/utils/exec.js +144 -0
  374. package/dist/utils/json.d.ts +12 -0
  375. package/dist/utils/json.js +92 -0
  376. package/dist/utils/theme.d.ts +1 -1
  377. package/dist/utils/theme.js +1 -1
  378. package/dist/utils/toolDisplay.js +0 -1
  379. package/package.json +13 -7
  380. package/dist/agent/coordinator.d.ts +0 -49
  381. package/dist/agent/coordinator.js +0 -77
  382. package/dist/settings/manager.test.js +0 -73
  383. package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
  384. package/dist/tool-system/builtin/remote-trigger.js +0 -54
  385. package/dist/tool-system/builtin/send-message.d.ts +0 -6
  386. package/dist/tool-system/builtin/send-message.js +0 -47
  387. /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
@@ -15,6 +15,17 @@
15
15
  * classification. See docs/architecture/17 for the trust framing.
16
16
  */
17
17
  import { safeSpawn } from "../runtime/safe-spawn.js";
18
+ import { resolveGit, isGitAvailable } from "../utils/exec.js";
19
+ /**
20
+ * On a fresh macOS without the Command Line Tools, `/usr/bin/git` is an `xcrun`
21
+ * stub that spawns fine (so `isGitAvailable()`'s findExecutable check is fooled)
22
+ * but exits non-zero with an xcrun error on stderr. Classify that as a
23
+ * missing-git condition so runGit returns GIT_NOT_FOUND and the host shows the
24
+ * friendly "install Git" guidance instead of leaking the raw xcrun error.
25
+ */
26
+ export function isMissingDeveloperToolsStderr(stderr) {
27
+ return /xcrun: error|not a developer tool|no developer tools/i.test(stderr);
28
+ }
18
29
  /**
19
30
  * Force git to never prompt interactively. gitOps runs in headless contexts
20
31
  * (the Electron main process, the agent worker) that have no TTY, so a
@@ -37,7 +48,18 @@ export function nonInteractiveGitEnv(base) {
37
48
  };
38
49
  }
39
50
  async function runGit(args, cwd, timeoutMs = 60_000) {
40
- const r = await safeSpawn("git", args, {
51
+ // Up-front check: if git isn't installed/reachable at all, return a clear,
52
+ // actionable error instead of a raw "spawn git ENOENT". The desktop host
53
+ // turns GIT_NOT_FOUND into a friendly "install Git" prompt.
54
+ if (!isGitAvailable()) {
55
+ return {
56
+ ok: false,
57
+ error: "GIT_NOT_FOUND: git was not found. Install Git (https://git-scm.com/downloads) " +
58
+ "and restart, or set the `git.path` setting to your git binary.",
59
+ };
60
+ }
61
+ // Resolve git: user `git.path` override, else PATH×PATHEXT (.cmd/.exe on Win).
62
+ const r = await safeSpawn(resolveGit(), args, {
41
63
  cwd: cwd ?? process.cwd(),
42
64
  env: nonInteractiveGitEnv(process.env),
43
65
  timeoutMs,
@@ -51,6 +73,16 @@ async function runGit(args, cwd, timeoutMs = 60_000) {
51
73
  if (r.exitCode === 0) {
52
74
  return { ok: true, stdout: r.stdout.trim() };
53
75
  }
76
+ // mac CLT-missing stub: git spawned but exited non-zero with an xcrun error —
77
+ // treat as not-installed so the host shows install guidance (not a raw error).
78
+ if (isMissingDeveloperToolsStderr(r.stderr)) {
79
+ return {
80
+ ok: false,
81
+ error: "GIT_NOT_FOUND: git was not found (the macOS Command Line Tools are not installed). " +
82
+ "Run `xcode-select --install`, or install Git from https://git-scm.com/downloads, " +
83
+ "then restart — or set the `git.path` setting to your git binary.",
84
+ };
85
+ }
54
86
  return {
55
87
  ok: false,
56
88
  error: `git ${args.join(" ")} exited ${r.exitCode}: ${r.stderr.trim() || "unknown error"}`,
@@ -67,6 +99,11 @@ async function runGit(args, cwd, timeoutMs = 60_000) {
67
99
  * (.claude-plugin and .agents/plugins). On any failure of the sparse setup we
68
100
  * fall back to checking out the full tree, so correctness never depends on
69
101
  * sparse support being present.
102
+ *
103
+ * Pass `full: true` to skip sparse entirely and check out the whole tree. This
104
+ * is required when the caller reads arbitrary plugin content from the clone
105
+ * (installing a plugin or a plugin subdir), as opposed to a marketplace where
106
+ * only the manifest dirs are needed up front.
70
107
  */
71
108
  export async function gitClone(url, destDir, options) {
72
109
  const sparsePaths = options?.sparsePaths ?? [".claude-plugin", ".agents/plugins"];
@@ -77,9 +114,20 @@ export async function gitClone(url, destDir, options) {
77
114
  const cloned = await runGit(args);
78
115
  if (!cloned.ok)
79
116
  return cloned;
117
+ // Full-tree clone: skip sparse setup and check out everything. Used by plugin
118
+ // content clones, which read files anywhere in the repo.
119
+ if (options?.full) {
120
+ const checkout = await runGit(["checkout"], destDir);
121
+ if (!checkout.ok)
122
+ return checkout;
123
+ return cloned;
124
+ }
80
125
  // Restrict the working tree to the manifest dirs, then check out. Use
81
126
  // non-cone mode so nested paths like ".agents/plugins" match exactly.
82
- const init = await runGit(["sparse-checkout", "set", "--no-cone", ...sparsePaths], destDir);
127
+ // `--` before the manifest-derived paths: a malicious manifest path shaped
128
+ // like `--stdin`/`--cone` would otherwise be parsed as a sparse-checkout flag
129
+ // (arg-injection). No RCE flag here as in ls-remote, but match the discipline.
130
+ const init = await runGit(["sparse-checkout", "set", "--no-cone", "--", ...sparsePaths], destDir);
83
131
  if (!init.ok) {
84
132
  // Sparse unsupported/failed — fall back to a normal full checkout so the
85
133
  // manifest is still present.
@@ -96,15 +144,36 @@ export async function gitClone(url, destDir, options) {
96
144
  * harmlessly and the caller proceeds (the files are already present).
97
145
  */
98
146
  export async function gitSparseCheckoutAdd(repoDir, relPath) {
99
- return runGit(["sparse-checkout", "add", relPath], repoDir);
147
+ return runGit(["sparse-checkout", "add", "--", relPath], repoDir);
100
148
  }
101
149
  export async function gitRevParseHead(repoDir) {
102
150
  return runGit(["rev-parse", "HEAD"], repoDir);
103
151
  }
152
+ /**
153
+ * Resolve a ref (default HEAD) on a remote git URL WITHOUT cloning — one
154
+ * cheap network round-trip. Returns the 40-char SHA on success.
155
+ */
156
+ export async function gitLsRemote(url, ref) {
157
+ // `git ls-remote <url> <ref>` prints "<sha>\t<refname>"; take the first sha.
158
+ // SECURITY: the `--` end-of-options separator is REQUIRED before a
159
+ // user-controlled `url`. Without it a marketplace/plugin URL shaped like
160
+ // `--upload-pack=<cmd>` is parsed as a git flag and git EXECUTES <cmd> (RCE).
161
+ // gitClone already does this (`-- url destDir`); ls-remote must match.
162
+ const r = await runGit(["ls-remote", "--", url, ref ?? "HEAD"]);
163
+ if (!r.ok)
164
+ return r;
165
+ const sha = r.stdout.split(/\s+/)[0] ?? "";
166
+ if (!/^[0-9a-f]{7,40}$/i.test(sha)) {
167
+ return { ok: false, error: `ls-remote: no sha for ${ref ?? "HEAD"} in: ${r.stdout.slice(0, 120)}` };
168
+ }
169
+ return { ok: true, stdout: sha };
170
+ }
104
171
  export async function gitFetchAndReset(repoDir, ref) {
105
172
  const fetchArgs = ["fetch", "--depth", "1", "origin"];
173
+ // `--` before a (possibly user/manifest-derived) ref so a flag-shaped ref
174
+ // can't be parsed as a git fetch option. Consistent with clone/ls-remote.
106
175
  if (ref)
107
- fetchArgs.push(ref);
176
+ fetchArgs.push("--", ref);
108
177
  const fetch = await runGit(fetchArgs, repoDir);
109
178
  if (!fetch.ok)
110
179
  return fetch;
@@ -0,0 +1,16 @@
1
+ export interface UpdateCheck {
2
+ name: string;
3
+ updateAvailable: boolean;
4
+ currentCommit?: string;
5
+ latestCommit?: string;
6
+ reason?: string;
7
+ }
8
+ /**
9
+ * Check whether a remote (git-sourced) plugin has a newer commit upstream,
10
+ * WITHOUT cloning. Compares the recorded install commit (meta.commit) against
11
+ * `git ls-remote`. Local-source plugins, older installs without a recorded
12
+ * commit, and ls-remote failures all resolve to `updateAvailable: false` with
13
+ * an explanatory `reason` rather than throwing — the only hard error is an
14
+ * unknown plugin name.
15
+ */
16
+ export declare function checkPluginUpdate(name: string): Promise<UpdateCheck>;
@@ -0,0 +1,32 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { gitLsRemote } from "../gitOps.js";
3
+ import { pluginMetaPath } from "./paths.js";
4
+ import { parseSource } from "./parseSource.js";
5
+ import { CSMeta, PluginInstallError } from "./types.js";
6
+ /**
7
+ * Check whether a remote (git-sourced) plugin has a newer commit upstream,
8
+ * WITHOUT cloning. Compares the recorded install commit (meta.commit) against
9
+ * `git ls-remote`. Local-source plugins, older installs without a recorded
10
+ * commit, and ls-remote failures all resolve to `updateAvailable: false` with
11
+ * an explanatory `reason` rather than throwing — the only hard error is an
12
+ * unknown plugin name.
13
+ */
14
+ export async function checkPluginUpdate(name) {
15
+ const metaPath = pluginMetaPath(name);
16
+ if (!existsSync(metaPath))
17
+ throw new PluginInstallError(`no plugin named '${name}'`);
18
+ const meta = CSMeta.parse(JSON.parse(readFileSync(metaPath, "utf-8")));
19
+ const parsed = parseSource(meta.source);
20
+ if (parsed.kind !== "remote") {
21
+ return { name, updateAvailable: false, reason: "not a remote source" };
22
+ }
23
+ if (!meta.commit) {
24
+ return { name, updateAvailable: false, reason: "no recorded commit to compare" };
25
+ }
26
+ const latest = await gitLsRemote(parsed.url, parsed.ref);
27
+ if (!latest.ok) {
28
+ return { name, updateAvailable: false, currentCommit: meta.commit, reason: latest.error };
29
+ }
30
+ const updateAvailable = latest.stdout.toLowerCase() !== meta.commit.toLowerCase();
31
+ return { name, updateAvailable, currentCommit: meta.commit, latestCommit: latest.stdout };
32
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Map a Codex plugin's slash commands into CC's `commands/` layout.
3
+ *
4
+ * Codex `prompts/*.md` and CC `commands/*.md` are isomorphic: each is a
5
+ * markdown file (optional `description`/`argument-hint` frontmatter) whose
6
+ * basename becomes the command name, and codeshell's `pluginCommandsLoader`
7
+ * already scans `commands/*.md`. So the conversion is a flat copy of every
8
+ * `.md` from `prompts/` (and any explicit `commands/`) into `dest/commands/`.
9
+ *
10
+ * Placeholder syntax differs (Codex `$1`/`$FILE` vs CC `$ARGUMENTS`) but the
11
+ * body is copied verbatim — v1 inert, the same stance taken for `codex_`
12
+ * agent fields (see convertAgents.ts). Non-`.md` files are ignored, matching
13
+ * Codex's own behaviour. On a filename collision an explicit `commands/`
14
+ * entry wins over `prompts/` (the more specific source).
15
+ *
16
+ * Async fs throughout: this runs in the Electron main process during install;
17
+ * sync copies block the event loop and freeze the UI (see convertSkills.ts).
18
+ */
19
+ export declare function copyCodexCommands(sourceDir: string, destDir: string): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir, mkdir, copyFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ /**
5
+ * Map a Codex plugin's slash commands into CC's `commands/` layout.
6
+ *
7
+ * Codex `prompts/*.md` and CC `commands/*.md` are isomorphic: each is a
8
+ * markdown file (optional `description`/`argument-hint` frontmatter) whose
9
+ * basename becomes the command name, and codeshell's `pluginCommandsLoader`
10
+ * already scans `commands/*.md`. So the conversion is a flat copy of every
11
+ * `.md` from `prompts/` (and any explicit `commands/`) into `dest/commands/`.
12
+ *
13
+ * Placeholder syntax differs (Codex `$1`/`$FILE` vs CC `$ARGUMENTS`) but the
14
+ * body is copied verbatim — v1 inert, the same stance taken for `codex_`
15
+ * agent fields (see convertAgents.ts). Non-`.md` files are ignored, matching
16
+ * Codex's own behaviour. On a filename collision an explicit `commands/`
17
+ * entry wins over `prompts/` (the more specific source).
18
+ *
19
+ * Async fs throughout: this runs in the Electron main process during install;
20
+ * sync copies block the event loop and freeze the UI (see convertSkills.ts).
21
+ */
22
+ export async function copyCodexCommands(sourceDir, destDir) {
23
+ // prompts/ first, then commands/ — later writes overwrite earlier, so an
24
+ // explicit commands/ entry wins the collision.
25
+ const written = await copyMdFiles(join(sourceDir, "prompts"), join(destDir, "commands"));
26
+ await copyMdFiles(join(sourceDir, "commands"), join(destDir, "commands"), written);
27
+ }
28
+ /** Flat-copy *.md from srcDir into destDir; returns the set of basenames written. */
29
+ async function copyMdFiles(srcDir, destDir, written = new Set()) {
30
+ if (!existsSync(srcDir))
31
+ return written;
32
+ let ensuredDest = false;
33
+ for (const dirent of await readdir(srcDir, { withFileTypes: true })) {
34
+ if (!dirent.isFile() && !dirent.isSymbolicLink())
35
+ continue;
36
+ if (!dirent.name.endsWith(".md"))
37
+ continue;
38
+ if (!ensuredDest) {
39
+ await mkdir(destDir, { recursive: true });
40
+ ensuredDest = true;
41
+ }
42
+ await copyFile(join(srcDir, dirent.name), join(destDir, dirent.name));
43
+ written.add(dirent.name);
44
+ }
45
+ return written;
46
+ }
@@ -1,8 +1,11 @@
1
+ export declare function normalizeCodexMcpFields(cfg: Record<string, unknown>): Record<string, unknown>;
1
2
  /**
2
3
  * Resolve a Codex manifest's mcpServers declaration into a plain server map.
3
- * - object returned as-is (inline)
4
+ * Each server's env-secret fields are normalized from Codex's snake_case to
5
+ * our runtime camelCase (see normalizeCodexMcpFields).
6
+ * - object → normalized inline map
4
7
  * - string → read that file relative to sourceDir; accept either a bare map
5
- * or a `{ mcpServers: {...} }` wrapper.
8
+ * or a `{ mcpServers: {...} }` wrapper, then normalize.
6
9
  * - undefined → {}
7
10
  */
8
11
  export declare function resolveCodexMcpServers(sourceDir: string, decl: string | Record<string, unknown> | undefined): Record<string, unknown>;
@@ -1,18 +1,58 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { isAbsolute, relative, resolve, sep } from "node:path";
3
3
  import { PluginInstallError } from "../types.js";
4
+ /**
5
+ * Codex `.mcp.json` writes env-secret fields in snake_case
6
+ * (`bearer_token_env_var`, `env_http_headers`, `env_vars`), but our runtime
7
+ * MCP manager reads camelCase (`bearerTokenEnvVar`, `envHeaders`, `envVars` —
8
+ * see buildStdioEnv / buildHttpHeaders in tool-system/mcp-manager.ts). Nothing
9
+ * between install and connect runs these through a zod schema (install spreads
10
+ * the object verbatim; loadPluginMcp casts raw JSON), so without this re-key an
11
+ * imported Codex plugin's secret references would silently never apply and the
12
+ * server would connect without its auth header / env. Re-key one server config
13
+ * object. An already-present camelCase key wins over its snake_case twin so we
14
+ * never clobber the canonical form. Non-object input passes through unchanged.
15
+ */
16
+ const CODEX_FIELD_MAP = {
17
+ bearer_token_env_var: "bearerTokenEnvVar",
18
+ env_http_headers: "envHeaders",
19
+ env_vars: "envVars",
20
+ };
21
+ export function normalizeCodexMcpFields(cfg) {
22
+ if (!cfg || typeof cfg !== "object" || Array.isArray(cfg))
23
+ return cfg;
24
+ const out = {};
25
+ for (const [key, value] of Object.entries(cfg)) {
26
+ const mapped = CODEX_FIELD_MAP[key];
27
+ // Skip the snake_case form if its camelCase twin is already present.
28
+ if (mapped && mapped in cfg)
29
+ continue;
30
+ out[mapped ?? key] = value;
31
+ }
32
+ return out;
33
+ }
34
+ /** Apply normalizeCodexMcpFields to every server in a name → config map. */
35
+ function normalizeServerMap(servers) {
36
+ const out = {};
37
+ for (const [name, cfg] of Object.entries(servers)) {
38
+ out[name] = normalizeCodexMcpFields(cfg);
39
+ }
40
+ return out;
41
+ }
4
42
  /**
5
43
  * Resolve a Codex manifest's mcpServers declaration into a plain server map.
6
- * - object returned as-is (inline)
44
+ * Each server's env-secret fields are normalized from Codex's snake_case to
45
+ * our runtime camelCase (see normalizeCodexMcpFields).
46
+ * - object → normalized inline map
7
47
  * - string → read that file relative to sourceDir; accept either a bare map
8
- * or a `{ mcpServers: {...} }` wrapper.
48
+ * or a `{ mcpServers: {...} }` wrapper, then normalize.
9
49
  * - undefined → {}
10
50
  */
11
51
  export function resolveCodexMcpServers(sourceDir, decl) {
12
52
  if (decl === undefined)
13
53
  return {};
14
54
  if (typeof decl === "object")
15
- return decl;
55
+ return normalizeServerMap(decl);
16
56
  // `decl` is an untrusted, unvalidated manifest field from a (possibly
17
57
  // remote-cloned) plugin. Resolve it and assert it stays inside sourceDir so
18
58
  // a value like "../../../../etc/secret.json" can't escape the plugin dir and
@@ -39,7 +79,7 @@ export function resolveCodexMcpServers(sourceDir, decl) {
39
79
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
40
80
  return {};
41
81
  if ("mcpServers" in parsed) {
42
- return parsed.mcpServers ?? {};
82
+ return normalizeServerMap(parsed.mcpServers ?? {});
43
83
  }
44
- return parsed;
84
+ return normalizeServerMap(parsed);
45
85
  }
@@ -7,6 +7,7 @@ import { CodexPluginManifest, PluginInstallError } from "./types.js";
7
7
  import { convertCodexAgentToml } from "./codex/convertAgents.js";
8
8
  import { resolveCodexMcpServers } from "./codex/convertMcp.js";
9
9
  import { copyCodexSkills } from "./codex/convertSkills.js";
10
+ import { copyCodexCommands } from "./codex/convertCommands.js";
10
11
  import { appendInstallEntry, pluginInstallKey } from "../installedPlugins.js";
11
12
  /**
12
13
  * Install a local plugin directory into ~/.code-shell/plugins/<name>/.
@@ -35,7 +36,12 @@ export async function installPluginFromPath(sourceDir, name, installedAt) {
35
36
  // here; awaiting it yields the event loop so the Electron main process
36
37
  // keeps answering IPC (no UI freeze during install).
37
38
  await cp(sourceDir, tmpDir, { recursive: true });
38
- meta = { name, format: "cc", source: sourceDir, installedAt };
39
+ // Read the declared version from .claude-plugin/plugin.json so the
40
+ // install record (and the UI's "installed version" chip) shows e.g.
41
+ // "v0.1.0" instead of falling back to the literal "local". Best-effort:
42
+ // CC plugins commonly omit version, and the file may be absent/malformed.
43
+ const version = await readCcPluginVersion(sourceDir);
44
+ meta = { name, format: "cc", version, source: sourceDir, installedAt };
39
45
  }
40
46
  else {
41
47
  const manifest = CodexPluginManifest.parse(JSON.parse(await readFile(join(sourceDir, ".codex-plugin", "plugin.json"), "utf-8")));
@@ -43,6 +49,8 @@ export async function installPluginFromPath(sourceDir, name, installedAt) {
43
49
  await copyCodexSkills(sourceDir, tmpDir);
44
50
  // agents (TOML → MD)
45
51
  await convertAgentsInto(sourceDir, tmpDir, name);
52
+ // prompts/ (+ commands/) → commands/ (Codex slash commands == CC commands)
53
+ await copyCodexCommands(sourceDir, tmpDir);
46
54
  // mcp → mcp-servers.json keyed <plugin>:<server>
47
55
  const servers = resolveCodexMcpServers(sourceDir, manifest.mcpServers);
48
56
  const keyed = {};
@@ -74,6 +82,22 @@ export async function installPluginFromPath(sourceDir, name, installedAt) {
74
82
  throw err;
75
83
  }
76
84
  }
85
+ /**
86
+ * Best-effort read of a CC plugin's declared version from
87
+ * <sourceDir>/.claude-plugin/plugin.json. Returns undefined when the file is
88
+ * absent, unparseable, or has no string `version` — the caller then records the
89
+ * install without a version (UI shows the source tag instead of a number).
90
+ */
91
+ async function readCcPluginVersion(sourceDir) {
92
+ try {
93
+ const raw = await readFile(join(sourceDir, ".claude-plugin", "plugin.json"), "utf-8");
94
+ const v = JSON.parse(raw).version;
95
+ return typeof v === "string" && v.length > 0 ? v : undefined;
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
77
101
  /** Walk <sourceDir>/agents/**.toml → <destDir>/agents/**.md (structure preserved). */
78
102
  async function convertAgentsInto(sourceDir, destDir, pluginName) {
79
103
  const agentsSrc = join(sourceDir, "agents");
@@ -0,0 +1,43 @@
1
+ /** Options for the unified local-install entry. */
2
+ export interface InstallLocalOptions {
3
+ /**
4
+ * When the target name is already installed, replace it instead of throwing
5
+ * "already installed". The replace is atomic (rename-backup + rollback via
6
+ * {@link reinstallAtomic}), so a failed overwrite keeps the old version and
7
+ * its registration intact. Unlike a manual uninstall+install, this preserves
8
+ * the user's disable flags in settings (an upgrade is not a removal).
9
+ */
10
+ overwrite?: boolean;
11
+ }
12
+ /**
13
+ * Install a local plugin from a .zip archive.
14
+ *
15
+ * Mirrors installFromSource (the git path): extract to a private temp dir,
16
+ * locate the plugin root inside it, hand off to the local installer (which
17
+ * does CC/Codex detect + convert + register), then always clean up the temp
18
+ * dir. On any failure nothing is left behind.
19
+ *
20
+ * `name` is optional — when omitted it is derived from the plugin manifest
21
+ * (`name` field) or, failing that, the archive's top-level directory. The
22
+ * caller stamps `installedAt` to keep this pure of the unavailable Date.now().
23
+ */
24
+ export declare function installPluginFromArchive(zipPath: string, installedAt: string, name?: string, options?: InstallLocalOptions): Promise<{
25
+ dir: string;
26
+ name: string;
27
+ }>;
28
+ /**
29
+ * Unified local-install entry for the desktop/UI: takes either a directory or
30
+ * a .zip and installs it as a global plugin, deriving the name when not given.
31
+ * The host (which has Date.now) stamps `installedAt`.
32
+ *
33
+ * For a directory we still run findPluginRoot/deriveName so a user can point at
34
+ * a repo checkout whose plugin sits one level down, and so the name comes from
35
+ * the manifest rather than forcing the caller to guess it.
36
+ */
37
+ export declare function installLocalPlugin(input: {
38
+ kind: "dir" | "zip";
39
+ path: string;
40
+ }, installedAt: string, name?: string, options?: InstallLocalOptions): Promise<{
41
+ dir: string;
42
+ name: string;
43
+ }>;
@@ -0,0 +1,134 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { extractZip } from "./unzip.js";
6
+ import { installPluginFromPath } from "./install.js";
7
+ import { reinstallAtomic } from "./update.js";
8
+ import { pluginInstallDir } from "./paths.js";
9
+ import { PluginInstallError } from "./types.js";
10
+ /**
11
+ * Install `root` as plugin `name`, atomically replacing an already-installed
12
+ * plugin of the same name when `overwrite` is set. When no same-name plugin
13
+ * exists this is a plain install (overwrite is a no-op). `root` must stay valid
14
+ * for the duration of the call (zip callers keep their extracted temp dir alive
15
+ * until this resolves).
16
+ */
17
+ async function installRootMaybeOverwrite(root, resolvedName, installedAt, overwrite) {
18
+ const finalDir = pluginInstallDir(resolvedName);
19
+ if (overwrite && existsSync(finalDir)) {
20
+ await reinstallAtomic(resolvedName, root, () => installPluginFromPath(root, resolvedName, installedAt));
21
+ return finalDir;
22
+ }
23
+ return installPluginFromPath(root, resolvedName, installedAt);
24
+ }
25
+ /**
26
+ * Install a local plugin from a .zip archive.
27
+ *
28
+ * Mirrors installFromSource (the git path): extract to a private temp dir,
29
+ * locate the plugin root inside it, hand off to the local installer (which
30
+ * does CC/Codex detect + convert + register), then always clean up the temp
31
+ * dir. On any failure nothing is left behind.
32
+ *
33
+ * `name` is optional — when omitted it is derived from the plugin manifest
34
+ * (`name` field) or, failing that, the archive's top-level directory. The
35
+ * caller stamps `installedAt` to keep this pure of the unavailable Date.now().
36
+ */
37
+ export async function installPluginFromArchive(zipPath, installedAt, name, options) {
38
+ if (!existsSync(zipPath) || !statSync(zipPath).isFile()) {
39
+ throw new PluginInstallError(`archive is not a file: ${zipPath}`);
40
+ }
41
+ const tmp = await mkdtemp(join(tmpdir(), "cs-tmp-zip-"));
42
+ try {
43
+ await extractZip(zipPath, tmp);
44
+ const root = await findPluginRoot(tmp);
45
+ const resolvedName = normalizePluginName(name ?? (await deriveName(root)));
46
+ // Overwrite (when set) runs inside this try so the extracted `root` is still
47
+ // present while reinstallAtomic installs from it; the finally removes tmp.
48
+ const dir = await installRootMaybeOverwrite(root, resolvedName, installedAt, options?.overwrite ?? false);
49
+ return { dir, name: resolvedName };
50
+ }
51
+ finally {
52
+ await rm(tmp, { recursive: true, force: true });
53
+ }
54
+ }
55
+ /**
56
+ * Unified local-install entry for the desktop/UI: takes either a directory or
57
+ * a .zip and installs it as a global plugin, deriving the name when not given.
58
+ * The host (which has Date.now) stamps `installedAt`.
59
+ *
60
+ * For a directory we still run findPluginRoot/deriveName so a user can point at
61
+ * a repo checkout whose plugin sits one level down, and so the name comes from
62
+ * the manifest rather than forcing the caller to guess it.
63
+ */
64
+ export async function installLocalPlugin(input, installedAt, name, options) {
65
+ if (input.kind === "zip") {
66
+ return installPluginFromArchive(input.path, installedAt, name, options);
67
+ }
68
+ if (!existsSync(input.path) || !statSync(input.path).isDirectory()) {
69
+ throw new PluginInstallError(`source is not a directory: ${input.path}`);
70
+ }
71
+ const root = await findPluginRoot(input.path);
72
+ const resolvedName = normalizePluginName(name ?? (await deriveName(root)));
73
+ const dir = await installRootMaybeOverwrite(root, resolvedName, installedAt, options?.overwrite ?? false);
74
+ return { dir, name: resolvedName };
75
+ }
76
+ /**
77
+ * A plugin root is a dir that holds a recognizable manifest or component dir.
78
+ * Zip archives commonly wrap everything in a single top-level folder, so if
79
+ * `dir` itself isn't a plugin root but contains exactly one subdirectory, dive
80
+ * one level. Only one level — deeper nesting is treated as malformed.
81
+ */
82
+ async function findPluginRoot(dir) {
83
+ if (looksLikePluginRoot(dir))
84
+ return dir;
85
+ const entries = await readdir(dir, { withFileTypes: true });
86
+ const subDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith("."));
87
+ if (subDirs.length === 1) {
88
+ const nested = join(dir, subDirs[0].name);
89
+ if (looksLikePluginRoot(nested))
90
+ return nested;
91
+ }
92
+ throw new PluginInstallError("no plugin found in archive (expected .claude-plugin/plugin.json, .codex-plugin/plugin.json, or a skills/commands/agents/hooks directory)");
93
+ }
94
+ function looksLikePluginRoot(dir) {
95
+ return (existsSync(join(dir, ".claude-plugin", "plugin.json")) ||
96
+ existsSync(join(dir, ".codex-plugin", "plugin.json")) ||
97
+ ["skills", "commands", "agents", "hooks"].some((d) => existsSync(join(dir, d))));
98
+ }
99
+ /** Read the `name` field from a CC or Codex manifest; fall back to the dir name. */
100
+ async function deriveName(root) {
101
+ for (const rel of [
102
+ [".claude-plugin", "plugin.json"],
103
+ [".codex-plugin", "plugin.json"],
104
+ ]) {
105
+ const manifestPath = join(root, ...rel);
106
+ if (!existsSync(manifestPath))
107
+ continue;
108
+ try {
109
+ const parsed = JSON.parse(await readFile(manifestPath, "utf-8"));
110
+ if (typeof parsed.name === "string" && parsed.name.trim())
111
+ return parsed.name.trim();
112
+ }
113
+ catch {
114
+ // Malformed manifest → fall through to dir-name derivation.
115
+ }
116
+ }
117
+ return root.split(/[\\/]/).filter(Boolean).pop() ?? "plugin";
118
+ }
119
+ /**
120
+ * Coerce an arbitrary derived name into a safe single path segment:
121
+ * lowercase, only [a-z0-9._-], collapse the rest to '-'. assertSafePluginName
122
+ * (called by installPluginFromPath) rejects separators outright; this makes a
123
+ * manifest name like "My Plugin" usable instead of erroring.
124
+ */
125
+ function normalizePluginName(raw) {
126
+ const cleaned = raw
127
+ .trim()
128
+ .toLowerCase()
129
+ .replace(/[^a-z0-9._-]+/g, "-")
130
+ .replace(/^[-.]+|[-.]+$/g, "");
131
+ if (!cleaned)
132
+ throw new PluginInstallError(`cannot derive a valid plugin name from: ${JSON.stringify(raw)}`);
133
+ return cleaned;
134
+ }
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdtempSync, readFileSync, writeFileSync, rmSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { tmpdir } from "node:os";
4
- import { gitClone } from "../gitOps.js";
4
+ import { gitClone, gitRevParseHead } from "../gitOps.js";
5
5
  import { installPluginFromPath } from "./install.js";
6
6
  import { pluginMetaPath } from "./paths.js";
7
7
  import { PluginInstallError } from "./types.js";
@@ -22,7 +22,7 @@ export async function installPluginFromSource(parsed, name, installedAt) {
22
22
  }
23
23
  const tmp = mkdtempSync(join(tmpdir(), "cs-tmp-clone-"));
24
24
  try {
25
- const clone = await gitClone(parsed.url, tmp, { ref: parsed.ref });
25
+ const clone = await gitClone(parsed.url, tmp, { ref: parsed.ref, full: true });
26
26
  if (!clone.ok) {
27
27
  throw new PluginInstallError(`clone failed: ${clone.error}`);
28
28
  }
@@ -36,6 +36,12 @@ export async function installPluginFromSource(parsed, name, installedAt) {
36
36
  const metaPath = pluginMetaPath(name);
37
37
  const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
38
38
  meta.source = parsed.raw;
39
+ // Record the cloned repo's HEAD sha so `checkPluginUpdate` can compare it
40
+ // against `git ls-remote` later. A failed rev-parse only means "can't check
41
+ // updates", never an install failure — so omit commit and carry on.
42
+ const head = await gitRevParseHead(tmp);
43
+ if (head.ok)
44
+ meta.commit = head.stdout;
39
45
  writeFileSync(metaPath, JSON.stringify(meta, null, 2));
40
46
  return dir;
41
47
  }
@@ -11,12 +11,16 @@ export function pluginAgentDirs(disabledPlugins = []) {
11
11
  const out = [];
12
12
  const data = readInstalledPlugins();
13
13
  for (const [key, entries] of Object.entries(data.plugins)) {
14
- if (disabled.has(pluginNameFromKey(key)))
14
+ const pluginName = pluginNameFromKey(key);
15
+ if (disabled.has(pluginName))
15
16
  continue;
16
17
  for (const entry of entries) {
17
18
  const dir = join(entry.installPath, "agents");
19
+ // Carry pluginName so a plugin agent's bare skill allowlist
20
+ // (`director-skill`) can be namespaced to `<pluginName>:director-skill`
21
+ // at spawn time — see resolveAgentTypeOverrides.
18
22
  if (existsSync(dir))
19
- out.push({ dir, source: "plugin" });
23
+ out.push({ dir, source: "plugin", pluginName });
20
24
  }
21
25
  }
22
26
  return out;
@@ -1,7 +1,14 @@
1
- import type { MCPServerConfig } from "../../types.js";
1
+ import type { MCPServerConfig, MCPServerOverride } from "../../types.js";
2
2
  /**
3
3
  * Merge each registered plugin's MCP servers into a copy of `base`.
4
4
  * Plugin keys are `<plugin>:<server>`. A key already present in `base`
5
5
  * (user-configured) is NOT overwritten. Disabled plugins are skipped.
6
+ *
7
+ * `overrides` lets the user *supplement* a plugin server's env/credential
8
+ * fields without editing the plugin's manifest: for a plugin-sourced server,
9
+ * the override's {@link OVERRIDE_FIELDS} are layered on top (override wins),
10
+ * while command/args/url/transport stay from the plugin. Overrides do NOT
11
+ * apply to user-added (base) servers — those are edited via `mcpServers`
12
+ * directly — and an override for an unknown/disabled server has no effect.
6
13
  */
7
- export declare function mergePluginMcpServers(base: Record<string, MCPServerConfig>, disabledPlugins?: string[]): Record<string, MCPServerConfig>;
14
+ export declare function mergePluginMcpServers(base: Record<string, MCPServerConfig>, disabledPlugins?: string[], overrides?: Record<string, MCPServerOverride>): Record<string, MCPServerConfig>;