@vetta-org/plugin-cli 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to `@vetta-org/plugin-cli` are documented in this file.
4
4
 
5
+ ## [0.1.3] — 2026-09-14
6
+
7
+ ### Added
8
+
9
+ - `docs` 每次都打印刷新手册的命令,并新增 `--check-latest` 对比 registry 上的最新 SDK,落后时
10
+ 直接说出来。手册随 SDK 进 `node_modules`,所以老工程里的手册与 `AGENTS.md` 都停在初始化那天;
11
+ `npx` 默认取最新的 CLI,它的输出是这条链路上唯一不会过期的位置。查不到 registry(离线、私服)
12
+ 时明说查不到,不会据此断言手册过期。
13
+ - 在能力市场仓库根跑 `docs` 时,提示 `cd` 进能力目录,而不是让人在仓库根装一份用不上的 SDK。
14
+
15
+ ### Changed
16
+
17
+ - 脚手架的 `AGENTS.md` 与 hub `AGENTS.md` 增加「先确认手册是否最新」一步,并写明 `docs` 的输出
18
+ 与自身冲突时以前者为准。
19
+ - 新建工程的 SDK 范围提到 `^0.3.2`(团队成员的角色槽位与跨插件引用)。
20
+
5
21
  ## [0.1.2] — 2026-09-14
6
22
 
7
23
  ### Fixed
package/README.md CHANGED
@@ -49,12 +49,19 @@ skip an update when `marketplaceVersion` did not change. `sync` reconciles all t
49
49
 
50
50
  ```bash
51
51
  npx @vetta-org/plugin-cli docs
52
+ npx @vetta-org/plugin-cli docs --check-latest
52
53
  ```
53
54
 
54
55
  Prints where the manual bundled with the installed `@vetta-org/plugin-sdk` lives, which SDK version
55
56
  it documents, and which plugin (and hub) the current directory belongs to. The manual is always the
56
57
  one this project compiles against, so it never describes contracts the user's host lacks.
57
58
 
59
+ That pinning is also why the manual goes stale: a project created months ago still carries the manual
60
+ from that day, and so does its `AGENTS.md`. Every run therefore prints how to refresh it, and
61
+ `--check-latest` compares the installed SDK against the registry and says so outright when it is
62
+ behind. Since `npx` resolves this CLI to the latest published version, its output is the one link in
63
+ the chain that cannot be out of date — when it disagrees with a checked-in brief, it wins.
64
+
58
65
  ## Install a plugin
59
66
 
60
67
  Install an npm-distributed plugin into the running Vetta Desktop app:
