@vetta-org/plugin-cli 0.1.4 → 0.1.5

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.5] — 2026-09-14
6
+
7
+ ### Changed
8
+
9
+ - **脚手架的 `AGENTS.md` 削薄成纯指引**:原先写在里面的「不可违反的几条」(Tailwind-only、
10
+ 错误必须 notify、最小权限、MF 顶层 JSX、`agent_mode` 已废弃、依赖用发布版本、`dist/` 进
11
+ 版本库)全部移进手册的 `README.md#不可违反的红线`。写进说明书的规则会在所有存量工程里就地
12
+ 凝固——它是 `init` 当天的快照,之后既不自更新、用户也没有理由回头看它。规则放进手册才能
13
+ 随 SDK 升级一起到位;说明书越薄,需要回头迁移老仓库的理由就越少。
14
+
15
+ ### Added
16
+
17
+ - `AGENTS.md` 带版本戳(`<!-- vetta-guide-revision: N -->`),`docs` 每次比对并在落后时打印
18
+ `This brief is stale ... npx @vetta-org/plugin-cli init --refresh-guide`。此前「说明书旧了」
19
+ 只能靠人记得,而这正是它凝固的原因。没有 `AGENTS.md` 的工程不提示——「没有」不是「旧」。
20
+
5
21
  ## [0.1.4] — 2026-09-14
6
22
 
7
23
  ### Added
package/README.md CHANGED
@@ -28,6 +28,11 @@ is purely derived and holds no user content — leaving source, manifest and con
28
28
  the id and display name from the `plugin.json` already on disk. At a marketplace root it rewrites the
29
29
  hub brief instead.
30
30
 
31
+ You will rarely have to remember this: the brief carries a revision stamp, and `docs` compares it on
32
+ every run and says outright when it is behind. The brief itself holds no rules — those live in the
33
+ manual, which travels with the SDK — so it changes seldom, and a project that refreshes once keeps
34
+ itself current from then on.
35
+
31
36
  ## Remove a plugin
32
37
 
33
38
  ```bash
@@ -7,8 +7,21 @@
7
7
  * 因此任何 Agent(Claude Code、Cursor、Vetta 自己)在任何陌生目录都能自举,而且读到的
8
8
  * 永远是这个工程实际编译所针对的那份合同。
9
9
  *
10
+ * **这里刻意只留指引,不留知识。** 写进这个文件的每一条规则都会在所有存量工程里就地凝固:
11
+ * 它是 `init` 当天的快照,之后既不会自更新,用户也没有理由回来看它。规则属于手册——手册
12
+ * 随 SDK 升级一起到位。这个文件越薄,需要回头迁移老仓库的理由就越少。
13
+ *
10
14
  * 单位是插件目录本身:外面是能力市场仓库、是单插件仓库、还是一堆别的东西,都不影响这里。
11
15
  */
16
+ /**
17
+ * 说明书的版本戳。
18
+ *
19
+ * `docs` 读它来判断一份说明书是不是旧的——没有这个戳,「该不该刷新」就只能靠人记得,而这
20
+ * 恰恰是它凝固的原因。**改动模板内容时必须一并推进它**,否则存量工程不会收到提示。
21
+ */
22
+ export declare const AGENTS_GUIDE_REVISION = 2;
23
+ /** 从一份 AGENTS.md 正文里读出版本戳;不是本模板生成的(或早于版本戳)时返回 undefined。 */
24
+ export declare function readAgentsGuideRevision(content: string): number | undefined;
12
25
  export declare function renderAgentsGuide(input: {
13
26
  pluginId: string;
14
27
  displayName: string;
@@ -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,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"]}
1
+ {"version":3,"file":"agents-template.d.ts","sourceRoot":"","sources":["../src/agents-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,4HAA4D;AAC5D,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAwD1F","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 * 它是 `init` 当天的快照,之后既不会自更新,用户也没有理由回来看它。规则属于手册——手册\n * 随 SDK 升级一起到位。这个文件越薄,需要回头迁移老仓库的理由就越少。\n *\n * 单位是插件目录本身:外面是能力市场仓库、是单插件仓库、还是一堆别的东西,都不影响这里。\n */\n\n/**\n * 说明书的版本戳。\n *\n * `docs` 读它来判断一份说明书是不是旧的——没有这个戳,「该不该刷新」就只能靠人记得,而这\n * 恰恰是它凝固的原因。**改动模板内容时必须一并推进它**,否则存量工程不会收到提示。\n */\nexport const AGENTS_GUIDE_REVISION = 2;\n\n/** 从一份 AGENTS.md 正文里读出版本戳;不是本模板生成的(或早于版本戳)时返回 undefined。 */\nexport function readAgentsGuideRevision(content: string): number | undefined {\n\tconst match = /<!--\\s*vetta-guide-revision:\\s*(\\d+)\\s*-->/.exec(content);\n\tif (!match) return undefined;\n\treturn Number(match[1]);\n}\n\nexport function renderAgentsGuide(input: { pluginId: string; displayName: string }): string {\n\treturn `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->\n# ${input.displayName}\n\nVetta 桌面插件工程(插件 id:\\`${input.pluginId}\\`)。\n\n**本文件不讲规则,只告诉你去哪读。** 规则在手册里,手册随 SDK 升级;写在这里的任何一条都会\n停在这个工程创建那天。两者冲突时一律以手册为准。\n\n## 第一步:装依赖,找到手册\n\n\\`\\`\\`bash\nnpm install\nnpx vetta-plugin-cli docs --check-latest\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 版本,以及本工程与所属 hub 的位置。\n**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件时各插件还可能钉不同版本。\n\n输出里出现 \\`Manual is behind\\` \\`This brief is stale\\` 就按它给的命令升级,再重跑一次。\n\n## 第二步:按顺序读手册\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\\` / \\`styling-and-pitfalls.md\\` |\n\n实现任一扩展点**之前**再读对应那章,不要凭记忆写 SDK API——这套合同变化很快。\n\n## 常用命令\n\n\\`\\`\\`bash\nnpm run dev # Vite + Module Federation 开发服务器\nnpm run build # 产出 dist/\nnpm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)\n\nnpx vetta-plugin-cli watch # 热更新:宿主改从本工程目录加载,改完即生效\nnpx vetta-plugin-cli reload ${input.pluginId} # 装完提示有 pending 版本时用它\nnpx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)\nnpx vetta-plugin-cli sync # hub 仓库根上跑:把索引与各能力目录对账\n\\`\\`\\`\n\n细节都在 \\`getting-started.md\\`。\\`docs\\` 打印了 \\`Marketplace index:\\` 就说明这个目录之上有能力\n市场索引,改完 \\`version\\` / \\`permissions\\` / \\`pluginApiVersion\\` 后要回仓库根跑 \\`sync\\`。\n\n## 信息不足时\n\n插件 id、展示名、要用哪些权限、功能边界、是否立刻安装——**问用户**,不要自己假定。\n`;\n}\n"]}
package/dist/cli.js CHANGED
@@ -3647,7 +3647,7 @@ var require_npa = __commonJS((exports, module) => {
3647
3647
  });
