rush-ai 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -124,7 +124,7 @@ rush-ai task status <id> --json
124
124
  - **多环境 profile**:在不同 Rush 环境之间切换
125
125
  - **shell 补全**:bash / zsh / fish
126
126
  - **CI 友好**:`--json` 输出、`--ci` 模式、出错返回非 0 退出码
127
- - **插件分发**:`marketplace` + `plugin install` 一条命令把 Rush 生态的 skill / command / rule / MCP 装到 Claude Code、Codex、Cursor 三家 IDE;首次 `plugin install --target codex` 自动把 marketplace 完整目录镜像到 Codex 桌面端 Plugins 页(含未装的插件,无需先 `marketplace add`)
127
+ - **插件分发**:`marketplace` + `plugin install` 一条命令把 Rush 生态的 skill / command / rule / MCP 默认装到 Claude Code、Codex、Cursor;Claude-3p 作为显式 target 用 `--target claude-3p` 安装;首次 `plugin install --target codex` 自动把 marketplace 完整目录镜像到 Codex 桌面端 Plugins 页(含未装的插件,无需先 `marketplace add`)
128
128
 
129
129
  ## 命令一览
130
130
 
@@ -201,7 +201,7 @@ registry,仍然可以直接使用独立的 `reskill` CLI。
201
201
  | `marketplace add <source>` | 注册 marketplace(`github:owner/repo`、`directory:/abs/path`、`rush://<host>`),同时把 catalog 镜像到 Codex 桌面端 |
202
202
  | `marketplace sync [<n>]` | 增量刷新 marketplace 镜像;`--all` 全部刷;`--target codex` 指定目标 |
203
203
  | `marketplace list` / `remove` / `update` | 管理本地 marketplace 缓存;`remove` 同步清掉 Codex 端注册和镜像 |
204
- | `plugin install <ref>` | 一条命令同步装到 Claude Code + Codex + Cursor;`--target` 单独装;`--dry-run` / `--force` / `--secret KEY=VALUE` |
204
+ | `plugin install <ref>` | 默认同步装到 Claude Code + Codex + Cursor;`--target` 可指定单个或多个目标,Claude-3p 需显式 `--target claude-3p`;`--dry-run` / `--force` / `--secret KEY=VALUE` |
205
205
  | `plugin list` / `uninstall` / `update` | 对称管理 |
206
206
 
207
207
  `<ref>` 格式:`<name>` 或 `<name>@<marketplace>`(例 `my-plugin@rush`)。
@@ -212,8 +212,8 @@ registry,仍然可以直接使用独立的 `reskill` CLI。
212
212
  - `--secret KEY=VALUE`(可重复)预设 MCP 凭证,跳过交互式输入
213
213
  - `--force` 重新拉取 manifest 并更新 secrets
214
214
 
215
- `claude-plugins-official` source 特性:
216
- - 首次 `plugin install xxx@claude-plugins-official` 自动注册 Anthropic 官方 marketplace
215
+ `@rush` 中的 Claude 官方镜像特性:
216
+ - Claude Code 官方 marketplace 镜像也统一使用 `@rush` 后缀;旧 `@claude-plugins-official` 后缀已废弃,CLI 会提示改用 `@rush`
217
217
  - 支持 URL source 按需 clone:外部 git repo 的 plugin 自动 clone 到 `~/.rush/plugin-cache/`
218
218
  - 支持 git-subdir 模式(一个 repo 内多个 plugin)
219
219
  - `--force` 重新 clone 最新版本
@@ -232,7 +232,7 @@ registry,仍然可以直接使用独立的 `reskill` CLI。
232
232
 
233
233
  `plugin install` 触发的同步发生在装完目标 plugin 之后(仅 `--target codex`
234
234
  成功时),把 marketplace 里其它插件也一并镜像为 stub。所以从干净状态直接
235
- 跑一条 `rush-ai plugin install context7@claude-plugins-official --target codex`
235
+ 跑一条 `rush-ai plugin install context7@rush --target codex`
236
236
  就能在 Codex Plugins 页同时拿到 225 个 plugin 的完整目录。
237
237
 
238
238
  ### 认证 / 配置 / 其他
@@ -291,11 +291,16 @@ rush-ai plugin install my-plugin@rush
291
291
  # ✓ Codex (skills + MCP)
292
292
  # ✓ Cursor (skills + MCP)
293
293
 
294
+ # Claude-3p 第一期不进默认 target,需要显式安装
295
+ rush-ai plugin install my-plugin@rush --target claude-3p
296
+ # ✓ Claude-3p (commands + skills + hooks + MCP)
297
+
294
298
  # 带 MCP secrets(CI/非交互模式)
295
299
  rush-ai plugin install my-plugin@rush --secret SHIMO_TOKEN=xxx --secret API_KEY=yyy
296
300
 
297
301
  # 只装某一家
298
302
  rush-ai plugin install my-plugin@rush --target claude-code
303
+ rush-ai plugin install my-plugin@rush --target claude-3p
299
304
 
300
305
  # 更新 secrets / 刷新 skill
301
306
  rush-ai plugin install my-plugin@rush --force --secret SHIMO_TOKEN=new-value
@@ -303,16 +308,20 @@ rush-ai plugin install my-plugin@rush --force --secret SHIMO_TOKEN=new-value
303
308
  # 预览不落盘
304
309
  rush-ai plugin install my-plugin@rush --dry-run
305
310
 
306
- # Anthropic 官方 marketplace(170+ 社区 plugin,自动注册)
307
- rush-ai plugin install sentry@claude-plugins-official
308
- rush-ai plugin install aikido@claude-plugins-official --target claude-code
311
+ # Anthropic 官方 marketplace 镜像(通过 @rush 统一访问,自动注册)
312
+ rush-ai plugin install sentry@rush
313
+ rush-ai plugin install aikido@rush --target claude-code
309
314
 
310
315
  # GitHub marketplace 也支持
311
316
  rush-ai marketplace add github:kanyun-inc/rush-plugin
312
317
  rush-ai plugin install rush