@@ -1 +1 @@
1
- {"version":3,"file":"agents-template.d.ts","sourceRoot":"","sources":["../src/agents-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAiF1F","sourcesContent":["/**\n * 随脚手架落地的 Agent 说明书。\n *\n * 它替代的是「把开发知识写死在工作台插件的 skill 里」那套做法:那份 skill 只有 Vetta\n * 自己的 Agent 读得到,而且每加一个能力就要改一次、还要等 App 发版才到用户手里。这里\n * 反过来——工程自带说明书,说明书只说「去哪读手册」,手册随 SDK 版本进 node_modules。\n * 因此任何 Agent(Claude Code、Cursor、Vetta 自己)在任何陌生目录都能自举,而且读到的\n * 永远是这个工程实际编译所针对的那份合同。\n *\n * 单位是插件目录本身:外面是能力市场仓库、是单插件仓库、还是一堆别的东西,都不影响这里。\n */\nexport function renderAgentsGuide(input: { pluginId: string; displayName: string }): string {\n\treturn `# ${input.displayName}\n\nVetta 桌面插件工程。**先读手册再写代码**——不要凭记忆写 SDK API,这套合同变化很快。\n\n## 第一步:装依赖,然后找到手册\n\n\\`\\`\\`bash\nnpm install\nnpx vetta-plugin-cli docs\n\\`\\`\\`\n\n\\`npm install\\` 必须先跑:\\`vetta-plugin-cli\\` 是 \\`@vetta-org/plugin-cli\\` 的命令名,装完才在\n\\`node_modules/.bin\\` 里。还没装就想跑,用全名 \\`npx @vetta-org/plugin-cli docs\\`。\n\n它打印出随 \\`@vetta-org/plugin-sdk\\` 一起装进 \\`node_modules\\` 的手册目录**绝对路径**,以及\n这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件\n时各插件还可能钉不同的 SDK 版本。\n\n拿到路径后,用 read 工具按这个顺序打开:\n\n| 顺序 | 文件 | 何时读 |\n| --- | --- | --- |\n| 1 | \\`README.md\\` | 总是先读:能力矩阵、信任模型、导航 |\n| 2 | \\`getting-started.md\\` | 首次写代码、构建、安装调试 |\n| 3 | \\`manifest.md\\` | 写/改 \\`plugin.json\\`、贡献智能体与团队 |\n| 4 | \\`permissions.md\\` | 选定权限列表之前 |\n| 5 | 按扩展点选读 | \\`ui-slots.md\\` / \\`conversation-and-agent.md\\` / \\`message-cards.md\\` / \\`mcp.md\\` / \\`ai.md\\` / \\`browser.md\\` / \\`app-actions.md\\` |\n\n实现任一扩展点**之前**再读对应那章。手册是唯一真源,本文件与它冲突时以手册为准。\n\n## 开发闭环\n\n\\`\\`\\`bash\nnpm run dev # Vite + Module Federation 开发服务器\nnpm run build # 产出 dist/\nnpm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)\nnpx vetta-plugin-cli watch # 开热更新:宿主改从本工程目录加载,改完即生效\nnpx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)\n\\`\\`\\`\n\n\\`install:vetta\\` 走 \\`vetta-plugin-cli add .\\`:它找到本工程打出来的归档,交给正在运行的\nDesktop 校验、授权、安装。它**不会**直接写 \\`~/.vetta/plugins\\`。\n\n装完若提示有 pending 版本,用 \\`npx vetta-plugin-cli reload ${input.pluginId}\\` 让宿主应用它。\n\n开发期建议开热更新(\\`watch\\`):之后改源码即时生效,不用每次重新打包安装。改 \\`plugin.json\\`\n的权限或命令声明时仍需重新安装一次,让宿主把授权落盘。\\`watch --stop\\` 关闭。\n\n## 如果这个目录之上有能力市场索引\n\n\\`vetta-plugin-cli docs\\` 会告诉你有没有(它会打印 \\`Marketplace index:\\`)。有的话,**改完\n\\`version\\` / \\`permissions\\` / \\`pluginApiVersion\\` 之后要回仓库根跑一次**:\n\n\\`\\`\\`bash\nnpx @vetta-org/plugin-cli sync # 从各能力目录回填索引,并推进 marketplaceVersion\nnpx @vetta-org/plugin-cli sync --check # 只报不写,CI 用\n\\`\\`\\`\n\n索引里的 \\`version\\` 与 \\`plugin.json\\` 的 \\`version\\` 必须**完全相等**,否则宿主同步直接失败;\n而内容变了却不换 \\`marketplaceVersion\\` 时,客户端既不报错也不更新——用户只是永远收不到。\n\\`add .\\` 装完若检测到索引还停在旧版本,会当场提醒你。\n\n## 不可违反的几条\n\n- **样式只用 Tailwind \\`className\\`**。禁止新建业务 CSS、禁止在 \\`style.css\\` 里写 \\`button\\`/\\`div\\`/\\`*\\`\n 这类选择器——插件与宿主共享同一个页面,全局选择器会污染整个 UI。\n- **可能失败的路径必须上报**:读文件、解析、网络、外部库的 catch 里调用\n \\`ctx.ui.notify({ message, error })\\`(无需权限)。禁止只写死「失败」文案并丢掉原始 error。\n- **权限按需最小声明**。构建期会校验产物用到的能力与 \\`plugin.json\\` 的声明是否匹配,缺了直接\n 构建失败。但 UI 槽位不在这条校验里——那类缺权限在运行时只是静默跳过,所以对着手册核对。\n- **不要写 \\`agent_mode\\`**(已废弃,无运行时语义)。想收窄某个工具的使用场景,把「何时不该用它 +\n 替代做法」写进该工具 description 的反向触发段。\n- **顶层不要出现依赖共享 React 的 JSX**,放进组件或 \\`activate\\` 内(Module Federation 的加载时序)。\n- 依赖用 registry 上已发布的 semver,不要 \\`workspace:*\\`。\n- **\\`dist/\\` 要进版本库**。插件通过仓库目录分发时,宿主直接读 \\`plugin.json\\` 指向的 \\`entry\\`\n 与 \\`styles\\`,它不会替你构建——目录里没有构建产物就装不上。\n## 信息不足时\n\n插件 id、展示名、要用哪些权限、功能边界、是否立刻安装——**问用户**,不要自己假定。\n`;\n}\n"]}
1
+ {"version":3,"file":"agents-template.d.ts","sourceRoot":"","sources":["../src/agents-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CA2F1F","sourcesContent":["/**\n * 随脚手架落地的 Agent 说明书。\n *\n * 它替代的是「把开发知识写死在工作台插件的 skill 里」那套做法:那份 skill 只有 Vetta\n * 自己的 Agent 读得到,而且每加一个能力就要改一次、还要等 App 发版才到用户手里。这里\n * 反过来——工程自带说明书,说明书只说「去哪读手册」,手册随 SDK 版本进 node_modules。\n * 因此任何 Agent(Claude Code、Cursor、Vetta 自己)在任何陌生目录都能自举,而且读到的\n * 永远是这个工程实际编译所针对的那份合同。\n *\n * 单位是插件目录本身:外面是能力市场仓库、是单插件仓库、还是一堆别的东西,都不影响这里。\n */\nexport function renderAgentsGuide(input: { pluginId: string; displayName: string }): string {\n\treturn `# ${input.displayName}\n\nVetta 桌面插件工程。**先读手册再写代码**——不要凭记忆写 SDK API,这套合同变化很快。\n\n## 第一步:装依赖,然后找到手册\n\n\\`\\`\\`bash\nnpm install\nnpx vetta-plugin-cli docs\n\\`\\`\\`\n\n\\`npm install\\` 必须先跑:\\`vetta-plugin-cli\\` 是 \\`@vetta-org/plugin-cli\\` 的命令名,装完才在\n\\`node_modules/.bin\\` 里。还没装就想跑,用全名 \\`npx @vetta-org/plugin-cli docs\\`。\n\n**手册会过期。** 它是随 SDK 装进 \\`node_modules\\` 的快照,工程不升级就永远停在初始化那天的\n版本——本文件同样是那天的快照。开工前先确认一次:\n\n\\`\\`\\`bash\nnpx vetta-plugin-cli docs --check-latest\n\\`\\`\\`\n\n落后就按它打印的命令升级 SDK 再重读手册。\\`docs\\` 的输出永远比本文件新(\\`npx\\` 默认取最新的\nCLI),**两者冲突时以它为准**。\n\n它打印出随 \\`@vetta-org/plugin-sdk\\` 一起装进 \\`node_modules\\` 的手册目录**绝对路径**,以及\n这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件\n时各插件还可能钉不同的 SDK 版本。\n\n拿到路径后,用 read 工具按这个顺序打开:\n\n| 顺序 | 文件 | 何时读 |\n| --- | --- | --- |\n| 1 | \\`README.md\\` | 总是先读:能力矩阵、信任模型、导航 |\n| 2 | \\`getting-started.md\\` | 首次写代码、构建、安装调试 |\n| 3 | \\`manifest.md\\` | 写/改 \\`plugin.json\\`、贡献智能体与团队 |\n| 4 | \\`permissions.md\\` | 选定权限列表之前 |\n| 5 | 按扩展点选读 | \\`ui-slots.md\\` / \\`conversation-and-agent.md\\` / \\`message-cards.md\\` / \\`mcp.md\\` / \\`ai.md\\` / \\`browser.md\\` / \\`app-actions.md\\` |\n\n实现任一扩展点**之前**再读对应那章。手册是唯一真源,本文件与它冲突时以手册为准。\n\n## 开发闭环\n\n\\`\\`\\`bash\nnpm run dev # Vite + Module Federation 开发服务器\nnpm run build # 产出 dist/\nnpm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)\nnpx vetta-plugin-cli watch # 开热更新:宿主改从本工程目录加载,改完即生效\nnpx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)\n\\`\\`\\`\n\n\\`install:vetta\\` 走 \\`vetta-plugin-cli add .\\`:它找到本工程打出来的归档,交给正在运行的\nDesktop 校验、授权、安装。它**不会**直接写 \\`~/.vetta/plugins\\`。\n\n装完若提示有 pending 版本,用 \\`npx vetta-plugin-cli reload ${input.pluginId}\\` 让宿主应用它。\n\n开发期建议开热更新(\\`watch\\`):之后改源码即时生效,不用每次重新打包安装。改 \\`plugin.json\\`\n的权限或命令声明时仍需重新安装一次,让宿主把授权落盘。\\`watch --stop\\` 关闭。\n\n## 如果这个目录之上有能力市场索引\n\n\\`vetta-plugin-cli docs\\` 会告诉你有没有(它会打印 \\`Marketplace index:\\`)。有的话,**改完\n\\`version\\` / \\`permissions\\` / \\`pluginApiVersion\\` 之后要回仓库根跑一次**:\n\n\\`\\`\\`bash\nnpx @vetta-org/plugin-cli sync # 从各能力目录回填索引,并推进 marketplaceVersion\nnpx @vetta-org/plugin-cli sync --check # 只报不写,CI 用\n\\`\\`\\`\n\n索引里的 \\`version\\` 与 \\`plugin.json\\` 的 \\`version\\` 必须**完全相等**,否则宿主同步直接失败;\n而内容变了却不换 \\`marketplaceVersion\\` 时,客户端既不报错也不更新——用户只是永远收不到。\n\\`add .\\` 装完若检测到索引还停在旧版本,会当场提醒你。\n\n## 不可违反的几条\n\n- **样式只用 Tailwind \\`className\\`**。禁止新建业务 CSS、禁止在 \\`style.css\\` 里写 \\`button\\`/\\`div\\`/\\`*\\`\n 这类选择器——插件与宿主共享同一个页面,全局选择器会污染整个 UI。\n- **可能失败的路径必须上报**:读文件、解析、网络、外部库的 catch 里调用\n \\`ctx.ui.notify({ message, error })\\`(无需权限)。禁止只写死「失败」文案并丢掉原始 error。\n- **权限按需最小声明**。构建期会校验产物用到的能力与 \\`plugin.json\\` 的声明是否匹配,缺了直接\n 构建失败。但 UI 槽位不在这条校验里——那类缺权限在运行时只是静默跳过,所以对着手册核对。\n- **不要写 \\`agent_mode\\`**(已废弃,无运行时语义)。想收窄某个工具的使用场景,把「何时不该用它 +\n 替代做法」写进该工具 description 的反向触发段。\n- **顶层不要出现依赖共享 React 的 JSX**,放进组件或 \\`activate\\` 内(Module Federation 的加载时序)。\n- 依赖用 registry 上已发布的 semver,不要 \\`workspace:*\\`。\n- **\\`dist/\\` 要进版本库**。插件通过仓库目录分发时,宿主直接读 \\`plugin.json\\` 指向的 \\`entry\\`\n 与 \\`styles\\`,它不会替你构建——目录里没有构建产物就装不上。\n## 信息不足时\n\n插件 id、展示名、要用哪些权限、功能边界、是否立刻安装——**问用户**,不要自己假定。\n`;\n}\n"]}
package/dist/cli.js CHANGED
@@ -15402,6 +15402,7 @@ var PluginSkillPresentationSchema = Type.Object({
15402
15402
  surfaces: Type.Optional(SkillSurfaceVisibilitySchema),
15403
15403
  skills: Type.Optional(Type.Record(NonWhitespaceStringSchema, PluginSkillPresentationRuleSchema))
15404
15404
  }, { additionalProperties: false });
15405
+ var PluginAgentRoleSchema = Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" });
15405
15406
  var PluginAgentProfileManifestSchema = Type.Object({
15406
15407
  id: Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" }),
15407
15408
  name: NonWhitespaceStringSchema,
@@ -15411,11 +15412,14 @@ var PluginAgentProfileManifestSchema = Type.Object({
15411
15412
  systemPromptPath: Type.Optional(NonWhitespaceStringSchema),
15412
15413
  systemPrompt: Type.Optional(Type.String({ maxLength: 64000 })),
15413
15414
  abilities: Type.Optional(Type.Union([Type.Literal("all"), Type.Literal("own")])),
15414
- legacyIds: Type.Optional(Type.Array(NonWhitespaceStringSchema, { maxItems: 16 }))
15415
+ legacyIds: Type.Optional(Type.Array(NonWhitespaceStringSchema, { maxItems: 16 })),
15416
+ roles: Type.Optional(Type.Array(PluginAgentRoleSchema, { maxItems: 8 }))
15415
15417
  }, { additionalProperties: false });
15416
15418
  var PluginAgentTeamMemberManifestSchema = Type.Object({
15417
- agent: NonWhitespaceStringSchema,
15418
- responsibility: Type.String({ maxLength: 2000 })
15419
+ agent: Type.Optional(NonWhitespaceStringSchema),
15420
+ role: Type.Optional(PluginAgentRoleSchema),
15421
+ responsibility: Type.String({ maxLength: 2000 }),
15422
+ optional: Type.Optional(Type.Boolean())
15419
15423
  }, { additionalProperties: false });
15420
15424
  var PluginAgentTeamManifestSchema = Type.Object({
15421
15425
  id: Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" }),
@@ -18810,6 +18814,34 @@ var runNpmPack = async (packageSpec, destination) => {
18810
18814
  });
18811
18815
  return parsePackOutput(output2);
18812
18816
  };
18817
+ async function readLatestNpmVersion(packageName) {
18818
+ const invocation = npmInvocation();
18819
+ const args = [...invocation.args, "view", packageName, "version", "--json"];
18820
+ try {
18821
+ const output2 = await new Promise((resolvePromise, rejectPromise) => {
18822
+ const child = spawn(invocation.command, args, {
18823
+ shell: false,
18824
+ windowsHide: true,
18825
+ stdio: ["ignore", "pipe", "pipe"]
18826
+ });
18827
+ let stdout = "";
18828
+ child.stdout.setEncoding("utf8").on("data", (chunk) => {
18829
+ stdout += chunk;
18830
+ });
18831
+ child.stderr.resume();
18832
+ child.once("error", rejectPromise);
18833
+ child.once("exit", (code) => code === 0 ? resolvePromise(stdout) : rejectPromise(new Error("npm view failed")));
18834
+ });
18835
+ const parsed = JSON.parse(output2);
18836
+ if (typeof parsed === "string")
18837
+ return parsed;
18838
+ if (Array.isArray(parsed) && typeof parsed.at(-1) === "string")
18839
+ return parsed.at(-1);
18840
+ return;
18841
+ } catch {
18842
+ return;
18843
+ }
18844
+ }
18813
18845
  function registryPackageName(packageSpec) {
18814
18846
  const parsed = import_npm_package_arg.default(packageSpec);
18815
18847
  if (!parsed.registry || !parsed.name || !REGISTRY_SPEC_TYPES.has(parsed.type)) {
@@ -18908,6 +18940,16 @@ npx vetta-plugin-cli docs
18908
18940
  \`npm install\` 必须先跑:\`vetta-plugin-cli\` 是 \`@vetta-org/plugin-cli\` 的命令名,装完才在
18909
18941
  \`node_modules/.bin\` 里。还没装就想跑,用全名 \`npx @vetta-org/plugin-cli docs\`。
18910
18942
 
18943
+ **手册会过期。** 它是随 SDK 装进 \`node_modules\` 的快照,工程不升级就永远停在初始化那天的
18944
+ 版本——本文件同样是那天的快照。开工前先确认一次:
18945
+
18946
+ \`\`\`bash
18947
+ npx vetta-plugin-cli docs --check-latest
18948
+ \`\`\`
18949
+
18950
+ 落后就按它打印的命令升级 SDK 再重读手册。\`docs\` 的输出永远比本文件新(\`npx\` 默认取最新的
18951
+ CLI),**两者冲突时以它为准**。
18952
+
18911
18953
  它打印出随 \`@vetta-org/plugin-sdk\` 一起装进 \`node_modules\` 的手册目录**绝对路径**,以及
18912
18954
  这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件
18913
18955
  时各插件还可能钉不同的 SDK 版本。
@@ -18991,7 +19033,7 @@ Vetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skil
18991
19033
  \`\`\`bash
18992
19034
  cd abilities/plugins/<slug> # ← 开发单位是这个目录
18993
19035
  npm install
18994
- npx vetta-plugin-cli docs # 手册(随该目录装的 SDK 版本)
19036
+ npx vetta-plugin-cli docs --check-latest # 手册(随该目录装的 SDK 版本;顺带查是否落后)
18995
19037
  npm run install:vetta # 装进正在运行的 Vetta
18996
19038
  npx vetta-plugin-cli watch # 热更新
18997
19039
  \`\`\`
@@ -18999,6 +19041,9 @@ npx vetta-plugin-cli watch # 热更新
18999
19041
  每个插件目录自带 \`AGENTS.md\`,里面有该读哪些手册、以及不可违反的几条。**先 \`cd\` 进去再动手**:
19000
19042
  所有开发命令都作用于「最近的那个能力目录」,站在仓库根上它们不知道你指的是哪一个。
19001
19043
 
19044
+ 手册与各目录的 \`AGENTS.md\` 都是那次 \`init\` 当天的快照,各能力还可能钉着不同的 SDK 版本。
19045
+ 动手前先跑 \`docs --check-latest\`,它的输出永远比这两份文件新,冲突时以它为准。
19046
+
19002
19047
  新建一个插件:
19003
19048
 
19004
19049
  \`\`\`bash
@@ -19077,7 +19122,7 @@ maintained by \`npx @vetta-org/plugin-cli sync\`. See \`AGENTS.md\` for the work
19077
19122
  }
19078
19123
 
19079
19124
  // src/init.ts
19080
- var DEFAULT_SDK_RANGE = "^0.3.1";
19125
+ var DEFAULT_SDK_RANGE = "^0.3.2";
19081
19126
  var DEFAULT_VITE_RANGE = "^0.2.0";
19082
19127
  var PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
19083
19128
  function remoteNameFromId(pluginId) {
@@ -19539,7 +19584,7 @@ var HELP_TEXT = `Vetta plugin manager
19539
19584
  Usage:
19540
19585
  vetta-plugin-cli add <npm-package|zip-path|http-url> [--json]
19541
19586
  vetta-plugin-cli reload <plugin-id> [--json]
19542
- vetta-plugin-cli docs [--json]
19587
+ vetta-plugin-cli docs [--check-latest] [--json]
19543
19588
  vetta-plugin-cli init --id <plugin-id> [--name <display>] [dir] [--json]
19544
19589
  vetta-plugin-cli init hub --name <slug> --repository <url> --min-app-version <x.y.z> [dir]
19545
19590
  vetta-plugin-cli watch [dir] [--stop] [--json]
@@ -19606,14 +19651,23 @@ function parsePluginDocsCommand(argv) {
19606
19651
  return { type: "help" };
19607
19652
  let parsed;
19608
19653
  try {
19609
- parsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: "boolean" } } });
19654
+ parsed = parseArgs({
19655
+ args: argv.slice(1),
19656
+ allowPositionals: true,
19657
+ strict: true,
19658
+ options: { json: { type: "boolean" }, "check-latest": { type: "boolean" } }
19659
+ });
19610
19660
  } catch (error) {
19611
19661
  return { type: "error", message: formatParseError(error) };
19612
19662
  }
19613
19663
  const [unexpected] = parsed.positionals;
19614
19664
  if (unexpected)
19615
19665
  return { type: "error", message: `Unexpected argument: ${unexpected}` };
19616
- return { type: "docs", json: parsed.values.json === true };
19666
+ return {
19667
+ type: "docs",
19668
+ json: parsed.values.json === true,
19669
+ checkLatest: parsed.values["check-latest"] === true
19670
+ };
19617
19671
  }
19618
19672
  function parsePluginInitCommand(argv) {
19619
19673
  if (argv[0] !== "init")
@@ -19772,7 +19826,8 @@ var defaultDependencies = {
19772
19826
  cwd: () => process.cwd(),
19773
19827
  runAction: defaultRunAction,
19774
19828
  writeStdout: (value) => process.stdout.write(value),
19775
- writeStderr: (value) => process.stderr.write(value)
19829
+ writeStderr: (value) => process.stderr.write(value),
19830
+ readLatestSdkVersion: () => readLatestNpmVersion("@vetta-org/plugin-sdk")
19776
19831
  };
19777
19832
  function isHttpUrl(source) {
19778
19833
  try {
@@ -19884,7 +19939,7 @@ async function runPluginCommand(command, dependencies = defaultDependencies) {
19884
19939
  return 2;
19885
19940
  }
19886
19941
  if (command.type === "docs") {
19887
- return runDocsCommand(command, dependencies);
19942
+ return await runDocsCommand(command, dependencies);
19888
19943
  }
19889
19944
  if (command.type === "init") {
19890
19945
  return runInitCommand(command, dependencies);
@@ -19953,11 +20008,13 @@ async function runPluginCommand(command, dependencies = defaultDependencies) {
19953
20008
  await resolvedNpm?.cleanup();
19954
20009
  }
19955
20010
  }
19956
- function runDocsCommand(command, dependencies) {
20011
+ async function runDocsCommand(command, dependencies) {
19957
20012
  const cwd = dependencies.cwd?.() ?? process.cwd();
19958
20013
  const manualDir = resolveManualDir(cwd);
19959
20014
  if (!manualDir) {
19960
- const message = `Plugin manual not found. Install the SDK first: npm i -D @vetta-org/plugin-sdk
20015
+ const inHubRoot = findPluginHub(cwd) !== undefined && findPluginProject(cwd) === undefined;
20016
+ const message = inHubRoot ? `Plugin manual not found at the hub root. cd into an ability directory (abilities/plugins/<slug>), then run npm install.
20017
+ ` : `Plugin manual not found. Install the SDK first: npm i -D @vetta-org/plugin-sdk
19961
20018
  `;
19962
20019
  if (command.json) {
19963
20020
  dependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: "MANUAL_NOT_FOUND", message: message.trim() } })}
@@ -19970,12 +20027,16 @@ function runDocsCommand(command, dependencies) {
19970
20027
  const project = findPluginProject(cwd);
19971
20028
  const hub = findPluginHub(cwd);
19972
20029
  const sdkVersion = readManualSdkVersion(manualDir);
20030
+ const latestVersion = command.checkLatest ? await dependencies.readLatestSdkVersion?.() : undefined;
20031
+ const outdated = sdkVersion !== undefined && latestVersion !== undefined && compareSemver(sdkVersion, latestVersion) < 0;
19973
20032
  if (command.json) {
19974
20033
  dependencies.writeStdout(`${JSON.stringify({
19975
20034
  ok: true,
19976
20035
  manualDir,
19977
20036
  entry: join6(manualDir, "README.md"),
19978
20037
  sdkVersion,
20038
+ refreshCommand: SDK_REFRESH_COMMAND,
20039
+ ...command.checkLatest ? { latestVersion, outdated } : {},
19979
20040
  project: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,
19980
20041
  hub: hub ? {
19981
20042
  root: hub.root,
@@ -19993,6 +20054,13 @@ function runDocsCommand(command, dependencies) {
19993
20054
  ];
19994
20055
  if (project)
19995
20056
  lines.push(`Current plugin: ${project.pluginId} (${project.root})`);
20057
+ if (outdated) {
20058
+ lines.push(`Manual is behind: ${sdkVersion} → ${latestVersion}. Refresh it with: ${SDK_REFRESH_COMMAND}`);
20059
+ } else if (command.checkLatest && latestVersion === undefined) {
20060
+ lines.push(`Could not reach the registry; cannot tell whether ${sdkVersion ?? "this manual"} is current.`);
20061
+ } else {
20062
+ lines.push(`Manual follows the installed SDK. To refresh it: ${SDK_REFRESH_COMMAND}`);
20063
+ }
19996
20064
  if (hub) {
19997
20065
  lines.push(`Marketplace index: ${hub.manifestPath}`);
19998
20066
  lines.push("After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.");
@@ -20002,6 +20070,24 @@ function runDocsCommand(command, dependencies) {
20002
20070
  `);
20003
20071
  return 0;
20004
20072
  }
20073
+ var SDK_REFRESH_COMMAND = "npm i -D @vetta-org/plugin-sdk@latest && npx vetta-plugin-cli docs";
20074
+ function compareSemver(left, right) {
20075
+ const parse2 = (value) => {
20076
+ const match2 = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(value.trim());
20077
+ if (!match2)
20078
+ return [0, 0, 0, false];
20079
+ return [Number(match2[1]), Number(match2[2]), Number(match2[3]), match2[4] !== undefined];
20080
+ };
20081
+ const a = parse2(left);
20082
+ const b2 = parse2(right);
20083
+ for (let index = 0;index < 3; index += 1) {
20084
+ if (a[index] !== b2[index])
20085
+ return a[index] < b2[index] ? -1 : 1;
20086
+ }
20087
+ if (a[3] === b2[3])
20088
+ return 0;
20089
+ return a[3] ? -1 : 1;
20090
+ }
20005
20091
  function runInitCommand(command, dependencies) {
20006
20092
  const cwd = dependencies.cwd?.() ?? process.cwd();
20007
20093
  try {
package/dist/command.d.ts CHANGED
@@ -27,6 +27,7 @@ export type PluginDocsCommand = {
27
27
  } | {
28
28
  type: "docs";
29
29
  json: boolean;
30
+ checkLatest: boolean;
30
31
  };
31
32
  export type PluginInitCommand = {
32
33
  type: "help";
@@ -86,6 +87,8 @@ export interface PluginCommandDependencies {
86
87
  runAction(actionId: string, input: unknown): Promise<unknown>;
87
88
  writeStdout(value: string): void;
88
89
  writeStderr(value: string): void;
90
+ /** `docs --check-latest` 查询 registry 上最新的 SDK 版本;查不到(离线、私服)返回 undefined。 */
91
+ readLatestSdkVersion?(): Promise<string | undefined>;
89
92
  }
90
93
  export type PluginAddCommandDependencies = PluginCommandDependencies;
91
94
  export declare function parsePluginAddCommand(argv: string[]): PluginAddCommand | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAIA,OAAO,EAA2B,KAAK,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAK1F,MAAM,MAAM,gBAAgB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnC,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpH,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,sBAAsB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnD,MAAM,MAAM,aAAa,GACtB,gBAAgB,GAChB,iBAAiB,GACjB,sBAAsB,GACtB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,CAAC;AAEtB,MAAM,WAAW,yBAAyB;IACzC,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC1E,yGAAiD;IACjD,GAAG,CAAC,IAAI,MAAM,CAAC;IACf,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,4BAA4B,GAAG,yBAAyB,CAAC;AAiCrE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAalF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,GAAG,SAAS,CAaxF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAYpF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAgCpF;AA8CD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,kBAAkB,GAAG,SAAS,CAsBtF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,sBAAsB,GAAG,SAAS,CAkB9F;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAiBpF;AAwID,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,gBAAgB,EACzB,YAAY,GAAE,4BAAkD,GAC9D,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,aAAa,EACtB,YAAY,GAAE,yBAA+C,GAC3D,OAAO,CAAC,MAAM,CAAC,CAoFjB;AA+RD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE","sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { ActionRpcError, createActionRpcClient, readActionRpcEndpoint } from \"@vetta/action-rpc\";\nimport { resolveNpmPluginArchive, type ResolvedNpmPluginArchive } from \"./npm-package.js\";\nimport { initHubRepository, initPluginProject } from \"./init.js\";\nimport { describeIndexDrift, syncMarketplaceIndex } from \"./sync.js\";\nimport { findPluginHub, findPluginProject, type PluginProject, readManualSdkVersion, resolveManualDir } from \"./workspace.js\";\n\nexport type PluginAddCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"add\"; source: string; json: boolean };\n\nexport type PluginReloadCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"reload\"; pluginId: string; json: boolean };\n\nexport type PluginDocsCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"docs\"; json: boolean };\n\nexport type PluginInitCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"init\"; targetDir?: string; pluginId: string; displayName?: string; json: boolean }\n\t| { type: \"init-hub\"; targetDir?: string; name: string; repository: string; minAppVersion: string; json: boolean };\n\nexport type PluginWatchCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"watch\"; dir?: string; stop: boolean; json: boolean };\n\nexport type PluginUninstallCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"uninstall\"; pluginId?: string; json: boolean };\n\nexport type PluginSyncCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"sync\"; check: boolean; json: boolean };\n\nexport type PluginCommand =\n\t| PluginAddCommand\n\t| PluginSyncCommand\n\t| PluginUninstallCommand\n\t| PluginReloadCommand\n\t| PluginDocsCommand\n\t| PluginInitCommand\n\t| PluginWatchCommand;\n\nexport interface PluginCommandDependencies {\n\tresolveNpmArchive(packageSpec: string): Promise<ResolvedNpmPluginArchive>;\n\t/** 命令执行时所在目录;缺省用 process.cwd(),测试与非交互调用方可以覆盖。 */\n\tcwd?(): string;\n\trunAction(actionId: string, input: unknown): Promise<unknown>;\n\twriteStdout(value: string): void;\n\twriteStderr(value: string): void;\n}\n\nexport type PluginAddCommandDependencies = PluginCommandDependencies;\n\nconst HELP_TEXT = `Vetta plugin manager\n\nUsage:\n vetta-plugin-cli add <npm-package|zip-path|http-url> [--json]\n vetta-plugin-cli reload <plugin-id> [--json]\n vetta-plugin-cli docs [--json]\n vetta-plugin-cli init --id <plugin-id> [--name <display>] [dir] [--json]\n vetta-plugin-cli init hub --name <slug> --repository <url> --min-app-version <x.y.z> [dir]\n vetta-plugin-cli watch [dir] [--stop] [--json]\n vetta-plugin-cli uninstall [plugin-id] [--json]\n vetta-plugin-cli sync [--check] [--json]\n\nExamples:\n npx @vetta-org/plugin-cli add @example/vetta-plugin-demo\n npx @vetta-org/plugin-cli add @example/vetta-plugin-demo@1.2.0\n npx @vetta-org/plugin-cli add . # 当前插件工程(先 pack)\n npx @vetta-org/plugin-cli add ./release/demo-1.2.0.zip\n npx @vetta-org/plugin-cli reload demo\n npx @vetta-org/plugin-cli docs\n npx @vetta-org/plugin-cli init --id my-plugin --name \"My Plugin\"\n npx @vetta-org/plugin-cli init hub --name my-market --repository https://github.com/me/my-market --min-app-version 0.55.0\n npx @vetta-org/plugin-cli watch # 让宿主改从工程目录加载,改完即生效\n npx @vetta-org/plugin-cli uninstall # 卸载当前插件工程对应的插件\n npx @vetta-org/plugin-cli sync # 在市场仓库根对账 .vetta/marketplace.json\n npx @vetta-org/plugin-cli sync --check # 只报不写,给 CI 用\n`;\n\nfunction formatParseError(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function parsePluginAddCommand(argv: string[]): PluginAddCommand | undefined {\n\tif (argv[0] !== \"add\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: \"boolean\" } } });\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [source, unexpected] = parsed.positionals;\n\tif (!source) return { type: \"error\", message: \"Missing <npm-package|zip-path|http-url>\" };\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"add\", source, json: parsed.values.json === true };\n}\n\nexport function parsePluginReloadCommand(argv: string[]): PluginReloadCommand | undefined {\n\tif (argv[0] !== \"reload\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: \"boolean\" } } });\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [pluginId, unexpected] = parsed.positionals;\n\tif (!pluginId) return { type: \"error\", message: \"Missing <plugin-id>\" };\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"reload\", pluginId, json: parsed.values.json === true };\n}\n\nexport function parsePluginDocsCommand(argv: string[]): PluginDocsCommand | undefined {\n\tif (argv[0] !== \"docs\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: \"boolean\" } } });\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"docs\", json: parsed.values.json === true };\n}\n\nexport function parsePluginInitCommand(argv: string[]): PluginInitCommand | undefined {\n\tif (argv[0] !== \"init\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tif (argv[1] === \"hub\") return parseInitHubCommand(argv.slice(2));\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: {\n\t\t\t\tid: { type: \"string\" },\n\t\t\t\tname: { type: \"string\" },\n\t\t\t\tjson: { type: \"boolean\" },\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst pluginId = parsed.values.id;\n\tif (typeof pluginId !== \"string\" || pluginId.length === 0) {\n\t\treturn { type: \"error\", message: \"Missing --id <plugin-id>\" };\n\t}\n\tconst [targetDir, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"init\",\n\t\t...(targetDir ? { targetDir } : {}),\n\t\tpluginId,\n\t\t...(typeof parsed.values.name === \"string\" ? { displayName: parsed.values.name } : {}),\n\t\tjson: parsed.values.json === true,\n\t};\n}\n\nfunction parseInitHubCommand(argv: string[]): PluginInitCommand {\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv,\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: {\n\t\t\t\tname: { type: \"string\" },\n\t\t\t\trepository: { type: \"string\" },\n\t\t\t\t\"min-app-version\": { type: \"string\" },\n\t\t\t\tjson: { type: \"boolean\" },\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst name = parsed.values.name;\n\tif (typeof name !== \"string\" || name.length === 0) return { type: \"error\", message: \"Missing --name <slug>\" };\n\tconst repository = parsed.values.repository;\n\tif (typeof repository !== \"string\" || repository.length === 0) {\n\t\treturn { type: \"error\", message: \"Missing --repository <https url>\" };\n\t}\n\t// 刻意不给默认值:太低会让装不动新 schema 的旧客户端也去激活快照,太高则部分用户直接\n\t// 看不到这个市场。这是发布决定,不该由工具替作者猜。\n\tconst minAppVersion = parsed.values[\"min-app-version\"];\n\tif (typeof minAppVersion !== \"string\" || minAppVersion.length === 0) {\n\t\treturn {\n\t\t\ttype: \"error\",\n\t\t\tmessage: \"Missing --min-app-version <x.y.z> (the oldest Vetta Desktop version your abilities support)\",\n\t\t};\n\t}\n\tconst [targetDir, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"init-hub\",\n\t\t...(targetDir ? { targetDir } : {}),\n\t\tname,\n\t\trepository,\n\t\tminAppVersion,\n\t\tjson: parsed.values.json === true,\n\t};\n}\n\nexport function parsePluginWatchCommand(argv: string[]): PluginWatchCommand | undefined {\n\tif (argv[0] !== \"watch\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" }, stop: { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [dir, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"watch\",\n\t\t...(dir ? { dir } : {}),\n\t\tstop: parsed.values.stop === true,\n\t\tjson: parsed.values.json === true,\n\t};\n}\n\nexport function parsePluginUninstallCommand(argv: string[]): PluginUninstallCommand | undefined {\n\tif (argv[0] !== \"uninstall\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [pluginId, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\t// 省略 id 时按 cwd 推断,语义与 add . / watch 一致:站在哪个插件里就作用于哪个。\n\treturn { type: \"uninstall\", ...(pluginId ? { pluginId } : {}), json: parsed.values.json === true };\n}\n\nexport function parsePluginSyncCommand(argv: string[]): PluginSyncCommand | undefined {\n\tif (argv[0] !== \"sync\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" }, check: { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"sync\", check: parsed.values.check === true, json: parsed.values.json === true };\n}\n\nasync function defaultRunAction(actionId: string, input: unknown): Promise<unknown> {\n\tconst client = createActionRpcClient(await readActionRpcEndpoint());\n\treturn client.run(actionId, input);\n}\n\nconst defaultDependencies: PluginCommandDependencies = {\n\tresolveNpmArchive: resolveNpmPluginArchive,\n\tcwd: () => process.cwd(),\n\trunAction: defaultRunAction,\n\twriteStdout: (value) => process.stdout.write(value),\n\twriteStderr: (value) => process.stderr.write(value),\n};\n\nfunction isHttpUrl(source: string): boolean {\n\ttry {\n\t\tconst url = new URL(source);\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isLocalZip(source: string): boolean {\n\tif (source.toLowerCase().endsWith(\".zip\")) return true;\n\tconst path = resolve(source);\n\t// 目录不是压缩包:它是一个插件工程,走 resolveProjectArchive 先找它打出来的产物。\n\treturn existsSync(path) && !statSync(path).isDirectory();\n}\n\nfunction isDirectorySource(source: string): boolean {\n\tconst path = resolve(source);\n\treturn existsSync(path) && statSync(path).isDirectory();\n}\n\n/**\n * 把「装当前这个工程」翻译成一个具体的归档路径。\n *\n * 这条路径是给 `install:vetta` 这类脚本用的:作者(或 Agent)在插件目录里跑一条命令就\n * 装进 Vetta,不必记住产物叫什么名字。找不到产物时给出该跑的那条命令,而不是报一个\n * 「文件不存在」让人自己猜。\n */\nfunction resolveProjectArchive(source: string): { archivePath: string; project: PluginProject } {\n\tconst from = resolve(source);\n\tconst project = findPluginProject(from);\n\tif (!project) {\n\t\tconst hub = findPluginHub(from);\n\t\tif (hub) {\n\t\t\tthrow new Error(\n\t\t\t\t`${from} indexes plugins but is not one itself. Run this from a plugin directory, or pass its path: vetta-plugin-cli add ./path/to/plugin`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(`No plugin.json found in ${from} or any parent directory.`);\n\t}\n\tconst archivePath = join(project.root, \"release\", `${project.pluginId}-${project.version}.zip`);\n\tif (!existsSync(archivePath)) {\n\t\tthrow new Error(\n\t\t\t`Packaged archive not found: ${archivePath}\\nBuild it first: npm run build && npx vetta-plugin pack`,\n\t\t);\n\t}\n\treturn { archivePath, project };\n}\n\n/** 装完立刻检查索引是否还停在旧版本;不在市场仓库里时什么也不说。 */\nfunction indexDriftHint(project: PluginProject): string | undefined {\n\tconst hub = findPluginHub(project.root);\n\tif (!hub) return undefined;\n\treturn describeIndexDrift({\n\t\thubRoot: hub.root,\n\t\tmanifestPath: hub.manifestPath,\n\t\tslug: project.pluginId,\n\t\tversion: project.version,\n\t});\n}\n\nfunction npmInstallInput(resolved: ResolvedNpmPluginArchive): Record<string, unknown> {\n\treturn {\n\t\toperation: \"install-from-path\",\n\t\tpath: resolved.archivePath,\n\t\tenable: true,\n\t\tsource: \"npm\",\n\t\texpectedSha256: resolved.expectedSha256,\n\t\texpectedId: resolved.packageManifest.vetta.pluginId,\n\t\texpectedVersion: resolved.packageManifest.version,\n\t\tnpm: {\n\t\t\tpackageName: resolved.packageManifest.name,\n\t\t\trequestedSpec: resolved.requestedSpec,\n\t\t\tresolvedVersion: resolved.packageManifest.version,\n\t\t\t...(resolved.integrity ? { integrity: resolved.integrity } : {}),\n\t\t},\n\t};\n}\n\nfunction resultSummary(result: unknown): string {\n\tif (typeof result !== \"object\" || result === null || Array.isArray(result)) return \"Plugin installed.\\n\";\n\tconst response = result as Record<string, unknown>;\n\tconst plugin =\n\t\ttypeof response.plugin === \"object\" && response.plugin !== null && !Array.isArray(response.plugin)\n\t\t\t? (response.plugin as Record<string, unknown>)\n\t\t\t: undefined;\n\tif (!plugin) return \"Plugin installed.\\n\";\n\tconst id = typeof plugin.id === \"string\" ? plugin.id : \"plugin\";\n\tconst version = typeof plugin.version === \"string\" ? `@${plugin.version}` : \"\";\n\tconst pending = typeof plugin.pendingVersion === \"string\"\n\t\t? ` Update ${plugin.pendingVersion} is pending reload. Run \\`vetta-plugin-cli reload ${id}\\` to apply it.`\n\t\t: \"\";\n\treturn `Installed ${id}${version}.${pending}\\n`;\n}\n\nfunction reloadResultSummary(result: unknown, requestedPluginId: string): string {\n\tif (typeof result !== \"object\" || result === null || Array.isArray(result)) {\n\t\treturn `Reloaded ${requestedPluginId}.\\n`;\n\t}\n\tconst response = result as Record<string, unknown>;\n\tconst plugin =\n\t\ttypeof response.plugin === \"object\" && response.plugin !== null && !Array.isArray(response.plugin)\n\t\t\t? (response.plugin as Record<string, unknown>)\n\t\t\t: undefined;\n\tconst id = typeof plugin?.id === \"string\" ? plugin.id : requestedPluginId;\n\tconst version = typeof plugin?.activeVersion === \"string\" ? `@${plugin.activeVersion}` : \"\";\n\treturn `Reloaded ${id}${version}.\\n`;\n}\n\nfunction isConnectionError(error: unknown): boolean {\n\tif (!(error instanceof Error)) return false;\n\tconst code = (error as NodeJS.ErrnoException).code;\n\treturn (\n\t\tcode === \"ENOENT\" ||\n\t\tcode === \"ECONNREFUSED\" ||\n\t\tcode === \"ECONNRESET\" ||\n\t\terror.message.includes(\"ECONNREFUSED\") ||\n\t\terror.message.includes(\"fetch failed\")\n\t);\n}\n\nexport async function runPluginAddCommand(\n\tcommand: PluginAddCommand,\n\tdependencies: PluginAddCommandDependencies = defaultDependencies,\n): Promise<number> {\n\treturn runPluginCommand(command, dependencies);\n}\n\nexport async function runPluginCommand(\n\tcommand: PluginCommand,\n\tdependencies: PluginCommandDependencies = defaultDependencies,\n): Promise<number> {\n\tif (command.type === \"help\") {\n\t\tdependencies.writeStdout(HELP_TEXT);\n\t\treturn 0;\n\t}\n\tif (command.type === \"error\") {\n\t\tdependencies.writeStderr(`${command.message}\\n`);\n\t\treturn 2;\n\t}\n\n\tif (command.type === \"docs\") {\n\t\treturn runDocsCommand(command, dependencies);\n\t}\n\tif (command.type === \"init\") {\n\t\treturn runInitCommand(command, dependencies);\n\t}\n\tif (command.type === \"init-hub\") {\n\t\treturn runInitHubCommand(command, dependencies);\n\t}\n\tif (command.type === \"sync\") {\n\t\treturn runSyncCommand(command, dependencies);\n\t}\n\n\tif (command.type === \"watch\") {\n\t\treturn runWatchCommand(command, dependencies);\n\t}\n\tif (command.type === \"uninstall\") {\n\t\treturn runUninstallCommand(command, dependencies);\n\t}\n\n\tlet resolvedNpm: ResolvedNpmPluginArchive | undefined;\n\tlet driftHint: string | undefined;\n\ttry {\n\t\tlet result: unknown;\n\t\tif (command.type === \"reload\") {\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"reload\",\n\t\t\t\tid: command.pluginId,\n\t\t\t});\n\t\t} else if (isHttpUrl(command.source)) {\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"install-from-url\",\n\t\t\t\turl: command.source,\n\t\t\t});\n\t\t} else if (isDirectorySource(command.source)) {\n\t\t\tconst { archivePath, project } = resolveProjectArchive(command.source);\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"install-from-path\",\n\t\t\t\tpath: archivePath,\n\t\t\t\tenable: true,\n\t\t\t});\n\t\t\tdriftHint = indexDriftHint(project);\n\t\t} else if (isLocalZip(command.source)) {\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"install-from-path\",\n\t\t\t\tpath: resolve(command.source),\n\t\t\t\tenable: true,\n\t\t\t});\n\t\t} else {\n\t\t\tresolvedNpm = await dependencies.resolveNpmArchive(command.source);\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", npmInstallInput(resolvedNpm));\n\t\t}\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, result, ...(driftHint ? { warning: driftHint } : {}) })}\\n`\n\t\t\t\t: command.type === \"reload\"\n\t\t\t\t\t? reloadResultSummary(result, command.pluginId)\n\t\t\t\t\t: `${resultSummary(result)}${driftHint ? `${driftHint}\\n` : \"\"}`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: error instanceof ActionRpcError ? error.code : command.type === \"reload\" ? \"PLUGIN_RELOAD_FAILED\" : \"PLUGIN_ADD_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\tif (error instanceof ActionRpcError) return 4;\n\t\treturn isConnectionError(error) ? 3 : 5;\n\t} finally {\n\t\tawait resolvedNpm?.cleanup();\n\t}\n}\n\n/**\n * 打印随 SDK 发布的手册目录。\n *\n * 存在的理由是「不要让任何人硬编码 node_modules 路径」:工作区会把依赖提升到仓库根,\n * 一仓多插件的 hub 里每个插件也可能各装一份。Agent 只需记住这一条命令,拿回来的永远是\n * 当前工程实际编译所针对的那个 SDK 版本的手册。\n */\nfunction runDocsCommand(command: { json: boolean }, dependencies: PluginCommandDependencies): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\tconst manualDir = resolveManualDir(cwd);\n\tif (!manualDir) {\n\t\tconst message =\n\t\t\t\"Plugin manual not found. Install the SDK first: npm i -D @vetta-org/plugin-sdk\\n\";\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: \"MANUAL_NOT_FOUND\", message: message.trim() } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(message);\n\t\t}\n\t\treturn 6;\n\t}\n\tconst project = findPluginProject(cwd);\n\tconst hub = findPluginHub(cwd);\n\tconst sdkVersion = readManualSdkVersion(manualDir);\n\tif (command.json) {\n\t\tdependencies.writeStdout(\n\t\t\t`${JSON.stringify({\n\t\t\t\tok: true,\n\t\t\t\tmanualDir,\n\t\t\t\tentry: join(manualDir, \"README.md\"),\n\t\t\t\tsdkVersion,\n\t\t\t\tproject: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,\n\t\t\t\thub: hub\n\t\t\t\t\t? {\n\t\t\t\t\t\t\troot: hub.root,\n\t\t\t\t\t\t\tmanifestPath: hub.manifestPath,\n\t\t\t\t\t\t\tsyncHint: \"After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.\",\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t})}\\n`,\n\t\t);\n\t\treturn 0;\n\t}\n\tconst lines = [\n\t\t`Plugin manual (@vetta-org/plugin-sdk${sdkVersion ? `@${sdkVersion}` : \"\"}):`,\n\t\t` ${manualDir}`,\n\t\t`Start here: ${join(manualDir, \"README.md\")}`,\n\t];\n\tif (project) lines.push(`Current plugin: ${project.pluginId} (${project.root})`);\n\tif (hub) {\n\t\tlines.push(`Marketplace index: ${hub.manifestPath}`);\n\t\t// Agent 几乎一定会先跑 docs,所以这是告诉它「索引要对账」的最佳时机。\n\t\tlines.push(\"After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.\");\n\t}\n\tdependencies.writeStdout(`${lines.join(\"\\n\")}\\n`);\n\treturn 0;\n}\n\n/** 在陌生目录里生成一个可直接开工的插件工程,并留下让任意 Agent 自举的 AGENTS.md。 */\nfunction runInitCommand(\n\tcommand: Extract<PluginCommand, { type: \"init\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tconst result = initPluginProject({\n\t\t\ttargetDir: resolve(cwd, command.targetDir ?? command.pluginId),\n\t\t\tpluginId: command.pluginId,\n\t\t\tdisplayName: command.displayName ?? command.pluginId,\n\t\t});\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, ...result })}\\n`\n\t\t\t\t: [\n\t\t\t\t\t\t`Created ${result.pluginId} at ${result.root}`,\n\t\t\t\t\t\t\"Next: npm install && npm run install:vetta\",\n\t\t\t\t\t\t\"The agent brief is in AGENTS.md; after npm install, run `npx vetta-plugin-cli docs` for the manual.\",\n\t\t\t\t\t]\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t\t.concat(\"\\n\"),\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"PLUGIN_INIT_FAILED\", message } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 5;\n\t}\n}\n\n/**\n * 让宿主改从工程目录加载本插件,之后改源码即时生效,不必每次 build → pack → install。\n *\n * 目标插件按 cwd 向上找,理由同 `add .`:一仓多插件时「我正站在哪个插件里」是唯一不会\n * 弄错的意图,而 id 靠人重复输入迟早会错配到另一个插件上。\n */\nasync function runWatchCommand(\n\tcommand: Extract<PluginCommand, { type: \"watch\" }>,\n\tdependencies: PluginCommandDependencies,\n): Promise<number> {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\tconst from = resolve(cwd, command.dir ?? \".\");\n\ttry {\n\t\tconst project = findPluginProject(from);\n\t\tif (!project) {\n\t\t\tconst hub = findPluginHub(from);\n\t\t\tthrow new Error(\n\t\t\t\thub\n\t\t\t\t\t? `${from} indexes plugins but is not one itself. Run this from a plugin directory, or pass its path.`\n\t\t\t\t\t: `No plugin.json found in ${from} or any parent directory.`,\n\t\t\t);\n\t\t}\n\t\tconst result = command.stop\n\t\t\t? await dependencies.runAction(\"plugins.manage\", { operation: \"dev-watch-stop\", id: project.pluginId })\n\t\t\t: await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\t\toperation: \"dev-watch\",\n\t\t\t\t\tid: project.pluginId,\n\t\t\t\t\tprojectDir: project.root,\n\t\t\t\t});\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, result })}\\n`\n\t\t\t\t: command.stop\n\t\t\t\t\t? `Stopped hot reload for ${project.pluginId}.\\n`\n\t\t\t\t\t: `Hot reload on for ${project.pluginId}. Vetta now loads it from ${project.root}.\\n`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: error instanceof ActionRpcError ? error.code : \"PLUGIN_WATCH_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\tif (error instanceof ActionRpcError) return 4;\n\t\treturn isConnectionError(error) ? 3 : 5;\n\t}\n}\n\n/**\n * 卸载一个插件。省略 id 时按 cwd 推断,语义与 `add .` / `watch` 一致。\n *\n * 刻意不在这里做二次确认:宿主自己会为写操作弹审批,CLI 再问一遍只是噪音。系统插件由\n * 宿主拒绝,这里不重复判断——那份名单不该有第二个真相源。\n */\nasync function runUninstallCommand(\n\tcommand: Extract<PluginCommand, { type: \"uninstall\" }>,\n\tdependencies: PluginCommandDependencies,\n): Promise<number> {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tlet pluginId = command.pluginId;\n\t\tif (!pluginId) {\n\t\t\tconst project = findPluginProject(cwd);\n\t\t\tif (!project) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No plugin.json found in ${cwd} or any parent directory. Pass the id: vetta-plugin-cli uninstall <plugin-id>`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tpluginId = project.pluginId;\n\t\t}\n\t\tconst result = await dependencies.runAction(\"plugins.manage\", { operation: \"uninstall\", id: pluginId });\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json ? `${JSON.stringify({ ok: true, result })}\\n` : `Uninstalled ${pluginId}.\\n`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: error instanceof ActionRpcError ? error.code : \"PLUGIN_UNINSTALL_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\tif (error instanceof ActionRpcError) return 4;\n\t\treturn isConnectionError(error) ? 3 : 5;\n\t}\n}\n\n/**\n * 对账能力市场索引。定位靠向上找 `.vetta/marketplace.json`,因此在仓库任何位置都能跑。\n *\n * `--check` 只报不写并以非零退出,给 CI 用:索引漂移的三种后果里,两种不在作者机器上复现,\n * 一种压根不报错,光靠人自觉看不住。\n */\nfunction runSyncCommand(\n\tcommand: Extract<PluginCommand, { type: \"sync\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\tconst hub = findPluginHub(cwd);\n\tif (!hub) {\n\t\tconst message = `No .vetta/marketplace.json found in ${cwd} or any parent directory. sync is for marketplace repositories.\\n`;\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"HUB_NOT_FOUND\", message: message.trim() } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(message);\n\t\t}\n\t\treturn 6;\n\t}\n\ttry {\n\t\tconst result = syncMarketplaceIndex({ hubRoot: hub.root, manifestPath: hub.manifestPath, apply: !command.check });\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: result.problems.length === 0, ...result })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStdout(formatSyncReport(result, command.check));\n\t\t}\n\t\tif (result.problems.length > 0) return 7;\n\t\t// --check 的职责就是「有漂移就红」,否则 CI 拦不住任何东西。\n\t\treturn command.check && result.changes.length > 0 ? 7 : 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"SYNC_FAILED\", message } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 5;\n\t}\n}\n\nfunction formatSyncReport(result: ReturnType<typeof syncMarketplaceIndex>, check: boolean): string {\n\tconst lines: string[] = [];\n\tfor (const change of result.changes) {\n\t\tlines.push(` ${change.slug}: ${change.field} ${JSON.stringify(change.from)} -> ${JSON.stringify(change.to)}`);\n\t}\n\tif (lines.length > 0) {\n\t\tlines.unshift(check ? \"Index is out of date:\" : \"Updated the index:\");\n\t}\n\tif (result.problems.length > 0) {\n\t\tlines.push(\"Problems:\");\n\t\tfor (const problem of result.problems) lines.push(` ${problem.slug}: ${problem.message}`);\n\t}\n\tif (result.unlisted.length > 0) {\n\t\tlines.push(\"Ability directories not listed in the index (add them by hand when ready to publish):\");\n\t\tfor (const dir of result.unlisted) lines.push(` ${dir}`);\n\t}\n\tif (lines.length === 0) return \"Index is in sync.\\n\";\n\tif (check && result.changes.length > 0) lines.push(\"Run `vetta-plugin-cli sync` to apply.\");\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** 生成一个合规的能力市场仓库骨架,连同仓库级 AGENTS.md 与对账用的 CI。 */\nfunction runInitHubCommand(\n\tcommand: Extract<PluginCommand, { type: \"init-hub\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tconst result = initHubRepository({\n\t\t\ttargetDir: resolve(cwd, command.targetDir ?? command.name),\n\t\t\tname: command.name,\n\t\t\trepository: command.repository,\n\t\t\tminAppVersion: command.minAppVersion,\n\t\t});\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, ...result })}\\n`\n\t\t\t\t: [\n\t\t\t\t\t\t`Created marketplace ${result.name} at ${result.root}`,\n\t\t\t\t\t\t\"Add an ability: npx @vetta-org/plugin-cli init --id <slug> --name \\\"<Display>\\\" abilities/plugins/<slug>\",\n\t\t\t\t\t\t\"Then list it in .vetta/marketplace.json and run: npx @vetta-org/plugin-cli sync\",\n\t\t\t\t\t\t\"The working agreement for agents is in AGENTS.md.\",\n\t\t\t\t\t].join(\"\\n\") + \"\\n\",\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"HUB_INIT_FAILED\", message } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 5;\n\t}\n}\n\nexport async function runPluginCli(argv: string[]): Promise<number> {\n\tif (argv.length === 0 || argv[0] === \"-h\" || argv[0] === \"--help\") {\n\t\treturn runPluginAddCommand({ type: \"help\" });\n\t}\n\tconst command = parsePluginAddCommand(argv) ?? parsePluginReloadCommand(argv) ?? parsePluginDocsCommand(argv) ?? parsePluginInitCommand(argv) ?? parsePluginWatchCommand(argv) ?? parsePluginUninstallCommand(argv) ?? parsePluginSyncCommand(argv);\n\tif (!command) {\n\t\tprocess.stderr.write(`Unknown command: ${argv[0]}\\n`);\n\t\treturn 2;\n\t}\n\treturn runPluginCommand(command);\n}\n"]}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiD,KAAK,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAKhH,MAAM,MAAM,gBAAgB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpH,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,sBAAsB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnD,MAAM,MAAM,aAAa,GACtB,gBAAgB,GAChB,iBAAiB,GACjB,sBAAsB,GACtB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,CAAC;AAEtB,MAAM,WAAW,yBAAyB;IACzC,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC1E,yGAAiD;IACjD,GAAG,CAAC,IAAI,MAAM,CAAC;IACf,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,wHAA4E;IAC5E,oBAAoB,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrD;AAED,MAAM,MAAM,4BAA4B,GAAG,yBAAyB,CAAC;AAiCrE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAalF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,GAAG,SAAS,CAaxF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAqBpF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAgCpF;AA8CD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,kBAAkB,GAAG,SAAS,CAsBtF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,sBAAsB,GAAG,SAAS,CAkB9F;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAiBpF;AAyID,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,gBAAgB,EACzB,YAAY,GAAE,4BAAkD,GAC9D,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,aAAa,EACtB,YAAY,GAAE,yBAA+C,GAC3D,OAAO,CAAC,MAAM,CAAC,CAoFjB;AA6UD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE","sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { ActionRpcError, createActionRpcClient, readActionRpcEndpoint } from \"@vetta/action-rpc\";\nimport { readLatestNpmVersion, resolveNpmPluginArchive, type ResolvedNpmPluginArchive } from \"./npm-package.js\";\nimport { initHubRepository, initPluginProject } from \"./init.js\";\nimport { describeIndexDrift, syncMarketplaceIndex } from \"./sync.js\";\nimport { findPluginHub, findPluginProject, type PluginProject, readManualSdkVersion, resolveManualDir } from \"./workspace.js\";\n\nexport type PluginAddCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"add\"; source: string; json: boolean };\n\nexport type PluginReloadCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"reload\"; pluginId: string; json: boolean };\n\nexport type PluginDocsCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"docs\"; json: boolean; checkLatest: boolean };\n\nexport type PluginInitCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"init\"; targetDir?: string; pluginId: string; displayName?: string; json: boolean }\n\t| { type: \"init-hub\"; targetDir?: string; name: string; repository: string; minAppVersion: string; json: boolean };\n\nexport type PluginWatchCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"watch\"; dir?: string; stop: boolean; json: boolean };\n\nexport type PluginUninstallCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"uninstall\"; pluginId?: string; json: boolean };\n\nexport type PluginSyncCommand =\n\t| { type: \"help\" }\n\t| { type: \"error\"; message: string }\n\t| { type: \"sync\"; check: boolean; json: boolean };\n\nexport type PluginCommand =\n\t| PluginAddCommand\n\t| PluginSyncCommand\n\t| PluginUninstallCommand\n\t| PluginReloadCommand\n\t| PluginDocsCommand\n\t| PluginInitCommand\n\t| PluginWatchCommand;\n\nexport interface PluginCommandDependencies {\n\tresolveNpmArchive(packageSpec: string): Promise<ResolvedNpmPluginArchive>;\n\t/** 命令执行时所在目录;缺省用 process.cwd(),测试与非交互调用方可以覆盖。 */\n\tcwd?(): string;\n\trunAction(actionId: string, input: unknown): Promise<unknown>;\n\twriteStdout(value: string): void;\n\twriteStderr(value: string): void;\n\t/** `docs --check-latest` 查询 registry 上最新的 SDK 版本;查不到(离线、私服)返回 undefined。 */\n\treadLatestSdkVersion?(): Promise<string | undefined>;\n}\n\nexport type PluginAddCommandDependencies = PluginCommandDependencies;\n\nconst HELP_TEXT = `Vetta plugin manager\n\nUsage:\n vetta-plugin-cli add <npm-package|zip-path|http-url> [--json]\n vetta-plugin-cli reload <plugin-id> [--json]\n vetta-plugin-cli docs [--check-latest] [--json]\n vetta-plugin-cli init --id <plugin-id> [--name <display>] [dir] [--json]\n vetta-plugin-cli init hub --name <slug> --repository <url> --min-app-version <x.y.z> [dir]\n vetta-plugin-cli watch [dir] [--stop] [--json]\n vetta-plugin-cli uninstall [plugin-id] [--json]\n vetta-plugin-cli sync [--check] [--json]\n\nExamples:\n npx @vetta-org/plugin-cli add @example/vetta-plugin-demo\n npx @vetta-org/plugin-cli add @example/vetta-plugin-demo@1.2.0\n npx @vetta-org/plugin-cli add . # 当前插件工程(先 pack)\n npx @vetta-org/plugin-cli add ./release/demo-1.2.0.zip\n npx @vetta-org/plugin-cli reload demo\n npx @vetta-org/plugin-cli docs\n npx @vetta-org/plugin-cli init --id my-plugin --name \"My Plugin\"\n npx @vetta-org/plugin-cli init hub --name my-market --repository https://github.com/me/my-market --min-app-version 0.55.0\n npx @vetta-org/plugin-cli watch # 让宿主改从工程目录加载,改完即生效\n npx @vetta-org/plugin-cli uninstall # 卸载当前插件工程对应的插件\n npx @vetta-org/plugin-cli sync # 在市场仓库根对账 .vetta/marketplace.json\n npx @vetta-org/plugin-cli sync --check # 只报不写,给 CI 用\n`;\n\nfunction formatParseError(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function parsePluginAddCommand(argv: string[]): PluginAddCommand | undefined {\n\tif (argv[0] !== \"add\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: \"boolean\" } } });\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [source, unexpected] = parsed.positionals;\n\tif (!source) return { type: \"error\", message: \"Missing <npm-package|zip-path|http-url>\" };\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"add\", source, json: parsed.values.json === true };\n}\n\nexport function parsePluginReloadCommand(argv: string[]): PluginReloadCommand | undefined {\n\tif (argv[0] !== \"reload\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: \"boolean\" } } });\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [pluginId, unexpected] = parsed.positionals;\n\tif (!pluginId) return { type: \"error\", message: \"Missing <plugin-id>\" };\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"reload\", pluginId, json: parsed.values.json === true };\n}\n\nexport function parsePluginDocsCommand(argv: string[]): PluginDocsCommand | undefined {\n\tif (argv[0] !== \"docs\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" }, \"check-latest\": { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"docs\",\n\t\tjson: parsed.values.json === true,\n\t\tcheckLatest: parsed.values[\"check-latest\"] === true,\n\t};\n}\n\nexport function parsePluginInitCommand(argv: string[]): PluginInitCommand | undefined {\n\tif (argv[0] !== \"init\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tif (argv[1] === \"hub\") return parseInitHubCommand(argv.slice(2));\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: {\n\t\t\t\tid: { type: \"string\" },\n\t\t\t\tname: { type: \"string\" },\n\t\t\t\tjson: { type: \"boolean\" },\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst pluginId = parsed.values.id;\n\tif (typeof pluginId !== \"string\" || pluginId.length === 0) {\n\t\treturn { type: \"error\", message: \"Missing --id <plugin-id>\" };\n\t}\n\tconst [targetDir, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"init\",\n\t\t...(targetDir ? { targetDir } : {}),\n\t\tpluginId,\n\t\t...(typeof parsed.values.name === \"string\" ? { displayName: parsed.values.name } : {}),\n\t\tjson: parsed.values.json === true,\n\t};\n}\n\nfunction parseInitHubCommand(argv: string[]): PluginInitCommand {\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv,\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: {\n\t\t\t\tname: { type: \"string\" },\n\t\t\t\trepository: { type: \"string\" },\n\t\t\t\t\"min-app-version\": { type: \"string\" },\n\t\t\t\tjson: { type: \"boolean\" },\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst name = parsed.values.name;\n\tif (typeof name !== \"string\" || name.length === 0) return { type: \"error\", message: \"Missing --name <slug>\" };\n\tconst repository = parsed.values.repository;\n\tif (typeof repository !== \"string\" || repository.length === 0) {\n\t\treturn { type: \"error\", message: \"Missing --repository <https url>\" };\n\t}\n\t// 刻意不给默认值:太低会让装不动新 schema 的旧客户端也去激活快照,太高则部分用户直接\n\t// 看不到这个市场。这是发布决定,不该由工具替作者猜。\n\tconst minAppVersion = parsed.values[\"min-app-version\"];\n\tif (typeof minAppVersion !== \"string\" || minAppVersion.length === 0) {\n\t\treturn {\n\t\t\ttype: \"error\",\n\t\t\tmessage: \"Missing --min-app-version <x.y.z> (the oldest Vetta Desktop version your abilities support)\",\n\t\t};\n\t}\n\tconst [targetDir, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"init-hub\",\n\t\t...(targetDir ? { targetDir } : {}),\n\t\tname,\n\t\trepository,\n\t\tminAppVersion,\n\t\tjson: parsed.values.json === true,\n\t};\n}\n\nexport function parsePluginWatchCommand(argv: string[]): PluginWatchCommand | undefined {\n\tif (argv[0] !== \"watch\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" }, stop: { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [dir, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn {\n\t\ttype: \"watch\",\n\t\t...(dir ? { dir } : {}),\n\t\tstop: parsed.values.stop === true,\n\t\tjson: parsed.values.json === true,\n\t};\n}\n\nexport function parsePluginUninstallCommand(argv: string[]): PluginUninstallCommand | undefined {\n\tif (argv[0] !== \"uninstall\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [pluginId, unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\t// 省略 id 时按 cwd 推断,语义与 add . / watch 一致:站在哪个插件里就作用于哪个。\n\treturn { type: \"uninstall\", ...(pluginId ? { pluginId } : {}), json: parsed.values.json === true };\n}\n\nexport function parsePluginSyncCommand(argv: string[]): PluginSyncCommand | undefined {\n\tif (argv[0] !== \"sync\") return undefined;\n\tif (argv[1] === \"-h\" || argv[1] === \"--help\") return { type: \"help\" };\n\tlet parsed: ReturnType<typeof parseArgs>;\n\ttry {\n\t\tparsed = parseArgs({\n\t\t\targs: argv.slice(1),\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: { json: { type: \"boolean\" }, check: { type: \"boolean\" } },\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tconst [unexpected] = parsed.positionals;\n\tif (unexpected) return { type: \"error\", message: `Unexpected argument: ${unexpected}` };\n\treturn { type: \"sync\", check: parsed.values.check === true, json: parsed.values.json === true };\n}\n\nasync function defaultRunAction(actionId: string, input: unknown): Promise<unknown> {\n\tconst client = createActionRpcClient(await readActionRpcEndpoint());\n\treturn client.run(actionId, input);\n}\n\nconst defaultDependencies: PluginCommandDependencies = {\n\tresolveNpmArchive: resolveNpmPluginArchive,\n\tcwd: () => process.cwd(),\n\trunAction: defaultRunAction,\n\twriteStdout: (value) => process.stdout.write(value),\n\twriteStderr: (value) => process.stderr.write(value),\n\treadLatestSdkVersion: () => readLatestNpmVersion(\"@vetta-org/plugin-sdk\"),\n};\n\nfunction isHttpUrl(source: string): boolean {\n\ttry {\n\t\tconst url = new URL(source);\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isLocalZip(source: string): boolean {\n\tif (source.toLowerCase().endsWith(\".zip\")) return true;\n\tconst path = resolve(source);\n\t// 目录不是压缩包:它是一个插件工程,走 resolveProjectArchive 先找它打出来的产物。\n\treturn existsSync(path) && !statSync(path).isDirectory();\n}\n\nfunction isDirectorySource(source: string): boolean {\n\tconst path = resolve(source);\n\treturn existsSync(path) && statSync(path).isDirectory();\n}\n\n/**\n * 把「装当前这个工程」翻译成一个具体的归档路径。\n *\n * 这条路径是给 `install:vetta` 这类脚本用的:作者(或 Agent)在插件目录里跑一条命令就\n * 装进 Vetta,不必记住产物叫什么名字。找不到产物时给出该跑的那条命令,而不是报一个\n * 「文件不存在」让人自己猜。\n */\nfunction resolveProjectArchive(source: string): { archivePath: string; project: PluginProject } {\n\tconst from = resolve(source);\n\tconst project = findPluginProject(from);\n\tif (!project) {\n\t\tconst hub = findPluginHub(from);\n\t\tif (hub) {\n\t\t\tthrow new Error(\n\t\t\t\t`${from} indexes plugins but is not one itself. Run this from a plugin directory, or pass its path: vetta-plugin-cli add ./path/to/plugin`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(`No plugin.json found in ${from} or any parent directory.`);\n\t}\n\tconst archivePath = join(project.root, \"release\", `${project.pluginId}-${project.version}.zip`);\n\tif (!existsSync(archivePath)) {\n\t\tthrow new Error(\n\t\t\t`Packaged archive not found: ${archivePath}\\nBuild it first: npm run build && npx vetta-plugin pack`,\n\t\t);\n\t}\n\treturn { archivePath, project };\n}\n\n/** 装完立刻检查索引是否还停在旧版本;不在市场仓库里时什么也不说。 */\nfunction indexDriftHint(project: PluginProject): string | undefined {\n\tconst hub = findPluginHub(project.root);\n\tif (!hub) return undefined;\n\treturn describeIndexDrift({\n\t\thubRoot: hub.root,\n\t\tmanifestPath: hub.manifestPath,\n\t\tslug: project.pluginId,\n\t\tversion: project.version,\n\t});\n}\n\nfunction npmInstallInput(resolved: ResolvedNpmPluginArchive): Record<string, unknown> {\n\treturn {\n\t\toperation: \"install-from-path\",\n\t\tpath: resolved.archivePath,\n\t\tenable: true,\n\t\tsource: \"npm\",\n\t\texpectedSha256: resolved.expectedSha256,\n\t\texpectedId: resolved.packageManifest.vetta.pluginId,\n\t\texpectedVersion: resolved.packageManifest.version,\n\t\tnpm: {\n\t\t\tpackageName: resolved.packageManifest.name,\n\t\t\trequestedSpec: resolved.requestedSpec,\n\t\t\tresolvedVersion: resolved.packageManifest.version,\n\t\t\t...(resolved.integrity ? { integrity: resolved.integrity } : {}),\n\t\t},\n\t};\n}\n\nfunction resultSummary(result: unknown): string {\n\tif (typeof result !== \"object\" || result === null || Array.isArray(result)) return \"Plugin installed.\\n\";\n\tconst response = result as Record<string, unknown>;\n\tconst plugin =\n\t\ttypeof response.plugin === \"object\" && response.plugin !== null && !Array.isArray(response.plugin)\n\t\t\t? (response.plugin as Record<string, unknown>)\n\t\t\t: undefined;\n\tif (!plugin) return \"Plugin installed.\\n\";\n\tconst id = typeof plugin.id === \"string\" ? plugin.id : \"plugin\";\n\tconst version = typeof plugin.version === \"string\" ? `@${plugin.version}` : \"\";\n\tconst pending = typeof plugin.pendingVersion === \"string\"\n\t\t? ` Update ${plugin.pendingVersion} is pending reload. Run \\`vetta-plugin-cli reload ${id}\\` to apply it.`\n\t\t: \"\";\n\treturn `Installed ${id}${version}.${pending}\\n`;\n}\n\nfunction reloadResultSummary(result: unknown, requestedPluginId: string): string {\n\tif (typeof result !== \"object\" || result === null || Array.isArray(result)) {\n\t\treturn `Reloaded ${requestedPluginId}.\\n`;\n\t}\n\tconst response = result as Record<string, unknown>;\n\tconst plugin =\n\t\ttypeof response.plugin === \"object\" && response.plugin !== null && !Array.isArray(response.plugin)\n\t\t\t? (response.plugin as Record<string, unknown>)\n\t\t\t: undefined;\n\tconst id = typeof plugin?.id === \"string\" ? plugin.id : requestedPluginId;\n\tconst version = typeof plugin?.activeVersion === \"string\" ? `@${plugin.activeVersion}` : \"\";\n\treturn `Reloaded ${id}${version}.\\n`;\n}\n\nfunction isConnectionError(error: unknown): boolean {\n\tif (!(error instanceof Error)) return false;\n\tconst code = (error as NodeJS.ErrnoException).code;\n\treturn (\n\t\tcode === \"ENOENT\" ||\n\t\tcode === \"ECONNREFUSED\" ||\n\t\tcode === \"ECONNRESET\" ||\n\t\terror.message.includes(\"ECONNREFUSED\") ||\n\t\terror.message.includes(\"fetch failed\")\n\t);\n}\n\nexport async function runPluginAddCommand(\n\tcommand: PluginAddCommand,\n\tdependencies: PluginAddCommandDependencies = defaultDependencies,\n): Promise<number> {\n\treturn runPluginCommand(command, dependencies);\n}\n\nexport async function runPluginCommand(\n\tcommand: PluginCommand,\n\tdependencies: PluginCommandDependencies = defaultDependencies,\n): Promise<number> {\n\tif (command.type === \"help\") {\n\t\tdependencies.writeStdout(HELP_TEXT);\n\t\treturn 0;\n\t}\n\tif (command.type === \"error\") {\n\t\tdependencies.writeStderr(`${command.message}\\n`);\n\t\treturn 2;\n\t}\n\n\tif (command.type === \"docs\") {\n\t\treturn await runDocsCommand(command, dependencies);\n\t}\n\tif (command.type === \"init\") {\n\t\treturn runInitCommand(command, dependencies);\n\t}\n\tif (command.type === \"init-hub\") {\n\t\treturn runInitHubCommand(command, dependencies);\n\t}\n\tif (command.type === \"sync\") {\n\t\treturn runSyncCommand(command, dependencies);\n\t}\n\n\tif (command.type === \"watch\") {\n\t\treturn runWatchCommand(command, dependencies);\n\t}\n\tif (command.type === \"uninstall\") {\n\t\treturn runUninstallCommand(command, dependencies);\n\t}\n\n\tlet resolvedNpm: ResolvedNpmPluginArchive | undefined;\n\tlet driftHint: string | undefined;\n\ttry {\n\t\tlet result: unknown;\n\t\tif (command.type === \"reload\") {\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"reload\",\n\t\t\t\tid: command.pluginId,\n\t\t\t});\n\t\t} else if (isHttpUrl(command.source)) {\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"install-from-url\",\n\t\t\t\turl: command.source,\n\t\t\t});\n\t\t} else if (isDirectorySource(command.source)) {\n\t\t\tconst { archivePath, project } = resolveProjectArchive(command.source);\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"install-from-path\",\n\t\t\t\tpath: archivePath,\n\t\t\t\tenable: true,\n\t\t\t});\n\t\t\tdriftHint = indexDriftHint(project);\n\t\t} else if (isLocalZip(command.source)) {\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\toperation: \"install-from-path\",\n\t\t\t\tpath: resolve(command.source),\n\t\t\t\tenable: true,\n\t\t\t});\n\t\t} else {\n\t\t\tresolvedNpm = await dependencies.resolveNpmArchive(command.source);\n\t\t\tresult = await dependencies.runAction(\"plugins.manage\", npmInstallInput(resolvedNpm));\n\t\t}\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, result, ...(driftHint ? { warning: driftHint } : {}) })}\\n`\n\t\t\t\t: command.type === \"reload\"\n\t\t\t\t\t? reloadResultSummary(result, command.pluginId)\n\t\t\t\t\t: `${resultSummary(result)}${driftHint ? `${driftHint}\\n` : \"\"}`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: error instanceof ActionRpcError ? error.code : command.type === \"reload\" ? \"PLUGIN_RELOAD_FAILED\" : \"PLUGIN_ADD_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\tif (error instanceof ActionRpcError) return 4;\n\t\treturn isConnectionError(error) ? 3 : 5;\n\t} finally {\n\t\tawait resolvedNpm?.cleanup();\n\t}\n}\n\n/**\n * 打印随 SDK 发布的手册目录。\n *\n * 存在的理由是「不要让任何人硬编码 node_modules 路径」:工作区会把依赖提升到仓库根,\n * 一仓多插件的 hub 里每个插件也可能各装一份。Agent 只需记住这一条命令,拿回来的永远是\n * 当前工程实际编译所针对的那个 SDK 版本的手册。\n */\nasync function runDocsCommand(\n\tcommand: { json: boolean; checkLatest: boolean },\n\tdependencies: PluginCommandDependencies,\n): Promise<number> {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\tconst manualDir = resolveManualDir(cwd);\n\tif (!manualDir) {\n\t\t// 能力市场仓库的根目录通常没装 SDK,手册在各能力目录里。直接说「装 SDK」会把人引到\n\t\t// 仓库根去装一份用不上的依赖。\n\t\tconst inHubRoot = findPluginHub(cwd) !== undefined && findPluginProject(cwd) === undefined;\n\t\tconst message = inHubRoot\n\t\t\t? \"Plugin manual not found at the hub root. cd into an ability directory (abilities/plugins/<slug>), then run npm install.\\n\"\n\t\t\t: \"Plugin manual not found. Install the SDK first: npm i -D @vetta-org/plugin-sdk\\n\";\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: \"MANUAL_NOT_FOUND\", message: message.trim() } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(message);\n\t\t}\n\t\treturn 6;\n\t}\n\tconst project = findPluginProject(cwd);\n\tconst hub = findPluginHub(cwd);\n\tconst sdkVersion = readManualSdkVersion(manualDir);\n\tconst latestVersion = command.checkLatest ? await dependencies.readLatestSdkVersion?.() : undefined;\n\tconst outdated = sdkVersion !== undefined && latestVersion !== undefined && compareSemver(sdkVersion, latestVersion) < 0;\n\n\tif (command.json) {\n\t\tdependencies.writeStdout(\n\t\t\t`${JSON.stringify({\n\t\t\t\tok: true,\n\t\t\t\tmanualDir,\n\t\t\t\tentry: join(manualDir, \"README.md\"),\n\t\t\t\tsdkVersion,\n\t\t\t\trefreshCommand: SDK_REFRESH_COMMAND,\n\t\t\t\t...(command.checkLatest ? { latestVersion, outdated } : {}),\n\t\t\t\tproject: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,\n\t\t\t\thub: hub\n\t\t\t\t\t? {\n\t\t\t\t\t\t\troot: hub.root,\n\t\t\t\t\t\t\tmanifestPath: hub.manifestPath,\n\t\t\t\t\t\t\tsyncHint: \"After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.\",\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t})}\\n`,\n\t\t);\n\t\treturn 0;\n\t}\n\tconst lines = [\n\t\t`Plugin manual (@vetta-org/plugin-sdk${sdkVersion ? `@${sdkVersion}` : \"\"}):`,\n\t\t` ${manualDir}`,\n\t\t`Start here: ${join(manualDir, \"README.md\")}`,\n\t];\n\tif (project) lines.push(`Current plugin: ${project.pluginId} (${project.root})`);\n\tif (outdated) {\n\t\tlines.push(`Manual is behind: ${sdkVersion} → ${latestVersion}. Refresh it with: ${SDK_REFRESH_COMMAND}`);\n\t} else if (command.checkLatest && latestVersion === undefined) {\n\t\tlines.push(`Could not reach the registry; cannot tell whether ${sdkVersion ?? \"this manual\"} is current.`);\n\t} else {\n\t\t// 手册是随 SDK 装进 node_modules 的快照,工程不升级它就永远停在初始化那天的版本。\n\t\t// 这条命令必须每次都打印:读到它的 Agent 手上的 AGENTS.md 往往也是同一天的快照。\n\t\tlines.push(`Manual follows the installed SDK. To refresh it: ${SDK_REFRESH_COMMAND}`);\n\t}\n\tif (hub) {\n\t\tlines.push(`Marketplace index: ${hub.manifestPath}`);\n\t\t// Agent 几乎一定会先跑 docs,所以这是告诉它「索引要对账」的最佳时机。\n\t\tlines.push(\"After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.\");\n\t}\n\tdependencies.writeStdout(`${lines.join(\"\\n\")}\\n`);\n\treturn 0;\n}\n\n/**\n * 刷新手册的命令。\n *\n * 手册不从网络现取,而是随 SDK 进 `node_modules`——Agent 读到的合同因此与工程实际编译的\n * 版本一致。代价是它不会自己变新,所以「怎么变新」必须由 CLI 每次说一遍:`npx` 默认取最新的\n * CLI,它的输出是这条链路上唯一不会过期的位置。\n */\nconst SDK_REFRESH_COMMAND = \"npm i -D @vetta-org/plugin-sdk@latest && npx vetta-plugin-cli docs\";\n\n/** 够用的 semver 比较:只看 major.minor.patch,预发布后缀一律当作小于正式版。 */\nfunction compareSemver(left: string, right: string): number {\n\tconst parse = (value: string): readonly [number, number, number, boolean] => {\n\t\tconst match = /^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$/.exec(value.trim());\n\t\tif (!match) return [0, 0, 0, false];\n\t\treturn [Number(match[1]), Number(match[2]), Number(match[3]), match[4] !== undefined];\n\t};\n\tconst a = parse(left);\n\tconst b = parse(right);\n\tfor (let index = 0; index < 3; index += 1) {\n\t\tif (a[index] !== b[index]) return a[index]! < b[index]! ? -1 : 1;\n\t}\n\tif (a[3] === b[3]) return 0;\n\treturn a[3] ? -1 : 1;\n}\n\n/** 在陌生目录里生成一个可直接开工的插件工程,并留下让任意 Agent 自举的 AGENTS.md。 */\nfunction runInitCommand(\n\tcommand: Extract<PluginCommand, { type: \"init\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tconst result = initPluginProject({\n\t\t\ttargetDir: resolve(cwd, command.targetDir ?? command.pluginId),\n\t\t\tpluginId: command.pluginId,\n\t\t\tdisplayName: command.displayName ?? command.pluginId,\n\t\t});\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, ...result })}\\n`\n\t\t\t\t: [\n\t\t\t\t\t\t`Created ${result.pluginId} at ${result.root}`,\n\t\t\t\t\t\t\"Next: npm install && npm run install:vetta\",\n\t\t\t\t\t\t\"The agent brief is in AGENTS.md; after npm install, run `npx vetta-plugin-cli docs` for the manual.\",\n\t\t\t\t\t]\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t\t.concat(\"\\n\"),\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"PLUGIN_INIT_FAILED\", message } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 5;\n\t}\n}\n\n/**\n * 让宿主改从工程目录加载本插件,之后改源码即时生效,不必每次 build → pack → install。\n *\n * 目标插件按 cwd 向上找,理由同 `add .`:一仓多插件时「我正站在哪个插件里」是唯一不会\n * 弄错的意图,而 id 靠人重复输入迟早会错配到另一个插件上。\n */\nasync function runWatchCommand(\n\tcommand: Extract<PluginCommand, { type: \"watch\" }>,\n\tdependencies: PluginCommandDependencies,\n): Promise<number> {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\tconst from = resolve(cwd, command.dir ?? \".\");\n\ttry {\n\t\tconst project = findPluginProject(from);\n\t\tif (!project) {\n\t\t\tconst hub = findPluginHub(from);\n\t\t\tthrow new Error(\n\t\t\t\thub\n\t\t\t\t\t? `${from} indexes plugins but is not one itself. Run this from a plugin directory, or pass its path.`\n\t\t\t\t\t: `No plugin.json found in ${from} or any parent directory.`,\n\t\t\t);\n\t\t}\n\t\tconst result = command.stop\n\t\t\t? await dependencies.runAction(\"plugins.manage\", { operation: \"dev-watch-stop\", id: project.pluginId })\n\t\t\t: await dependencies.runAction(\"plugins.manage\", {\n\t\t\t\t\toperation: \"dev-watch\",\n\t\t\t\t\tid: project.pluginId,\n\t\t\t\t\tprojectDir: project.root,\n\t\t\t\t});\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, result })}\\n`\n\t\t\t\t: command.stop\n\t\t\t\t\t? `Stopped hot reload for ${project.pluginId}.\\n`\n\t\t\t\t\t: `Hot reload on for ${project.pluginId}. Vetta now loads it from ${project.root}.\\n`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: error instanceof ActionRpcError ? error.code : \"PLUGIN_WATCH_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\tif (error instanceof ActionRpcError) return 4;\n\t\treturn isConnectionError(error) ? 3 : 5;\n\t}\n}\n\n/**\n * 卸载一个插件。省略 id 时按 cwd 推断,语义与 `add .` / `watch` 一致。\n *\n * 刻意不在这里做二次确认:宿主自己会为写操作弹审批,CLI 再问一遍只是噪音。系统插件由\n * 宿主拒绝,这里不重复判断——那份名单不该有第二个真相源。\n */\nasync function runUninstallCommand(\n\tcommand: Extract<PluginCommand, { type: \"uninstall\" }>,\n\tdependencies: PluginCommandDependencies,\n): Promise<number> {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tlet pluginId = command.pluginId;\n\t\tif (!pluginId) {\n\t\t\tconst project = findPluginProject(cwd);\n\t\t\tif (!project) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No plugin.json found in ${cwd} or any parent directory. Pass the id: vetta-plugin-cli uninstall <plugin-id>`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tpluginId = project.pluginId;\n\t\t}\n\t\tconst result = await dependencies.runAction(\"plugins.manage\", { operation: \"uninstall\", id: pluginId });\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json ? `${JSON.stringify({ ok: true, result })}\\n` : `Uninstalled ${pluginId}.\\n`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(\n\t\t\t\t`${JSON.stringify({ ok: false, error: { code: error instanceof ActionRpcError ? error.code : \"PLUGIN_UNINSTALL_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\tif (error instanceof ActionRpcError) return 4;\n\t\treturn isConnectionError(error) ? 3 : 5;\n\t}\n}\n\n/**\n * 对账能力市场索引。定位靠向上找 `.vetta/marketplace.json`,因此在仓库任何位置都能跑。\n *\n * `--check` 只报不写并以非零退出,给 CI 用:索引漂移的三种后果里,两种不在作者机器上复现,\n * 一种压根不报错,光靠人自觉看不住。\n */\nfunction runSyncCommand(\n\tcommand: Extract<PluginCommand, { type: \"sync\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\tconst hub = findPluginHub(cwd);\n\tif (!hub) {\n\t\tconst message = `No .vetta/marketplace.json found in ${cwd} or any parent directory. sync is for marketplace repositories.\\n`;\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"HUB_NOT_FOUND\", message: message.trim() } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(message);\n\t\t}\n\t\treturn 6;\n\t}\n\ttry {\n\t\tconst result = syncMarketplaceIndex({ hubRoot: hub.root, manifestPath: hub.manifestPath, apply: !command.check });\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: result.problems.length === 0, ...result })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStdout(formatSyncReport(result, command.check));\n\t\t}\n\t\tif (result.problems.length > 0) return 7;\n\t\t// --check 的职责就是「有漂移就红」,否则 CI 拦不住任何东西。\n\t\treturn command.check && result.changes.length > 0 ? 7 : 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"SYNC_FAILED\", message } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 5;\n\t}\n}\n\nfunction formatSyncReport(result: ReturnType<typeof syncMarketplaceIndex>, check: boolean): string {\n\tconst lines: string[] = [];\n\tfor (const change of result.changes) {\n\t\tlines.push(` ${change.slug}: ${change.field} ${JSON.stringify(change.from)} -> ${JSON.stringify(change.to)}`);\n\t}\n\tif (lines.length > 0) {\n\t\tlines.unshift(check ? \"Index is out of date:\" : \"Updated the index:\");\n\t}\n\tif (result.problems.length > 0) {\n\t\tlines.push(\"Problems:\");\n\t\tfor (const problem of result.problems) lines.push(` ${problem.slug}: ${problem.message}`);\n\t}\n\tif (result.unlisted.length > 0) {\n\t\tlines.push(\"Ability directories not listed in the index (add them by hand when ready to publish):\");\n\t\tfor (const dir of result.unlisted) lines.push(` ${dir}`);\n\t}\n\tif (lines.length === 0) return \"Index is in sync.\\n\";\n\tif (check && result.changes.length > 0) lines.push(\"Run `vetta-plugin-cli sync` to apply.\");\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** 生成一个合规的能力市场仓库骨架,连同仓库级 AGENTS.md 与对账用的 CI。 */\nfunction runInitHubCommand(\n\tcommand: Extract<PluginCommand, { type: \"init-hub\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tconst result = initHubRepository({\n\t\t\ttargetDir: resolve(cwd, command.targetDir ?? command.name),\n\t\t\tname: command.name,\n\t\t\trepository: command.repository,\n\t\t\tminAppVersion: command.minAppVersion,\n\t\t});\n\t\tdependencies.writeStdout(\n\t\t\tcommand.json\n\t\t\t\t? `${JSON.stringify({ ok: true, ...result })}\\n`\n\t\t\t\t: [\n\t\t\t\t\t\t`Created marketplace ${result.name} at ${result.root}`,\n\t\t\t\t\t\t\"Add an ability: npx @vetta-org/plugin-cli init --id <slug> --name \\\"<Display>\\\" abilities/plugins/<slug>\",\n\t\t\t\t\t\t\"Then list it in .vetta/marketplace.json and run: npx @vetta-org/plugin-cli sync\",\n\t\t\t\t\t\t\"The working agreement for agents is in AGENTS.md.\",\n\t\t\t\t\t].join(\"\\n\") + \"\\n\",\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (command.json) {\n\t\t\tdependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: \"HUB_INIT_FAILED\", message } })}\\n`);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 5;\n\t}\n}\n\nexport async function runPluginCli(argv: string[]): Promise<number> {\n\tif (argv.length === 0 || argv[0] === \"-h\" || argv[0] === \"--help\") {\n\t\treturn runPluginAddCommand({ type: \"help\" });\n\t}\n\tconst command = parsePluginAddCommand(argv) ?? parsePluginReloadCommand(argv) ?? parsePluginDocsCommand(argv) ?? parsePluginInitCommand(argv) ?? parsePluginWatchCommand(argv) ?? parsePluginUninstallCommand(argv) ?? parsePluginSyncCommand(argv);\n\tif (!command) {\n\t\tprocess.stderr.write(`Unknown command: ${argv[0]}\\n`);\n\t\treturn 2;\n\t}\n\treturn runPluginCommand(command);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"hub-template.d.ts","sourceRoot":"","sources":["../src/hub-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAgEpE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAmB1C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAYnF","sourcesContent":["/**\n * 能力市场仓库的骨架。\n *\n * 手写一个合规的 hub 成本不低:`.vetta/marketplace.json` 的必填字段、目录约定、以及那几条\n * 只在别人机器上复现的发布约束,都得先读一遍文档才知道。这里把它变成一条命令。\n *\n * 仓库级 `AGENTS.md` 是关键的一半:落在仓库根的 Agent 需要知道「能力目录才是开发单位、\n * 索引由 sync 对账」,否则它会去手改 marketplace.json。\n */\n\nexport function renderHubAgentsGuide(input: { name: string }): string {\n\treturn `# ${input.name}\n\nVetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skill / scene / bundle),\n每个能力是 \\`abilities/\\` 下的一个自包含目录。\n\n## 开发时站在能力目录里,不是站在这里\n\n> 仓库根没有 \\`node_modules\\`,所以在根上执行时用全名 \\`@vetta-org/plugin-cli\\`;进了能力目录、\n> \\`npm install\\` 之后,裸命令 \\`vetta-plugin-cli\\` 才在 \\`node_modules/.bin\\` 里。\n\n\\`\\`\\`bash\ncd abilities/plugins/<slug> # ← 开发单位是这个目录\nnpm install\nnpx vetta-plugin-cli docs # 手册(随该目录装的 SDK 版本)\nnpm run install:vetta # 装进正在运行的 Vetta\nnpx vetta-plugin-cli watch # 热更新\n\\`\\`\\`\n\n每个插件目录自带 \\`AGENTS.md\\`,里面有该读哪些手册、以及不可违反的几条。**先 \\`cd\\` 进去再动手**:\n所有开发命令都作用于「最近的那个能力目录」,站在仓库根上它们不知道你指的是哪一个。\n\n新建一个插件:\n\n\\`\\`\\`bash\nnpx @vetta-org/plugin-cli init --id <slug> --name \"<Display Name>\" abilities/plugins/<slug>\n\\`\\`\\`\n\n它只创建目录,**不会**动索引——新能力什么时候上架是人的决定。想好了再按下面的方式登记。\n\n## 索引由工具对账,不要手改派生字段\n\n\\`.vetta/marketplace.json\\` 里能力的 \\`version\\`、\\`config.api_version\\`、\\`config.permissions\\`、\n\\`config.commands\\` 全都是从能力包推导出来的。改完能力后:\n\n\\`\\`\\`bash\nnpx @vetta-org/plugin-cli sync # 回填派生字段,并推进 marketplaceVersion\nnpx @vetta-org/plugin-cli sync --check # 只报不写,非零退出(CI 用)\n\\`\\`\\`\n\n要**手写**的只有身份与展示:\\`slug\\`、\\`name\\`、\\`description\\`、\\`source.path\\`、\\`category\\`、\\`tags\\`、\n\\`detail\\`。新能力上架时手动加一条这样的条目,其余字段交给 \\`sync\\`。\n\n三条容易踩的约束,\\`sync --check\\` 会替你守住:\n\n| 约束 | 漏了会怎样 |\n| --- | --- |\n| 条目 \\`version\\` 必须 == 能力包里的版本 | 宿主同步**直接失败** |\n| \\`plugin.json\\` 的 \\`entry\\` / \\`styles\\` 必须在已发布目录里真实存在 | 本地能装,市场上装不了 |\n| 改了任何内容必须换 \\`marketplaceVersion\\` | 客户端不报错、也不更新,用户永远收不到 |\n\n第三条最阴险——它不报错。\n\n## 为什么插件目录里要提交 \\`dist/\\`\n\n客户端按 \\`source.path\\` 直接读目录并安装,**它不会替你构建**。所以构建产物必须在仓库里。\n脚手架生成的插件目录因此不忽略 \\`dist/\\`。\n\n## 发布\n\n1. 改能力 → 在能力目录里 build\n2. 回仓库根 \\`npx @vetta-org/plugin-cli sync\\`\n3. 提交并推送;客户端在 \\`marketplaceVersion\\` 变化时拉新快照\n`;\n}\n\nexport function renderHubWorkflow(): string {\n\treturn `name: marketplace\n\non:\n pull_request:\n push:\n branches: [main]\n\njobs:\n index:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 22\n # 索引与能力包漂移的后果有两种不在作者机器上复现、一种压根不报错,所以在这里拦。\n - run: npx --yes @vetta-org/plugin-cli sync --check\n`;\n}\n\nexport function renderHubReadme(input: { name: string; repository: string }): string {\n\treturn `# ${input.name}\n\nA Vetta ability marketplace. Add it in Vetta Desktop under **能力市场 → 添加来源**:\n\n\\`\\`\\`\n${input.repository}\n\\`\\`\\`\n\nAbilities live under \\`abilities/\\`. The index is \\`.vetta/marketplace.json\\`; its derived fields are\nmaintained by \\`npx @vetta-org/plugin-cli sync\\`. See \\`AGENTS.md\\` for the working agreement.\n`;\n}\n"]}
1
+ {"version":3,"file":"hub-template.d.ts","sourceRoot":"","sources":["../src/hub-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAmEpE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAmB1C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAYnF","sourcesContent":["/**\n * 能力市场仓库的骨架。\n *\n * 手写一个合规的 hub 成本不低:`.vetta/marketplace.json` 的必填字段、目录约定、以及那几条\n * 只在别人机器上复现的发布约束,都得先读一遍文档才知道。这里把它变成一条命令。\n *\n * 仓库级 `AGENTS.md` 是关键的一半:落在仓库根的 Agent 需要知道「能力目录才是开发单位、\n * 索引由 sync 对账」,否则它会去手改 marketplace.json。\n */\n\nexport function renderHubAgentsGuide(input: { name: string }): string {\n\treturn `# ${input.name}\n\nVetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skill / scene / bundle),\n每个能力是 \\`abilities/\\` 下的一个自包含目录。\n\n## 开发时站在能力目录里,不是站在这里\n\n> 仓库根没有 \\`node_modules\\`,所以在根上执行时用全名 \\`@vetta-org/plugin-cli\\`;进了能力目录、\n> \\`npm install\\` 之后,裸命令 \\`vetta-plugin-cli\\` 才在 \\`node_modules/.bin\\` 里。\n\n\\`\\`\\`bash\ncd abilities/plugins/<slug> # ← 开发单位是这个目录\nnpm install\nnpx vetta-plugin-cli docs --check-latest # 手册(随该目录装的 SDK 版本;顺带查是否落后)\nnpm run install:vetta # 装进正在运行的 Vetta\nnpx vetta-plugin-cli watch # 热更新\n\\`\\`\\`\n\n每个插件目录自带 \\`AGENTS.md\\`,里面有该读哪些手册、以及不可违反的几条。**先 \\`cd\\` 进去再动手**:\n所有开发命令都作用于「最近的那个能力目录」,站在仓库根上它们不知道你指的是哪一个。\n\n手册与各目录的 \\`AGENTS.md\\` 都是那次 \\`init\\` 当天的快照,各能力还可能钉着不同的 SDK 版本。\n动手前先跑 \\`docs --check-latest\\`,它的输出永远比这两份文件新,冲突时以它为准。\n\n新建一个插件:\n\n\\`\\`\\`bash\nnpx @vetta-org/plugin-cli init --id <slug> --name \"<Display Name>\" abilities/plugins/<slug>\n\\`\\`\\`\n\n它只创建目录,**不会**动索引——新能力什么时候上架是人的决定。想好了再按下面的方式登记。\n\n## 索引由工具对账,不要手改派生字段\n\n\\`.vetta/marketplace.json\\` 里能力的 \\`version\\`、\\`config.api_version\\`、\\`config.permissions\\`、\n\\`config.commands\\` 全都是从能力包推导出来的。改完能力后:\n\n\\`\\`\\`bash\nnpx @vetta-org/plugin-cli sync # 回填派生字段,并推进 marketplaceVersion\nnpx @vetta-org/plugin-cli sync --check # 只报不写,非零退出(CI 用)\n\\`\\`\\`\n\n要**手写**的只有身份与展示:\\`slug\\`、\\`name\\`、\\`description\\`、\\`source.path\\`、\\`category\\`、\\`tags\\`、\n\\`detail\\`。新能力上架时手动加一条这样的条目,其余字段交给 \\`sync\\`。\n\n三条容易踩的约束,\\`sync --check\\` 会替你守住:\n\n| 约束 | 漏了会怎样 |\n| --- | --- |\n| 条目 \\`version\\` 必须 == 能力包里的版本 | 宿主同步**直接失败** |\n| \\`plugin.json\\` 的 \\`entry\\` / \\`styles\\` 必须在已发布目录里真实存在 | 本地能装,市场上装不了 |\n| 改了任何内容必须换 \\`marketplaceVersion\\` | 客户端不报错、也不更新,用户永远收不到 |\n\n第三条最阴险——它不报错。\n\n## 为什么插件目录里要提交 \\`dist/\\`\n\n客户端按 \\`source.path\\` 直接读目录并安装,**它不会替你构建**。所以构建产物必须在仓库里。\n脚手架生成的插件目录因此不忽略 \\`dist/\\`。\n\n## 发布\n\n1. 改能力 → 在能力目录里 build\n2. 回仓库根 \\`npx @vetta-org/plugin-cli sync\\`\n3. 提交并推送;客户端在 \\`marketplaceVersion\\` 变化时拉新快照\n`;\n}\n\nexport function renderHubWorkflow(): string {\n\treturn `name: marketplace\n\non:\n pull_request:\n push:\n branches: [main]\n\njobs:\n index:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 22\n # 索引与能力包漂移的后果有两种不在作者机器上复现、一种压根不报错,所以在这里拦。\n - run: npx --yes @vetta-org/plugin-cli sync --check\n`;\n}\n\nexport function renderHubReadme(input: { name: string; repository: string }): string {\n\treturn `# ${input.name}\n\nA Vetta ability marketplace. Add it in Vetta Desktop under **能力市场 → 添加来源**:\n\n\\`\\`\\`\n${input.repository}\n\\`\\`\\`\n\nAbilities live under \\`abilities/\\`. The index is \\`.vetta/marketplace.json\\`; its derived fields are\nmaintained by \\`npx @vetta-org/plugin-cli sync\\`. See \\`AGENTS.md\\` for the working agreement.\n`;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { renderAgentsGuide } from "./agents-template.js";
4
4
  export { renderHubAgentsGuide, renderHubReadme, renderHubWorkflow } from "./hub-template.js";
5
5
  export { type SyncChange, type SyncChangeKind, type SyncInput, type SyncProblem, type SyncResult, syncMarketplaceIndex, } from "./sync.js";
6
6
  export { findPluginHub, findPluginProject, type PluginHub, type PluginProject, readManualSdkVersion, resolveManualDir, } from "./workspace.js";
7
- export { type NpmPackResult, type NpmPackRunner, type NpmPluginPackageManifest, type ResolvedNpmPluginArchive, resolveNpmPluginArchive, runNpmPack, } from "./npm-package.js";
7
+ export { type NpmPackResult, type NpmPackRunner, type NpmPluginPackageManifest, type ResolvedNpmPluginArchive, readLatestNpmVersion, resolveNpmPluginArchive, runNpmPack, } from "./npm-package.js";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EACN,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,oBAAoB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,aAAa,EACb,iBAAiB,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,oBAAoB,EACpB,gBAAgB,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,uBAAuB,EACvB,UAAU,GACV,MAAM,kBAAkB,CAAC","sourcesContent":["export {\n\tparsePluginAddCommand,\n\tparsePluginDocsCommand,\n\tparsePluginInitCommand,\n\tparsePluginSyncCommand,\n\tparsePluginUninstallCommand,\n\tparsePluginWatchCommand,\n\tparsePluginReloadCommand,\n\ttype PluginAddCommand,\n\ttype PluginAddCommandDependencies,\n\ttype PluginCommand,\n\ttype PluginCommandDependencies,\n\ttype PluginDocsCommand,\n\ttype PluginInitCommand,\n\ttype PluginSyncCommand,\n\ttype PluginUninstallCommand,\n\ttype PluginWatchCommand,\n\ttype PluginReloadCommand,\n\trunPluginAddCommand,\n\trunPluginCommand,\n\trunPluginCli,\n} from \"./command.js\";\nexport {\n\tDEFAULT_SDK_RANGE,\n\tDEFAULT_VITE_RANGE,\n\tinitHubRepository,\n\tinitPluginProject,\n\ttype InitHubInput,\n\ttype InitHubResult,\n\ttype InitPluginInput,\n\ttype InitPluginResult,\n} from \"./init.js\";\nexport { renderAgentsGuide } from \"./agents-template.js\";\nexport { renderHubAgentsGuide, renderHubReadme, renderHubWorkflow } from \"./hub-template.js\";\nexport {\n\ttype SyncChange,\n\ttype SyncChangeKind,\n\ttype SyncInput,\n\ttype SyncProblem,\n\ttype SyncResult,\n\tsyncMarketplaceIndex,\n} from \"./sync.js\";\nexport {\n\tfindPluginHub,\n\tfindPluginProject,\n\ttype PluginHub,\n\ttype PluginProject,\n\treadManualSdkVersion,\n\tresolveManualDir,\n} from \"./workspace.js\";\nexport {\n\ttype NpmPackResult,\n\ttype NpmPackRunner,\n\ttype NpmPluginPackageManifest,\n\ttype ResolvedNpmPluginArchive,\n\tresolveNpmPluginArchive,\n\trunNpmPack,\n} from \"./npm-package.js\";\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EACN,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,oBAAoB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,aAAa,EACb,iBAAiB,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,oBAAoB,EACpB,gBAAgB,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,oBAAoB,EACpB,uBAAuB,EACvB,UAAU,GACV,MAAM,kBAAkB,CAAC","sourcesContent":["export {\n\tparsePluginAddCommand,\n\tparsePluginDocsCommand,\n\tparsePluginInitCommand,\n\tparsePluginSyncCommand,\n\tparsePluginUninstallCommand,\n\tparsePluginWatchCommand,\n\tparsePluginReloadCommand,\n\ttype PluginAddCommand,\n\ttype PluginAddCommandDependencies,\n\ttype PluginCommand,\n\ttype PluginCommandDependencies,\n\ttype PluginDocsCommand,\n\ttype PluginInitCommand,\n\ttype PluginSyncCommand,\n\ttype PluginUninstallCommand,\n\ttype PluginWatchCommand,\n\ttype PluginReloadCommand,\n\trunPluginAddCommand,\n\trunPluginCommand,\n\trunPluginCli,\n} from \"./command.js\";\nexport {\n\tDEFAULT_SDK_RANGE,\n\tDEFAULT_VITE_RANGE,\n\tinitHubRepository,\n\tinitPluginProject,\n\ttype InitHubInput,\n\ttype InitHubResult,\n\ttype InitPluginInput,\n\ttype InitPluginResult,\n} from \"./init.js\";\nexport { renderAgentsGuide } from \"./agents-template.js\";\nexport { renderHubAgentsGuide, renderHubReadme, renderHubWorkflow } from \"./hub-template.js\";\nexport {\n\ttype SyncChange,\n\ttype SyncChangeKind,\n\ttype SyncInput,\n\ttype SyncProblem,\n\ttype SyncResult,\n\tsyncMarketplaceIndex,\n} from \"./sync.js\";\nexport {\n\tfindPluginHub,\n\tfindPluginProject,\n\ttype PluginHub,\n\ttype PluginProject,\n\treadManualSdkVersion,\n\tresolveManualDir,\n} from \"./workspace.js\";\nexport {\n\ttype NpmPackResult,\n\ttype NpmPackRunner,\n\ttype NpmPluginPackageManifest,\n\ttype ResolvedNpmPluginArchive,\n\treadLatestNpmVersion,\n\tresolveNpmPluginArchive,\n\trunNpmPack,\n} from \"./npm-package.js\";\n"]}
package/dist/index.js CHANGED
@@ -15401,6 +15401,7 @@ var PluginSkillPresentationSchema = Type.Object({
15401
15401
  surfaces: Type.Optional(SkillSurfaceVisibilitySchema),
15402
15402
  skills: Type.Optional(Type.Record(NonWhitespaceStringSchema, PluginSkillPresentationRuleSchema))
15403
15403
  }, { additionalProperties: false });
15404
+ var PluginAgentRoleSchema = Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" });
15404
15405
  var PluginAgentProfileManifestSchema = Type.Object({
15405
15406
  id: Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" }),
15406
15407
  name: NonWhitespaceStringSchema,
@@ -15410,11 +15411,14 @@ var PluginAgentProfileManifestSchema = Type.Object({
15410
15411
  systemPromptPath: Type.Optional(NonWhitespaceStringSchema),
15411
15412
  systemPrompt: Type.Optional(Type.String({ maxLength: 64000 })),
15412
15413
  abilities: Type.Optional(Type.Union([Type.Literal("all"), Type.Literal("own")])),
15413
- legacyIds: Type.Optional(Type.Array(NonWhitespaceStringSchema, { maxItems: 16 }))
15414
+ legacyIds: Type.Optional(Type.Array(NonWhitespaceStringSchema, { maxItems: 16 })),
15415
+ roles: Type.Optional(Type.Array(PluginAgentRoleSchema, { maxItems: 8 }))
15414
15416
  }, { additionalProperties: false });
15415
15417
  var PluginAgentTeamMemberManifestSchema = Type.Object({
15416
- agent: NonWhitespaceStringSchema,
15417
- responsibility: Type.String({ maxLength: 2000 })
15418
+ agent: Type.Optional(NonWhitespaceStringSchema),
15419
+ role: Type.Optional(PluginAgentRoleSchema),
15420
+ responsibility: Type.String({ maxLength: 2000 }),
15421
+ optional: Type.Optional(Type.Boolean())
15418
15422
  }, { additionalProperties: false });
15419
15423
  var PluginAgentTeamManifestSchema = Type.Object({
15420
15424
  id: Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" }),
@@ -18809,6 +18813,34 @@ var runNpmPack = async (packageSpec, destination) => {
18809
18813
  });
18810
18814
  return parsePackOutput(output2);
18811
18815
  };
18816
+ async function readLatestNpmVersion(packageName) {
18817
+ const invocation = npmInvocation();
18818
+ const args = [...invocation.args, "view", packageName, "version", "--json"];
18819
+ try {
18820
+ const output2 = await new Promise((resolvePromise, rejectPromise) => {
18821
+ const child = spawn(invocation.command, args, {
18822
+ shell: false,
18823
+ windowsHide: true,
18824
+ stdio: ["ignore", "pipe", "pipe"]
18825
+ });
18826
+ let stdout = "";
18827
+ child.stdout.setEncoding("utf8").on("data", (chunk) => {
18828
+ stdout += chunk;
18829
+ });
18830
+ child.stderr.resume();
18831
+ child.once("error", rejectPromise);
18832
+ child.once("exit", (code) => code === 0 ? resolvePromise(stdout) : rejectPromise(new Error("npm view failed")));
18833
+ });
18834
+ const parsed = JSON.parse(output2);
18835
+ if (typeof parsed === "string")
18836
+ return parsed;
18837
+ if (Array.isArray(parsed) && typeof parsed.at(-1) === "string")
18838
+ return parsed.at(-1);
18839
+ return;
18840
+ } catch {
18841
+ return;
18842
+ }
18843
+ }
18812
18844
  function registryPackageName(packageSpec) {
18813
18845
  const parsed = import_npm_package_arg.default(packageSpec);
18814
18846
  if (!parsed.registry || !parsed.name || !REGISTRY_SPEC_TYPES.has(parsed.type)) {
@@ -18907,6 +18939,16 @@ npx vetta-plugin-cli docs
18907
18939
  \`npm install\` 必须先跑:\`vetta-plugin-cli\` 是 \`@vetta-org/plugin-cli\` 的命令名,装完才在
18908
18940
  \`node_modules/.bin\` 里。还没装就想跑,用全名 \`npx @vetta-org/plugin-cli docs\`。
18909
18941
 
18942
+ **手册会过期。** 它是随 SDK 装进 \`node_modules\` 的快照,工程不升级就永远停在初始化那天的
18943
+ 版本——本文件同样是那天的快照。开工前先确认一次:
18944
+
18945
+ \`\`\`bash
18946
+ npx vetta-plugin-cli docs --check-latest
18947
+ \`\`\`
18948
+
18949
+ 落后就按它打印的命令升级 SDK 再重读手册。\`docs\` 的输出永远比本文件新(\`npx\` 默认取最新的
18950
+ CLI),**两者冲突时以它为准**。
18951
+
18910
18952
  它打印出随 \`@vetta-org/plugin-sdk\` 一起装进 \`node_modules\` 的手册目录**绝对路径**,以及
18911
18953
  这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件
18912
18954
  时各插件还可能钉不同的 SDK 版本。
@@ -18990,7 +19032,7 @@ Vetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skil
18990
19032
  \`\`\`bash
18991
19033
  cd abilities/plugins/<slug> # ← 开发单位是这个目录
18992
19034
  npm install
18993
- npx vetta-plugin-cli docs # 手册(随该目录装的 SDK 版本)
19035
+ npx vetta-plugin-cli docs --check-latest # 手册(随该目录装的 SDK 版本;顺带查是否落后)
18994
19036
  npm run install:vetta # 装进正在运行的 Vetta
18995
19037
  npx vetta-plugin-cli watch # 热更新
18996
19038
  \`\`\`
@@ -18998,6 +19040,9 @@ npx vetta-plugin-cli watch # 热更新
18998
19040
  每个插件目录自带 \`AGENTS.md\`,里面有该读哪些手册、以及不可违反的几条。**先 \`cd\` 进去再动手**:
18999
19041
  所有开发命令都作用于「最近的那个能力目录」,站在仓库根上它们不知道你指的是哪一个。
19000
19042
 
19043
+ 手册与各目录的 \`AGENTS.md\` 都是那次 \`init\` 当天的快照,各能力还可能钉着不同的 SDK 版本。
19044
+ 动手前先跑 \`docs --check-latest\`,它的输出永远比这两份文件新,冲突时以它为准。
19045
+
19001
19046
  新建一个插件:
19002
19047
 
19003
19048
  \`\`\`bash
@@ -19076,7 +19121,7 @@ maintained by \`npx @vetta-org/plugin-cli sync\`. See \`AGENTS.md\` for the work
19076
19121
  }
19077
19122
 
19078
19123
  // src/init.ts
19079
- var DEFAULT_SDK_RANGE = "^0.3.1";
19124
+ var DEFAULT_SDK_RANGE = "^0.3.2";
19080
19125
  var DEFAULT_VITE_RANGE = "^0.2.0";
19081
19126
  var PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
19082
19127
  function remoteNameFromId(pluginId) {
@@ -19538,7 +19583,7 @@ var HELP_TEXT = `Vetta plugin manager
19538
19583
  Usage:
19539
19584
  vetta-plugin-cli add <npm-package|zip-path|http-url> [--json]
19540
19585
  vetta-plugin-cli reload <plugin-id> [--json]
19541
- vetta-plugin-cli docs [--json]
19586
+ vetta-plugin-cli docs [--check-latest] [--json]
19542
19587
  vetta-plugin-cli init --id <plugin-id> [--name <display>] [dir] [--json]
19543
19588
  vetta-plugin-cli init hub --name <slug> --repository <url> --min-app-version <x.y.z> [dir]
19544
19589
  vetta-plugin-cli watch [dir] [--stop] [--json]
@@ -19605,14 +19650,23 @@ function parsePluginDocsCommand(argv) {
19605
19650
  return { type: "help" };
19606
19651
  let parsed;
19607
19652
  try {
19608
- parsed = parseArgs({ args: argv.slice(1), allowPositionals: true, strict: true, options: { json: { type: "boolean" } } });
19653
+ parsed = parseArgs({
19654
+ args: argv.slice(1),
19655
+ allowPositionals: true,
19656
+ strict: true,
19657
+ options: { json: { type: "boolean" }, "check-latest": { type: "boolean" } }
19658
+ });
19609
19659
  } catch (error) {
19610
19660
  return { type: "error", message: formatParseError(error) };
19611
19661
  }
19612
19662
  const [unexpected] = parsed.positionals;
19613
19663
  if (unexpected)
19614
19664
  return { type: "error", message: `Unexpected argument: ${unexpected}` };
19615
- return { type: "docs", json: parsed.values.json === true };
19665
+ return {
19666
+ type: "docs",
19667
+ json: parsed.values.json === true,
19668
+ checkLatest: parsed.values["check-latest"] === true
19669
+ };
19616
19670
  }
19617
19671
  function parsePluginInitCommand(argv) {
19618
19672
  if (argv[0] !== "init")
@@ -19771,7 +19825,8 @@ var defaultDependencies = {
19771
19825
  cwd: () => process.cwd(),
19772
19826
  runAction: defaultRunAction,
19773
19827
  writeStdout: (value) => process.stdout.write(value),
19774
- writeStderr: (value) => process.stderr.write(value)
19828
+ writeStderr: (value) => process.stderr.write(value),
19829
+ readLatestSdkVersion: () => readLatestNpmVersion("@vetta-org/plugin-sdk")
19775
19830
  };
19776
19831
  function isHttpUrl(source) {
19777
19832
  try {
@@ -19883,7 +19938,7 @@ async function runPluginCommand(command, dependencies = defaultDependencies) {
19883
19938
  return 2;
19884
19939
  }
19885
19940
  if (command.type === "docs") {
19886
- return runDocsCommand(command, dependencies);
19941
+ return await runDocsCommand(command, dependencies);
19887
19942
  }
19888
19943
  if (command.type === "init") {
19889
19944
  return runInitCommand(command, dependencies);
@@ -19952,11 +20007,13 @@ async function runPluginCommand(command, dependencies = defaultDependencies) {
19952
20007
  await resolvedNpm?.cleanup();
19953
20008
  }
19954
20009
  }
19955
- function runDocsCommand(command, dependencies) {
20010
+ async function runDocsCommand(command, dependencies) {
19956
20011
  const cwd = dependencies.cwd?.() ?? process.cwd();
19957
20012
  const manualDir = resolveManualDir(cwd);
19958
20013
  if (!manualDir) {
19959
- const message = `Plugin manual not found. Install the SDK first: npm i -D @vetta-org/plugin-sdk
20014
+ const inHubRoot = findPluginHub(cwd) !== undefined && findPluginProject(cwd) === undefined;
20015
+ const message = inHubRoot ? `Plugin manual not found at the hub root. cd into an ability directory (abilities/plugins/<slug>), then run npm install.
20016
+ ` : `Plugin manual not found. Install the SDK first: npm i -D @vetta-org/plugin-sdk
19960
20017
  `;
19961
20018
  if (command.json) {
19962
20019
  dependencies.writeStdout(`${JSON.stringify({ ok: false, error: { code: "MANUAL_NOT_FOUND", message: message.trim() } })}
@@ -19969,12 +20026,16 @@ function runDocsCommand(command, dependencies) {
19969
20026
  const project = findPluginProject(cwd);
19970
20027
  const hub = findPluginHub(cwd);
19971
20028
  const sdkVersion = readManualSdkVersion(manualDir);
20029
+ const latestVersion = command.checkLatest ? await dependencies.readLatestSdkVersion?.() : undefined;
20030
+ const outdated = sdkVersion !== undefined && latestVersion !== undefined && compareSemver(sdkVersion, latestVersion) < 0;
19972
20031
  if (command.json) {
19973
20032
  dependencies.writeStdout(`${JSON.stringify({
19974
20033
  ok: true,
19975
20034
  manualDir,
19976
20035
  entry: join6(manualDir, "README.md"),
19977
20036
  sdkVersion,
20037
+ refreshCommand: SDK_REFRESH_COMMAND,
20038
+ ...command.checkLatest ? { latestVersion, outdated } : {},
19978
20039
  project: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,
19979
20040
  hub: hub ? {
19980
20041
  root: hub.root,
@@ -19992,6 +20053,13 @@ function runDocsCommand(command, dependencies) {
19992
20053
  ];
19993
20054
  if (project)
19994
20055
  lines.push(`Current plugin: ${project.pluginId} (${project.root})`);
20056
+ if (outdated) {
20057
+ lines.push(`Manual is behind: ${sdkVersion} → ${latestVersion}. Refresh it with: ${SDK_REFRESH_COMMAND}`);
20058
+ } else if (command.checkLatest && latestVersion === undefined) {
20059
+ lines.push(`Could not reach the registry; cannot tell whether ${sdkVersion ?? "this manual"} is current.`);
20060
+ } else {
20061
+ lines.push(`Manual follows the installed SDK. To refresh it: ${SDK_REFRESH_COMMAND}`);
20062
+ }
19995
20063
  if (hub) {
19996
20064
  lines.push(`Marketplace index: ${hub.manifestPath}`);
19997
20065
  lines.push("After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.");
@@ -20001,6 +20069,24 @@ function runDocsCommand(command, dependencies) {
20001
20069
  `);
20002
20070
  return 0;
20003
20071
  }
20072
+ var SDK_REFRESH_COMMAND = "npm i -D @vetta-org/plugin-sdk@latest && npx vetta-plugin-cli docs";
20073
+ function compareSemver(left, right) {
20074
+ const parse2 = (value) => {
20075
+ const match2 = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(value.trim());
20076
+ if (!match2)
20077
+ return [0, 0, 0, false];
20078
+ return [Number(match2[1]), Number(match2[2]), Number(match2[3]), match2[4] !== undefined];
20079
+ };
20080
+ const a = parse2(left);
20081
+ const b2 = parse2(right);
20082
+ for (let index = 0;index < 3; index += 1) {
20083
+ if (a[index] !== b2[index])
20084
+ return a[index] < b2[index] ? -1 : 1;
20085
+ }
20086
+ if (a[3] === b2[3])
20087
+ return 0;
20088
+ return a[3] ? -1 : 1;
20089
+ }
20004
20090
  function runInitCommand(command, dependencies) {
20005
20091
  const cwd = dependencies.cwd?.() ?? process.cwd();
20006
20092
  try {
@@ -20213,6 +20299,7 @@ export {
20213
20299
  renderHubAgentsGuide,
20214
20300
  renderAgentsGuide,
20215
20301
  readManualSdkVersion,
20302
+ readLatestNpmVersion,
20216
20303
  parsePluginWatchCommand,
20217
20304
  parsePluginUninstallCommand,
20218
20305
  parsePluginSyncCommand,
package/dist/init.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** 与脚手架一同落地的依赖范围;两个包各自独立发布,不要合成一个版本。 */
2
- export declare const DEFAULT_SDK_RANGE = "^0.3.1";
2
+ export declare const DEFAULT_SDK_RANGE = "^0.3.2";
3
3
  export declare const DEFAULT_VITE_RANGE = "^0.2.0";
4
4
  export interface InitPluginInput {
5
5
  readonly targetDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAKA,0GAAwC;AACxC,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAI3C,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAUD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAkH1E;AAKD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6BAAiB;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2DAA6B;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CA6CpE","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { renderAgentsGuide } from \"./agents-template.js\";\nimport { renderHubAgentsGuide, renderHubReadme, renderHubWorkflow } from \"./hub-template.js\";\n\n/** 与脚手架一同落地的依赖范围;两个包各自独立发布,不要合成一个版本。 */\nexport const DEFAULT_SDK_RANGE = \"^0.3.1\";\nexport const DEFAULT_VITE_RANGE = \"^0.2.0\";\n\nconst PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;\n\nexport interface InitPluginInput {\n\treadonly targetDir: string;\n\treadonly pluginId: string;\n\treadonly displayName: string;\n\treadonly sdkRange?: string;\n\treadonly viteRange?: string;\n}\n\nexport interface InitPluginResult {\n\treadonly root: string;\n\treadonly pluginId: string;\n\treadonly files: readonly string[];\n}\n\nfunction remoteNameFromId(pluginId: string): string {\n\treturn pluginId.replace(/-/g, \"_\").replace(/[^A-Za-z0-9_$]/g, \"_\");\n}\n\nfunction json(value: unknown): string {\n\treturn `${JSON.stringify(value, null, \"\\t\")}\\n`;\n}\n\nexport function initPluginProject(input: InitPluginInput): InitPluginResult {\n\tif (!PLUGIN_ID_PATTERN.test(input.pluginId)) {\n\t\tthrow new Error(`Invalid plugin id ${JSON.stringify(input.pluginId)}: use lowercase kebab-case starting with a letter`);\n\t}\n\tconst root = resolve(input.targetDir);\n\tif (existsSync(join(root, \"plugin.json\"))) {\n\t\tthrow new Error(`Refusing to overwrite an existing plugin at ${root}`);\n\t}\n\n\tconst remote = remoteNameFromId(input.pluginId);\n\n\tconst files: Record<string, string> = {\n\t\t\"plugin.json\": json({\n\t\t\tid: input.pluginId,\n\t\t\tname: input.displayName,\n\t\t\tversion: \"0.1.0\",\n\t\t\tpluginApiVersion: \"^2.0.0\",\n\t\t\tentry: \"dist/mf-manifest.json\",\n\t\t\tmoduleFederation: { remoteName: remote, expose: \"./plugin\" },\n\t\t\tstyles: [\"dist/style.css\"],\n\t\t\tpermissions: [],\n\t\t\tdescription: input.displayName,\n\t\t\tauthor: \"\",\n\t\t\ticon: \"solar:widget-add-bold\",\n\t\t\tguidingWords: [],\n\t\t}),\n\t\t\"package.json\": json({\n\t\t\tname: input.pluginId,\n\t\t\tversion: \"0.1.0\",\n\t\t\tprivate: true,\n\t\t\ttype: \"module\",\n\t\t\tscripts: {\n\t\t\t\tdev: \"vetta-plugin dev\",\n\t\t\t\tbuild: \"vite build\",\n\t\t\t\tcheck: \"tsc --noEmit\",\n\t\t\t\tpack: \"vetta-plugin pack\",\n\t\t\t\tvalidate: \"vetta-plugin validate\",\n\t\t\t\tdocs: \"vetta-plugin-cli docs\",\n\t\t\t\t// 一条命令走完「构建 → 打包 → 装进正在运行的 Vetta」。\n\t\t\t\t\"install:vetta\": \"vite build && vetta-plugin pack && vetta-plugin-cli add .\",\n\t\t\t},\n\t\t\tdevDependencies: {\n\t\t\t\t\"@tailwindcss/vite\": \"^4.1.12\",\n\t\t\t\t\"@types/react\": \"^19.1.1\",\n\t\t\t\t\"@types/react-dom\": \"^19.1.1\",\n\t\t\t\t\"@vetta-org/plugin-cli\": \"^0.1.1\",\n\t\t\t\t\"@vetta-org/plugin-sdk\": input.sdkRange ?? DEFAULT_SDK_RANGE,\n\t\t\t\t\"@vetta-org/plugin-vite\": input.viteRange ?? DEFAULT_VITE_RANGE,\n\t\t\t\treact: \"19.1.1\",\n\t\t\t\t\"react-dom\": \"19.1.1\",\n\t\t\t\ttailwindcss: \"^4.1.12\",\n\t\t\t\ttypescript: \"^5.9.2\",\n\t\t\t\tvite: \"^7.1.7\",\n\t\t\t},\n\t\t}),\n\t\t\"tsconfig.json\": json({\n\t\t\tcompilerOptions: {\n\t\t\t\ttarget: \"ES2022\",\n\t\t\t\tmodule: \"ESNext\",\n\t\t\t\tlib: [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n\t\t\t\tstrict: true,\n\t\t\t\tesModuleInterop: true,\n\t\t\t\tskipLibCheck: true,\n\t\t\t\tmoduleResolution: \"bundler\",\n\t\t\t\tjsx: \"react-jsx\",\n\t\t\t\tjsxImportSource: \"react\",\n\t\t\t\tnoEmit: true,\n\t\t\t},\n\t\t\tinclude: [\"src/**/*.ts\", \"src/**/*.tsx\"],\n\t\t}),\n\t\t\"vite.config.ts\": `import tailwindcss from \"@tailwindcss/vite\";\nimport { vettaPluginFederation } from \"@vetta-org/plugin-vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n\tplugins: [\n\t\ttailwindcss(),\n\t\tvettaPluginFederation({\n\t\t\tname: \"${remote}\",\n\t\t\tentry: \"./src/index.tsx\",\n\t\t}),\n\t],\n\tesbuild: { jsx: \"automatic\", jsxImportSource: \"react\" },\n});\n`,\n\t\t\"src/index.tsx\": `import { definePlugin } from \"@vetta-org/plugin-sdk\";\n// Tailwind pipeline only — business CSS here would leak into the host page.\nimport \"./style.css\";\n\nexport default definePlugin({\n\tactivate(ctx) {\n\t\t// Read the manual before adding contributions: npx vetta-plugin-cli docs\n\t\tvoid ctx;\n\t},\n});\n`,\n\t\t\"src/style.css\": `/* Tailwind entry only. No business selectors — they inject into the host page. */\n@layer theme, base, components, utilities;\n@import \"tailwindcss/theme.css\" layer(theme);\n@import \"tailwindcss/utilities.css\" layer(utilities);\n`,\n\t\t// dist/ 刻意不忽略:插件通过仓库目录分发时,宿主直接读 plugin.json 指向的 entry 与\n\t\t// styles,它不会替你构建——目录里没有构建产物就装不上,而且那是一个只在别人机器上\n\t\t// 复现的失败。\n\t\t\".gitignore\": \"release/\\nnode_modules/\\n\",\n\t\t\"AGENTS.md\": renderAgentsGuide({ pluginId: input.pluginId, displayName: input.displayName }),\n\t};\n\n\tmkdirSync(join(root, \"src\"), { recursive: true });\n\tfor (const [relativePath, content] of Object.entries(files)) {\n\t\twriteFileSync(join(root, relativePath), content, \"utf8\");\n\t}\n\n\treturn { root, pluginId: input.pluginId, files: Object.keys(files).sort() };\n}\n\nconst HUB_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\nconst APP_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/;\n\nexport interface InitHubInput {\n\treadonly targetDir: string;\n\t/** 市场名(slug)。 */\n\treadonly name: string;\n\treadonly repository: string;\n\t/** 本市场的能力所支持的最老 Vetta 版本。 */\n\treadonly minAppVersion: string;\n}\n\nexport interface InitHubResult {\n\treadonly root: string;\n\treadonly name: string;\n\treadonly files: readonly string[];\n}\n\n/** 能力按类型分目录;空目录留 .gitkeep,否则 git 不会带上它们,作者得自己猜该放哪。 */\nconst HUB_ABILITY_DIRS = [\"plugins\", \"mcp\", \"skills\", \"scenes\"] as const;\n\nexport function initHubRepository(input: InitHubInput): InitHubResult {\n\tif (!HUB_NAME_PATTERN.test(input.name)) {\n\t\tthrow new Error(`Invalid marketplace name ${JSON.stringify(input.name)}: use lowercase kebab-case`);\n\t}\n\tlet repository: URL;\n\ttry {\n\t\trepository = new URL(input.repository);\n\t} catch {\n\t\tthrow new Error(`Invalid repository URL: ${input.repository}`);\n\t}\n\tif (repository.protocol !== \"https:\") throw new Error(\"Repository URL must use https://\");\n\tif (!APP_VERSION_PATTERN.test(input.minAppVersion)) {\n\t\tthrow new Error(`Invalid --min-app-version ${JSON.stringify(input.minAppVersion)}: expected a version like 0.55.0`);\n\t}\n\n\tconst root = resolve(input.targetDir);\n\tconst manifestRelativePath = join(\".vetta\", \"marketplace.json\");\n\tif (existsSync(join(root, manifestRelativePath))) {\n\t\tthrow new Error(`Refusing to overwrite an existing marketplace at ${root}`);\n\t}\n\n\tconst files: Record<string, string> = {\n\t\t[manifestRelativePath]: json({\n\t\t\tschemaVersion: 2,\n\t\t\tname: input.name,\n\t\t\tmarketplaceVersion: \"1.0.0\",\n\t\t\trepository: repository.toString().replace(/\\/$/, \"\"),\n\t\t\tminAppVersion: input.minAppVersion,\n\t\t\tabilities: [],\n\t\t}),\n\t\t\"AGENTS.md\": renderHubAgentsGuide({ name: input.name }),\n\t\t\"README.md\": renderHubReadme({ name: input.name, repository: repository.toString().replace(/\\/$/, \"\") }),\n\t\t[join(\".github\", \"workflows\", \"marketplace.yml\")]: renderHubWorkflow(),\n\t\t// dist/ 刻意不忽略:客户端直接读能力目录安装,不会替作者构建。\n\t\t\".gitignore\": \"node_modules/\\nrelease/\\n\",\n\t};\n\tfor (const dir of HUB_ABILITY_DIRS) files[join(\"abilities\", dir, \".gitkeep\")] = \"\";\n\n\tfor (const [relativePath, content] of Object.entries(files)) {\n\t\tconst target = join(root, relativePath);\n\t\tmkdirSync(dirname(target), { recursive: true });\n\t\twriteFileSync(target, content, \"utf8\");\n\t}\n\n\treturn { root, name: input.name, files: Object.keys(files).sort() };\n}\n"]}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAKA,0GAAwC;AACxC,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAI3C,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAUD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAkH1E;AAKD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6BAAiB;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2DAA6B;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CA6CpE","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { renderAgentsGuide } from \"./agents-template.js\";\nimport { renderHubAgentsGuide, renderHubReadme, renderHubWorkflow } from \"./hub-template.js\";\n\n/** 与脚手架一同落地的依赖范围;两个包各自独立发布,不要合成一个版本。 */\nexport const DEFAULT_SDK_RANGE = \"^0.3.2\";\nexport const DEFAULT_VITE_RANGE = \"^0.2.0\";\n\nconst PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;\n\nexport interface InitPluginInput {\n\treadonly targetDir: string;\n\treadonly pluginId: string;\n\treadonly displayName: string;\n\treadonly sdkRange?: string;\n\treadonly viteRange?: string;\n}\n\nexport interface InitPluginResult {\n\treadonly root: string;\n\treadonly pluginId: string;\n\treadonly files: readonly string[];\n}\n\nfunction remoteNameFromId(pluginId: string): string {\n\treturn pluginId.replace(/-/g, \"_\").replace(/[^A-Za-z0-9_$]/g, \"_\");\n}\n\nfunction json(value: unknown): string {\n\treturn `${JSON.stringify(value, null, \"\\t\")}\\n`;\n}\n\nexport function initPluginProject(input: InitPluginInput): InitPluginResult {\n\tif (!PLUGIN_ID_PATTERN.test(input.pluginId)) {\n\t\tthrow new Error(`Invalid plugin id ${JSON.stringify(input.pluginId)}: use lowercase kebab-case starting with a letter`);\n\t}\n\tconst root = resolve(input.targetDir);\n\tif (existsSync(join(root, \"plugin.json\"))) {\n\t\tthrow new Error(`Refusing to overwrite an existing plugin at ${root}`);\n\t}\n\n\tconst remote = remoteNameFromId(input.pluginId);\n\n\tconst files: Record<string, string> = {\n\t\t\"plugin.json\": json({\n\t\t\tid: input.pluginId,\n\t\t\tname: input.displayName,\n\t\t\tversion: \"0.1.0\",\n\t\t\tpluginApiVersion: \"^2.0.0\",\n\t\t\tentry: \"dist/mf-manifest.json\",\n\t\t\tmoduleFederation: { remoteName: remote, expose: \"./plugin\" },\n\t\t\tstyles: [\"dist/style.css\"],\n\t\t\tpermissions: [],\n\t\t\tdescription: input.displayName,\n\t\t\tauthor: \"\",\n\t\t\ticon: \"solar:widget-add-bold\",\n\t\t\tguidingWords: [],\n\t\t}),\n\t\t\"package.json\": json({\n\t\t\tname: input.pluginId,\n\t\t\tversion: \"0.1.0\",\n\t\t\tprivate: true,\n\t\t\ttype: \"module\",\n\t\t\tscripts: {\n\t\t\t\tdev: \"vetta-plugin dev\",\n\t\t\t\tbuild: \"vite build\",\n\t\t\t\tcheck: \"tsc --noEmit\",\n\t\t\t\tpack: \"vetta-plugin pack\",\n\t\t\t\tvalidate: \"vetta-plugin validate\",\n\t\t\t\tdocs: \"vetta-plugin-cli docs\",\n\t\t\t\t// 一条命令走完「构建 → 打包 → 装进正在运行的 Vetta」。\n\t\t\t\t\"install:vetta\": \"vite build && vetta-plugin pack && vetta-plugin-cli add .\",\n\t\t\t},\n\t\t\tdevDependencies: {\n\t\t\t\t\"@tailwindcss/vite\": \"^4.1.12\",\n\t\t\t\t\"@types/react\": \"^19.1.1\",\n\t\t\t\t\"@types/react-dom\": \"^19.1.1\",\n\t\t\t\t\"@vetta-org/plugin-cli\": \"^0.1.1\",\n\t\t\t\t\"@vetta-org/plugin-sdk\": input.sdkRange ?? DEFAULT_SDK_RANGE,\n\t\t\t\t\"@vetta-org/plugin-vite\": input.viteRange ?? DEFAULT_VITE_RANGE,\n\t\t\t\treact: \"19.1.1\",\n\t\t\t\t\"react-dom\": \"19.1.1\",\n\t\t\t\ttailwindcss: \"^4.1.12\",\n\t\t\t\ttypescript: \"^5.9.2\",\n\t\t\t\tvite: \"^7.1.7\",\n\t\t\t},\n\t\t}),\n\t\t\"tsconfig.json\": json({\n\t\t\tcompilerOptions: {\n\t\t\t\ttarget: \"ES2022\",\n\t\t\t\tmodule: \"ESNext\",\n\t\t\t\tlib: [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n\t\t\t\tstrict: true,\n\t\t\t\tesModuleInterop: true,\n\t\t\t\tskipLibCheck: true,\n\t\t\t\tmoduleResolution: \"bundler\",\n\t\t\t\tjsx: \"react-jsx\",\n\t\t\t\tjsxImportSource: \"react\",\n\t\t\t\tnoEmit: true,\n\t\t\t},\n\t\t\tinclude: [\"src/**/*.ts\", \"src/**/*.tsx\"],\n\t\t}),\n\t\t\"vite.config.ts\": `import tailwindcss from \"@tailwindcss/vite\";\nimport { vettaPluginFederation } from \"@vetta-org/plugin-vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n\tplugins: [\n\t\ttailwindcss(),\n\t\tvettaPluginFederation({\n\t\t\tname: \"${remote}\",\n\t\t\tentry: \"./src/index.tsx\",\n\t\t}),\n\t],\n\tesbuild: { jsx: \"automatic\", jsxImportSource: \"react\" },\n});\n`,\n\t\t\"src/index.tsx\": `import { definePlugin } from \"@vetta-org/plugin-sdk\";\n// Tailwind pipeline only — business CSS here would leak into the host page.\nimport \"./style.css\";\n\nexport default definePlugin({\n\tactivate(ctx) {\n\t\t// Read the manual before adding contributions: npx vetta-plugin-cli docs\n\t\tvoid ctx;\n\t},\n});\n`,\n\t\t\"src/style.css\": `/* Tailwind entry only. No business selectors — they inject into the host page. */\n@layer theme, base, components, utilities;\n@import \"tailwindcss/theme.css\" layer(theme);\n@import \"tailwindcss/utilities.css\" layer(utilities);\n`,\n\t\t// dist/ 刻意不忽略:插件通过仓库目录分发时,宿主直接读 plugin.json 指向的 entry 与\n\t\t// styles,它不会替你构建——目录里没有构建产物就装不上,而且那是一个只在别人机器上\n\t\t// 复现的失败。\n\t\t\".gitignore\": \"release/\\nnode_modules/\\n\",\n\t\t\"AGENTS.md\": renderAgentsGuide({ pluginId: input.pluginId, displayName: input.displayName }),\n\t};\n\n\tmkdirSync(join(root, \"src\"), { recursive: true });\n\tfor (const [relativePath, content] of Object.entries(files)) {\n\t\twriteFileSync(join(root, relativePath), content, \"utf8\");\n\t}\n\n\treturn { root, pluginId: input.pluginId, files: Object.keys(files).sort() };\n}\n\nconst HUB_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\nconst APP_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/;\n\nexport interface InitHubInput {\n\treadonly targetDir: string;\n\t/** 市场名(slug)。 */\n\treadonly name: string;\n\treadonly repository: string;\n\t/** 本市场的能力所支持的最老 Vetta 版本。 */\n\treadonly minAppVersion: string;\n}\n\nexport interface InitHubResult {\n\treadonly root: string;\n\treadonly name: string;\n\treadonly files: readonly string[];\n}\n\n/** 能力按类型分目录;空目录留 .gitkeep,否则 git 不会带上它们,作者得自己猜该放哪。 */\nconst HUB_ABILITY_DIRS = [\"plugins\", \"mcp\", \"skills\", \"scenes\"] as const;\n\nexport function initHubRepository(input: InitHubInput): InitHubResult {\n\tif (!HUB_NAME_PATTERN.test(input.name)) {\n\t\tthrow new Error(`Invalid marketplace name ${JSON.stringify(input.name)}: use lowercase kebab-case`);\n\t}\n\tlet repository: URL;\n\ttry {\n\t\trepository = new URL(input.repository);\n\t} catch {\n\t\tthrow new Error(`Invalid repository URL: ${input.repository}`);\n\t}\n\tif (repository.protocol !== \"https:\") throw new Error(\"Repository URL must use https://\");\n\tif (!APP_VERSION_PATTERN.test(input.minAppVersion)) {\n\t\tthrow new Error(`Invalid --min-app-version ${JSON.stringify(input.minAppVersion)}: expected a version like 0.55.0`);\n\t}\n\n\tconst root = resolve(input.targetDir);\n\tconst manifestRelativePath = join(\".vetta\", \"marketplace.json\");\n\tif (existsSync(join(root, manifestRelativePath))) {\n\t\tthrow new Error(`Refusing to overwrite an existing marketplace at ${root}`);\n\t}\n\n\tconst files: Record<string, string> = {\n\t\t[manifestRelativePath]: json({\n\t\t\tschemaVersion: 2,\n\t\t\tname: input.name,\n\t\t\tmarketplaceVersion: \"1.0.0\",\n\t\t\trepository: repository.toString().replace(/\\/$/, \"\"),\n\t\t\tminAppVersion: input.minAppVersion,\n\t\t\tabilities: [],\n\t\t}),\n\t\t\"AGENTS.md\": renderHubAgentsGuide({ name: input.name }),\n\t\t\"README.md\": renderHubReadme({ name: input.name, repository: repository.toString().replace(/\\/$/, \"\") }),\n\t\t[join(\".github\", \"workflows\", \"marketplace.yml\")]: renderHubWorkflow(),\n\t\t// dist/ 刻意不忽略:客户端直接读能力目录安装,不会替作者构建。\n\t\t\".gitignore\": \"node_modules/\\nrelease/\\n\",\n\t};\n\tfor (const dir of HUB_ABILITY_DIRS) files[join(\"abilities\", dir, \".gitkeep\")] = \"\";\n\n\tfor (const [relativePath, content] of Object.entries(files)) {\n\t\tconst target = join(root, relativePath);\n\t\tmkdirSync(dirname(target), { recursive: true });\n\t\twriteFileSync(target, content, \"utf8\");\n\t}\n\n\treturn { root, name: input.name, files: Object.keys(files).sort() };\n}\n"]}
@@ -23,5 +23,12 @@ export interface NpmPackResult {
23
23
  }
24
24
  export type NpmPackRunner = (packageSpec: string, destination: string) => Promise<NpmPackResult>;
25
25
  export declare const runNpmPack: NpmPackRunner;
26
+ /**
27
+ * 查询 registry 上某个包的最新版本。
28
+ *
29
+ * 走 `npm view` 而不是直接请求 registry:用户的私服地址、代理、鉴权都配在 npm 里,自己发
30
+ * 请求等于把那套配置重写一遍。查不到(离线、私服没有这个包)返回 undefined,调用方降级。
31
+ */
32
+ export declare function readLatestNpmVersion(packageName: string): Promise<string | undefined>;
26
33
  export declare function resolveNpmPluginArchive(packageSpec: string, pack?: NpmPackRunner): Promise<ResolvedNpmPluginArchive>;
27
34
  //# sourceMappingURL=npm-package.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"npm-package.d.ts","sourceRoot":"","sources":["../src/npm-package.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,wBAAwB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,wBAAwB,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,wFAAwF;AACxF,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QACN,aAAa,EAAE,CAAC,CAAC;QACjB,IAAI,EAAE,gBAAgB,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CACF;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAyBjG,eAAO,MAAM,UAAU,EAAE,aA4BxB,CAAC;AA0CF,wBAAsB,uBAAuB,CAC5C,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,aAA0B,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CAsCnC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { mkdtemp, readFile, realpath, rm, stat } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { basename, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseVettaNpmPluginPackage } from \"@vetta-org/plugin-sdk/npm-package\";\nimport npa from \"npm-package-arg\";\nimport { x as extractTar } from \"tar\";\n\nconst MAX_NPM_TARBALL_BYTES = 512 * 1024 * 1024;\nconst MAX_PLUGIN_ARCHIVE_BYTES = 512 * 1024 * 1024;\nconst REGISTRY_SPEC_TYPES = new Set([\"tag\", \"version\", \"range\"]);\n\nexport interface ResolvedNpmPluginArchive {\n\tarchivePath: string;\n\tcleanup(): Promise<void>;\n\texpectedSha256: string;\n\tintegrity?: string;\n\tpackageManifest: NpmPluginPackageManifest;\n\trequestedSpec: string;\n}\n\n/** Public projection kept local so the bundled CLI has no published type dependency. */\nexport interface NpmPluginPackageManifest {\n\tname: string;\n\tversion: string;\n\tvetta: {\n\t\tschemaVersion: 1;\n\t\ttype: \"desktop-plugin\";\n\t\tpluginId: string;\n\t\tarchive: string;\n\t};\n}\n\nexport interface NpmPackResult {\n\tfilename: string;\n\tintegrity?: string;\n}\n\nexport type NpmPackRunner = (packageSpec: string, destination: string) => Promise<NpmPackResult>;\n\nfunction parsePackOutput(stdout: string): NpmPackResult {\n\tconst parsed: unknown = JSON.parse(stdout);\n\tif (!Array.isArray(parsed) || parsed.length !== 1 || typeof parsed[0] !== \"object\" || parsed[0] === null) {\n\t\tthrow new Error(\"npm pack returned an invalid JSON response\");\n\t}\n\tconst entry = parsed[0] as Record<string, unknown>;\n\tif (typeof entry.filename !== \"string\" || entry.filename.length === 0 || basename(entry.filename) !== entry.filename) {\n\t\tthrow new Error(\"npm pack returned an invalid tarball filename\");\n\t}\n\treturn {\n\t\tfilename: entry.filename,\n\t\tintegrity: typeof entry.integrity === \"string\" ? entry.integrity : undefined,\n\t};\n}\n\nfunction npmInvocation(): { command: string; args: string[] } {\n\tconst npmExecPath = process.env.npm_execpath;\n\tif (npmExecPath) {\n\t\treturn { command: process.execPath, args: [npmExecPath] };\n\t}\n\treturn { command: process.platform === \"win32\" ? \"npm.cmd\" : \"npm\", args: [] };\n}\n\nexport const runNpmPack: NpmPackRunner = async (packageSpec, destination) => {\n\tconst invocation = npmInvocation();\n\tconst args = [\n\t\t...invocation.args,\n\t\t\"pack\",\n\t\tpackageSpec,\n\t\t\"--ignore-scripts\",\n\t\t\"--json\",\n\t\t\"--pack-destination\",\n\t\tdestination,\n\t];\n\tconst output = await new Promise<string>((resolvePromise, rejectPromise) => {\n\t\tconst child = spawn(invocation.command, args, { shell: false, windowsHide: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout.setEncoding(\"utf8\").on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\t\tchild.stderr.setEncoding(\"utf8\").on(\"data\", (chunk: string) => {\n\t\t\tstderr += chunk;\n\t\t});\n\t\tchild.once(\"error\", rejectPromise);\n\t\tchild.once(\"exit\", (code, signal) => {\n\t\t\tif (code === 0) resolvePromise(stdout);\n\t\t\telse rejectPromise(new Error(stderr.trim() || `npm pack failed (code=${code}, signal=${signal})`));\n\t\t});\n\t});\n\treturn parsePackOutput(output);\n};\n\nfunction registryPackageName(packageSpec: string): string {\n\tconst parsed = npa(packageSpec);\n\tif (!parsed.registry || !parsed.name || !REGISTRY_SPEC_TYPES.has(parsed.type)) {\n\t\tthrow new Error(\"add accepts npm registry package names, tags, versions, or ranges\");\n\t}\n\treturn parsed.name;\n}\n\nfunction assertPathInside(root: string, candidate: string, label: string): void {\n\tconst rel = relative(root, candidate);\n\tif (!rel || rel.startsWith(\"..\") || isAbsolute(rel)) throw new Error(`${label} escapes the temporary package root`);\n}\n\nasync function extractRegularFile(tarballPath: string, destination: string, archivePath: string): Promise<string> {\n\tconst tarPath = `package/${archivePath.replace(/\\\\/g, \"/\")}`;\n\tlet matched = false;\n\tawait extractTar({\n\t\tfile: tarballPath,\n\t\tcwd: destination,\n\t\tstrict: true,\n\t\tfilter(path, entry) {\n\t\t\tif (path !== tarPath) return false;\n\t\t\tif (!(\"type\" in entry) || entry.type !== \"File\") {\n\t\t\t\tthrow new Error(`npm package entry must be a regular file: ${archivePath}`);\n\t\t\t}\n\t\t\tif (matched) throw new Error(`npm package entry is duplicated: ${archivePath}`);\n\t\t\tmatched = true;\n\t\t\treturn true;\n\t\t},\n\t\tstrip: 1,\n\t});\n\tif (!matched) throw new Error(`npm package entry is missing: ${archivePath}`);\n\tconst outputPath = resolve(destination, archivePath);\n\tassertPathInside(destination, outputPath, \"npm package entry\");\n\tconst [resolvedRoot, resolvedOutput, info] = await Promise.all([realpath(destination), realpath(outputPath), stat(outputPath)]);\n\tassertPathInside(resolvedRoot, resolvedOutput, \"npm package entry\");\n\tif (!info.isFile()) throw new Error(`npm package entry must be a regular file: ${archivePath}`);\n\treturn resolvedOutput;\n}\n\nexport async function resolveNpmPluginArchive(\n\tpackageSpec: string,\n\tpack: NpmPackRunner = runNpmPack,\n): Promise<ResolvedNpmPluginArchive> {\n\tconst requestedPackageName = registryPackageName(packageSpec);\n\tconst temporaryRoot = await mkdtemp(join(tmpdir(), \"vetta-plugin-add-\"));\n\tconst cleanup = () => rm(temporaryRoot, { recursive: true, force: true });\n\ttry {\n\t\tconst packed = await pack(packageSpec, temporaryRoot);\n\t\tconst tarballPath = resolve(temporaryRoot, packed.filename);\n\t\tassertPathInside(temporaryRoot, tarballPath, \"npm tarball\");\n\t\tconst tarballInfo = await stat(tarballPath);\n\t\tif (!tarballInfo.isFile() || tarballInfo.size > MAX_NPM_TARBALL_BYTES) {\n\t\t\tthrow new Error(\"npm plugin package tarball is missing or exceeds the 512 MB limit\");\n\t\t}\n\n\t\tconst packageJsonPath = await extractRegularFile(tarballPath, temporaryRoot, \"package.json\");\n\t\tconst packageManifest = parseVettaNpmPluginPackage(JSON.parse(await readFile(packageJsonPath, \"utf8\")) as unknown);\n\t\tif (packageManifest.name !== requestedPackageName) {\n\t\t\tthrow new Error(\n\t\t\t\t`npm package name mismatch: requested ${requestedPackageName}, received ${packageManifest.name}`,\n\t\t\t);\n\t\t}\n\t\tconst archivePath = await extractRegularFile(tarballPath, temporaryRoot, packageManifest.vetta.archive);\n\t\tconst archive = await readFile(archivePath);\n\t\tif (archive.length > MAX_PLUGIN_ARCHIVE_BYTES) {\n\t\t\tthrow new Error(\"Vetta plugin archive exceeds the 512 MB limit\");\n\t\t}\n\n\t\treturn {\n\t\t\tarchivePath,\n\t\t\tcleanup,\n\t\t\texpectedSha256: createHash(\"sha256\").update(archive).digest(\"hex\"),\n\t\t\tintegrity: packed.integrity,\n\t\t\tpackageManifest,\n\t\t\trequestedSpec: packageSpec,\n\t\t};\n\t} catch (error) {\n\t\tawait cleanup();\n\t\tthrow error;\n\t}\n}\n"]}
1
+ {"version":3,"file":"npm-package.d.ts","sourceRoot":"","sources":["../src/npm-package.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,wBAAwB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,wBAAwB,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,wFAAwF;AACxF,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QACN,aAAa,EAAE,CAAC,CAAC;QACjB,IAAI,EAAE,gBAAgB,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CACF;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAyBjG,eAAO,MAAM,UAAU,EAAE,aA4BxB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0B3F;AA0CD,wBAAsB,uBAAuB,CAC5C,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,aAA0B,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CAsCnC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { mkdtemp, readFile, realpath, rm, stat } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { basename, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseVettaNpmPluginPackage } from \"@vetta-org/plugin-sdk/npm-package\";\nimport npa from \"npm-package-arg\";\nimport { x as extractTar } from \"tar\";\n\nconst MAX_NPM_TARBALL_BYTES = 512 * 1024 * 1024;\nconst MAX_PLUGIN_ARCHIVE_BYTES = 512 * 1024 * 1024;\nconst REGISTRY_SPEC_TYPES = new Set([\"tag\", \"version\", \"range\"]);\n\nexport interface ResolvedNpmPluginArchive {\n\tarchivePath: string;\n\tcleanup(): Promise<void>;\n\texpectedSha256: string;\n\tintegrity?: string;\n\tpackageManifest: NpmPluginPackageManifest;\n\trequestedSpec: string;\n}\n\n/** Public projection kept local so the bundled CLI has no published type dependency. */\nexport interface NpmPluginPackageManifest {\n\tname: string;\n\tversion: string;\n\tvetta: {\n\t\tschemaVersion: 1;\n\t\ttype: \"desktop-plugin\";\n\t\tpluginId: string;\n\t\tarchive: string;\n\t};\n}\n\nexport interface NpmPackResult {\n\tfilename: string;\n\tintegrity?: string;\n}\n\nexport type NpmPackRunner = (packageSpec: string, destination: string) => Promise<NpmPackResult>;\n\nfunction parsePackOutput(stdout: string): NpmPackResult {\n\tconst parsed: unknown = JSON.parse(stdout);\n\tif (!Array.isArray(parsed) || parsed.length !== 1 || typeof parsed[0] !== \"object\" || parsed[0] === null) {\n\t\tthrow new Error(\"npm pack returned an invalid JSON response\");\n\t}\n\tconst entry = parsed[0] as Record<string, unknown>;\n\tif (typeof entry.filename !== \"string\" || entry.filename.length === 0 || basename(entry.filename) !== entry.filename) {\n\t\tthrow new Error(\"npm pack returned an invalid tarball filename\");\n\t}\n\treturn {\n\t\tfilename: entry.filename,\n\t\tintegrity: typeof entry.integrity === \"string\" ? entry.integrity : undefined,\n\t};\n}\n\nfunction npmInvocation(): { command: string; args: string[] } {\n\tconst npmExecPath = process.env.npm_execpath;\n\tif (npmExecPath) {\n\t\treturn { command: process.execPath, args: [npmExecPath] };\n\t}\n\treturn { command: process.platform === \"win32\" ? \"npm.cmd\" : \"npm\", args: [] };\n}\n\nexport const runNpmPack: NpmPackRunner = async (packageSpec, destination) => {\n\tconst invocation = npmInvocation();\n\tconst args = [\n\t\t...invocation.args,\n\t\t\"pack\",\n\t\tpackageSpec,\n\t\t\"--ignore-scripts\",\n\t\t\"--json\",\n\t\t\"--pack-destination\",\n\t\tdestination,\n\t];\n\tconst output = await new Promise<string>((resolvePromise, rejectPromise) => {\n\t\tconst child = spawn(invocation.command, args, { shell: false, windowsHide: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout.setEncoding(\"utf8\").on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\t\tchild.stderr.setEncoding(\"utf8\").on(\"data\", (chunk: string) => {\n\t\t\tstderr += chunk;\n\t\t});\n\t\tchild.once(\"error\", rejectPromise);\n\t\tchild.once(\"exit\", (code, signal) => {\n\t\t\tif (code === 0) resolvePromise(stdout);\n\t\t\telse rejectPromise(new Error(stderr.trim() || `npm pack failed (code=${code}, signal=${signal})`));\n\t\t});\n\t});\n\treturn parsePackOutput(output);\n};\n\n/**\n * 查询 registry 上某个包的最新版本。\n *\n * 走 `npm view` 而不是直接请求 registry:用户的私服地址、代理、鉴权都配在 npm 里,自己发\n * 请求等于把那套配置重写一遍。查不到(离线、私服没有这个包)返回 undefined,调用方降级。\n */\nexport async function readLatestNpmVersion(packageName: string): Promise<string | undefined> {\n\tconst invocation = npmInvocation();\n\tconst args = [...invocation.args, \"view\", packageName, \"version\", \"--json\"];\n\ttry {\n\t\tconst output = await new Promise<string>((resolvePromise, rejectPromise) => {\n\t\t\tconst child = spawn(invocation.command, args, {\n\t\t\t\tshell: false,\n\t\t\t\twindowsHide: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t\tlet stdout = \"\";\n\t\t\tchild.stdout.setEncoding(\"utf8\").on(\"data\", (chunk: string) => {\n\t\t\t\tstdout += chunk;\n\t\t\t});\n\t\t\tchild.stderr.resume();\n\t\t\tchild.once(\"error\", rejectPromise);\n\t\t\tchild.once(\"exit\", (code) => (code === 0 ? resolvePromise(stdout) : rejectPromise(new Error(\"npm view failed\"))));\n\t\t});\n\t\tconst parsed: unknown = JSON.parse(output);\n\t\tif (typeof parsed === \"string\") return parsed;\n\t\t// 带上 range 时 npm 会返回一个数组,最后一个是最新的。\n\t\tif (Array.isArray(parsed) && typeof parsed.at(-1) === \"string\") return parsed.at(-1) as string;\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction registryPackageName(packageSpec: string): string {\n\tconst parsed = npa(packageSpec);\n\tif (!parsed.registry || !parsed.name || !REGISTRY_SPEC_TYPES.has(parsed.type)) {\n\t\tthrow new Error(\"add accepts npm registry package names, tags, versions, or ranges\");\n\t}\n\treturn parsed.name;\n}\n\nfunction assertPathInside(root: string, candidate: string, label: string): void {\n\tconst rel = relative(root, candidate);\n\tif (!rel || rel.startsWith(\"..\") || isAbsolute(rel)) throw new Error(`${label} escapes the temporary package root`);\n}\n\nasync function extractRegularFile(tarballPath: string, destination: string, archivePath: string): Promise<string> {\n\tconst tarPath = `package/${archivePath.replace(/\\\\/g, \"/\")}`;\n\tlet matched = false;\n\tawait extractTar({\n\t\tfile: tarballPath,\n\t\tcwd: destination,\n\t\tstrict: true,\n\t\tfilter(path, entry) {\n\t\t\tif (path !== tarPath) return false;\n\t\t\tif (!(\"type\" in entry) || entry.type !== \"File\") {\n\t\t\t\tthrow new Error(`npm package entry must be a regular file: ${archivePath}`);\n\t\t\t}\n\t\t\tif (matched) throw new Error(`npm package entry is duplicated: ${archivePath}`);\n\t\t\tmatched = true;\n\t\t\treturn true;\n\t\t},\n\t\tstrip: 1,\n\t});\n\tif (!matched) throw new Error(`npm package entry is missing: ${archivePath}`);\n\tconst outputPath = resolve(destination, archivePath);\n\tassertPathInside(destination, outputPath, \"npm package entry\");\n\tconst [resolvedRoot, resolvedOutput, info] = await Promise.all([realpath(destination), realpath(outputPath), stat(outputPath)]);\n\tassertPathInside(resolvedRoot, resolvedOutput, \"npm package entry\");\n\tif (!info.isFile()) throw new Error(`npm package entry must be a regular file: ${archivePath}`);\n\treturn resolvedOutput;\n}\n\nexport async function resolveNpmPluginArchive(\n\tpackageSpec: string,\n\tpack: NpmPackRunner = runNpmPack,\n): Promise<ResolvedNpmPluginArchive> {\n\tconst requestedPackageName = registryPackageName(packageSpec);\n\tconst temporaryRoot = await mkdtemp(join(tmpdir(), \"vetta-plugin-add-\"));\n\tconst cleanup = () => rm(temporaryRoot, { recursive: true, force: true });\n\ttry {\n\t\tconst packed = await pack(packageSpec, temporaryRoot);\n\t\tconst tarballPath = resolve(temporaryRoot, packed.filename);\n\t\tassertPathInside(temporaryRoot, tarballPath, \"npm tarball\");\n\t\tconst tarballInfo = await stat(tarballPath);\n\t\tif (!tarballInfo.isFile() || tarballInfo.size > MAX_NPM_TARBALL_BYTES) {\n\t\t\tthrow new Error(\"npm plugin package tarball is missing or exceeds the 512 MB limit\");\n\t\t}\n\n\t\tconst packageJsonPath = await extractRegularFile(tarballPath, temporaryRoot, \"package.json\");\n\t\tconst packageManifest = parseVettaNpmPluginPackage(JSON.parse(await readFile(packageJsonPath, \"utf8\")) as unknown);\n\t\tif (packageManifest.name !== requestedPackageName) {\n\t\t\tthrow new Error(\n\t\t\t\t`npm package name mismatch: requested ${requestedPackageName}, received ${packageManifest.name}`,\n\t\t\t);\n\t\t}\n\t\tconst archivePath = await extractRegularFile(tarballPath, temporaryRoot, packageManifest.vetta.archive);\n\t\tconst archive = await readFile(archivePath);\n\t\tif (archive.length > MAX_PLUGIN_ARCHIVE_BYTES) {\n\t\t\tthrow new Error(\"Vetta plugin archive exceeds the 512 MB limit\");\n\t\t}\n\n\t\treturn {\n\t\t\tarchivePath,\n\t\t\tcleanup,\n\t\t\texpectedSha256: createHash(\"sha256\").update(archive).digest(\"hex\"),\n\t\t\tintegrity: packed.integrity,\n\t\t\tpackageManifest,\n\t\t\trequestedSpec: packageSpec,\n\t\t};\n\t} catch (error) {\n\t\tawait cleanup();\n\t\tthrow error;\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vetta-org/plugin-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Install npm-distributed plugins into Vetta Desktop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",