3648
3648
 
3649
3649
  // src/command.ts
3650
- import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
3650
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
3651
3651
  import { join as join6, resolve as resolve5 } from "node:path";
3652
3652
  import { parseArgs } from "node:util";
3653
3653
 
@@ -18920,107 +18920,80 @@ async function resolveNpmPluginArchive(packageSpec, pack = runNpmPack) {
18920
18920
  }
18921
18921
  }
18922
18922
 
18923
- // src/init.ts
18924
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18925
- import { dirname, join as join3, resolve as resolve2 } from "node:path";
18926
-
18927
18923
  // src/agents-template.ts
18924
+ var AGENTS_GUIDE_REVISION = 2;
18925
+ function readAgentsGuideRevision(content) {
18926
+ const match2 = /<!--\s*vetta-guide-revision:\s*(\d+)\s*-->/.exec(content);
18927
+ if (!match2)
18928
+ return;
18929
+ return Number(match2[1]);
18930
+ }
18928
18931
  function renderAgentsGuide(input) {
18929
- return `# ${input.displayName}
18932
+ return `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->
18933
+ # ${input.displayName}
18934
+
18935
+ Vetta 桌面插件工程(插件 id:\`${input.pluginId}\`)。
18930
18936
 
18931
- Vetta 桌面插件工程。**先读手册再写代码**——不要凭记忆写 SDK API,这套合同变化很快。
18937
+ **本文件不讲规则,只告诉你去哪读。** 规则在手册里,手册随 SDK 升级;写在这里的任何一条都会
18938
+ 停在这个工程创建那天。两者冲突时一律以手册为准。
18932
18939
 
18933
- ## 第一步:装依赖,然后找到手册
18940
+ ## 第一步:装依赖,找到手册
18934
18941
 
18935
18942
  \`\`\`bash
18936
18943
  npm install
18937
- npx vetta-plugin-cli docs
18944
+ npx vetta-plugin-cli docs --check-latest
18938
18945
  \`\`\`
18939
18946
 
18940
18947
  \`npm install\` 必须先跑:\`vetta-plugin-cli\` 是 \`@vetta-org/plugin-cli\` 的命令名,装完才在
18941
18948
  \`node_modules/.bin\` 里。还没装就想跑,用全名 \`npx @vetta-org/plugin-cli docs\`。
18942
18949
 
18943
- **手册会过期。** 它是随 SDK 装进 \`node_modules\` 的快照,工程不升级就永远停在初始化那天的
18944
- 版本——本文件同样是那天的快照。开工前先确认一次:
18950
+ 它打印手册目录的**绝对路径**、手册对应的 SDK 版本,以及本工程与所属 hub 的位置。
18951
+ **不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件时各插件还可能钉不同版本。
18945
18952
 
18946
- \`\`\`bash
18947
- npx vetta-plugin-cli docs --check-latest
18948
- \`\`\`
18949
-
18950
- 落后就按它打印的命令升级 SDK 再重读手册。\`docs\` 的输出永远比本文件新(\`npx\` 默认取最新的
18951
- CLI),**两者冲突时以它为准**。
18952
-
18953
- 它打印出随 \`@vetta-org/plugin-sdk\` 一起装进 \`node_modules\` 的手册目录**绝对路径**,以及
18954
- 这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件
18955
- 时各插件还可能钉不同的 SDK 版本。
18953
+ 输出里出现 \`Manual is behind\` 或 \`This brief is stale\` 就按它给的命令升级,再重跑一次。
18956
18954
 
18957
- 拿到路径后,用 read 工具按这个顺序打开:
18955
+ ## 第二步:按顺序读手册
18958
18956
 
18959
18957
  | 顺序 | 文件 | 何时读 |
18960
18958
  | --- | --- | --- |
18961
- | 1 | \`README.md\` | 总是先读:能力矩阵、信任模型、导航 |
18959
+ | 1 | \`README.md\` | **总是先读**:能力矩阵、信任模型、**不可违反的红线**、导航 |
18962
18960
  | 2 | \`getting-started.md\` | 首次写代码、构建、安装调试 |
18963
18961
  | 3 | \`manifest.md\` | 写/改 \`plugin.json\`、贡献智能体与团队 |
18964
18962
  | 4 | \`permissions.md\` | 选定权限列表之前 |
18965
- | 5 | 按扩展点选读 | \`ui-slots.md\` / \`conversation-and-agent.md\` / \`message-cards.md\` / \`mcp.md\` / \`ai.md\` / \`browser.md\` / \`app-actions.md\` |
18963
+ | 5 | 按扩展点选读 | \`ui-slots.md\` / \`conversation-and-agent.md\` / \`message-cards.md\` / \`mcp.md\` / \`ai.md\` / \`browser.md\` / \`app-actions.md\` / \`styling-and-pitfalls.md\` |
18966
18964
 
18967
- 实现任一扩展点**之前**再读对应那章。手册是唯一真源,本文件与它冲突时以手册为准。
18965
+ 实现任一扩展点**之前**再读对应那章,不要凭记忆写 SDK API——这套合同变化很快。
18968
18966
 
18969
- ## 开发闭环
18967
+ ## 常用命令
18970
18968
 
18971
18969
  \`\`\`bash
18972
18970
  npm run dev # Vite + Module Federation 开发服务器
18973
18971
  npm run build # 产出 dist/
18974
18972
  npm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)
18975
- npx vetta-plugin-cli watch # 开热更新:宿主改从本工程目录加载,改完即生效
18976
- npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)
18977
- \`\`\`
18978
-
18979
- \`install:vetta\` 走 \`vetta-plugin-cli add .\`:它找到本工程打出来的归档,交给正在运行的
18980
- Desktop 校验、授权、安装。它**不会**直接写 \`~/.vetta/plugins\`。
18981
-
18982
- 装完若提示有 pending 版本,用 \`npx vetta-plugin-cli reload ${input.pluginId}\` 让宿主应用它。
18983
-
18984
- 开发期建议开热更新(\`watch\`):之后改源码即时生效,不用每次重新打包安装。改 \`plugin.json\`
18985
- 的权限或命令声明时仍需重新安装一次,让宿主把授权落盘。\`watch --stop\` 关闭。
18986
18973
 
18987
- ## 如果这个目录之上有能力市场索引
18988
-
18989
- \`vetta-plugin-cli docs\` 会告诉你有没有(它会打印 \`Marketplace index:\`)。有的话,**改完
18990
- \`version\` / \`permissions\` / \`pluginApiVersion\` 之后要回仓库根跑一次**:
18991
-
18992
- \`\`\`bash
18993
- npx @vetta-org/plugin-cli sync # 从各能力目录回填索引,并推进 marketplaceVersion
18994
- npx @vetta-org/plugin-cli sync --check # 只报不写,CI 用
18974
+ npx vetta-plugin-cli watch # 热更新:宿主改从本工程目录加载,改完即生效
18975
+ npx vetta-plugin-cli reload ${input.pluginId} # 装完提示有 pending 版本时用它
18976
+ npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)
18977
+ npx vetta-plugin-cli sync # hub 仓库根上跑:把索引与各能力目录对账
18995
18978
  \`\`\`
18996
18979
 
18997
- 索引里的 \`version\` \`plugin.json\` \`version\` 必须**完全相等**,否则宿主同步直接失败;
18998
- 而内容变了却不换 \`marketplaceVersion\` 时,客户端既不报错也不更新——用户只是永远收不到。
18999
- \`add .\` 装完若检测到索引还停在旧版本,会当场提醒你。
19000
-
19001
- ## 不可违反的几条
19002
-
19003
- - **样式只用 Tailwind \`className\`**。禁止新建业务 CSS、禁止在 \`style.css\` 里写 \`button\`/\`div\`/\`*\`
19004
- 这类选择器——插件与宿主共享同一个页面,全局选择器会污染整个 UI。
19005
- - **可能失败的路径必须上报**:读文件、解析、网络、外部库的 catch 里调用
19006
- \`ctx.ui.notify({ message, error })\`(无需权限)。禁止只写死「失败」文案并丢掉原始 error。
19007
- - **权限按需最小声明**。构建期会校验产物用到的能力与 \`plugin.json\` 的声明是否匹配,缺了直接
19008
- 构建失败。但 UI 槽位不在这条校验里——那类缺权限在运行时只是静默跳过,所以对着手册核对。
19009
- - **不要写 \`agent_mode\`**(已废弃,无运行时语义)。想收窄某个工具的使用场景,把「何时不该用它 +
19010
- 替代做法」写进该工具 description 的反向触发段。
19011
- - **顶层不要出现依赖共享 React 的 JSX**,放进组件或 \`activate\` 内(Module Federation 的加载时序)。
19012
- - 依赖用 registry 上已发布的 semver,不要 \`workspace:*\`。
19013
- - **\`dist/\` 要进版本库**。插件通过仓库目录分发时,宿主直接读 \`plugin.json\` 指向的 \`entry\`
19014
- 与 \`styles\`,它不会替你构建——目录里没有构建产物就装不上。
18980
+ 细节都在 \`getting-started.md\`。\`docs\` 打印了 \`Marketplace index:\` 就说明这个目录之上有能力
18981
+ 市场索引,改完 \`version\` / \`permissions\` / \`pluginApiVersion\` 后要回仓库根跑 \`sync\`。
18982
+
19015
18983
  ## 信息不足时
19016
18984
 
19017
18985
  插件 id、展示名、要用哪些权限、功能边界、是否立刻安装——**问用户**,不要自己假定。
19018
18986
  `;
19019
18987
  }
19020
18988
 
18989
+ // src/init.ts
18990
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18991
+ import { dirname, join as join3, resolve as resolve2 } from "node:path";
18992
+
19021
18993
  // src/hub-template.ts
19022
18994
  function renderHubAgentsGuide(input) {
19023
- return `# ${input.name}
18995
+ return `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->
18996
+ # ${input.name}
19024
18997
 
19025
18998
  Vetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skill / scene / bundle),
19026
18999
  每个能力是 \`abilities/\` 下的一个自包含目录。
@@ -20061,6 +20034,7 @@ async function runDocsCommand(command, dependencies) {
20061
20034
  const sdkVersion = readManualSdkVersion(manualDir);
20062
20035
  const latestVersion = command.checkLatest ? await dependencies.readLatestSdkVersion?.() : undefined;
20063
20036
  const outdated = sdkVersion !== undefined && latestVersion !== undefined && compareSemver(sdkVersion, latestVersion) < 0;
20037
+ const guide = inspectAgentsGuide(project?.root ?? hub?.root ?? cwd);
20064
20038
  if (command.json) {
20065
20039
  dependencies.writeStdout(`${JSON.stringify({
20066
20040
  ok: true,
@@ -20068,6 +20042,7 @@ async function runDocsCommand(command, dependencies) {
20068
20042
  entry: join6(manualDir, "README.md"),
20069
20043
  sdkVersion,
20070
20044
  refreshCommand: SDK_REFRESH_COMMAND,
20045
+ guide,
20071
20046
  ...command.checkLatest ? { latestVersion, outdated } : {},
20072
20047
  project: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,
20073
20048
  hub: hub ? {
@@ -20093,6 +20068,9 @@ async function runDocsCommand(command, dependencies) {
20093
20068
  } else {
20094
20069
  lines.push(`Manual follows the installed SDK. To refresh it: ${SDK_REFRESH_COMMAND}`);
20095
20070
  }
20071
+ if (guide.stale) {
20072
+ lines.push(`This brief is stale (AGENTS.md revision ${guide.revision ?? "unstamped"} < ${AGENTS_GUIDE_REVISION}). Refresh it with: ${GUIDE_REFRESH_COMMAND}`);
20073
+ }
20096
20074
  if (hub) {
20097
20075
  lines.push(`Marketplace index: ${hub.manifestPath}`);
20098
20076
  lines.push("After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.");
@@ -20103,6 +20081,23 @@ async function runDocsCommand(command, dependencies) {
20103
20081
  return 0;
20104
20082
  }
20105
20083
  var SDK_REFRESH_COMMAND = "npm i -D @vetta-org/plugin-sdk@latest && npx vetta-plugin-cli docs";
20084
+ var GUIDE_REFRESH_COMMAND = "npx @vetta-org/plugin-cli init --refresh-guide";
20085
+ function inspectAgentsGuide(root) {
20086
+ const path = join6(root, "AGENTS.md");
20087
+ if (!existsSync4(path))
20088
+ return { present: false, stale: false };
20089
+ let revision;
20090
+ try {
20091
+ revision = readAgentsGuideRevision(readFileSync4(path, "utf8"));
20092
+ } catch {
20093
+ return { present: true, stale: false };
20094
+ }
20095
+ return {
20096
+ present: true,
20097
+ ...revision === undefined ? {} : { revision },
20098
+ stale: revision === undefined || revision < AGENTS_GUIDE_REVISION
20099
+ };
20100
+ }
20106
20101
  function compareSemver(left, right) {
20107
20102
  const parse2 = (value) => {
20108
20103
  const match2 = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(value.trim());
package/dist/command.d.ts CHANGED
@@ -104,5 +104,13 @@ export declare function parsePluginUninstallCommand(argv: string[]): PluginUnins
104
104
  export declare function parsePluginSyncCommand(argv: string[]): PluginSyncCommand | undefined;
105
105
  export declare function runPluginAddCommand(command: PluginAddCommand, dependencies?: PluginAddCommandDependencies): Promise<number>;
106
106
  export declare function runPluginCommand(command: PluginCommand, dependencies?: PluginCommandDependencies): Promise<number>;
107
+ export interface AgentsGuideStatus {
108
+ /** 本工程有没有 AGENTS.md。 */
109
+ readonly present: boolean;
110
+ /** 读到的版本戳;没有戳(模板早于版本戳,或是手写的)时缺省。 */
111
+ readonly revision?: number;
112
+ /** 落后于当前 CLI 的模板。没有 AGENTS.md 时为 false——那是「没有」,不是「旧」。 */
113
+ readonly stale: boolean;
114
+ }
107
115
  export declare function runPluginCli(argv: string[]): Promise<number>;
108
116
  //# sourceMappingURL=command.d.ts.map
@@ -1 +1 @@
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,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC5D;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;AAkCrE,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,CAuCpF;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,CAuFjB;AA6WD,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, refreshAgentsGuide } 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: \"refresh-guide\"; targetDir?: 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 --refresh-guide [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\t\"refresh-guide\": { type: \"boolean\" },\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tif (parsed.values[\"refresh-guide\"] === true) {\n\t\tconst [dir, extra] = parsed.positionals;\n\t\tif (extra) return { type: \"error\", message: `Unexpected argument: ${extra}` };\n\t\t// 刷新是就地重写,工程的 id 和展示名从磁盘上读,不再由命令行给。\n\t\treturn { type: \"refresh-guide\", ...(dir ? { targetDir: dir } : {}), json: parsed.values.json === true };\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 === \"refresh-guide\") {\n\t\treturn runRefreshGuideCommand(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/**\n * 在已有工程里把 AGENTS.md 重写成当前 CLI 的版本。\n *\n * `init` 拒绝覆盖已有工程,所以老目录里那份说明书从落地起就停在原地。它是纯派生产物,重写\n * 它不会碰用户写过的任何东西——这也是唯一一个能这么做的脚手架文件。\n */\nfunction runRefreshGuideCommand(\n\tcommand: Extract<PluginCommand, { type: \"refresh-guide\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tconst result = refreshAgentsGuide(resolve(cwd, command.targetDir ?? \".\"));\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: `Rewrote ${result.file}\\nNext: npx vetta-plugin-cli docs --check-latest\\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: \"GUIDE_REFRESH_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 7;\n\t}\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;AAMhH,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,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC5D;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;AAkCrE,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,CAuCpF;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,CAuFjB;AAoGD,MAAM,WAAW,iBAAiB;IACjC,sCAAwB;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8FAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,iHAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB;AA0SD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE","sourcesContent":["import { existsSync, readFileSync, 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 { AGENTS_GUIDE_REVISION, readAgentsGuideRevision } from \"./agents-template.js\";\nimport { initHubRepository, initPluginProject, refreshAgentsGuide } 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: \"refresh-guide\"; targetDir?: 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 --refresh-guide [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\t\"refresh-guide\": { type: \"boolean\" },\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn { type: \"error\", message: formatParseError(error) };\n\t}\n\tif (parsed.values[\"refresh-guide\"] === true) {\n\t\tconst [dir, extra] = parsed.positionals;\n\t\tif (extra) return { type: \"error\", message: `Unexpected argument: ${extra}` };\n\t\t// 刷新是就地重写,工程的 id 和展示名从磁盘上读,不再由命令行给。\n\t\treturn { type: \"refresh-guide\", ...(dir ? { targetDir: dir } : {}), json: parsed.values.json === true };\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 === \"refresh-guide\") {\n\t\treturn runRefreshGuideCommand(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\tconst guide = inspectAgentsGuide(project?.root ?? hub?.root ?? cwd);\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\tguide,\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 (guide.stale) {\n\t\t// 说明书同样是快照,而且用户没有理由回头看它。这里是唯一会被读到的位置。\n\t\tlines.push(`This brief is stale (AGENTS.md revision ${guide.revision ?? \"unstamped\"} < ${AGENTS_GUIDE_REVISION}). Refresh it with: ${GUIDE_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/** 刷新说明书的命令。与手册各刷各的:一个随 SDK 走,一个随 CLI 走。 */\nconst GUIDE_REFRESH_COMMAND = \"npx @vetta-org/plugin-cli init --refresh-guide\";\n\nexport interface AgentsGuideStatus {\n\t/** 本工程有没有 AGENTS.md。 */\n\treadonly present: boolean;\n\t/** 读到的版本戳;没有戳(模板早于版本戳,或是手写的)时缺省。 */\n\treadonly revision?: number;\n\t/** 落后于当前 CLI 的模板。没有 AGENTS.md 时为 false——那是「没有」,不是「旧」。 */\n\treadonly stale: boolean;\n}\n\n/**\n * 判断工程里的 AGENTS.md 是不是旧模板。\n *\n * 说明书凝固在 `init` 那天,而用户没有理由回头看它——所以「它旧了」这件事只能由每次都会被\n * 跑到的 `docs` 说出来。没有版本戳的一律当作旧的:那是版本戳出现之前的模板。\n */\nfunction inspectAgentsGuide(root: string): AgentsGuideStatus {\n\tconst path = join(root, \"AGENTS.md\");\n\tif (!existsSync(path)) return { present: false, stale: false };\n\tlet revision: number | undefined;\n\ttry {\n\t\trevision = readAgentsGuideRevision(readFileSync(path, \"utf8\"));\n\t} catch {\n\t\treturn { present: true, stale: false };\n\t}\n\treturn {\n\t\tpresent: true,\n\t\t...(revision === undefined ? {} : { revision }),\n\t\tstale: revision === undefined || revision < AGENTS_GUIDE_REVISION,\n\t};\n}\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/**\n * 在已有工程里把 AGENTS.md 重写成当前 CLI 的版本。\n *\n * `init` 拒绝覆盖已有工程,所以老目录里那份说明书从落地起就停在原地。它是纯派生产物,重写\n * 它不会碰用户写过的任何东西——这也是唯一一个能这么做的脚手架文件。\n */\nfunction runRefreshGuideCommand(\n\tcommand: Extract<PluginCommand, { type: \"refresh-guide\" }>,\n\tdependencies: PluginCommandDependencies,\n): number {\n\tconst cwd = dependencies.cwd?.() ?? process.cwd();\n\ttry {\n\t\tconst result = refreshAgentsGuide(resolve(cwd, command.targetDir ?? \".\"));\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: `Rewrote ${result.file}\\nNext: npx vetta-plugin-cli docs --check-latest\\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: \"GUIDE_REFRESH_FAILED\", message } })}\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tdependencies.writeStderr(`${message}\\n`);\n\t\t}\n\t\treturn 7;\n\t}\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,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"]}
1
+ {"version":3,"file":"hub-template.d.ts","sourceRoot":"","sources":["../src/hub-template.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAoEpE;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":["import { AGENTS_GUIDE_REVISION } from \"./agents-template.js\";\n\n/**\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 `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->\n# ${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
@@ -1,6 +1,6 @@
1
- export { parsePluginAddCommand, parsePluginDocsCommand, parsePluginInitCommand, parsePluginSyncCommand, parsePluginUninstallCommand, parsePluginWatchCommand, parsePluginReloadCommand, type PluginAddCommand, type PluginAddCommandDependencies, type PluginCommand, type PluginCommandDependencies, type PluginDocsCommand, type PluginInitCommand, type PluginSyncCommand, type PluginUninstallCommand, type PluginWatchCommand, type PluginReloadCommand, runPluginAddCommand, runPluginCommand, runPluginCli, } from "./command.js";
1
+ export { parsePluginAddCommand, parsePluginDocsCommand, parsePluginInitCommand, parsePluginSyncCommand, parsePluginUninstallCommand, parsePluginWatchCommand, parsePluginReloadCommand, type PluginAddCommand, type PluginAddCommandDependencies, type PluginCommand, type PluginCommandDependencies, type AgentsGuideStatus, type PluginDocsCommand, type PluginInitCommand, type PluginSyncCommand, type PluginUninstallCommand, type PluginWatchCommand, type PluginReloadCommand, runPluginAddCommand, runPluginCommand, runPluginCli, } from "./command.js";
2
2
  export { DEFAULT_SDK_RANGE, DEFAULT_VITE_RANGE, initHubRepository, initPluginProject, refreshAgentsGuide, type RefreshGuideResult, type InitHubInput, type InitHubResult, type InitPluginInput, type InitPluginResult, } from "./init.js";
3
- export { renderAgentsGuide } from "./agents-template.js";
3
+ export { AGENTS_GUIDE_REVISION, readAgentsGuideRevision, 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";
@@ -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,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,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\trefreshAgentsGuide,\n\ttype RefreshGuideResult,\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"]}
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,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,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,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 AgentsGuideStatus,\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\trefreshAgentsGuide,\n\ttype RefreshGuideResult,\n\ttype InitHubInput,\n\ttype InitHubResult,\n\ttype InitPluginInput,\n\ttype InitPluginResult,\n} from \"./init.js\";\nexport {\n\tAGENTS_GUIDE_REVISION,\n\treadAgentsGuideRevision,\n\trenderAgentsGuide,\n} 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
@@ -3646,7 +3646,7 @@ var require_npa = __commonJS((exports, module) => {
3646
3646
  });
3647
3647
 
3648
3648
  // src/command.ts
3649
- import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
3649
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
3650
3650
  import { join as join6, resolve as resolve5 } from "node:path";
3651
3651
  import { parseArgs } from "node:util";
3652
3652
 
@@ -18919,107 +18919,80 @@ async function resolveNpmPluginArchive(packageSpec, pack = runNpmPack) {
18919
18919
  }
18920
18920
  }
18921
18921
 
18922
- // src/init.ts
18923
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18924
- import { dirname, join as join3, resolve as resolve2 } from "node:path";
18925
-
18926
18922
  // src/agents-template.ts
18923
+ var AGENTS_GUIDE_REVISION = 2;
18924
+ function readAgentsGuideRevision(content) {
18925
+ const match2 = /<!--\s*vetta-guide-revision:\s*(\d+)\s*-->/.exec(content);
18926
+ if (!match2)
18927
+ return;
18928
+ return Number(match2[1]);
18929
+ }
18927
18930
  function renderAgentsGuide(input) {
18928
- return `# ${input.displayName}
18931
+ return `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->
18932
+ # ${input.displayName}
18933
+
18934
+ Vetta 桌面插件工程(插件 id:\`${input.pluginId}\`)。
18929
18935
 
18930
- Vetta 桌面插件工程。**先读手册再写代码**——不要凭记忆写 SDK API,这套合同变化很快。
18936
+ **本文件不讲规则,只告诉你去哪读。** 规则在手册里,手册随 SDK 升级;写在这里的任何一条都会
18937
+ 停在这个工程创建那天。两者冲突时一律以手册为准。
18931
18938
 
18932
- ## 第一步:装依赖,然后找到手册
18939
+ ## 第一步:装依赖,找到手册
18933
18940
 
18934
18941
  \`\`\`bash
18935
18942
  npm install
18936
- npx vetta-plugin-cli docs
18943
+ npx vetta-plugin-cli docs --check-latest
18937
18944
  \`\`\`
18938
18945
 
18939
18946
  \`npm install\` 必须先跑:\`vetta-plugin-cli\` 是 \`@vetta-org/plugin-cli\` 的命令名,装完才在
18940
18947
  \`node_modules/.bin\` 里。还没装就想跑,用全名 \`npx @vetta-org/plugin-cli docs\`。
18941
18948
 
18942
- **手册会过期。** 它是随 SDK 装进 \`node_modules\` 的快照,工程不升级就永远停在初始化那天的
18943
- 版本——本文件同样是那天的快照。开工前先确认一次:
18949
+ 它打印手册目录的**绝对路径**、手册对应的 SDK 版本,以及本工程与所属 hub 的位置。
18950
+ **不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件时各插件还可能钉不同版本。
18944
18951
 
18945
- \`\`\`bash
18946
- npx vetta-plugin-cli docs --check-latest
18947
- \`\`\`
18948
-
18949
- 落后就按它打印的命令升级 SDK 再重读手册。\`docs\` 的输出永远比本文件新(\`npx\` 默认取最新的
18950
- CLI),**两者冲突时以它为准**。
18951
-
18952
- 它打印出随 \`@vetta-org/plugin-sdk\` 一起装进 \`node_modules\` 的手册目录**绝对路径**,以及
18953
- 这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件
18954
- 时各插件还可能钉不同的 SDK 版本。
18952
+ 输出里出现 \`Manual is behind\` 或 \`This brief is stale\` 就按它给的命令升级,再重跑一次。
18955
18953
 
18956
- 拿到路径后,用 read 工具按这个顺序打开:
18954
+ ## 第二步:按顺序读手册
18957
18955
 
18958
18956
  | 顺序 | 文件 | 何时读 |
18959
18957
  | --- | --- | --- |
18960
- | 1 | \`README.md\` | 总是先读:能力矩阵、信任模型、导航 |
18958
+ | 1 | \`README.md\` | **总是先读**:能力矩阵、信任模型、**不可违反的红线**、导航 |
18961
18959
  | 2 | \`getting-started.md\` | 首次写代码、构建、安装调试 |
18962
18960
  | 3 | \`manifest.md\` | 写/改 \`plugin.json\`、贡献智能体与团队 |
18963
18961
  | 4 | \`permissions.md\` | 选定权限列表之前 |
18964
- | 5 | 按扩展点选读 | \`ui-slots.md\` / \`conversation-and-agent.md\` / \`message-cards.md\` / \`mcp.md\` / \`ai.md\` / \`browser.md\` / \`app-actions.md\` |
18962
+ | 5 | 按扩展点选读 | \`ui-slots.md\` / \`conversation-and-agent.md\` / \`message-cards.md\` / \`mcp.md\` / \`ai.md\` / \`browser.md\` / \`app-actions.md\` / \`styling-and-pitfalls.md\` |
18965
18963
 
18966
- 实现任一扩展点**之前**再读对应那章。手册是唯一真源,本文件与它冲突时以手册为准。
18964
+ 实现任一扩展点**之前**再读对应那章,不要凭记忆写 SDK API——这套合同变化很快。
18967
18965
 
18968
- ## 开发闭环
18966
+ ## 常用命令
18969
18967
 
18970
18968
  \`\`\`bash
18971
18969
  npm run dev # Vite + Module Federation 开发服务器
18972
18970
  npm run build # 产出 dist/
18973
18971
  npm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)
18974
- npx vetta-plugin-cli watch # 开热更新:宿主改从本工程目录加载,改完即生效
18975
- npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)
18976
- \`\`\`
18977
-
18978
- \`install:vetta\` 走 \`vetta-plugin-cli add .\`:它找到本工程打出来的归档,交给正在运行的
18979
- Desktop 校验、授权、安装。它**不会**直接写 \`~/.vetta/plugins\`。
18980
-
18981
- 装完若提示有 pending 版本,用 \`npx vetta-plugin-cli reload ${input.pluginId}\` 让宿主应用它。
18982
-
18983
- 开发期建议开热更新(\`watch\`):之后改源码即时生效,不用每次重新打包安装。改 \`plugin.json\`
18984
- 的权限或命令声明时仍需重新安装一次,让宿主把授权落盘。\`watch --stop\` 关闭。
18985
18972
 
18986
- ## 如果这个目录之上有能力市场索引
18987
-
18988
- \`vetta-plugin-cli docs\` 会告诉你有没有(它会打印 \`Marketplace index:\`)。有的话,**改完
18989
- \`version\` / \`permissions\` / \`pluginApiVersion\` 之后要回仓库根跑一次**:
18990
-
18991
- \`\`\`bash
18992
- npx @vetta-org/plugin-cli sync # 从各能力目录回填索引,并推进 marketplaceVersion
18993
- npx @vetta-org/plugin-cli sync --check # 只报不写,CI 用
18973
+ npx vetta-plugin-cli watch # 热更新:宿主改从本工程目录加载,改完即生效
18974
+ npx vetta-plugin-cli reload ${input.pluginId} # 装完提示有 pending 版本时用它
18975
+ npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)
18976
+ npx vetta-plugin-cli sync # hub 仓库根上跑:把索引与各能力目录对账
18994
18977
  \`\`\`
18995
18978
 
18996
- 索引里的 \`version\` \`plugin.json\` \`version\` 必须**完全相等**,否则宿主同步直接失败;
18997
- 而内容变了却不换 \`marketplaceVersion\` 时,客户端既不报错也不更新——用户只是永远收不到。
18998
- \`add .\` 装完若检测到索引还停在旧版本,会当场提醒你。
18999
-
19000
- ## 不可违反的几条
19001
-
19002
- - **样式只用 Tailwind \`className\`**。禁止新建业务 CSS、禁止在 \`style.css\` 里写 \`button\`/\`div\`/\`*\`
19003
- 这类选择器——插件与宿主共享同一个页面,全局选择器会污染整个 UI。
19004
- - **可能失败的路径必须上报**:读文件、解析、网络、外部库的 catch 里调用
19005
- \`ctx.ui.notify({ message, error })\`(无需权限)。禁止只写死「失败」文案并丢掉原始 error。
19006
- - **权限按需最小声明**。构建期会校验产物用到的能力与 \`plugin.json\` 的声明是否匹配,缺了直接
19007
- 构建失败。但 UI 槽位不在这条校验里——那类缺权限在运行时只是静默跳过,所以对着手册核对。
19008
- - **不要写 \`agent_mode\`**(已废弃,无运行时语义)。想收窄某个工具的使用场景,把「何时不该用它 +
19009
- 替代做法」写进该工具 description 的反向触发段。
19010
- - **顶层不要出现依赖共享 React 的 JSX**,放进组件或 \`activate\` 内(Module Federation 的加载时序)。
19011
- - 依赖用 registry 上已发布的 semver,不要 \`workspace:*\`。
19012
- - **\`dist/\` 要进版本库**。插件通过仓库目录分发时,宿主直接读 \`plugin.json\` 指向的 \`entry\`
19013
- 与 \`styles\`,它不会替你构建——目录里没有构建产物就装不上。
18979
+ 细节都在 \`getting-started.md\`。\`docs\` 打印了 \`Marketplace index:\` 就说明这个目录之上有能力
18980
+ 市场索引,改完 \`version\` / \`permissions\` / \`pluginApiVersion\` 后要回仓库根跑 \`sync\`。
18981
+
19014
18982
  ## 信息不足时
19015
18983
 
19016
18984
  插件 id、展示名、要用哪些权限、功能边界、是否立刻安装——**问用户**,不要自己假定。
19017
18985
  `;
19018
18986
  }
19019
18987
 
18988
+ // src/init.ts
18989
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18990
+ import { dirname, join as join3, resolve as resolve2 } from "node:path";
18991
+
19020
18992
  // src/hub-template.ts
19021
18993
  function renderHubAgentsGuide(input) {
19022
- return `# ${input.name}
18994
+ return `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->
18995
+ # ${input.name}
19023
18996
 
19024
18997
  Vetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skill / scene / bundle),
19025
18998
  每个能力是 \`abilities/\` 下的一个自包含目录。
@@ -20060,6 +20033,7 @@ async function runDocsCommand(command, dependencies) {
20060
20033
  const sdkVersion = readManualSdkVersion(manualDir);
20061
20034
  const latestVersion = command.checkLatest ? await dependencies.readLatestSdkVersion?.() : undefined;
20062
20035
  const outdated = sdkVersion !== undefined && latestVersion !== undefined && compareSemver(sdkVersion, latestVersion) < 0;
20036
+ const guide = inspectAgentsGuide(project?.root ?? hub?.root ?? cwd);
20063
20037
  if (command.json) {
20064
20038
  dependencies.writeStdout(`${JSON.stringify({
20065
20039
  ok: true,
@@ -20067,6 +20041,7 @@ async function runDocsCommand(command, dependencies) {
20067
20041
  entry: join6(manualDir, "README.md"),
20068
20042
  sdkVersion,
20069
20043
  refreshCommand: SDK_REFRESH_COMMAND,
20044
+ guide,
20070
20045
  ...command.checkLatest ? { latestVersion, outdated } : {},
20071
20046
  project: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,
20072
20047
  hub: hub ? {
@@ -20092,6 +20067,9 @@ async function runDocsCommand(command, dependencies) {
20092
20067
  } else {
20093
20068
  lines.push(`Manual follows the installed SDK. To refresh it: ${SDK_REFRESH_COMMAND}`);
20094
20069
  }
20070
+ if (guide.stale) {
20071
+ lines.push(`This brief is stale (AGENTS.md revision ${guide.revision ?? "unstamped"} < ${AGENTS_GUIDE_REVISION}). Refresh it with: ${GUIDE_REFRESH_COMMAND}`);
20072
+ }
20095
20073
  if (hub) {
20096
20074
  lines.push(`Marketplace index: ${hub.manifestPath}`);
20097
20075
  lines.push("After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.");
@@ -20102,6 +20080,23 @@ async function runDocsCommand(command, dependencies) {
20102
20080
  return 0;
20103
20081
  }
20104
20082
  var SDK_REFRESH_COMMAND = "npm i -D @vetta-org/plugin-sdk@latest && npx vetta-plugin-cli docs";
20083
+ var GUIDE_REFRESH_COMMAND = "npx @vetta-org/plugin-cli init --refresh-guide";
20084
+ function inspectAgentsGuide(root) {
20085
+ const path = join6(root, "AGENTS.md");
20086
+ if (!existsSync4(path))
20087
+ return { present: false, stale: false };
20088
+ let revision;
20089
+ try {
20090
+ revision = readAgentsGuideRevision(readFileSync4(path, "utf8"));
20091
+ } catch {
20092
+ return { present: true, stale: false };
20093
+ }
20094
+ return {
20095
+ present: true,
20096
+ ...revision === undefined ? {} : { revision },
20097
+ stale: revision === undefined || revision < AGENTS_GUIDE_REVISION
20098
+ };
20099
+ }
20105
20100
  function compareSemver(left, right) {
20106
20101
  const parse2 = (value) => {
20107
20102
  const match2 = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(value.trim());
@@ -20354,6 +20349,7 @@ export {
20354
20349
  refreshAgentsGuide,
20355
20350
  readManualSdkVersion,
20356
20351
  readLatestNpmVersion,
20352
+ readAgentsGuideRevision,
20357
20353
  parsePluginWatchCommand,
20358
20354
  parsePluginUninstallCommand,
20359
20355
  parsePluginSyncCommand,
@@ -20366,5 +20362,6 @@ export {
20366
20362
  findPluginProject,
20367
20363
  findPluginHub,
20368
20364
  DEFAULT_VITE_RANGE,
20369
- DEFAULT_SDK_RANGE
20365
+ DEFAULT_SDK_RANGE,
20366
+ AGENTS_GUIDE_REVISION
20370
20367
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vetta-org/plugin-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Install npm-distributed plugins into Vetta Desktop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",