313
318
  ```
314
319
 
315
- 这条路径和每个 IDE 自己的 `/plugin install` 等价 —— 装完之后 IDE 的 `/plugin list` 能看到、`/plugin uninstall` 也能卸载。不用这条路径就忽略;日常的 `task create` / `task push` 和插件分发完全独立。
320
+ Claude Code 侧与原生 plugin 清单互通;Codex、Cursor、Claude-3p
321
+ rush-ai 写入各自原生/适配目录。后续可用 `rush-ai plugin list` /
322
+ `rush-ai plugin uninstall` / `rush-ai plugin update` 统一管理;Claude-3p
323
+ 安装后需新开对话生效。不用这条路径就忽略;日常的 `task create` /
324
+ `task push` 和插件分发完全独立。
316
325
 
317
326
  ### MCP 安装
318
327
 
@@ -4,7 +4,7 @@ import {
4
4
  buildRushPluginContentLoader,
5
5
  pathExists,
6
6
  pickContentLoader
7
- } from "./chunk-E2CKW6JZ.js";
7
+ } from "./chunk-C7KJUHEF.js";
8
8
  import "./chunk-OIKYNVKO.js";
9
9
  import "./chunk-T5S6NCHZ.js";
10
10
  export {
@@ -13,4 +13,4 @@ export {
13
13
  pathExists,
14
14
  pickContentLoader
15
15
  };
16
- //# sourceMappingURL=_codex-content-loader-EXSX7ZGI.js.map
16
+ //# sourceMappingURL=_codex-content-loader-WAPA56U3.js.map
@@ -21,24 +21,35 @@ function normalizeClaudeMcpServers(ref, manifest, resolver = defaultRushBinaryRe
21
21
  return void 0;
22
22
  }
23
23
  if (!isRushOwnPlugin(ref)) {
24
- return { ...servers };
24
+ return normalizeRemoteMcpDefaults(servers);
25
25
  }
26
26
  const rushServer = servers[RUSH_MCP_SERVER_KEY];
27
27
  if (!rushServer) {
28
- return { ...servers };
28
+ return normalizeRemoteMcpDefaults(servers);
29
29
  }
30
30
  const currentCommand = rushServer.command;
31
31
  if (typeof currentCommand === "string" && isAbsolute(currentCommand)) {
32
- return { ...servers };
32
+ return normalizeRemoteMcpDefaults(servers);
33
33
  }
34
34
  const resolved = resolver();
35
35
  if (!resolved || !isAbsolute(resolved)) {
36
- return { ...servers };
36
+ return normalizeRemoteMcpDefaults(servers);
37
37
  }
38
- return {
38
+ return normalizeRemoteMcpDefaults({
39
39
  ...servers,
40
40
  [RUSH_MCP_SERVER_KEY]: { ...rushServer, command: resolved }
41
- };
41
+ });
42
+ }
43
+ function normalizeRemoteMcpDefaults(servers) {
44
+ const out = {};
45
+ for (const [name, server] of Object.entries(servers)) {
46
+ if (typeof server.url === "string" && server.url.length > 0 && typeof server.command !== "string" && typeof server.type !== "string") {
47
+ out[name] = { ...server, type: "http" };
48
+ continue;
49
+ }
50
+ out[name] = { ...server };
51
+ }
52
+ return out;
42
53
  }
43
54
  async function readExternalMcpServers(sourceDir, relativeRef) {
44
55
  if (typeof relativeRef !== "string" || relativeRef.length === 0) return null;
@@ -95,4 +106,4 @@ export {
95
106
  readExternalMcpServers,
96
107
  defaultRushBinaryResolver
97
108
  };
98
- //# sourceMappingURL=chunk-X45FKY3L.js.map
109
+ //# sourceMappingURL=chunk-22YQT6XF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/installers/claude-code/mcp.ts"],"sourcesContent":["/**\n * MCP command 规范化(task-6 产物)。\n *\n * 仅对 rush-ai 自己的插件(`rush@rush-marketplace`)做 MCP command 绝对路径规范化;\n * 其他 plugin 透传(见 plan §7.1 / spec §1.5)。\n *\n * 说明:resolver 层(`plugins/resolver.ts` 的 `normalizeRushMcpCommand`)已经做过\n * 同样的规范化。本文件提供一个 Installer 侧的**兜底**:如果 resolver 没解析到绝对路径\n * (例如 rushAiBinaryResolver 返回 undefined,resolver 选择不改),Installer 写\n * plugin.json 前再尝试一次——用 `which rush-ai`(PATH 解析)。**不 fallback 到\n * `process.argv[0]`**(那通常是 `node`,会写错误 command)。\n *\n * 为什么在两个层都做:\n * - Resolver 层:给各家 Installer 一个统一的起点(避免 Claude Code / Codex / Cursor\n * Installer 各自重复实现)\n * - Installer 层:resolver 的兜底策略有意保持宽松(失败就透传),Installer 层可以\n * 更激进(Claude Code 的 plugin.json 要求绝对路径更严)\n *\n * 注意:对 rush plugin 且 resolver + 本兜底都拿不到绝对路径时,我们仍然写入 plugin.json\n * 的原始值——不抛错、不阻塞 install。Claude Code 运行时会自己 fail(下游问题),但\n * Installer 保持 \"能装就装\" 的语义。\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport {\n isAbsolute,\n relative as pathRelative,\n resolve as pathResolve,\n} from 'node:path';\nimport type { McpServerConfig, PluginManifest, PluginRef } from '../types.js';\n\n/** rush 自身 plugin 的 identifier(与 resolver 层保持一致) */\nexport const RUSH_AI_PLUGIN_NAME = 'rush' as const;\nexport const RUSH_AI_MARKETPLACE_NAME = 'rush-marketplace' as const;\nexport const RUSH_MCP_SERVER_KEY = 'rush' as const;\n\n/**\n * 决定是否应对该 plugin 做 MCP command 规范化。\n *\n * 约束条件(plan §7.1):\n * - `ref.name === 'rush'`\n * - `ref.marketplace === 'rush-marketplace'`\n *\n * 第三方 marketplace 的 rush plugin(同名但不同 marketplace)**不**命中——\n * 只有我们自己发布的插件才保证 `rush-ai` binary 可解析到绝对路径。\n */\nexport function isRushOwnPlugin(ref: PluginRef): boolean {\n return (\n ref.name === RUSH_AI_PLUGIN_NAME &&\n ref.marketplace === RUSH_AI_MARKETPLACE_NAME\n );\n}\n\n/**\n * 规范化后的 MCP servers 对象——返回 Claude Code plugin.json 顶层所需的 `mcpServers`\n * 结构(`Record<string, McpServerConfig>`)。\n *\n * - `manifest.mcpServers` 是 `string`(外部文件引用)→ 返回 `undefined`,调用方需要走\n * {@link readExternalMcpServers} 读源文件,并把结果作为 normalizer 的输入再调一次\n * - 非 rush 插件 → 原样返回\n * - rush 插件:仅对 `rush` server key 做 command 规范化(其他 key 保持)\n *\n * 规范化策略(rush 插件且 command 非绝对路径时):\n * 1. 调用者注入的 `resolver`(通常是 `whichRushAiBinary`)\n * 2. 返回值是绝对路径 → 用它\n * 3. 否则保留原始 command(不抛错,让 Claude Code 运行时报)\n */\nexport function normalizeClaudeMcpServers(\n ref: PluginRef,\n manifest: PluginManifest,\n resolver: () => string | undefined = defaultRushBinaryResolver\n): Record<string, McpServerConfig> | undefined {\n const servers = manifest.mcpServers;\n if (servers === undefined || servers === null) return undefined;\n if (typeof servers === 'string') {\n // 字符串外部文件引用(典型:plugin resolver 隐式注入的 `\"./.mcp.json\"`,\n // 或作者显式声明的相对路径引用)。Installer 主流程负责读盘解析后再调\n // normalizer 一次(见 readExternalMcpServers)。这里返回 undefined,\n // installer 应该把它当作\"未直接内嵌\"。\n return undefined;\n }\n if (!isRushOwnPlugin(ref)) {\n // 第三方 plugin:保留作者配置,并为远程 MCP 补齐 Claude/Cursor 兼容的默认 type。\n return normalizeRemoteMcpDefaults(servers);\n }\n\n // rush own plugin —— 仅规范化 `rush` server key\n const rushServer = servers[RUSH_MCP_SERVER_KEY];\n if (!rushServer) {\n return normalizeRemoteMcpDefaults(servers);\n }\n const currentCommand = rushServer.command;\n if (typeof currentCommand === 'string' && isAbsolute(currentCommand)) {\n // 已经是绝对路径,不动\n return normalizeRemoteMcpDefaults(servers);\n }\n\n const resolved = resolver();\n if (!resolved || !isAbsolute(resolved)) {\n // 兜底失败:保留原值,不阻塞 install\n return normalizeRemoteMcpDefaults(servers);\n }\n\n return normalizeRemoteMcpDefaults({\n ...servers,\n [RUSH_MCP_SERVER_KEY]: { ...rushServer, command: resolved },\n });\n}\n\nfunction normalizeRemoteMcpDefaults(\n servers: Record<string, McpServerConfig>\n): Record<string, McpServerConfig> {\n const out: Record<string, McpServerConfig> = {};\n for (const [name, server] of Object.entries(servers)) {\n if (\n typeof server.url === 'string' &&\n server.url.length > 0 &&\n typeof server.command !== 'string' &&\n typeof server.type !== 'string'\n ) {\n out[name] = { ...server, type: 'http' };\n continue;\n }\n out[name] = { ...server };\n }\n return out;\n}\n\n/**\n * 当 `manifest.mcpServers` 是字符串外部文件引用时,从源 plugin 目录读取该\n * `.mcp.json` 文件并解析成 `Record<string, McpServerConfig>`。\n *\n * 触发场景:\n * - plugin 作者显式声明 `\"mcpServers\": \"./.mcp.json\"`\n * - resolver 隐式注入(plugin.json 没声明 + 目录有 `.mcp.json`)—— 见\n * `plugins/resolver.ts` 的 `injectImplicitMcpRef`\n *\n * 与 Codex installer 的 `copyAuthorProvidedMcp` 形成对称:那边读完后 copy 到\n * 版本目录 + 解析 mcpKeys;Claude Code 这边只需要解析后塞回 normalizer 流程。\n *\n * 容忍两种 .mcp.json 形态:\n * - `{ \"mcpServers\": { ... } }`(rush-ai 自己写出的,带 wrapper)\n * - `{ \"<server-name>\": { ... } }`(claude-plugins-official 的 external_plugins/*\n * 不带 wrapper)\n *\n * 安全:拒绝 `../` 逃逸 sourceDir 的引用,缺失文件 / 损坏 JSON / 路径非法 →\n * 返回 `null`,调用方按 \"无 MCP\" 继续 install(不阻塞)。\n */\nexport async function readExternalMcpServers(\n sourceDir: string,\n relativeRef: string\n): Promise<Record<string, McpServerConfig> | null> {\n if (typeof relativeRef !== 'string' || relativeRef.length === 0) return null;\n const srcPath = pathResolve(sourceDir, relativeRef);\n // 路径穿越守护:必须落在 sourceDir 下\n const rel = pathRelative(pathResolve(sourceDir), srcPath);\n if (rel === '..' || rel.startsWith('..') || rel.startsWith('/')) return null;\n\n let raw: string;\n try {\n raw = await readFile(srcPath, 'utf8');\n } catch {\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null;\n }\n\n // 形态 1:`{ mcpServers: {...} }`\n const obj = parsed as { mcpServers?: unknown } & Record<string, unknown>;\n if (\n obj.mcpServers &&\n typeof obj.mcpServers === 'object' &&\n !Array.isArray(obj.mcpServers)\n ) {\n return obj.mcpServers as Record<string, McpServerConfig>;\n }\n\n // 形态 2:`{ <server-name>: {...} }` —— external_plugins/* 形态\n // 简单识别:所有 value 都是 object(不含 mcpServers 这个 key)\n const flat = obj as Record<string, unknown>;\n if (Object.keys(flat).length === 0) return null;\n const allObjects = Object.values(flat).every(\n (v) => v !== null && typeof v === 'object' && !Array.isArray(v)\n );\n if (!allObjects) return null;\n return flat as Record<string, McpServerConfig>;\n}\n\n/**\n * 默认 rush-ai binary 解析:`which rush-ai`(PATH 解析)。\n *\n * 作为 Installer 层兜底——resolver 层已用 `process.argv[1]` 试过一次;本函数\n * 用更稳的 PATH 查找逻辑。\n *\n * **不 fallback 到 `process.argv[0]`**(那通常是 `node`,会把 rush MCP command\n * 规范化成 node 路径,运行时语义错误)——宁可返回 `undefined`(保留原 command),\n * 也不冒险写入错误绝对路径。\n *\n * 失败返回 `undefined`,调用方保留原值。\n */\nexport function defaultRushBinaryResolver(): string | undefined {\n try {\n const result = execFileSync('which', ['rush-ai'], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n if (result.length > 0 && isAbsolute(result)) {\n return result;\n }\n } catch {\n // `which` 失败 —— rush-ai 未在 PATH 上,放弃兜底\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
@@ -16,6 +16,13 @@ import { execFileSync, spawn } from "child_process";
16
16
  import { randomUUID } from "crypto";
17
17
  import { mkdir, rename, rm, stat, writeFile } from "fs/promises";
18
18
  import { resolve as pathResolve, resolve } from "path";
