@cjhyy/code-shell-core 0.5.0-rc.1 → 0.5.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 (278) hide show
  1. package/dist/agent/agent-definition-registry.d.ts +18 -3
  2. package/dist/agent/agent-definition-registry.js +47 -18
  3. package/dist/agent/agent-definition.d.ts +17 -0
  4. package/dist/agent/agent-definition.js +22 -1
  5. package/dist/arena/arena.js +11 -12
  6. package/dist/arena/context/context-tools.js +2 -4
  7. package/dist/arena/context/within-root.d.ts +7 -0
  8. package/dist/arena/context/within-root.js +15 -0
  9. package/dist/arena/iterate/phases/argue.js +8 -2
  10. package/dist/arena/iterate/phases/revise.js +1 -1
  11. package/dist/arena/iterate/phases/tournament.js +3 -3
  12. package/dist/arena/phases/adjudication.js +1 -4
  13. package/dist/arena/phases/build-consensus.js +1 -4
  14. package/dist/arena/phases/cross-review.js +2 -8
  15. package/dist/arena/phases/debate-rounds.js +1 -4
  16. package/dist/arena/phases/participant-research.js +1 -4
  17. package/dist/arena/phases/planning-detail-expansion.js +8 -10
  18. package/dist/arena/planner.js +0 -1
  19. package/dist/arena/providers/docs.d.ts +1 -0
  20. package/dist/arena/providers/docs.js +5 -2
  21. package/dist/arena/providers/repo.d.ts +1 -0
  22. package/dist/arena/providers/repo.js +5 -2
  23. package/dist/arena/strategies/utils.js +36 -3
  24. package/dist/arena/transitions.d.ts +7 -2
  25. package/dist/arena/transitions.js +9 -4
  26. package/dist/arena/types.d.ts +8 -2
  27. package/dist/automation/cron-expr.d.ts +31 -0
  28. package/dist/automation/cron-expr.js +151 -0
  29. package/dist/automation/index.d.ts +41 -0
  30. package/dist/automation/index.js +39 -0
  31. package/dist/automation/runner.d.ts +67 -0
  32. package/dist/automation/runner.js +56 -0
  33. package/dist/automation/scheduler.d.ts +156 -0
  34. package/dist/automation/scheduler.js +546 -0
  35. package/dist/automation/store.d.ts +39 -0
  36. package/dist/automation/store.js +119 -0
  37. package/dist/automation/write-policy.d.ts +34 -0
  38. package/dist/automation/write-policy.js +113 -0
  39. package/dist/automation/write-run.d.ts +51 -0
  40. package/dist/automation/write-run.js +38 -0
  41. package/dist/capability-control/index.d.ts +5 -0
  42. package/dist/capability-control/index.js +3 -0
  43. package/dist/capability-control/overlay.d.ts +49 -0
  44. package/dist/capability-control/overlay.js +89 -0
  45. package/dist/capability-control/project.d.ts +65 -0
  46. package/dist/capability-control/project.js +147 -0
  47. package/dist/capability-control/service.d.ts +70 -0
  48. package/dist/capability-control/service.js +147 -0
  49. package/dist/capability-control/types.d.ts +62 -0
  50. package/dist/capability-control/types.js +17 -0
  51. package/dist/cli/agent-server-stdio.d.ts +12 -1
  52. package/dist/cli/agent-server-stdio.js +114 -19
  53. package/dist/cli/agent-server-tcp.d.ts +20 -0
  54. package/dist/cli/agent-server-tcp.js +120 -0
  55. package/dist/cli/graceful-shutdown.d.ts +27 -0
  56. package/dist/cli/graceful-shutdown.js +30 -0
  57. package/dist/context/compaction.js +23 -15
  58. package/dist/context/manager.d.ts +0 -20
  59. package/dist/context/manager.js +0 -37
  60. package/dist/cron/cron-runtime.d.ts +2 -0
  61. package/dist/cron/cron-runtime.js +2 -0
  62. package/dist/cron/cron-store.d.ts +2 -0
  63. package/dist/cron/cron-store.js +2 -0
  64. package/dist/cron/scheduler.d.ts +5 -32
  65. package/dist/cron/scheduler.js +5 -113
  66. package/dist/data/openrouter-sync.js +1 -1
  67. package/dist/engine/engine.d.ts +208 -14
  68. package/dist/engine/engine.js +754 -169
  69. package/dist/engine/goal.d.ts +42 -0
  70. package/dist/engine/goal.js +52 -0
  71. package/dist/engine/image-compression.d.ts +81 -0
  72. package/dist/engine/image-compression.js +186 -0
  73. package/dist/engine/image-policy.d.ts +152 -0
  74. package/dist/engine/image-policy.js +165 -0
  75. package/dist/engine/reactive-threshold.d.ts +13 -0
  76. package/dist/engine/reactive-threshold.js +17 -0
  77. package/dist/engine/runtime.js +7 -0
  78. package/dist/engine/session-title.d.ts +13 -0
  79. package/dist/engine/session-title.js +34 -0
  80. package/dist/engine/turn-loop.d.ts +29 -8
  81. package/dist/engine/turn-loop.js +498 -316
  82. package/dist/git/parse-log.d.ts +13 -0
  83. package/dist/git/parse-log.js +21 -0
  84. package/dist/git/utils.d.ts +8 -6
  85. package/dist/git/utils.js +57 -26
  86. package/dist/git/worktree.js +33 -18
  87. package/dist/hooks/events.d.ts +23 -1
  88. package/dist/hooks/goal-stop-hook.d.ts +30 -0
  89. package/dist/hooks/goal-stop-hook.js +102 -0
  90. package/dist/hooks/hook-output.d.ts +33 -0
  91. package/dist/hooks/hook-output.js +79 -0
  92. package/dist/hooks/registry.d.ts +7 -0
  93. package/dist/hooks/registry.js +38 -1
  94. package/dist/hooks/shell-runner.d.ts +2 -0
  95. package/dist/hooks/shell-runner.js +43 -2
  96. package/dist/index.d.ts +31 -6
  97. package/dist/index.js +30 -3
  98. package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
  99. package/dist/llm/capabilities/reasoning-control.js +38 -0
  100. package/dist/llm/capabilities/rules.js +33 -3
  101. package/dist/llm/capabilities/types.d.ts +22 -2
  102. package/dist/llm/clamp-max-tokens.d.ts +11 -0
  103. package/dist/llm/clamp-max-tokens.js +17 -0
  104. package/dist/llm/client-base.d.ts +41 -4
  105. package/dist/llm/client-base.js +67 -13
  106. package/dist/llm/client-factory.d.ts +3 -3
  107. package/dist/llm/client-factory.js +2 -2
  108. package/dist/llm/model-pool.d.ts +15 -11
  109. package/dist/llm/model-pool.js +23 -20
  110. package/dist/llm/provider-catalog.d.ts +2 -2
  111. package/dist/llm/providers/anthropic.d.ts +32 -2
  112. package/dist/llm/providers/anthropic.js +147 -8
  113. package/dist/llm/providers/openai.d.ts +11 -2
  114. package/dist/llm/providers/openai.js +253 -52
  115. package/dist/llm/reasoning-setting.d.ts +51 -0
  116. package/dist/llm/reasoning-setting.js +32 -0
  117. package/dist/llm/stop-reason.d.ts +13 -0
  118. package/dist/llm/stop-reason.js +21 -0
  119. package/dist/llm/strip-vision.d.ts +32 -0
  120. package/dist/llm/strip-vision.js +94 -0
  121. package/dist/llm/types.d.ts +5 -6
  122. package/dist/logging/logger.js +18 -4
  123. package/dist/logging/sanitize-messages.d.ts +10 -0
  124. package/dist/logging/sanitize-messages.js +90 -0
  125. package/dist/lsp/client.js +28 -8
  126. package/dist/lsp/manager.js +2 -1
  127. package/dist/lsp/root-path.d.ts +9 -0
  128. package/dist/lsp/root-path.js +12 -0
  129. package/dist/onboarding.js +18 -11
  130. package/dist/plugins/gitOps.d.ts +34 -0
  131. package/dist/plugins/gitOps.js +59 -3
  132. package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
  133. package/dist/plugins/installer/codex/convertAgents.js +55 -0
  134. package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
  135. package/dist/plugins/installer/codex/convertMcp.js +45 -0
  136. package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
  137. package/dist/plugins/installer/codex/convertSkills.js +33 -0
  138. package/dist/plugins/installer/detectFormat.d.ts +2 -0
  139. package/dist/plugins/installer/detectFormat.js +6 -0
  140. package/dist/plugins/installer/install.d.ts +7 -0
  141. package/dist/plugins/installer/install.js +99 -0
  142. package/dist/plugins/installer/installFromSource.d.ts +13 -0
  143. package/dist/plugins/installer/installFromSource.js +45 -0
  144. package/dist/plugins/installer/list.d.ts +9 -0
  145. package/dist/plugins/installer/list.js +30 -0
  146. package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
  147. package/dist/plugins/installer/loadPluginAgents.js +23 -0
  148. package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
  149. package/dist/plugins/installer/loadPluginMcp.js +74 -0
  150. package/dist/plugins/installer/parseSource.d.ts +20 -0
  151. package/dist/plugins/installer/parseSource.js +74 -0
  152. package/dist/plugins/installer/paths.d.ts +5 -0
  153. package/dist/plugins/installer/paths.js +25 -0
  154. package/dist/plugins/installer/types.d.ts +49 -0
  155. package/dist/plugins/installer/types.js +27 -0
  156. package/dist/plugins/installer/uninstall.d.ts +2 -0
  157. package/dist/plugins/installer/uninstall.js +14 -0
  158. package/dist/plugins/installer/update.d.ts +14 -0
  159. package/dist/plugins/installer/update.js +65 -0
  160. package/dist/plugins/loadPluginHooks.d.ts +8 -1
  161. package/dist/plugins/loadPluginHooks.js +11 -1
  162. package/dist/plugins/marketplaceManager.d.ts +8 -1
  163. package/dist/plugins/marketplaceManager.js +38 -4
  164. package/dist/plugins/pluginCommandHook.js +34 -0
  165. package/dist/plugins/pluginInstaller.d.ts +21 -0
  166. package/dist/plugins/pluginInstaller.js +128 -29
  167. package/dist/plugins/schemas.js +40 -6
  168. package/dist/plugins/types.d.ts +4 -0
  169. package/dist/preset/index.js +16 -0
  170. package/dist/prompt/composer.d.ts +4 -0
  171. package/dist/prompt/composer.js +28 -4
  172. package/dist/prompt/sections/orchestration.md +8 -0
  173. package/dist/protocol/chat-session-manager.d.ts +8 -1
  174. package/dist/protocol/chat-session-manager.js +10 -0
  175. package/dist/protocol/chat-session.d.ts +17 -0
  176. package/dist/protocol/chat-session.js +28 -0
  177. package/dist/protocol/redact.d.ts +50 -0
  178. package/dist/protocol/redact.js +71 -0
  179. package/dist/protocol/server.d.ts +41 -2
  180. package/dist/protocol/server.js +231 -33
  181. package/dist/protocol/tcp-transport.d.ts +45 -0
  182. package/dist/protocol/tcp-transport.js +74 -0
  183. package/dist/protocol/types.d.ts +23 -0
  184. package/dist/protocol/types.js +6 -0
  185. package/dist/remote/bridge.d.ts +11 -2
  186. package/dist/remote/bridge.js +61 -33
  187. package/dist/run/ArtifactTracker.js +5 -4
  188. package/dist/run/EngineRunner.d.ts +27 -1
  189. package/dist/run/EngineRunner.js +46 -8
  190. package/dist/run/FileRunStore.js +32 -8
  191. package/dist/run/RunLock.d.ts +28 -2
  192. package/dist/run/RunLock.js +49 -7
  193. package/dist/run/RunManager.js +51 -3
  194. package/dist/run/RunQueue.d.ts +1 -0
  195. package/dist/run/RunQueue.js +8 -2
  196. package/dist/run/factory.d.ts +7 -0
  197. package/dist/run/factory.js +1 -0
  198. package/dist/run/index.d.ts +2 -2
  199. package/dist/run/index.js +1 -1
  200. package/dist/run/redirect-target.d.ts +7 -0
  201. package/dist/run/redirect-target.js +13 -0
  202. package/dist/run/types.js +1 -1
  203. package/dist/services/browser-open.d.ts +13 -0
  204. package/dist/services/browser-open.js +16 -0
  205. package/dist/services/dream-consolidation.d.ts +57 -0
  206. package/dist/services/dream-consolidation.js +151 -0
  207. package/dist/services/memory-orchestrator.js +18 -0
  208. package/dist/services/notifier.d.ts +8 -0
  209. package/dist/services/notifier.js +39 -25
  210. package/dist/services/oauth.js +5 -7
  211. package/dist/services/session-memory-sort.d.ts +8 -0
  212. package/dist/services/session-memory-sort.js +9 -0
  213. package/dist/services/session-memory.js +8 -9
  214. package/dist/session/file-history.js +10 -3
  215. package/dist/session/session-manager.d.ts +23 -1
  216. package/dist/session/session-manager.js +73 -2
  217. package/dist/settings/disk-defaults.d.ts +35 -0
  218. package/dist/settings/disk-defaults.js +24 -0
  219. package/dist/settings/manager.d.ts +33 -0
  220. package/dist/settings/manager.js +94 -1
  221. package/dist/settings/personalization.d.ts +19 -0
  222. package/dist/settings/personalization.js +7 -0
  223. package/dist/settings/schema.d.ts +1142 -126
  224. package/dist/settings/schema.js +119 -10
  225. package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
  226. package/dist/tool-system/builtin/add-marketplace.js +76 -0
  227. package/dist/tool-system/builtin/agent-registry.d.ts +11 -0
  228. package/dist/tool-system/builtin/agent-registry.js +8 -4
  229. package/dist/tool-system/builtin/agent.d.ts +29 -10
  230. package/dist/tool-system/builtin/agent.js +108 -52
  231. package/dist/tool-system/builtin/apply-patch/index.js +22 -3
  232. package/dist/tool-system/builtin/arena.js +6 -7
  233. package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
  234. package/dist/tool-system/builtin/complete-goal.js +45 -0
  235. package/dist/tool-system/builtin/cron.js +53 -9
  236. package/dist/tool-system/builtin/edit.d.ts +2 -1
  237. package/dist/tool-system/builtin/edit.js +6 -1
  238. package/dist/tool-system/builtin/generate-image.d.ts +25 -0
  239. package/dist/tool-system/builtin/generate-image.js +146 -0
  240. package/dist/tool-system/builtin/glob.js +7 -0
  241. package/dist/tool-system/builtin/grep.js +6 -0
  242. package/dist/tool-system/builtin/index.d.ts +18 -1
  243. package/dist/tool-system/builtin/index.js +62 -1
  244. package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
  245. package/dist/tool-system/builtin/notebook-edit.js +8 -1
  246. package/dist/tool-system/builtin/read.d.ts +2 -1
  247. package/dist/tool-system/builtin/read.js +9 -1
  248. package/dist/tool-system/builtin/sleep.js +9 -3
  249. package/dist/tool-system/builtin/task.js +4 -1
  250. package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
  251. package/dist/tool-system/builtin/update-automation-memory.js +60 -0
  252. package/dist/tool-system/builtin/view-image.d.ts +18 -0
  253. package/dist/tool-system/builtin/view-image.js +96 -0
  254. package/dist/tool-system/builtin/web-search.d.ts +4 -1
  255. package/dist/tool-system/builtin/web-search.js +36 -3
  256. package/dist/tool-system/builtin/write.d.ts +2 -1
  257. package/dist/tool-system/builtin/write.js +7 -1
  258. package/dist/tool-system/context.d.ts +12 -0
  259. package/dist/tool-system/executor.d.ts +0 -1
  260. package/dist/tool-system/executor.js +34 -40
  261. package/dist/tool-system/investigation-guard.d.ts +4 -1
  262. package/dist/tool-system/investigation-guard.js +19 -3
  263. package/dist/tool-system/mcp-manager.d.ts +36 -12
  264. package/dist/tool-system/mcp-manager.js +173 -10
  265. package/dist/tool-system/path-policy.d.ts +94 -0
  266. package/dist/tool-system/path-policy.js +279 -0
  267. package/dist/tool-system/permission.js +43 -50
  268. package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
  269. package/dist/tool-system/plan-mode-allowlist.js +43 -0
  270. package/dist/tool-system/registry.js +12 -1
  271. package/dist/tool-system/validation.d.ts +10 -0
  272. package/dist/tool-system/validation.js +16 -3
  273. package/dist/types.d.ts +90 -15
  274. package/dist/utils/format.js +4 -2
  275. package/dist/utils/lockfile.js +9 -2
  276. package/dist/utils/theme.d.ts +6 -0
  277. package/dist/utils/theme.js +4 -2
  278. package/package.json +2 -1
@@ -0,0 +1,55 @@
1
+ import { parse as parseToml } from "smol-toml";
2
+ import { stringify as stringifyYaml } from "yaml";
3
+ import { serializeAgentDefinition } from "../../../agent/agent-definition.js";
4
+ import { PluginInstallError } from "../types.js";
5
+ const MAPPED = new Set(["name", "description", "model", "developer_instructions"]);
6
+ /**
7
+ * Convert one Codex agent TOML into a CC agent Markdown string.
8
+ * Real CC fields: name, description, model, body(=developer_instructions).
9
+ * Every other field is preserved with a `codex_` prefix (v1 inert; see spec §7.1).
10
+ * `mcp_servers` values are rewritten to `<plugin>:<server>` to match the MCP merge key.
11
+ */
12
+ export function convertCodexAgentToml(toml, sourceName, pluginName) {
13
+ let raw;
14
+ try {
15
+ raw = parseToml(toml);
16
+ }
17
+ catch (err) {
18
+ throw new PluginInstallError(`${sourceName}: invalid TOML — ${err.message}`);
19
+ }
20
+ if (typeof raw.name !== "string" || !raw.name.trim()) {
21
+ throw new PluginInstallError(`${sourceName}: missing required 'name'`);
22
+ }
23
+ if (typeof raw.description !== "string" || !raw.description.trim()) {
24
+ throw new PluginInstallError(`${sourceName}: missing required 'description'`);
25
+ }
26
+ const body = typeof raw.developer_instructions === "string" ? raw.developer_instructions.trim() : "";
27
+ const base = serializeAgentDefinition({
28
+ name: raw.name.trim(),
29
+ description: raw.description.trim(),
30
+ systemPrompt: body,
31
+ ...(typeof raw.model === "string" && raw.model.trim() ? { model: raw.model.trim() } : {}),
32
+ });
33
+ // Collect codex_-prefixed extras.
34
+ const extras = {};
35
+ for (const [key, value] of Object.entries(raw)) {
36
+ if (MAPPED.has(key))
37
+ continue;
38
+ const outKey = `codex_${key}`;
39
+ if (key === "mcp_servers" && Array.isArray(value)) {
40
+ extras[outKey] = value.map((s) => (typeof s === "string" ? `${pluginName}:${s}` : s));
41
+ }
42
+ else {
43
+ extras[outKey] = value;
44
+ }
45
+ }
46
+ if (Object.keys(extras).length === 0)
47
+ return base;
48
+ // Splice the extra YAML lines into the existing frontmatter block.
49
+ const extraYaml = stringifyYaml(extras).trimEnd();
50
+ const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(base);
51
+ if (!match)
52
+ throw new PluginInstallError(`${sourceName}: internal — serializer produced no frontmatter`);
53
+ const [, fm, rest] = match;
54
+ return `---\n${fm}\n${extraYaml}\n---\n${rest}`;
55
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Resolve a Codex manifest's mcpServers declaration into a plain server map.
3
+ * - object → returned as-is (inline)
4
+ * - string → read that file relative to sourceDir; accept either a bare map
5
+ * or a `{ mcpServers: {...} }` wrapper.
6
+ * - undefined → {}
7
+ */
8
+ export declare function resolveCodexMcpServers(sourceDir: string, decl: string | Record<string, unknown> | undefined): Record<string, unknown>;
@@ -0,0 +1,45 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+ import { PluginInstallError } from "../types.js";
4
+ /**
5
+ * Resolve a Codex manifest's mcpServers declaration into a plain server map.
6
+ * - object → returned as-is (inline)
7
+ * - string → read that file relative to sourceDir; accept either a bare map
8
+ * or a `{ mcpServers: {...} }` wrapper.
9
+ * - undefined → {}
10
+ */
11
+ export function resolveCodexMcpServers(sourceDir, decl) {
12
+ if (decl === undefined)
13
+ return {};
14
+ if (typeof decl === "object")
15
+ return decl;
16
+ // `decl` is an untrusted, unvalidated manifest field from a (possibly
17
+ // remote-cloned) plugin. Resolve it and assert it stays inside sourceDir so
18
+ // a value like "../../../../etc/secret.json" can't escape the plugin dir and
19
+ // read arbitrary files into the install's mcp-servers.json.
20
+ const base = resolve(sourceDir);
21
+ const path = resolve(base, decl);
22
+ const rel = relative(base, path);
23
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
24
+ throw new PluginInstallError(`mcpServers ref escapes plugin dir: ${decl}`);
25
+ }
26
+ if (!existsSync(path)) {
27
+ throw new PluginInstallError(`mcpServers ref not found: ${decl}`);
28
+ }
29
+ let parsed;
30
+ try {
31
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
32
+ }
33
+ catch (err) {
34
+ throw new PluginInstallError(`invalid mcp json ${decl}: ${err instanceof Error ? err.message : String(err)}`);
35
+ }
36
+ // Only objects yield server maps. An array/number/string that happens to
37
+ // parse is not a valid mcp config — return an empty map rather than casting
38
+ // a non-object to Record.
39
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
40
+ return {};
41
+ if ("mcpServers" in parsed) {
42
+ return parsed.mcpServers ?? {};
43
+ }
44
+ return parsed;
45
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copy <sourceDir>/skills into <destDir>/skills verbatim (Codex & CC SKILL.md
3
+ * are isomorphic). Validates each SKILL.md has a frontmatter block first — a
4
+ * malformed skill fails the whole install (spec §10).
5
+ *
6
+ * Async fs throughout: this runs inside the Electron main process during
7
+ * install; a synchronous recursive copy (cpSync) blocks the event loop and
8
+ * freezes the UI. The sibling `cc` branch already awaits cp for this reason
9
+ * (see install.ts) — this mirrors the 178abc8 sync→async migration that
10
+ * originally missed this function.
11
+ */
12
+ export declare function copyCodexSkills(sourceDir: string, destDir: string): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir, readFile, cp } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { PluginInstallError } from "../types.js";
5
+ const FRONTMATTER = /^---\s*\n[\s\S]*?\n---/;
6
+ /**
7
+ * Copy <sourceDir>/skills into <destDir>/skills verbatim (Codex & CC SKILL.md
8
+ * are isomorphic). Validates each SKILL.md has a frontmatter block first — a
9
+ * malformed skill fails the whole install (spec §10).
10
+ *
11
+ * Async fs throughout: this runs inside the Electron main process during
12
+ * install; a synchronous recursive copy (cpSync) blocks the event loop and
13
+ * freezes the UI. The sibling `cc` branch already awaits cp for this reason
14
+ * (see install.ts) — this mirrors the 178abc8 sync→async migration that
15
+ * originally missed this function.
16
+ */
17
+ export async function copyCodexSkills(sourceDir, destDir) {
18
+ const skillsSrc = join(sourceDir, "skills");
19
+ if (!existsSync(skillsSrc))
20
+ return;
21
+ for (const dirent of await readdir(skillsSrc, { withFileTypes: true })) {
22
+ if (!dirent.isDirectory())
23
+ continue;
24
+ const skillFile = join(skillsSrc, dirent.name, "SKILL.md");
25
+ if (!existsSync(skillFile))
26
+ continue;
27
+ const raw = (await readFile(skillFile, "utf-8")).trim();
28
+ if (!FRONTMATTER.test(raw)) {
29
+ throw new PluginInstallError(`skills/${dirent.name}/SKILL.md: missing frontmatter`);
30
+ }
31
+ }
32
+ await cp(skillsSrc, join(destDir, "skills"), { recursive: true });
33
+ }
@@ -0,0 +1,2 @@
1
+ /** Binary detection: a Codex plugin has `.codex-plugin/plugin.json`; everything else is CC. */
2
+ export declare function detectPluginFormat(sourceDir: string): "codex" | "cc";
@@ -0,0 +1,6 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** Binary detection: a Codex plugin has `.codex-plugin/plugin.json`; everything else is CC. */
4
+ export function detectPluginFormat(sourceDir) {
5
+ return existsSync(join(sourceDir, ".codex-plugin", "plugin.json")) ? "codex" : "cc";
6
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Install a local plugin directory into ~/.code-shell/plugins/<name>/.
3
+ * Builds into a temp sibling dir, then renames into place — a conversion
4
+ * failure leaves nothing behind. `installedAt` is passed in (caller stamps the
5
+ * timestamp) to keep this function pure of the unavailable Date.now().
6
+ */
7
+ export declare function installPluginFromPath(sourceDir: string, name: string, installedAt: string): Promise<string>;
@@ -0,0 +1,99 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { mkdir, writeFile, readFile, readdir, cp, rm, rename, } from "node:fs/promises";
3
+ import { join, relative } from "node:path";
4
+ import { detectPluginFormat } from "./detectFormat.js";
5
+ import { pluginInstallDir, pluginsRoot, assertSafePluginName } from "./paths.js";
6
+ import { CodexPluginManifest, PluginInstallError } from "./types.js";
7
+ import { convertCodexAgentToml } from "./codex/convertAgents.js";
8
+ import { resolveCodexMcpServers } from "./codex/convertMcp.js";
9
+ import { copyCodexSkills } from "./codex/convertSkills.js";
10
+ import { appendInstallEntry, pluginInstallKey } from "../installedPlugins.js";
11
+ /**
12
+ * Install a local plugin directory into ~/.code-shell/plugins/<name>/.
13
+ * Builds into a temp sibling dir, then renames into place — a conversion
14
+ * failure leaves nothing behind. `installedAt` is passed in (caller stamps the
15
+ * timestamp) to keep this function pure of the unavailable Date.now().
16
+ */
17
+ export async function installPluginFromPath(sourceDir, name, installedAt) {
18
+ assertSafePluginName(name);
19
+ if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
20
+ throw new PluginInstallError(`source is not a directory: ${sourceDir}`);
21
+ }
22
+ const finalDir = pluginInstallDir(name);
23
+ if (existsSync(finalDir)) {
24
+ throw new PluginInstallError(`plugin '${name}' already installed; uninstall first or rename the source`);
25
+ }
26
+ await mkdir(pluginsRoot(), { recursive: true });
27
+ const tmpDir = join(pluginsRoot(), `.tmp-${name}-${process.pid}`);
28
+ await rm(tmpDir, { recursive: true, force: true });
29
+ await mkdir(tmpDir, { recursive: true });
30
+ try {
31
+ const format = detectPluginFormat(sourceDir);
32
+ let meta;
33
+ if (format === "cc") {
34
+ // Async cp: a recursive copy of a whole plugin dir is the dominant cost
35
+ // here; awaiting it yields the event loop so the Electron main process
36
+ // keeps answering IPC (no UI freeze during install).
37
+ await cp(sourceDir, tmpDir, { recursive: true });
38
+ meta = { name, format: "cc", source: sourceDir, installedAt };
39
+ }
40
+ else {
41
+ const manifest = CodexPluginManifest.parse(JSON.parse(await readFile(join(sourceDir, ".codex-plugin", "plugin.json"), "utf-8")));
42
+ // skills (verbatim copy)
43
+ await copyCodexSkills(sourceDir, tmpDir);
44
+ // agents (TOML → MD)
45
+ await convertAgentsInto(sourceDir, tmpDir, name);
46
+ // mcp → mcp-servers.json keyed <plugin>:<server>
47
+ const servers = resolveCodexMcpServers(sourceDir, manifest.mcpServers);
48
+ const keyed = {};
49
+ for (const [serverName, cfg] of Object.entries(servers)) {
50
+ const key = `${name}:${serverName}`;
51
+ keyed[key] = { ...cfg, name: key };
52
+ }
53
+ if (Object.keys(keyed).length > 0) {
54
+ await writeFile(join(tmpDir, "mcp-servers.json"), JSON.stringify(keyed, null, 2));
55
+ }
56
+ meta = { name, format: "codex", version: manifest.version, source: sourceDir, installedAt };
57
+ }
58
+ await writeFile(join(tmpDir, ".cs-meta.json"), JSON.stringify(meta, null, 2));
59
+ await rename(tmpDir, finalDir);
60
+ // Register so existing loaders (scanInstalledPlugins / loadPluginHooks)
61
+ // discover this local install. Marketplace tag "local" distinguishes it
62
+ // from cache/marketplace installs.
63
+ appendInstallEntry(pluginInstallKey(name, "local"), {
64
+ scope: "user",
65
+ installPath: finalDir,
66
+ version: meta.version ?? "local",
67
+ installedAt,
68
+ lastUpdated: installedAt,
69
+ });
70
+ return finalDir;
71
+ }
72
+ catch (err) {
73
+ await rm(tmpDir, { recursive: true, force: true });
74
+ throw err;
75
+ }
76
+ }
77
+ /** Walk <sourceDir>/agents/**.toml → <destDir>/agents/**.md (structure preserved). */
78
+ async function convertAgentsInto(sourceDir, destDir, pluginName) {
79
+ const agentsSrc = join(sourceDir, "agents");
80
+ if (!existsSync(agentsSrc))
81
+ return;
82
+ const walk = async (dir) => {
83
+ for (const dirent of await readdir(dir, { withFileTypes: true })) {
84
+ const abs = join(dir, dirent.name);
85
+ if (dirent.isDirectory()) {
86
+ await walk(abs);
87
+ continue;
88
+ }
89
+ if (!dirent.name.endsWith(".toml"))
90
+ continue;
91
+ const rel = relative(agentsSrc, abs).replace(/\.toml$/, ".md");
92
+ const outPath = join(destDir, "agents", rel);
93
+ await mkdir(join(outPath, ".."), { recursive: true });
94
+ const md = convertCodexAgentToml(await readFile(abs, "utf-8"), rel, pluginName);
95
+ await writeFile(outPath, md);
96
+ }
97
+ };
98
+ await walk(agentsSrc);
99
+ }
@@ -0,0 +1,13 @@
1
+ import type { ParsedSource } from "./parseSource.js";
2
+ /**
3
+ * Remote install orchestrator: a thin bridge over the existing pieces. Clone
4
+ * the git source to a private temp dir, hand the (sub)directory to the local
5
+ * installer (which does CC/Codex detect + convert + register), then rewrite
6
+ * the recorded `.cs-meta.json` source back to the original git string so
7
+ * `plugin list`/`update` see the git source instead of the throwaway clone.
8
+ *
9
+ * Guarantees: on any failure nothing is left behind — the temp clone is always
10
+ * removed, and `installPluginFromPath` itself leaves no half-built install dir.
11
+ * See spec docs/superpowers/specs/2026-05-29-plugin-remote-install-design.md.
12
+ */
13
+ export declare function installPluginFromSource(parsed: ParsedSource, name: string, installedAt: string): Promise<string>;
@@ -0,0 +1,45 @@
1
+ import { existsSync, mkdtempSync, readFileSync, writeFileSync, rmSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { gitClone } from "../gitOps.js";
5
+ import { installPluginFromPath } from "./install.js";
6
+ import { pluginMetaPath } from "./paths.js";
7
+ import { PluginInstallError } from "./types.js";
8
+ /**
9
+ * Remote install orchestrator: a thin bridge over the existing pieces. Clone
10
+ * the git source to a private temp dir, hand the (sub)directory to the local
11
+ * installer (which does CC/Codex detect + convert + register), then rewrite
12
+ * the recorded `.cs-meta.json` source back to the original git string so
13
+ * `plugin list`/`update` see the git source instead of the throwaway clone.
14
+ *
15
+ * Guarantees: on any failure nothing is left behind — the temp clone is always
16
+ * removed, and `installPluginFromPath` itself leaves no half-built install dir.
17
+ * See spec docs/superpowers/specs/2026-05-29-plugin-remote-install-design.md.
18
+ */
19
+ export async function installPluginFromSource(parsed, name, installedAt) {
20
+ if (parsed.kind !== "remote") {
21
+ throw new PluginInstallError("installPluginFromSource expects a remote source");
22
+ }
23
+ const tmp = mkdtempSync(join(tmpdir(), "cs-tmp-clone-"));
24
+ try {
25
+ const clone = await gitClone(parsed.url, tmp, { ref: parsed.ref });
26
+ if (!clone.ok) {
27
+ throw new PluginInstallError(`clone failed: ${clone.error}`);
28
+ }
29
+ const realSrc = parsed.subdir ? join(tmp, parsed.subdir) : tmp;
30
+ if (!existsSync(realSrc) || !statSync(realSrc).isDirectory()) {
31
+ throw new PluginInstallError(`subdir not found in repo: ${parsed.subdir}`);
32
+ }
33
+ const dir = await installPluginFromPath(realSrc, name, installedAt);
34
+ // Rewrite source: realSrc is the throwaway clone path; record the git
35
+ // string so list/update can re-clone later.
36
+ const metaPath = pluginMetaPath(name);
37
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
38
+ meta.source = parsed.raw;
39
+ writeFileSync(metaPath, JSON.stringify(meta, null, 2));
40
+ return dir;
41
+ }
42
+ finally {
43
+ rmSync(tmp, { recursive: true, force: true });
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ export interface PluginListRow {
2
+ name: string;
3
+ format: "cc" | "codex";
4
+ version?: string;
5
+ source: string;
6
+ installedAt: string;
7
+ }
8
+ /** Read .cs-meta.json from each registered plugin dir that has one (local installs). */
9
+ export declare function listInstalledPlugins(): PluginListRow[];
@@ -0,0 +1,30 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { readInstalledPlugins } from "../installedPlugins.js";
4
+ import { CSMeta } from "./types.js";
5
+ /** Read .cs-meta.json from each registered plugin dir that has one (local installs). */
6
+ export function listInstalledPlugins() {
7
+ const rows = [];
8
+ const data = readInstalledPlugins();
9
+ for (const entries of Object.values(data.plugins)) {
10
+ for (const entry of entries) {
11
+ const metaPath = join(entry.installPath, ".cs-meta.json");
12
+ if (!existsSync(metaPath))
13
+ continue;
14
+ try {
15
+ const meta = CSMeta.parse(JSON.parse(readFileSync(metaPath, "utf-8")));
16
+ rows.push({
17
+ name: meta.name,
18
+ format: meta.format,
19
+ version: meta.version,
20
+ source: meta.source,
21
+ installedAt: meta.installedAt,
22
+ });
23
+ }
24
+ catch {
25
+ // not a local cs-managed install; skip
26
+ }
27
+ }
28
+ }
29
+ return rows;
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentSourceDir } from "../../agent/agent-definition-registry.js";
2
+ /** Agent source dirs contributed by installed plugins (each <installPath>/agents). */
3
+ export declare function pluginAgentDirs(disabledPlugins?: string[]): AgentSourceDir[];
@@ -0,0 +1,23 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { readInstalledPlugins } from "../installedPlugins.js";
4
+ function pluginNameFromKey(key) {
5
+ const at = key.lastIndexOf("@");
6
+ return at > 0 ? key.slice(0, at) : key;
7
+ }
8
+ /** Agent source dirs contributed by installed plugins (each <installPath>/agents). */
9
+ export function pluginAgentDirs(disabledPlugins = []) {
10
+ const disabled = new Set(disabledPlugins);
11
+ const out = [];
12
+ const data = readInstalledPlugins();
13
+ for (const [key, entries] of Object.entries(data.plugins)) {
14
+ if (disabled.has(pluginNameFromKey(key)))
15
+ continue;
16
+ for (const entry of entries) {
17
+ const dir = join(entry.installPath, "agents");
18
+ if (existsSync(dir))
19
+ out.push({ dir, source: "plugin" });
20
+ }
21
+ }
22
+ return out;
23
+ }
@@ -0,0 +1,7 @@
1
+ import type { MCPServerConfig } from "../../types.js";
2
+ /**
3
+ * Merge each registered plugin's MCP servers into a copy of `base`.
4
+ * Plugin keys are `<plugin>:<server>`. A key already present in `base`
5
+ * (user-configured) is NOT overwritten. Disabled plugins are skipped.
6
+ */
7
+ export declare function mergePluginMcpServers(base: Record<string, MCPServerConfig>, disabledPlugins?: string[]): Record<string, MCPServerConfig>;
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { readInstalledPlugins } from "../installedPlugins.js";
4
+ function pluginNameFromKey(key) {
5
+ const at = key.lastIndexOf("@");
6
+ return at > 0 ? key.slice(0, at) : key;
7
+ }
8
+ /**
9
+ * Read a single plugin's MCP servers, already keyed `<plugin>:<server>`.
10
+ *
11
+ * Two on-disk shapes are supported:
12
+ * - `mcp-servers.json` — the Codex-install product: already keyed + named.
13
+ * - `.mcp.json` — what a CC plugin ships verbatim (Docker's mcp-toolkit etc.).
14
+ * It uses a `{ mcpServers: {...} }` wrapper with bare server names; we unwrap
15
+ * it and apply the `<plugin>:` prefix + `name` so it matches the keyed shape.
16
+ *
17
+ * `mcp-servers.json` wins if both exist. Malformed files yield {} (must not
18
+ * break startup).
19
+ */
20
+ function readPluginMcp(installPath, pluginName) {
21
+ const keyedPath = join(installPath, "mcp-servers.json");
22
+ if (existsSync(keyedPath)) {
23
+ try {
24
+ return JSON.parse(readFileSync(keyedPath, "utf-8"));
25
+ }
26
+ catch {
27
+ return {};
28
+ }
29
+ }
30
+ const rawPath = join(installPath, ".mcp.json");
31
+ if (existsSync(rawPath)) {
32
+ let parsed;
33
+ try {
34
+ parsed = JSON.parse(readFileSync(rawPath, "utf-8"));
35
+ }
36
+ catch {
37
+ return {};
38
+ }
39
+ const servers = parsed && typeof parsed === "object" && "mcpServers" in parsed
40
+ ? parsed.mcpServers ?? {}
41
+ : parsed ?? {};
42
+ const keyed = {};
43
+ for (const [serverName, cfg] of Object.entries(servers)) {
44
+ const key = `${pluginName}:${serverName}`;
45
+ keyed[key] = { ...cfg, name: key };
46
+ }
47
+ return keyed;
48
+ }
49
+ return {};
50
+ }
51
+ /**
52
+ * Merge each registered plugin's MCP servers into a copy of `base`.
53
+ * Plugin keys are `<plugin>:<server>`. A key already present in `base`
54
+ * (user-configured) is NOT overwritten. Disabled plugins are skipped.
55
+ */
56
+ export function mergePluginMcpServers(base, disabledPlugins = []) {
57
+ const disabled = new Set(disabledPlugins);
58
+ const merged = { ...base };
59
+ const data = readInstalledPlugins();
60
+ for (const [key, entries] of Object.entries(data.plugins)) {
61
+ const pluginName = pluginNameFromKey(key);
62
+ if (disabled.has(pluginName))
63
+ continue;
64
+ for (const entry of entries) {
65
+ const servers = readPluginMcp(entry.installPath, pluginName);
66
+ for (const [k, cfg] of Object.entries(servers)) {
67
+ if (k in merged)
68
+ continue; // user / earlier wins
69
+ merged[k] = cfg;
70
+ }
71
+ }
72
+ }
73
+ return merged;
74
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Result of classifying a `plugin install <source>` argument. Pure parse — no
3
+ * I/O, no execution. `local` resolves to an absolute path; `remote` carries a
4
+ * git-cloneable URL plus optional ref/subdir and the original raw string
5
+ * (written back into .cs-meta.json so `plugin list`/`update` see the git
6
+ * source, not the throwaway clone path). See spec
7
+ * docs/superpowers/specs/2026-05-29-plugin-remote-install-design.md §4–§5.
8
+ */
9
+ export type ParsedSource = {
10
+ kind: "local";
11
+ path: string;
12
+ } | {
13
+ kind: "remote";
14
+ url: string;
15
+ ref?: string;
16
+ subdir?: string;
17
+ raw: string;
18
+ inferredName: string;
19
+ };
20
+ export declare function parseSource(input: string): ParsedSource;
@@ -0,0 +1,74 @@
1
+ import { resolve } from "node:path";
2
+ import { githubRepoToCloneUrl } from "../gitOps.js";
3
+ /** SSH shorthand `git@host:org/repo.git` — has a `:` after the `git@host` part. */
4
+ function isSshUrl(s) {
5
+ return /^[^/]+@[^/]+:/.test(s) && !s.includes("://");
6
+ }
7
+ function isRemote(s) {
8
+ return (s.startsWith("github:") ||
9
+ s.startsWith("https://") ||
10
+ s.startsWith("http://") ||
11
+ s.startsWith("git://") ||
12
+ s.startsWith("file://") ||
13
+ isSshUrl(s));
14
+ }
15
+ /** Last path segment of a repo url/path, with a trailing `.git` stripped. */
16
+ function repoNameFromUrl(url) {
17
+ const noGit = url.replace(/\.git$/, "");
18
+ const seg = noGit.split(/[/:]/).filter(Boolean).pop() ?? noGit;
19
+ return seg;
20
+ }
21
+ /** Last path segment of a subdir. */
22
+ function lastSegment(p) {
23
+ return p.split("/").filter(Boolean).pop() ?? p;
24
+ }
25
+ export function parseSource(input) {
26
+ if (!isRemote(input)) {
27
+ return { kind: "local", path: resolve(input) };
28
+ }
29
+ const raw = input;
30
+ // 1. Split off `#subdir` (last `#`).
31
+ let rest = input;
32
+ let subdir;
33
+ const hashIdx = rest.indexOf("#");
34
+ if (hashIdx >= 0) {
35
+ subdir = rest.slice(hashIdx + 1) || undefined;
36
+ rest = rest.slice(0, hashIdx);
37
+ }
38
+ // 2. Split off `@ref`. SSH form `git@host:...` contains its own `@`, so
39
+ // strip the leading `user@` first, then look for a ref `@` in the tail.
40
+ let ref;
41
+ let base = rest;
42
+ if (isSshUrl(rest)) {
43
+ const at = rest.indexOf("@");
44
+ const head = rest.slice(0, at + 1); // "git@"
45
+ const tail = rest.slice(at + 1); // "host:org/repo.git@ref"
46
+ const tailAt = tail.lastIndexOf("@");
47
+ if (tailAt >= 0) {
48
+ ref = tail.slice(tailAt + 1) || undefined;
49
+ base = head + tail.slice(0, tailAt);
50
+ }
51
+ }
52
+ else {
53
+ const at = rest.lastIndexOf("@");
54
+ if (at >= 0) {
55
+ ref = rest.slice(at + 1) || undefined;
56
+ base = rest.slice(0, at);
57
+ }
58
+ }
59
+ // 3. Normalize url.
60
+ let url;
61
+ if (base.startsWith("github:")) {
62
+ url = githubRepoToCloneUrl(base.slice("github:".length));
63
+ }
64
+ else {
65
+ url = base;
66
+ }
67
+ const inferredName = subdir ? lastSegment(subdir) : repoNameFromUrl(url);
68
+ const result = { kind: "remote", url, raw, inferredName };
69
+ if (ref)
70
+ result.ref = ref;
71
+ if (subdir)
72
+ result.subdir = subdir;
73
+ return result;
74
+ }
@@ -0,0 +1,5 @@
1
+ /** A plugin name must be a single safe path segment — no separators, no traversal. */
2
+ export declare function assertSafePluginName(name: string): void;
3
+ export declare function pluginsRoot(): string;
4
+ export declare function pluginInstallDir(name: string): string;
5
+ export declare function pluginMetaPath(name: string): string;
@@ -0,0 +1,25 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { PluginInstallError } from "./types.js";
4
+ function userHome() {
5
+ return process.env.HOME ?? homedir();
6
+ }
7
+ /** A plugin name must be a single safe path segment — no separators, no traversal. */
8
+ export function assertSafePluginName(name) {
9
+ if (!name || name === "." || name === "..") {
10
+ throw new PluginInstallError(`invalid plugin name: ${JSON.stringify(name)}`);
11
+ }
12
+ if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
13
+ throw new PluginInstallError(`plugin name must be a single path segment: ${JSON.stringify(name)}`);
14
+ }
15
+ }
16
+ export function pluginsRoot() {
17
+ return join(userHome(), ".code-shell", "plugins");
18
+ }
19
+ export function pluginInstallDir(name) {
20
+ assertSafePluginName(name);
21
+ return join(pluginsRoot(), name);
22
+ }
23
+ export function pluginMetaPath(name) {
24
+ return join(pluginInstallDir(name), ".cs-meta.json");
25
+ }