19
+ var RUSH_API_FETCH_TIMEOUT_MS = 3e4;
20
+ function fetchInitWithTimeout(headers) {
21
+ return {
22
+ headers,
23
+ signal: AbortSignal.timeout(RUSH_API_FETCH_TIMEOUT_MS)
24
+ };
25
+ }
19
26
  async function fetchRushMarketplace(source, opts = {}) {
20
27
  const fetchFn = opts.fetchFn ?? fetch;
21
28
  const url = `${inferProtocol(source.host)}${source.host}/api/marketplace`;
@@ -25,7 +32,7 @@ async function fetchRushMarketplace(source, opts = {}) {
25
32
  if (opts.token) {
26
33
  headers.Authorization = `Bearer ${opts.token}`;
27
34
  }
28
- const response = await fetchFn(url, { headers });
35
+ const response = await fetchFn(url, fetchInitWithTimeout(headers));
29
36
  if (!response.ok) {
30
37
  throw new Error(
31
38
  `Failed to fetch rush marketplace from ${url}: ${response.status} ${response.statusText}`
@@ -48,7 +55,7 @@ async function fetchRushPlugin(source, pluginSlug, opts = {}) {
48
55
  if (opts.token) {
49
56
  headers.Authorization = `Bearer ${opts.token}`;
50
57
  }
51
- const response = await fetchFn(url, { headers });
58
+ const response = await fetchFn(url, fetchInitWithTimeout(headers));
52
59
  if (response.status === 401) {
53
60
  throw new Error(
54
61
  `Plugin '${pluginSlug}' requires authentication. Run 'rush-ai login' first.`
@@ -72,7 +79,7 @@ async function fetchRushOfficialFile(source, pluginSlug, filePath, opts = {}) {
72
79
  const url = `${inferProtocol(source.host)}${source.host}/api/marketplace/${encodeURIComponent(pluginSlug)}/files/${encoded}`;
73
80
  const headers = { Accept: "application/json" };
74
81
  if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
75
- const response = await fetchFn(url, { headers });
82
+ const response = await fetchFn(url, fetchInitWithTimeout(headers));
76
83
  if (response.status === 404) {
77
84
  throw new Error(
78
85
  `File '${filePath}' not found in official plugin '${pluginSlug}'`
@@ -102,6 +109,16 @@ async function materializeRushPlugin(source, pluginSlug, targetDir, opts = {}) {
102
109
  );
103
110
  return manifest;
104
111
  }
112
+ if (manifest.source === "rush-git-import") {
113
+ await materializeOfficialLocal(
114
+ source,
115
+ pluginSlug,
116
+ manifest,
117
+ targetDir,
118
+ opts
119
+ );
120
+ return manifest;
121
+ }
105
122
  return materializeRushUserPlugin(
106
123
  source,
107
124
  pluginSlug,
@@ -117,6 +134,7 @@ async function materializeRushUserPlugin(source, pluginSlug, manifest, targetDir
117
134
  if (opts.secrets && Object.keys(opts.secrets).length > 0) {
118
135
  mcpServers = substituteMcpSecrets(mcpServers, opts.secrets);
119
136
  }
137
+ mcpServers = normalizeMcpServerDefaults(mcpServers);
120
138
  }
121
139
  const pluginJson = buildPluginJsonShape(manifest, {
122
140
  mcpServers
@@ -137,14 +155,23 @@ async function materializeRushUserPlugin(source, pluginSlug, manifest, targetDir
137
155
  '{"skills":{}}\n',
138
156
  "utf8"
139
157
  );
158
+ const reskillHomeDir = resolve(tmpDir, ".reskill-home");
159
+ await mkdir(reskillHomeDir, { recursive: true });
140
160
  const registryUrl = `${inferProtocol(source.host)}${source.host}`;
141
- for (const skill of manifest.skills) {
161
+ for (const [index, skill] of manifest.skills.entries()) {
142
162
  try {
163
+ output.dim(
164
+ ` Fetching skill ${index + 1}/${manifest.skills.length}: ${skill.name}`
165
+ );
143
166
  const args = [
144
167
  "-y",
145
168
  "reskill@latest",
146
169
  "install",
147
170
  skill.name,
171
+ "--agent",
172
+ "claude-code",
173
+ "--mode",
174
+ "copy",
148
175
  "--no-save",
149
176
  "--skip-manifest",
150
177
  "-y",
@@ -158,7 +185,12 @@ async function materializeRushUserPlugin(source, pluginSlug, manifest, targetDir
158
185
  // 3 分钟 — 覆盖 npx 首次下载 reskill 包的冷启动场景
159
186
  // (warm cache 时 reskill 实际运行只需 ~7s)
160
187
  timeout: 18e4,
161
- stdio: "pipe"
188
+ stdio: "pipe",
189
+ env: {
190
+ ...process.env,
191
+ HOME: reskillHomeDir,
192
+ npm_config_cache: resolve(reskillHomeDir, ".npm")
193
+ }
162
194
  });
163
195
  } catch (err) {
164
196
  const stderr = err && typeof err === "object" && "stderr" in err ? err.stderr?.toString() ?? "" : "";
@@ -172,13 +204,23 @@ async function materializeRushUserPlugin(source, pluginSlug, manifest, targetDir
172
204
  }
173
205
  }
174
206
  const dotSkillsDir = resolve(tmpDir, ".skills");
207
+ const claudeSkillsDir = resolve(tmpDir, ".claude", "skills");
175
208
  const skillsDir = resolve(tmpDir, "skills");
176
209
  if (await stat(dotSkillsDir).then((s) => s.isDirectory()).catch(() => false)) {
177
210
  if (!await stat(skillsDir).then((s) => s.isDirectory()).catch(() => false)) {
178
211
  await rename(dotSkillsDir, skillsDir);
179
212
  }
180
213
  }
214
+ if (await stat(claudeSkillsDir).then((s) => s.isDirectory()).catch(() => false)) {
215
+ if (!await stat(skillsDir).then((s) => s.isDirectory()).catch(() => false)) {
216
+ await rename(claudeSkillsDir, skillsDir);
217
+ }
218
+ }
181
219
  await rm(resolve(tmpDir, "skills.json"), { force: true });
220
+ await rm(resolve(tmpDir, ".reskill-home"), {
221
+ recursive: true,
222
+ force: true
223
+ });
182
224
  await rm(resolve(tmpDir, ".cursor"), { recursive: true, force: true });
183
225
  await rm(resolve(tmpDir, ".claude"), { recursive: true, force: true });
184
226
  await rm(resolve(tmpDir, ".codex"), { recursive: true, force: true });
@@ -489,6 +531,21 @@ function substituteMcpSecrets(mcpServers, secrets) {
489
531
  }
490
532
  return result;
491
533
  }
534
+ function normalizeMcpServerDefaults(mcpServers) {
535
+ const result = {};
536
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
537
+ if (!serverConfig || typeof serverConfig !== "object") {
538
+ result[serverName] = serverConfig;
539
+ continue;
540
+ }
541
+ const cfg = { ...serverConfig };
542
+ if (typeof cfg.url === "string" && cfg.url.length > 0 && typeof cfg.command !== "string" && typeof cfg.type !== "string") {
543
+ cfg.type = "http";
544
+ }
545
+ result[serverName] = cfg;
546
+ }
547
+ return result;
548
+ }
492
549
  function substituteValue(value, secrets) {
493
550
  return value.replace(/\$\{([^}]+)\}/g, (match, key) => {
494
551
  return key in secrets ? secrets[key] : match;
@@ -540,6 +597,8 @@ function buildRushPluginContentLoader(source, opts = {}) {
540
597
  entry.name
541
598
  )}`;
542
599
  const isOfficial = detail.source === "claude-plugins-official";
600
+ const isGitImported = detail.source === "rush-git-import";
601
+ const usesOssProxy = isOfficial || isGitImported;
543
602
  const isExternalRedirect = isOfficial && !!detail.redirectTo;
544
603
  const skillEntries = Array.isArray(detail.skills) && detail.skills.length > 0 ? detail.skills.filter(
545
604
  (s) => !!s && typeof s.name === "string" && s.name.length > 0
@@ -549,7 +608,7 @@ function buildRushPluginContentLoader(source, opts = {}) {
549
608
  const url = `${webBase}/next/plugins/${encodeURIComponent(entry.name)}`;
550
609
  let rawSkillMd;
551
610
  if (isExternalRedirect) {
552
- } else if (isOfficial && typeof s.path === "string" && s.path.length > 0) {
611
+ } else if (usesOssProxy && typeof s.path === "string" && s.path.length > 0) {
553
612
  const skillMdPath = ensureSkillMdSuffix(s.path);
554
613
  try {
555
614
  const file = await fetchRushOfficialFile(
@@ -768,4 +827,4 @@ export {
768
827
  buildLocalCachePluginContentLoader,
769
828
  pathExists
770
829
  };
771
- //# sourceMappingURL=chunk-E2CKW6JZ.js.map
830
+ //# sourceMappingURL=chunk-C7KJUHEF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/marketplace/_codex-content-loader.ts","../src/marketplaces/rush.ts"],"sourcesContent":["/**\n * Codex marketplace mirror —— `PluginContentLoader` 的具体实现集合。\n *\n * Spec: `specs/rush-v2/web/plugins/codex-marketplace-mirror.spec.md` §6.1\n *\n * 不同 source kind 的 marketplace 用不同 loader 拉取 plugin 内容:\n *\n * - `rush://` → `buildRushPluginContentLoader`:调 `/api/marketplace/:slug` 拿 mcpServers\n * - `directory:`/`github:` → `buildLocalCachePluginContentLoader`:从 cache 目录里读\n * `<rootDir>/<entry.source.path>/.claude-plugin/plugin.json` + 同目录的 `.mcp.json`\n * + `skills/` 子目录\n * - `git`/`npm` → 暂未实现,调用方走默认空 loader\n *\n * 所有 loader 都**不应抛错**:失败时返回 `{}`(空载荷),让 sync 退化到最简 stub。\n */\n\nimport { constants as fsConstants } from 'node:fs';\nimport { access, readFile, stat } from 'node:fs/promises';\nimport { resolve as pathResolve, sep } from 'node:path';\nimport type {\n PluginContent,\n PluginContentInterface,\n PluginContentLoader,\n PluginContentManifest,\n} from '../../installers/codex/index.js';\nimport {\n fetchRushOfficialFile,\n fetchRushPlugin,\n} from '../../marketplaces/rush.js';\nimport type {\n MarketplacePluginEntry,\n ResolvedMarketplace,\n RushMarketplaceSource,\n} from '../../marketplaces/types.js';\nimport { getAuthToken } from '../../util/auth.js';\n\nexport interface BuildLoaderOptions {\n /** 测试用 fetch 注入;默认走 global fetch */\n fetchFn?: typeof fetch;\n /** 测试用 token 注入;默认从 keychain 读 */\n token?: string | null;\n}\n\n/**\n * 按 `resolved.source.kind` 选合适的 loader。\n * 未知 kind / Phase 2 source → 返回返回空载荷的 fallback loader。\n */\nexport function pickContentLoader(\n resolved: ResolvedMarketplace,\n opts: BuildLoaderOptions = {}\n): PluginContentLoader {\n switch (resolved.source.kind) {\n case 'rush':\n return buildRushPluginContentLoader(resolved.source, opts);\n case 'directory':\n case 'github':\n return buildLocalCachePluginContentLoader(resolved);\n default:\n return async () => ({});\n }\n}\n\n// ---------------------------------------------------------------------------\n// rush:// loader\n// ---------------------------------------------------------------------------\n\n/**\n * 调 `https://<host>/api/marketplace/:slug` 拿单个 plugin 详情。\n *\n * 注意:每个 plugin 一次 HTTP 调用。rush 当前 ≤ 3 个 plugin 时无压力;\n * 100+ plugin 时未来需要批量接口或并发限流(当前**串行**调用避免雪崩)。\n */\nexport function buildRushPluginContentLoader(\n source: RushMarketplaceSource,\n opts: BuildLoaderOptions = {}\n): PluginContentLoader {\n const token = opts.token ?? getAuthToken();\n const fetchOpts: { fetchFn?: typeof fetch; token?: string | null } = {};\n if (opts.fetchFn !== undefined) fetchOpts.fetchFn = opts.fetchFn;\n if (token !== undefined && token !== null) fetchOpts.token = token;\n // 拼 web 站点 base URL(与 fetchRushPlugin 内的 inferProtocol 同构 ——\n // localhost 用 http://,其他 https://)\n const webBase = inferWebBase(source.host);\n const fetchFn = opts.fetchFn ?? globalThis.fetch;\n return async (entry: MarketplacePluginEntry): Promise<PluginContent> => {\n if (typeof entry.name !== 'string' || entry.name.length === 0) return {};\n try {\n const detail = await fetchRushPlugin(source, entry.name, fetchOpts);\n const manifest = pickManifestFields(\n detail as unknown as Record<string, unknown>\n );\n const hasMcp =\n detail.mcpServers && Object.keys(detail.mcpServers).length > 0;\n // 路径与 web 路由 `/next/plugins/[slug]` 对齐\n const pluginWebUrl = `${webBase}/next/plugins/${encodeURIComponent(\n entry.name\n )}`;\n\n // II-3 / III-A 起 SKILL.md 取法分流:\n // - rush 用户 plugin(detail.source 缺省):走 `<host>/next/skills/<name>.md`\n // - 官方 local plugin(detail.source === 'claude-plugins-official'):走\n // OSS 文件代理 `/api/marketplace/<slug>/files/<skill.path>`\n // - 官方外部 plugin(含 redirectTo):sync 不下载真实内容(与现状 url/git-subdir 一致)\n // - III-A 用户 git 导入(detail.source === 'rush-git-import'):与官方 local 同链路\n const isOfficial = detail.source === 'claude-plugins-official';\n const isGitImported = detail.source === 'rush-git-import';\n const usesOssProxy = isOfficial || isGitImported;\n const isExternalRedirect = isOfficial && !!detail.redirectTo;\n\n const skillEntries =\n Array.isArray(detail.skills) && detail.skills.length > 0\n ? detail.skills.filter(\n (s): s is { name: string; version: string; path?: string } =>\n !!s && typeof s.name === 'string' && s.name.length > 0\n )\n : [];\n const skillsPlaceholders: Array<{\n name: string;\n url: string;\n rawSkillMd?: string;\n }> = [];\n for (const s of skillEntries) {\n // url 始终指向 web 详情页(plugin),不是 skill 单页 — 因为官方 skill 没有 reskill 单页\n const url = `${webBase}/next/plugins/${encodeURIComponent(entry.name)}`;\n let rawSkillMd: string | undefined;\n\n if (isExternalRedirect) {\n // 外部 plugin sync 阶段不取真内容(现状语义)\n } else if (\n usesOssProxy &&\n typeof s.path === 'string' &&\n s.path.length > 0\n ) {\n // 官方 local:走 OSS 文件代理\n const skillMdPath = ensureSkillMdSuffix(s.path);\n try {\n const file = await fetchRushOfficialFile(\n source,\n entry.name,\n skillMdPath,\n fetchOpts\n );\n if (typeof file.content === 'string' && file.content.length > 0) {\n rawSkillMd = file.content;\n }\n } catch {\n // 单个 skill md fetch 失败 → 占位流程接管\n }\n } else {\n // rush 用户 plugin:走 reskill skill md 端点\n const mdUrl = `${webBase}/next/skills/${encodeURIComponent(s.name)}.md`;\n try {\n const headers: Record<string, string> = {\n Accept: 'text/markdown,text/plain,*/*',\n };\n if (token) headers.Authorization = `Bearer ${token}`;\n const res = await fetchFn(mdUrl, { headers });\n if (res.ok) {\n const text = await res.text();\n if (text.length > 0) rawSkillMd = text;\n }\n } catch {\n // skill md fetch 失败 → 占位流程接管\n }\n }\n skillsPlaceholders.push({\n name: s.name,\n url,\n ...(rawSkillMd !== undefined ? { rawSkillMd } : {}),\n });\n }\n\n // 透传 mcpServers,env / args 中的 ${KEY} 占位符**不替换** ——\n // sync 阶段不能拿 secret(与 plugin install 路径分离)。\n return {\n ...(manifest !== undefined ? { manifest } : {}),\n ...(hasMcp\n ? { mcpServers: detail.mcpServers as Record<string, unknown> }\n : {}),\n ...(skillsPlaceholders.length > 0 ? { skillsPlaceholders } : {}),\n pluginWebUrl,\n };\n } catch {\n // 单个 plugin 详情拉失败 → 退化到空载荷(sync 写最简 stub)\n return {};\n }\n };\n}\n\nfunction inferWebBase(host: string): string {\n const hostname = host.split(':')[0];\n if (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '0.0.0.0'\n ) {\n return `http://${host}`;\n }\n return `https://${host}`;\n}\n\n// ---------------------------------------------------------------------------\n// directory:/github: loader(从本地 cache 读盘)\n// ---------------------------------------------------------------------------\n\n/**\n * 从 `resolved.rootDir/<entry.source.path>` 目录读取 plugin 内容:\n * - `.claude-plugin/plugin.json` → manifest(取 description / version)\n * - `.mcp.json` → mcpServers(直接 parse)\n * - `skills/` → 整目录 copy 到镜像\n *\n * source 字段两种形态:\n * - 字符串:`\"./plugins/foo\"` —— 相对 marketplace rootDir 的路径\n * - 对象:`{ source: \"git-subdir\", url, path, ref }` 等 —— v1 不读,返回 {}\n *\n * 路径穿越守护:拒绝 `../` 逃出 rootDir 的相对路径。\n */\nexport function buildLocalCachePluginContentLoader(\n resolved: ResolvedMarketplace\n): PluginContentLoader {\n return async (entry: MarketplacePluginEntry): Promise<PluginContent> => {\n const relPath = extractLocalPluginPath(entry);\n if (relPath === null) return {};\n\n const pluginDir = pathResolve(resolved.rootDir, relPath);\n // 必须用 path.sep 边界比较,否则 `../market-evil/plugin` 会被\n // `startsWith('/tmp/market')` 误判为安全(sibling path 共享前缀)。\n const root = pathResolve(resolved.rootDir);\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n const safe = pluginDir === root || pluginDir.startsWith(rootWithSep);\n if (!safe) return {};\n\n let manifest: PluginContent['manifest'] | undefined;\n let mcpServers: Record<string, unknown> | undefined;\n let skillsSourceDir: string | undefined;\n\n // 1. .claude-plugin/plugin.json —— 透传 description / version / author /\n // homepage / license / keywords / interface(让 Codex 详情页字段丰富)\n // + 顺便提取 inline mcpServers(plugin resolver 也接受 plugin.json\n // 内联 mcpServers 对象,没有独立 .mcp.json 时也得镜像走)\n try {\n const pj = await readFile(\n pathResolve(pluginDir, '.claude-plugin', 'plugin.json'),\n 'utf8'\n );\n const parsed = JSON.parse(pj) as Record<string, unknown>;\n manifest = pickManifestFields(parsed);\n // inline mcpServers\n const inline = (parsed as { mcpServers?: unknown }).mcpServers;\n if (\n inline &&\n typeof inline === 'object' &&\n !Array.isArray(inline) &&\n Object.keys(inline as Record<string, unknown>).length > 0\n ) {\n mcpServers = inline as Record<string, unknown>;\n }\n } catch {\n // 缺失 / 损坏 → 不带 manifest\n }\n\n // 2. .mcp.json(独立文件,优先级高于 plugin.json 内联)\n try {\n const raw = await readFile(pathResolve(pluginDir, '.mcp.json'), 'utf8');\n const parsed = JSON.parse(raw) as\n | Record<string, unknown>\n | { mcpServers?: Record<string, unknown> };\n // 两种形态都接受:\n // { mcpServers: {...} }(rush-ai 自己写的,包一层)\n // { foo: {...} } (claude-plugins-official 的 external_plugins/* 不包一层)\n let servers: Record<string, unknown> | null = null;\n if (\n parsed &&\n typeof parsed === 'object' &&\n !Array.isArray(parsed) &&\n 'mcpServers' in parsed &&\n typeof (parsed as { mcpServers?: unknown }).mcpServers === 'object'\n ) {\n servers = (parsed as { mcpServers: Record<string, unknown> })\n .mcpServers;\n } else if (\n parsed &&\n typeof parsed === 'object' &&\n !Array.isArray(parsed)\n ) {\n servers = parsed as Record<string, unknown>;\n }\n if (servers && Object.keys(servers).length > 0) {\n mcpServers = servers;\n }\n } catch {\n // 缺失 / 损坏 → 不影响已经从 plugin.json 内联拿到的 mcpServers\n }\n\n // 3. skills/\n const skillsDir = pathResolve(pluginDir, 'skills');\n if (await isDir(skillsDir)) {\n skillsSourceDir = skillsDir;\n }\n\n return {\n ...(manifest !== undefined ? { manifest } : {}),\n ...(mcpServers !== undefined ? { mcpServers } : {}),\n ...(skillsSourceDir !== undefined ? { skillsSourceDir } : {}),\n };\n };\n}\n\n/**\n * 从 marketplace plugin entry 推导出本地 plugin 目录的相对路径。\n *\n * 与 plugin resolver 的本地路径接受策略对齐(参考 `plugins/resolver.ts`):\n *\n * - `source: \"./plugins/foo\"` → `./plugins/foo` (字符串路径)\n * - `source: { path: \"plugins/foo\" }` → `plugins/foo` (任意 object,无 url)\n * - `source: { source: \"local\", path }` → `path` (rush-ai 自己写的)\n * - 完全无 `source` 字段 → `plugins/<entry.name>` (Claude Code 默认布局)\n * - 含 `url` 的 object(git-subdir/url/github)→ `null` (远端 source,本地没盘)\n *\n * 路径穿越的最终守护在调用方:用 path.sep 边界比较 pluginDir vs rootDir。\n */\nfunction extractLocalPluginPath(entry: MarketplacePluginEntry): string | null {\n if (typeof entry.source === 'string') {\n return entry.source;\n }\n if (\n entry.source &&\n typeof entry.source === 'object' &&\n !Array.isArray(entry.source)\n ) {\n const obj = entry.source as {\n path?: unknown;\n source?: unknown;\n url?: unknown;\n };\n // 含 url → 远端 source,本地没盘\n if (typeof obj.url === 'string' && obj.url.length > 0) {\n return null;\n }\n if (typeof obj.path === 'string' && obj.path.length > 0) {\n return obj.path;\n }\n }\n // 完全没写 source —— Claude Code 默认布局 plugins/<name>/\n if (\n entry.source === undefined &&\n typeof entry.name === 'string' &&\n entry.name.length > 0\n ) {\n return `plugins/${entry.name}`;\n }\n return null;\n}\n\nasync function isDir(p: string): Promise<boolean> {\n try {\n const s = await stat(p);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n\n// 保留:未来若需要先确认 plugin 目录存在再发 IO,可用 access\nexport async function pathExists(p: string): Promise<boolean> {\n try {\n await access(p, fsConstants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 从 plugin.json 解析对象里挑选 `PluginContentManifest` 关心的字段。\n *\n * 透传策略:\n * - 字符串类字段(description / version / homepage / license)→ 仅当非空字符串\n * - author → 接受 `{ name?, email?, url? }` 形态,子字段做类型校验\n * - keywords → 仅接受字符串数组\n * - interface → 嵌套 picker,子字段同 PluginContentInterface\n *\n * 缺失 / 类型不符的字段全部丢弃;不抛错。返回的 manifest 中至少含一个字段才返回,\n * 否则返回 undefined(让 sync 走\"无 manifest\"分支)。\n */\nfunction pickManifestFields(\n raw: Record<string, unknown>\n): PluginContentManifest | undefined {\n const m: {\n description?: string;\n version?: string;\n author?: { name?: string; email?: string; url?: string };\n homepage?: string;\n license?: string;\n keywords?: string[];\n interface?: PluginContentInterface;\n } = {};\n\n if (typeof raw.description === 'string' && raw.description.length > 0) {\n m.description = raw.description;\n }\n if (typeof raw.version === 'string' && raw.version.length > 0) {\n m.version = raw.version;\n }\n if (typeof raw.homepage === 'string' && raw.homepage.length > 0) {\n m.homepage = raw.homepage;\n }\n if (typeof raw.license === 'string' && raw.license.length > 0) {\n m.license = raw.license;\n }\n if (Array.isArray(raw.keywords)) {\n const kw = raw.keywords.filter(\n (x): x is string => typeof x === 'string' && x.length > 0\n );\n if (kw.length > 0) m.keywords = kw;\n }\n if (\n raw.author &&\n typeof raw.author === 'object' &&\n !Array.isArray(raw.author)\n ) {\n const a = raw.author as Record<string, unknown>;\n const author: { name?: string; email?: string; url?: string } = {};\n if (typeof a.name === 'string') author.name = a.name;\n if (typeof a.email === 'string') author.email = a.email;\n if (typeof a.url === 'string') author.url = a.url;\n if (Object.keys(author).length > 0) m.author = author;\n }\n if (\n raw.interface &&\n typeof raw.interface === 'object' &&\n !Array.isArray(raw.interface)\n ) {\n const iface = pickInterfaceFields(raw.interface as Record<string, unknown>);\n if (iface !== undefined) m.interface = iface;\n }\n\n return Object.keys(m).length > 0 ? m : undefined;\n}\n\nfunction pickInterfaceFields(\n raw: Record<string, unknown>\n): PluginContentInterface | undefined {\n const out: {\n displayName?: string;\n shortDescription?: string;\n longDescription?: string;\n developerName?: string;\n category?: string;\n capabilities?: string[];\n websiteURL?: string;\n privacyPolicyURL?: string;\n termsOfServiceURL?: string;\n defaultPrompt?: string[];\n brandColor?: string;\n composerIcon?: string;\n logo?: string;\n screenshots?: string[];\n } = {};\n\n const stringKeys = [\n 'displayName',\n 'shortDescription',\n 'longDescription',\n 'developerName',\n 'category',\n 'websiteURL',\n 'privacyPolicyURL',\n 'termsOfServiceURL',\n 'brandColor',\n 'composerIcon',\n 'logo',\n ] as const;\n for (const key of stringKeys) {\n const v = raw[key];\n if (typeof v === 'string' && v.length > 0) {\n out[key] = v;\n }\n }\n for (const key of ['capabilities', 'defaultPrompt', 'screenshots'] as const) {\n const v = raw[key];\n if (Array.isArray(v)) {\n const arr = v.filter(\n (x): x is string => typeof x === 'string' && x.length > 0\n );\n if (arr.length > 0) out[key] = arr;\n }\n }\n\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * II-3:把 skill 的 path 规范成\"指向 SKILL.md 文件\"的相对路径。\n *\n * `manifest.skills[].path` 在官方 plugin 中通常是目录(如 `skills/code-review`),\n * 真实的 markdown 是 `skills/code-review/SKILL.md`。如果上游已经直接给了\n * `<dir>/SKILL.md`,则不再追加。\n *\n * 不依赖 fs.stat(loader 在 sync 阶段,目录还没落盘);纯字符串规则。\n */\nfunction ensureSkillMdSuffix(p: string): string {\n // 已经是 .md 文件结尾 → 原样返回\n if (p.endsWith('.md') || p.endsWith('.MD')) return p;\n // 移除尾部 / 后追加\n const trimmed = p.endsWith('/') ? p.slice(0, -1) : p;\n return `${trimmed}/SKILL.md`;\n}\n","/**\n * `rush://` marketplace source 实现。\n *\n * 从 Rush 平台 Web API 获取 marketplace manifest 和 plugin 详情。\n *\n * Spec: specs/rush-v2/web/plugins/cli-rush-source.spec.md\n */\n\nimport { execFileSync, spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { resolve as pathResolve, resolve } from 'node:path';\nimport { output } from '../output/logger.js';\nimport type { RushMarketplaceSource } from './types.js';\n\nexport interface RushMarketplaceManifestResponse {\n name: string;\n plugins: Array<{\n name: string;\n version: string;\n description: string;\n /**\n * II-3:分类(业务串如 'database' / 'security' / 'productivity'),\n * 由 server `/api/marketplace` 下发。CLI 透传到本地 marketplace.json,\n * 在 codex mirror sync 时被 `normalizeCategory()` 映射成 Codex UI 大类。\n */\n category?: string;\n /**\n * II-3:作者元数据。CLI 透传到 codex mirror,让详情页\"开发者\"区块显示。\n * 字段子集任意可缺失(rush 用户 plugin 仅有 name = ldap)。\n */\n author?: { name?: string; email?: string; url?: string };\n /**\n * II-3:产品官网 URL。codex mirror 详情页\"网站\"链接的 fallback。\n */\n homepage?: string;\n /**\n * II-3:plugin 源代码位置。\n * - 字符串:rush 用户 plugin 时为 web 详情页 URL(codex 详情\"网站\"目标)\n * - 对象:官方外部 plugin 的原始 source.url + sha + ref(透传给 mirror\n * 提取 websiteURL;不影响本地 cache,cache 仍用 `./plugins/<name>`)\n */\n source?:\n | string\n | { url?: string; source?: string; sha?: string; ref?: string };\n }>;\n}\n\n/**\n * II-3 起 manifest 跨表 union:\n * - rush 用户 plugin(默认形态,无 `source` 字段)\n * - 官方 local plugin:`source: 'claude-plugins-official'`,含 mcpServers /\n * skills / agents 等;CLI 通过 `/api/marketplace/<slug>/files/<path>` 拉文件\n * - 官方外部 plugin:`source: 'claude-plugins-official'` + `redirectTo`,\n * CLI 临时 git clone 当 directory source 装\n */\nexport interface RushPluginManifestResponse {\n name: string;\n version: string;\n description: string;\n /** II-3:标识业务来源;缺省=rush 用户 plugin */\n source?: 'claude-plugins-official' | 'rush-git-import';\n /**\n * II-3:作者元数据(CLI 写到 plugin.json 顶层 + interface.developerName)。\n * 字段子集任意可缺失;rush 用户 plugin 仅有 name = ldap。\n */\n author?: { name?: string; email?: string; url?: string };\n /**\n * II-3:产品官网 / 仓库地址。CLI 写到 plugin.json 顶层 + interface.websiteURL。\n * 缺失 + 是外部 plugin → CLI 用 redirectTo 推导 GitHub URL。\n */\n homepage?: string;\n /**\n * 官方外部 plugin 才有;CLI 收到后切到 git clone 链路。\n * 当前仅支持 `kind: 'github'`(白名单 — server 端也只下发 github)。\n */\n redirectTo?: {\n kind: 'github';\n owner: string;\n repo: string;\n ref?: string;\n path?: string;\n };\n mcpServers?: Record<\n string,\n {\n command?: string;\n args?: string[];\n url?: string;\n env?: Record<string, string>;\n [key: string]: unknown;\n }\n >;\n requiredSecrets: Array<{\n key: string;\n type: string;\n description: string;\n helpUrl?: string;\n }>;\n /**\n * skills 列表。II-3 起官方 plugin 的 entry 多带 `path` 字段;\n * rush plugin 不带(CLI 走 /next/skills/<name>.md 端点)。\n */\n skills: Array<{\n name: string;\n version: string;\n /** 仅官方 plugin 携带 — 用于 OSS 文件代理拉 SKILL.md */\n path?: string;\n }>;\n /** II-3 官方 local plugin 才有;rush 不下发 */\n agents?: Array<{ name: string; path: string }>;\n hooks?: Array<{ name: string; path: string }>;\n commands?: Array<{ name: string; path: string }>;\n}\n\n/**\n * 官方 local plugin 的文件代理响应(GET /api/marketplace/<slug>/files/<path>)。\n *\n * 三种情况(互斥):\n * - 小文本 inline:`encoding='utf-8'` + `content`\n * - 二进制 / 大文件:`signedUrl` + `expiresAt`(CLI 直连 OSS)\n * - 截断:`truncated: true` + 无内容(CLI 跳过 + 提示)\n */\nexport interface RushOfficialFileResponse {\n path: string;\n size: number;\n mime: string | null;\n isBinary: boolean;\n truncated: boolean;\n encoding?: 'utf-8';\n content?: string;\n signedUrl?: string;\n expiresAt?: string;\n}\n\nexport interface FetchRushOptions {\n /** Auth token (Bearer) for private plugins */\n token?: string | null;\n /** Override fetch for testing */\n fetchFn?: typeof fetch;\n}\n\nconst RUSH_API_FETCH_TIMEOUT_MS = 30_000;\n\nfunction fetchInitWithTimeout(headers: Record<string, string>): RequestInit {\n return {\n headers,\n signal: AbortSignal.timeout(RUSH_API_FETCH_TIMEOUT_MS),\n };\n}\n\n/**\n * Fetch the full marketplace listing from a rush:// source.\n *\n * Calls: GET https://<host>/api/marketplace\n */\nexport async function fetchRushMarketplace(\n source: RushMarketplaceSource,\n opts: FetchRushOptions = {}\n): Promise<RushMarketplaceManifestResponse> {\n const fetchFn = opts.fetchFn ?? fetch;\n const url = `${inferProtocol(source.host)}${source.host}/api/marketplace`;\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (opts.token) {\n headers.Authorization = `Bearer ${opts.token}`;\n }\n\n const response = await fetchFn(url, fetchInitWithTimeout(headers));\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch rush marketplace from ${url}: ${response.status} ${response.statusText}`\n );\n }\n\n const data = (await response.json()) as RushMarketplaceManifestResponse;\n\n if (!data.plugins || !Array.isArray(data.plugins)) {\n throw new Error(\n `Invalid marketplace response from ${url}: missing 'plugins' array`\n );\n }\n\n return data;\n}\n\n/**\n * Fetch a single plugin's manifest from a rush:// source.\n *\n * Calls: GET https://<host>/api/marketplace/:slug\n */\nexport async function fetchRushPlugin(\n source: RushMarketplaceSource,\n pluginSlug: string,\n opts: FetchRushOptions = {}\n): Promise<RushPluginManifestResponse> {\n const fetchFn = opts.fetchFn ?? fetch;\n const url = `${inferProtocol(source.host)}${source.host}/api/marketplace/${encodeURIComponent(pluginSlug)}`;\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (opts.token) {\n headers.Authorization = `Bearer ${opts.token}`;\n }\n\n const response = await fetchFn(url, fetchInitWithTimeout(headers));\n\n if (response.status === 401) {\n throw new Error(\n `Plugin '${pluginSlug}' requires authentication. Run 'rush-ai login' first.`\n );\n }\n if (response.status === 404) {\n throw new Error(\n `Plugin '${pluginSlug}' not found in rush marketplace at ${source.host}`\n );\n }\n if (!response.ok) {\n throw new Error(\n `Failed to fetch plugin '${pluginSlug}' from ${url}: ${response.status} ${response.statusText}`\n );\n }\n\n return (await response.json()) as RushPluginManifestResponse;\n}\n\n/**\n * 拉官方 plugin 的单个文件(II-3:通过 rush API + OSS 代理,不连 GitHub)。\n *\n * Calls: GET https://<host>/api/marketplace/:slug/files/<path...>\n *\n * `path` 由 `manifest.skills[].path` / `manifest.agents[].path` 等字段提供 ——\n * 已经过 server 端 `claude_marketplace_plugin_files` 索引校验(防穿越)。\n *\n * 返回三种状态(见 `RushOfficialFileResponse`):\n * - inline content(小文本)\n * - signedUrl(二进制 / 大文件)\n * - truncated(同步阶段未传 OSS)\n *\n * 失败语义:\n * - 404:path 不在该 plugin 的文件索引(CLI 应跳过该文件 + warn)\n * - 5xx:server / OSS 异常(CLI 抛错让上层处理)\n */\nexport async function fetchRushOfficialFile(\n source: RushMarketplaceSource,\n pluginSlug: string,\n filePath: string,\n opts: FetchRushOptions = {}\n): Promise<RushOfficialFileResponse> {\n const fetchFn = opts.fetchFn ?? fetch;\n // path 用 / 分隔;逐段 encodeURIComponent 防特殊字符\n const encoded = filePath\n .split('/')\n .filter((s) => s.length > 0)\n .map((s) => encodeURIComponent(s))\n .join('/');\n const url = `${inferProtocol(source.host)}${source.host}/api/marketplace/${encodeURIComponent(pluginSlug)}/files/${encoded}`;\n\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (opts.token) headers.Authorization = `Bearer ${opts.token}`;\n\n const response = await fetchFn(url, fetchInitWithTimeout(headers));\n if (response.status === 404) {\n throw new Error(\n `File '${filePath}' not found in official plugin '${pluginSlug}'`\n );\n }\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file '${filePath}' from ${url}: ${response.status} ${response.statusText}`\n );\n }\n return (await response.json()) as RushOfficialFileResponse;\n}\n\n// ---------------------------------------------------------------------------\n// Materialize: download plugin manifest + skills to local cache directory\n// ---------------------------------------------------------------------------\n\nexport interface MaterializeRushPluginOptions extends FetchRushOptions {\n /**\n * Pre-resolved secrets to substitute into mcpServers placeholders.\n * If provided, `${KEY}` patterns in mcpServers env/headers are replaced.\n */\n secrets?: Record<string, string>;\n}\n\n/**\n * Materialize a rush:// plugin to a local cache directory.\n *\n * Downloads the plugin manifest from API, fetches SKILL.md for each skill,\n * and writes the standard `.claude-plugin/plugin.json` + `skills/<name>/SKILL.md`\n * directory structure that `resolvePlugin()` can consume.\n *\n * Uses atomic write (tmp dir + rename) to avoid half-written state on failure.\n *\n * @param source The rush:// marketplace source (for API host)\n * @param pluginSlug The plugin slug/name to fetch\n * @param targetDir Final directory (e.g. ~/.rush/marketplaces/rush-marketplace/plugins/<slug>/)\n * @param opts Auth token, fetch override, and pre-resolved secrets\n */\nexport async function materializeRushPlugin(\n source: RushMarketplaceSource,\n pluginSlug: string,\n targetDir: string,\n opts: MaterializeRushPluginOptions = {}\n): Promise<RushPluginManifestResponse> {\n // Validate pluginSlug to prevent path traversal\n validateSlug(pluginSlug, 'plugin slug');\n\n // 1. Fetch full plugin manifest from API\n const manifest = await fetchRushPlugin(source, pluginSlug, opts);\n\n // II-3 / III-A:根据 manifest 形态分流。\n if (manifest.source === 'claude-plugins-official') {\n if (manifest.redirectTo) {\n // 1a) 外部 plugin → git clone redirectTo 到 targetDir\n await materializeOfficialExternal(manifest, targetDir);\n return manifest;\n }\n // 1b) 官方 local plugin → 通过 OSS 文件代理拉文件\n await materializeOfficialLocal(\n source,\n pluginSlug,\n manifest,\n targetDir,\n opts\n );\n return manifest;\n }\n\n // III-A:用户 git 导入 plugin → 与官方 local 同链路(OSS 文件代理)\n if (manifest.source === 'rush-git-import') {\n await materializeOfficialLocal(\n source,\n pluginSlug,\n manifest,\n targetDir,\n opts\n );\n return manifest;\n }\n\n // 1c) Rush 用户 plugin(默认链路) → 走原 reskill 流程\n return materializeRushUserPlugin(\n source,\n pluginSlug,\n manifest,\n targetDir,\n opts\n );\n}\n\n/**\n * Rush 用户 plugin 的 materialize 实现(II-3 之前的现状逻辑)。\n *\n * 抽出独立函数让 `materializeRushPlugin` 顶层只做分流。\n */\nasync function materializeRushUserPlugin(\n source: RushMarketplaceSource,\n pluginSlug: string,\n manifest: RushPluginManifestResponse,\n targetDir: string,\n opts: MaterializeRushPluginOptions\n): Promise<RushPluginManifestResponse> {\n // 2. Build plugin.json content (PluginManifest shape)\n let mcpServers: Record<string, unknown> = {};\n if (manifest.mcpServers && Object.keys(manifest.mcpServers).length > 0) {\n mcpServers = { ...manifest.mcpServers };\n // Apply secret substitution if secrets provided\n if (opts.secrets && Object.keys(opts.secrets).length > 0) {\n mcpServers = substituteMcpSecrets(mcpServers, opts.secrets);\n }\n mcpServers = normalizeMcpServerDefaults(mcpServers);\n }\n\n const pluginJson: Record<string, unknown> = buildPluginJsonShape(manifest, {\n mcpServers,\n });\n\n // 3. Atomic write: prepare in tmp dir, then rename\n const tmpDir = `${targetDir}.${randomUUID()}.tmp`;\n\n try {\n // Write .claude-plugin/plugin.json\n const pluginJsonDir = resolve(tmpDir, '.claude-plugin');\n await mkdir(pluginJsonDir, { recursive: true });\n await writeFile(\n resolve(pluginJsonDir, 'plugin.json'),\n `${JSON.stringify(pluginJson, null, 2)}\\n`,\n 'utf8'\n );\n\n // 4. Install skills via reskill CLI (downloads complete skill directory)\n if (manifest.skills && manifest.skills.length > 0) {\n // reskill requires skills.json to exist in cwd\n await writeFile(\n resolve(tmpDir, 'skills.json'),\n '{\"skills\":{}}\\n',\n 'utf8'\n );\n const reskillHomeDir = resolve(tmpDir, '.reskill-home');\n await mkdir(reskillHomeDir, { recursive: true });\n const registryUrl = `${inferProtocol(source.host)}${source.host}`;\n for (const [index, skill] of manifest.skills.entries()) {\n try {\n output.dim(\n ` Fetching skill ${index + 1}/${manifest.skills.length}: ${skill.name}`\n );\n // Use execFileSync (not execSync) to avoid shell injection —\n // skill.name is untrusted input from the API.\n //\n // This materialization step only needs to download the skill files.\n // Keep reskill isolated from the user's real agents; the target IDE\n // install is handled later by rush-ai installers.\n const args = [\n '-y',\n 'reskill@latest',\n 'install',\n skill.name,\n '--agent',\n 'claude-code',\n '--mode',\n 'copy',\n '--no-save',\n '--skip-manifest',\n '-y',\n '-f',\n '-r',\n registryUrl,\n ...(opts.token ? ['-t', opts.token] : []),\n ];\n execFileSync('npx', args, {\n cwd: tmpDir,\n // 3 分钟 — 覆盖 npx 首次下载 reskill 包的冷启动场景\n // (warm cache 时 reskill 实际运行只需 ~7s)\n timeout: 180_000,\n stdio: 'pipe',\n env: {\n ...process.env,\n HOME: reskillHomeDir,\n npm_config_cache: resolve(reskillHomeDir, '.npm'),\n },\n });\n } catch (err) {\n const stderr =\n err && typeof err === 'object' && 'stderr' in err\n ? ((err as { stderr: Buffer }).stderr?.toString() ?? '')\n : '';\n // Auth errors should block install\n if (\n stderr.includes('401') ||\n stderr.includes('403') ||\n stderr.includes('Unauthorized') ||\n stderr.includes('Forbidden')\n ) {\n throw new SkillAuthError(skill.name, 401);\n }\n // Other failures are non-blocking — warn and continue\n output.warn(\n ` Warning: failed to install skill '${skill.name}': ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n }\n }\n\n // 4b. Normalize: reskill installs to `.skills/` (Claude style) but\n // ClaudeCodeInstaller copies from `skills/` (CAPABILITY_DIRS).\n // Rename `.skills/` → `skills/` if needed.\n const dotSkillsDir = resolve(tmpDir, '.skills');\n const claudeSkillsDir = resolve(tmpDir, '.claude', 'skills');\n const skillsDir = resolve(tmpDir, 'skills');\n if (\n await stat(dotSkillsDir)\n .then((s) => s.isDirectory())\n .catch(() => false)\n ) {\n if (\n !(await stat(skillsDir)\n .then((s) => s.isDirectory())\n .catch(() => false))\n ) {\n await rename(dotSkillsDir, skillsDir);\n }\n }\n if (\n await stat(claudeSkillsDir)\n .then((s) => s.isDirectory())\n .catch(() => false)\n ) {\n if (\n !(await stat(skillsDir)\n .then((s) => s.isDirectory())\n .catch(() => false))\n ) {\n await rename(claudeSkillsDir, skillsDir);\n }\n }\n\n // Remove reskill artifacts that we don't need in the plugin directory\n await rm(resolve(tmpDir, 'skills.json'), { force: true });\n await rm(resolve(tmpDir, '.reskill-home'), {\n recursive: true,\n force: true,\n });\n await rm(resolve(tmpDir, '.cursor'), { recursive: true, force: true });\n await rm(resolve(tmpDir, '.claude'), { recursive: true, force: true });\n await rm(resolve(tmpDir, '.codex'), { recursive: true, force: true });\n await rm(resolve(tmpDir, '.github'), { recursive: true, force: true });\n await rm(resolve(tmpDir, '.opencode'), { recursive: true, force: true });\n\n // 5. Atomic rename: tmp → target (remove target first if exists)\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(resolve(targetDir, '..'), { recursive: true });\n await rename(tmpDir, targetDir);\n } catch (err) {\n // Cleanup tmp on failure\n await rm(tmpDir, { recursive: true, force: true }).catch(() => {});\n throw err;\n }\n\n return manifest;\n}\n\n// ---------------------------------------------------------------------------\n// II-3: 官方 plugin materialize(local + 外部两种分支)\n// ---------------------------------------------------------------------------\n\n/**\n * 官方 local plugin:从 rush API 的 OSS 文件代理拉取所有组件文件,\n * 在 `targetDir` 下还原成标准 Claude plugin 目录结构。\n *\n * 写出文件清单:\n * - `.claude-plugin/plugin.json`(含 name / version / description / mcpServers)\n * - `.mcp.json`(manifest.mcpServers 已包含 — 但写一份兜底,让 resolver 的隐式\n * mcp 兜底逻辑也能命中)\n * - `skills/<name>/SKILL.md`(每个 skill)\n * - `agents/<path>` / `hooks/<path>` / `commands/<path>`\n *\n * 失败语义:单文件下载失败 → warn 后跳过;plugin.json 写失败 → 抛错(致命)。\n *\n * 文件路径全部走 manifest 提供的字符串,**不做拼接**:server 端\n * `/api/marketplace/<slug>/files/<path>` 严格在 `claude_marketplace_plugin_files`\n * 索引中匹配 path,CLI 拿到的 path 已是合法文件位置。\n */\nasync function materializeOfficialLocal(\n source: RushMarketplaceSource,\n pluginSlug: string,\n manifest: RushPluginManifestResponse,\n targetDir: string,\n opts: MaterializeRushPluginOptions\n): Promise<void> {\n // mcpServers 接受 secrets 替换(与 rush 用户 plugin 保持一致语义)\n let mcpServers: Record<string, unknown> = {};\n if (manifest.mcpServers && Object.keys(manifest.mcpServers).length > 0) {\n mcpServers = { ...manifest.mcpServers };\n if (opts.secrets && Object.keys(opts.secrets).length > 0) {\n mcpServers = substituteMcpSecrets(mcpServers, opts.secrets);\n }\n }\n\n const pluginJson: Record<string, unknown> = buildPluginJsonShape(manifest, {\n mcpServers,\n });\n\n const tmpDir = `${targetDir}.${randomUUID()}.tmp`;\n\n try {\n await mkdir(resolve(tmpDir, '.claude-plugin'), { recursive: true });\n await writeFile(\n resolve(tmpDir, '.claude-plugin', 'plugin.json'),\n `${JSON.stringify(pluginJson, null, 2)}\\n`,\n 'utf8'\n );\n\n // 写一份独立 .mcp.json(兼容某些 installer 强制读独立文件)\n if (Object.keys(mcpServers).length > 0) {\n await writeFile(\n resolve(tmpDir, '.mcp.json'),\n `${JSON.stringify({ mcpServers }, null, 2)}\\n`,\n 'utf8'\n );\n }\n\n // skills / agents / hooks / commands —— 全部通过 OSS 代理拉\n await fetchAndWriteOfficialFiles(\n source,\n pluginSlug,\n manifest,\n tmpDir,\n opts\n );\n\n // 原子 rename\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(resolve(targetDir, '..'), { recursive: true });\n await rename(tmpDir, targetDir);\n } catch (err) {\n await rm(tmpDir, { recursive: true, force: true }).catch(() => {});\n throw err;\n }\n}\n\n/**\n * 拉取 manifest 里所有组件文件(skills / agents / hooks / commands)→ 写到 targetDir。\n *\n * 串行限流(per-plugin ≤ ~30 个文件,可接受)。\n * 单个文件下载失败仅 warn,不阻塞其他。\n */\nasync function fetchAndWriteOfficialFiles(\n source: RushMarketplaceSource,\n pluginSlug: string,\n manifest: RushPluginManifestResponse,\n rootDir: string,\n opts: MaterializeRushPluginOptions\n): Promise<void> {\n type Entry = { kind: string; name: string; path: string };\n const entries: Entry[] = [];\n for (const s of manifest.skills ?? []) {\n if (s.path) {\n // skill 的 manifest.path 通常是目录(如 'skills/review'),实际 markdown\n // 在 `<dir>/SKILL.md`。如果上游已经直接给了 .md 文件,原样使用。\n const skillFilePath =\n s.path.endsWith('.md') || s.path.endsWith('.MD')\n ? s.path\n : `${s.path.endsWith('/') ? s.path.slice(0, -1) : s.path}/SKILL.md`;\n entries.push({ kind: 'skill', name: s.name, path: skillFilePath });\n }\n }\n for (const a of manifest.agents ?? []) {\n entries.push({ kind: 'agent', name: a.name, path: a.path });\n }\n for (const h of manifest.hooks ?? []) {\n entries.push({ kind: 'hook', name: h.name, path: h.path });\n }\n for (const c of manifest.commands ?? []) {\n entries.push({ kind: 'command', name: c.name, path: c.path });\n }\n if (entries.length === 0) return;\n\n const fetchOpts: { fetchFn?: typeof fetch; token?: string | null } = {};\n if (opts.fetchFn !== undefined) fetchOpts.fetchFn = opts.fetchFn;\n if (opts.token !== undefined && opts.token !== null) {\n fetchOpts.token = opts.token;\n }\n\n for (const e of entries) {\n try {\n const file = await fetchRushOfficialFile(\n source,\n pluginSlug,\n e.path,\n fetchOpts\n );\n if (file.truncated) {\n output.warn(\n ` Warning: ${e.kind} '${e.name}' file '${e.path}' was truncated during sync; please view on web`\n );\n continue;\n }\n // 二进制走 signedUrl —— 需要再下载一次(CLI 不持久化 OSS 凭证;signedUrl\n // 是预签名一次性 URL,直接 GET 即可)\n let body: Buffer | string | null = null;\n if (typeof file.content === 'string') {\n body = file.content;\n } else if (typeof file.signedUrl === 'string') {\n const fetchFn = opts.fetchFn ?? fetch;\n const res = await fetchFn(file.signedUrl);\n if (res.ok) {\n const arr = await res.arrayBuffer();\n body = Buffer.from(arr);\n } else {\n output.warn(\n ` Warning: failed to download signed URL for ${e.kind} '${e.name}': ${res.status}`\n );\n continue;\n }\n } else {\n output.warn(\n ` Warning: ${e.kind} '${e.name}' has no content or signedUrl`\n );\n continue;\n }\n\n // 落盘到 rootDir/<path>。path 已经过 server 严格校验(在\n // claude_marketplace_plugin_files 索引内),不会含 `..` 等危险序列;\n // 这里再用 path.resolve 边界防御。\n const dest = resolve(rootDir, e.path);\n const root = pathResolve(rootDir);\n const rootWithSep = root.endsWith('/') ? root : `${root}/`;\n if (dest !== root && !dest.startsWith(rootWithSep)) {\n output.warn(\n ` Warning: refusing to write ${e.kind} '${e.name}' outside plugin dir: ${e.path}`\n );\n continue;\n }\n await mkdir(resolve(dest, '..'), { recursive: true });\n await writeFile(dest, body);\n } catch (err) {\n output.warn(\n ` Warning: failed to fetch ${e.kind} '${e.name}' (${e.path}): ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n }\n}\n\n/**\n * 官方外部 plugin:根据 `manifest.redirectTo` git clone 目标 GitHub 仓库到 plugin 目录。\n *\n * 流程:\n * 1. tmp 目录 git clone(深度 1,可选 ref)\n * 2. 如果 redirectTo.path 存在 → 把子目录的内容 move 到 targetDir\n * 3. 否则整个仓库就是 plugin 根 → 直接 rename\n *\n * 安全约束(spec §7.10):\n * - 只接受 redirectTo.kind === 'github'\n * - URL 由 owner/repo 拼成 `https://github.com/<owner>/<repo>.git`,\n * 不接受 server 直接下发完整 URL(避免任意主机被信任)\n *\n * 失败语义:clone / cp 失败抛错,由调用方上抛 RushError(2)。\n */\nasync function materializeOfficialExternal(\n manifest: RushPluginManifestResponse,\n targetDir: string\n): Promise<void> {\n const r = manifest.redirectTo;\n if (!r || r.kind !== 'github') {\n throw new Error(\n `Plugin '${manifest.name}' has unsupported redirectTo (kind=${r?.kind ?? 'undefined'}). ` +\n `External plugins must redirect to GitHub.`\n );\n }\n if (!r.owner || !r.repo) {\n throw new Error(\n `Plugin '${manifest.name}' has invalid redirectTo: missing owner/repo`\n );\n }\n\n const url = `https://github.com/${r.owner}/${r.repo}.git`;\n const cloneTmp = `${targetDir}.${randomUUID()}.clone.tmp`;\n\n const args = ['clone', '--depth', '1'];\n if (r.ref) args.push('--branch', r.ref);\n args.push('--', url, cloneTmp);\n\n try {\n await runGitClone(args);\n\n // 决定用整个仓库还是子目录作为 plugin 根\n let sourceDir: string;\n if (r.path && r.path.length > 0) {\n // 防穿越:path 不能逃出 cloneTmp\n const candidate = resolve(cloneTmp, r.path);\n const cloneTmpAbs = pathResolve(cloneTmp);\n const cloneTmpWithSep = cloneTmpAbs.endsWith('/')\n ? cloneTmpAbs\n : `${cloneTmpAbs}/`;\n if (candidate !== cloneTmpAbs && !candidate.startsWith(cloneTmpWithSep)) {\n throw new Error(`redirectTo.path escapes repo root: '${r.path}'`);\n }\n const st = await stat(candidate).catch(() => null);\n if (!st || !st.isDirectory()) {\n throw new Error(\n `redirectTo.path '${r.path}' is not a directory in ${url}`\n );\n }\n sourceDir = candidate;\n } else {\n sourceDir = cloneTmp;\n }\n\n // 原子 rename\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(resolve(targetDir, '..'), { recursive: true });\n await rename(sourceDir, targetDir);\n\n // II-3:补齐上游 plugin.json 缺失的 homepage / author / interface\n // 让 codex sync 把它复制过去后详情页\"作者 / 网站\"能正常渲染。\n // 上游仓库的 plugin.json 通常无 homepage / interface 字段。\n await enrichExternalPluginJson(targetDir, manifest);\n } catch (err) {\n await rm(cloneTmp, { recursive: true, force: true }).catch(() => {});\n // 如果 sourceDir 已经 rename 出去再失败 — 几乎不可能,但 cleanup 兜底\n throw err;\n } finally {\n // 子目录方式:还有 cloneTmp 残留(其余文件没用),清理\n await rm(cloneTmp, { recursive: true, force: true }).catch(() => {});\n }\n}\n\n/**\n * II-3:合并 manifest 里的 author / homepage / interface 字段到 git clone 下来\n * 的 plugin.json,**仅填补缺失字段**,不覆盖上游已有声明。\n *\n * 安全:plugin.json 不存在 → 创建最小一份(name/version/description from manifest);\n * 读 / parse 失败 → silent skip,不阻塞安装(plugin 主体已落盘)。\n */\nexport async function enrichExternalPluginJson(\n pluginDir: string,\n manifest: RushPluginManifestResponse\n): Promise<void> {\n const pjPath = resolve(pluginDir, '.claude-plugin', 'plugin.json');\n let existing: Record<string, unknown> = {};\n try {\n const raw = await import('node:fs/promises').then((m) =>\n m.readFile(pjPath, 'utf8')\n );\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n existing = parsed as Record<string, unknown>;\n }\n } catch {\n // 缺失 / 损坏 → 用最小 plugin.json 代替\n await mkdir(resolve(pluginDir, '.claude-plugin'), { recursive: true });\n existing = {\n name: manifest.name,\n version: manifest.version || '1.0.0',\n description: manifest.description || '',\n };\n }\n\n // 推导 homepage:优先 manifest.homepage > redirectTo 推导的 GitHub URL\n let derivedHomepage = manifest.homepage;\n if (!derivedHomepage && manifest.redirectTo?.kind === 'github') {\n const r = manifest.redirectTo;\n derivedHomepage = `https://github.com/${r.owner}/${r.repo}`;\n if (r.path) derivedHomepage += `/tree/${r.ref ?? 'main'}/${r.path}`;\n }\n\n // 仅填补缺失:不覆盖上游已有\n if (!existing.author && manifest.author) {\n existing.author = { ...manifest.author };\n }\n if (!existing.homepage && derivedHomepage) {\n existing.homepage = derivedHomepage;\n }\n\n // interface 区块:合并补充字段(codex 详情页\"开发者 / 网站\")\n const ifaceExisting = (existing.interface as Record<string, unknown>) ?? {};\n const ifaceOut = { ...ifaceExisting };\n if (!ifaceOut.developerName && manifest.author?.name) {\n ifaceOut.developerName = manifest.author.name;\n }\n if (!ifaceOut.websiteURL && derivedHomepage) {\n ifaceOut.websiteURL = derivedHomepage;\n }\n if (Object.keys(ifaceOut).length > 0) {\n existing.interface = ifaceOut;\n }\n\n await writeFile(pjPath, `${JSON.stringify(existing, null, 2)}\\n`, 'utf8');\n}\n\n/** 简单 git clone runner(与 plugins/resolver.ts 的 defaultGitRunner 形态一致) */\nasync function runGitClone(args: string[]): Promise<void> {\n await new Promise<void>((res, rej) => {\n const child = spawn('git', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stderr = '';\n child.stderr?.on('data', (chunk: Buffer | string) => {\n stderr += typeof chunk === 'string' ? chunk : chunk.toString();\n });\n child.on('error', (err) => rej(err));\n child.on('close', (code) => {\n if (code === 0) res();\n else\n rej(\n new Error(`git ${args.join(' ')} exited ${code}: ${stderr.trim()}`)\n );\n });\n });\n}\n\n/**\n * 把 manifest 拼成 plugin.json 形态(写到 `.claude-plugin/plugin.json`)。\n *\n * II-3:除了基础字段,还把 author / homepage / interface(含 developerName /\n * websiteURL / category)写进来,让 codex mirror 复制 versionDir 后的 full plugin.json\n * 也含详情页要的元数据(不再依赖 sync stub 的 buildPluginJson 二次处理)。\n *\n * 字段优先级:\n * - websiteURL:homepage > redirectTo 推导的 GitHub URL(外部 plugin)\n * - developerName:author.name\n * - category:作为业务串原样落盘;codex mirror 的 normalizeCategory() 会再\n * 映射成 Codex UI 大类(这里不映射,避免 rush 用户 plugin 走不同路径出现不一致)\n */\nexport function buildPluginJsonShape(\n manifest: RushPluginManifestResponse,\n context: { mcpServers: Record<string, unknown> }\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n name: manifest.name,\n version: manifest.version || '1.0.0',\n description: manifest.description,\n };\n if (Object.keys(context.mcpServers).length > 0) {\n out.mcpServers = context.mcpServers;\n }\n\n // 顶层 author / homepage(codex 详情页\"信息\"区块读取)\n if (manifest.author && Object.keys(manifest.author).length > 0) {\n out.author = { ...manifest.author };\n }\n // homepage 优先用 manifest.homepage,否则用 redirectTo 推导(外部 plugin)\n let homepage = manifest.homepage;\n if (!homepage && manifest.redirectTo?.kind === 'github') {\n const r = manifest.redirectTo;\n homepage = `https://github.com/${r.owner}/${r.repo}`;\n if (r.path) homepage += `/tree/${r.ref ?? 'main'}/${r.path}`;\n }\n if (homepage) out.homepage = homepage;\n\n // interface 区块(codex 详情页\"概览\"区块读取)\n const ifaceOut: Record<string, unknown> = {};\n if (manifest.author?.name) ifaceOut.developerName = manifest.author.name;\n if (homepage) ifaceOut.websiteURL = homepage;\n if (Object.keys(ifaceOut).length > 0) out.interface = ifaceOut;\n\n return out;\n}\n\n/**\n * Error thrown when skill download fails due to auth issues (401/403).\n * This should block the install — the user needs to fix auth.\n */\nexport class SkillAuthError extends Error {\n constructor(skillName: string, status: number) {\n super(\n `Skill '${skillName}' requires authentication (HTTP ${status}). Run 'rush-ai auth login' first.`\n );\n this.name = 'SkillAuthError';\n }\n}\n\n/**\n * Substitute `${KEY}` placeholders in mcpServers config with actual secret values.\n *\n * Replaces patterns in `env` values and top-level string values of each server config.\n */\nfunction substituteMcpSecrets(\n mcpServers: Record<string, unknown>,\n secrets: Record<string, string>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [serverName, serverConfig] of Object.entries(mcpServers)) {\n if (!serverConfig || typeof serverConfig !== 'object') {\n result[serverName] = serverConfig;\n continue;\n }\n const cfg = { ...(serverConfig as Record<string, unknown>) };\n\n // Substitute in env values\n if (cfg.env && typeof cfg.env === 'object') {\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries(cfg.env as Record<string, string>)) {\n env[k] = substituteValue(v, secrets);\n }\n cfg.env = env;\n }\n\n // Substitute in headers values (for HTTP/SSE transports)\n if (cfg.headers && typeof cfg.headers === 'object') {\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(\n cfg.headers as Record<string, string>\n )) {\n headers[k] = substituteValue(v, secrets);\n }\n cfg.headers = headers;\n }\n\n result[serverName] = cfg;\n }\n return result;\n}\n\nfunction normalizeMcpServerDefaults(\n mcpServers: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [serverName, serverConfig] of Object.entries(mcpServers)) {\n if (!serverConfig || typeof serverConfig !== 'object') {\n result[serverName] = serverConfig;\n continue;\n }\n const cfg = { ...(serverConfig as Record<string, unknown>) };\n if (\n typeof cfg.url === 'string' &&\n cfg.url.length > 0 &&\n typeof cfg.command !== 'string' &&\n typeof cfg.type !== 'string'\n ) {\n cfg.type = 'http';\n }\n result[serverName] = cfg;\n }\n return result;\n}\n\nfunction substituteValue(\n value: string,\n secrets: Record<string, string>\n): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (match, key: string) => {\n return key in secrets ? secrets[key] : match;\n });\n}\n\n/**\n * Validate a slug/name used in file paths to prevent path traversal.\n * Rejects: empty, contains `..`, starts with `/`, or contains null bytes.\n * Allows: alphanumeric, hyphens, underscores, dots, `@`, `/` (for scoped names like @scope/name).\n */\nfunction validateSlug(value: string, label: string): void {\n if (\n !value ||\n value.includes('..') ||\n value.startsWith('/') ||\n value.includes('\\0')\n ) {\n throw new Error(\n `Invalid ${label} '${value}': must not be empty, contain '..', start with '/', or contain null bytes.`\n );\n }\n}\n\n/**\n * Infer protocol from host. Localhost/127.0.0.1 use http://, others https://.\n */\nfunction inferProtocol(host: string): string {\n const hostname = host.split(':')[0];\n if (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '0.0.0.0'\n ) {\n return 'http://';\n }\n return 'https://';\n}\n"],"mappings":"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","names":["stat","pathResolve","pathResolve","stat"]}