@vetta-org/plugin-cli 0.1.4 → 0.1.6

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,45 @@
2
2
 
3
3
  All notable changes to `@vetta-org/plugin-cli` are documented in this file.
4
4
 
5
+ ## [0.1.6] — 2026-09-14
6
+
7
+ ### Fixed
8
+
9
+ - **`init --refresh-guide` 不再覆盖手写的 `AGENTS.md`**。此前它无条件重写,而 `docs` 又把「没有
10
+ 版本戳」判成过期并给出这条命令——两者合起来是在引导用户删掉自己的文件。已知造成一个能力市场
11
+ 仓库根部 443 行手写市场规范被整份替换。
12
+
13
+ 没有 `vetta-guide-revision` 标记的文件现在一律拒绝覆盖(退出码 7),要覆盖得显式 `--force`;
14
+ 新增 `--dry-run` 把新模板打到 stdout 供人工合并。`docs` 对无标记文件改口为「看起来是手写的,
15
+ 请手动合并」,只有**带标记且落后**的才会被称作 stale 并给出刷新命令。
16
+
17
+ - 说明书标题不再印出未解析的本地化占位符。`plugin.json` 的 `name` 写成 `%plugin.name%` 时,按
18
+ `defaultLocale` 从 `locales/` 解析;解析不到退回插件 id。
19
+
20
+ - 说明书的命令清单改为读 `package.json` 的 `scripts`,只列真实存在的。老工程和自定义工程未必有
21
+ `dev` / `install:vetta`,照着跑只会得到一句 "Missing script";没有 `install:vetta` 时改列
22
+ `vetta-plugin-cli add .`。
23
+
24
+ - 更正 hub `AGENTS.md` 对 `sync` 的两处描述:`marketplaceVersion` 只在 semver 或纯整数时才推得动
25
+ (`YYYY.MM.DD-NN` 这类会报出来要手改),`config.api_version` / `permissions` / `commands`
26
+ **不回填**——宿主用 `plugin.json` 整个重算 `config`,`sync` 只在副本与真源不符时提醒删掉。
27
+
28
+ ## [0.1.5] — 2026-09-14
29
+
30
+ ### Changed
31
+
32
+ - **脚手架的 `AGENTS.md` 削薄成纯指引**:原先写在里面的「不可违反的几条」(Tailwind-only、
33
+ 错误必须 notify、最小权限、MF 顶层 JSX、`agent_mode` 已废弃、依赖用发布版本、`dist/` 进
34
+ 版本库)全部移进手册的 `README.md#不可违反的红线`。写进说明书的规则会在所有存量工程里就地
35
+ 凝固——它是 `init` 当天的快照,之后既不自更新、用户也没有理由回头看它。规则放进手册才能
36
+ 随 SDK 升级一起到位;说明书越薄,需要回头迁移老仓库的理由就越少。
37
+
38
+ ### Added
39
+
40
+ - `AGENTS.md` 带版本戳(`<!-- vetta-guide-revision: N -->`),`docs` 每次比对并在落后时打印
41
+ `This brief is stale ... npx @vetta-org/plugin-cli init --refresh-guide`。此前「说明书旧了」
42
+ 只能靠人记得,而这正是它凝固的原因。没有 `AGENTS.md` 的工程不提示——「没有」不是「旧」。
43
+
5
44
  ## [0.1.4] — 2026-09-14
6
45
 
7
46
  ### Added
package/README.md CHANGED
@@ -28,6 +28,16 @@ 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
+ It refuses to overwrite a brief that carries no `vetta-guide-revision` marker — that file is
32
+ indistinguishable from a hand-written one, and a marketplace root often holds a hand-written spec.
33
+ Use `--dry-run` to print the current template for manual merging, or `--force` to replace the file
34
+ outright.
35
+
36
+ You will rarely have to remember this: the brief carries a revision stamp, and `docs` compares it on
37
+ every run and says outright when it is behind. The brief itself holds no rules — those live in the
38
+ manual, which travels with the SDK — so it changes seldom, and a project that refreshes once keeps
39
+ itself current from then on.
40
+
31
41
  ## Remove a plugin
32
42
 
33
43
  ```bash
@@ -7,10 +7,25 @@
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 = 3;
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;
28
+ /** 工程 package.json 里实际存在的 script 名;缺省按脚手架的那套算。 */
29
+ scripts?: readonly string[];
15
30
  }): string;
16
31
  //# sourceMappingURL=agents-template.d.ts.map
@@ -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;AA4BD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,4FAAkD;IAClD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,GAAG,MAAM,CAiDT","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 = 3;\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\n/**\n * 渲染常用命令块。\n *\n * 只列工程真有的 script:模板写死 `npm run dev` / `install:vetta`,老工程和自定义工程未必有,\n * 照着跑就是一句 \"Missing script\"。读不到 package.json 时退回 CLI 直连命令——它们不依赖工程脚本。\n */\nfunction renderCommands(pluginId: string, scripts: readonly string[]): string {\n\tconst known: readonly (readonly [string, string])[] = [\n\t\t[\"dev\", \"npm run dev # 开发服务器\"],\n\t\t[\"build\", \"npm run build # 产出 dist/\"],\n\t\t[\"install:vetta\", \"npm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)\"],\n\t];\n\tconst lines = known.filter(([name]) => scripts.includes(name)).map(([, line]) => line);\n\tif (!scripts.includes(\"install:vetta\")) {\n\t\tlines.push(\"npx vetta-plugin-cli add . # 打包并装进正在运行的 Vetta\");\n\t}\n\treturn [\n\t\t...lines,\n\t\t\"\",\n\t\t\"npx vetta-plugin-cli watch # 热更新:宿主改从本工程目录加载,改完即生效\",\n\t\t`npx vetta-plugin-cli reload ${pluginId} # 装完提示有 pending 版本时用它`,\n\t\t\"npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)\",\n\t\t\"npx vetta-plugin-cli sync # 在 hub 仓库根上跑:把索引与各能力目录对账\",\n\t].join(\"\\n\");\n}\n\nexport function renderAgentsGuide(input: {\n\tpluginId: string;\n\tdisplayName: string;\n\t/** 工程 package.json 里实际存在的 script 名;缺省按脚手架的那套算。 */\n\tscripts?: readonly string[];\n}): 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\n${renderCommands(input.pluginId, input.scripts ?? [\"dev\", \"build\", \"install:vetta\"])}\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
 
@@ -15419,7 +15419,9 @@ var PluginAgentTeamMemberManifestSchema = Type.Object({
15419
15419
  agent: Type.Optional(NonWhitespaceStringSchema),
15420
15420
  role: Type.Optional(PluginAgentRoleSchema),
15421
15421
  responsibility: Type.String({ maxLength: 2000 }),
15422
- optional: Type.Optional(Type.Boolean())
15422
+ optional: Type.Optional(Type.Boolean()),
15423
+ instructions: Type.Optional(Type.String({ maxLength: 64000 })),
15424
+ instructionsPath: Type.Optional(NonWhitespaceStringSchema)
15423
15425
  }, { additionalProperties: false });
15424
15426
  var PluginAgentTeamManifestSchema = Type.Object({
15425
15427
  id: Type.String({ pattern: "^[a-z0-9][a-z0-9-]{0,63}$" }),
@@ -18920,107 +18922,93 @@ async function resolveNpmPluginArchive(packageSpec, pack = runNpmPack) {
18920
18922
  }
18921
18923
  }
18922
18924
 
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
18925
  // src/agents-template.ts
18926
+ var AGENTS_GUIDE_REVISION = 3;
18927
+ function readAgentsGuideRevision(content) {
18928
+ const match2 = /<!--\s*vetta-guide-revision:\s*(\d+)\s*-->/.exec(content);
18929
+ if (!match2)
18930
+ return;
18931
+ return Number(match2[1]);
18932
+ }
18933
+ function renderCommands(pluginId, scripts) {
18934
+ const known = [
18935
+ ["dev", "npm run dev # 开发服务器"],
18936
+ ["build", "npm run build # 产出 dist/"],
18937
+ ["install:vetta", "npm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)"]
18938
+ ];
18939
+ const lines = known.filter(([name]) => scripts.includes(name)).map(([, line]) => line);
18940
+ if (!scripts.includes("install:vetta")) {
18941
+ lines.push("npx vetta-plugin-cli add . # 打包并装进正在运行的 Vetta");
18942
+ }
18943
+ return [
18944
+ ...lines,
18945
+ "",
18946
+ "npx vetta-plugin-cli watch # 热更新:宿主改从本工程目录加载,改完即生效",
18947
+ `npx vetta-plugin-cli reload ${pluginId} # 装完提示有 pending 版本时用它`,
18948
+ "npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)",
18949
+ "npx vetta-plugin-cli sync # 在 hub 仓库根上跑:把索引与各能力目录对账"
18950
+ ].join(`
18951
+ `);
18952
+ }
18928
18953
  function renderAgentsGuide(input) {
18929
- return `# ${input.displayName}
18954
+ return `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->
18955
+ # ${input.displayName}
18930
18956
 
18931
- Vetta 桌面插件工程。**先读手册再写代码**——不要凭记忆写 SDK API,这套合同变化很快。
18957
+ Vetta 桌面插件工程(插件 id:\`${input.pluginId}\`)。
18932
18958
 
18933
- ## 第一步:装依赖,然后找到手册
18959
+ **本文件不讲规则,只告诉你去哪读。** 规则在手册里,手册随 SDK 升级;写在这里的任何一条都会
18960
+ 停在这个工程创建那天。两者冲突时一律以手册为准。
18961
+
18962
+ ## 第一步:装依赖,找到手册
18934
18963
 
18935
18964
  \`\`\`bash
18936
18965
  npm install
18937
- npx vetta-plugin-cli docs
18966
+ npx vetta-plugin-cli docs --check-latest
18938
18967
  \`\`\`
18939
18968
 
18940
18969
  \`npm install\` 必须先跑:\`vetta-plugin-cli\` 是 \`@vetta-org/plugin-cli\` 的命令名,装完才在
18941
18970
  \`node_modules/.bin\` 里。还没装就想跑,用全名 \`npx @vetta-org/plugin-cli docs\`。
18942
18971
 
18943
- **手册会过期。** 它是随 SDK 装进 \`node_modules\` 的快照,工程不升级就永远停在初始化那天的
18944
- 版本——本文件同样是那天的快照。开工前先确认一次:
18945
-
18946
- \`\`\`bash
18947
- npx vetta-plugin-cli docs --check-latest
18948
- \`\`\`
18949
-
18950
- 落后就按它打印的命令升级 SDK 再重读手册。\`docs\` 的输出永远比本文件新(\`npx\` 默认取最新的
18951
- CLI),**两者冲突时以它为准**。
18972
+ 它打印手册目录的**绝对路径**、手册对应的 SDK 版本,以及本工程与所属 hub 的位置。
18973
+ **不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件时各插件还可能钉不同版本。
18952
18974
 
18953
- 它打印出随 \`@vetta-org/plugin-sdk\` 一起装进 \`node_modules\` 的手册目录**绝对路径**,以及
18954
- 这份手册对应的 SDK 版本。**不要硬编码这个路径**:工作区可能把依赖提升到仓库根,一仓多插件
18955
- 时各插件还可能钉不同的 SDK 版本。
18975
+ 输出里出现 \`Manual is behind\`\`This brief is stale\` 就按它给的命令升级,再重跑一次。
18956
18976
 
18957
- 拿到路径后,用 read 工具按这个顺序打开:
18977
+ ## 第二步:按顺序读手册
18958
18978
 
18959
18979
  | 顺序 | 文件 | 何时读 |
18960
18980
  | --- | --- | --- |
18961
- | 1 | \`README.md\` | 总是先读:能力矩阵、信任模型、导航 |
18981
+ | 1 | \`README.md\` | **总是先读**:能力矩阵、信任模型、**不可违反的红线**、导航 |
18962
18982
  | 2 | \`getting-started.md\` | 首次写代码、构建、安装调试 |
18963
18983
  | 3 | \`manifest.md\` | 写/改 \`plugin.json\`、贡献智能体与团队 |
18964
18984
  | 4 | \`permissions.md\` | 选定权限列表之前 |
18965
- | 5 | 按扩展点选读 | \`ui-slots.md\` / \`conversation-and-agent.md\` / \`message-cards.md\` / \`mcp.md\` / \`ai.md\` / \`browser.md\` / \`app-actions.md\` |
18985
+ | 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
18986
 
18967
- 实现任一扩展点**之前**再读对应那章。手册是唯一真源,本文件与它冲突时以手册为准。
18987
+ 实现任一扩展点**之前**再读对应那章,不要凭记忆写 SDK API——这套合同变化很快。
18968
18988
 
18969
- ## 开发闭环
18989
+ ## 常用命令
18970
18990
 
18971
18991
  \`\`\`bash
18972
- npm run dev # Vite + Module Federation 开发服务器
18973
- npm run build # 产出 dist/
18974
- npm run install:vetta # 打包并装进正在运行的 Vetta(需要 Vetta 已启动)
18975
- npx vetta-plugin-cli watch # 开热更新:宿主改从本工程目录加载,改完即生效
18976
- npx vetta-plugin-cli uninstall # 卸载(省略 id 即本工程对应的插件)
18992
+ ${renderCommands(input.pluginId, input.scripts ?? ["dev", "build", "install:vetta"])}
18977
18993
  \`\`\`
18978
18994
 
18979
- \`install:vetta\` \`vetta-plugin-cli add .\`:它找到本工程打出来的归档,交给正在运行的
18980
- Desktop 校验、授权、安装。它**不会**直接写 \`~/.vetta/plugins\`。
18981
-
18982
- 装完若提示有 pending 版本,用 \`npx vetta-plugin-cli reload ${input.pluginId}\` 让宿主应用它。
18995
+ 细节都在 \`getting-started.md\`。\`docs\` 打印了 \`Marketplace index:\` 就说明这个目录之上有能力
18996
+ 市场索引,改完 \`version\` / \`permissions\` / \`pluginApiVersion\` 后要回仓库根跑 \`sync\`。
18983
18997
 
18984
- 开发期建议开热更新(\`watch\`):之后改源码即时生效,不用每次重新打包安装。改 \`plugin.json\`
18985
- 的权限或命令声明时仍需重新安装一次,让宿主把授权落盘。\`watch --stop\` 关闭。
18986
-
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 用
18995
- \`\`\`
18996
-
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\`,它不会替你构建——目录里没有构建产物就装不上。
19015
18998
  ## 信息不足时
19016
18999
 
19017
19000
  插件 id、展示名、要用哪些权限、功能边界、是否立刻安装——**问用户**,不要自己假定。
19018
19001
  `;
19019
19002
  }
19020
19003
 
19004
+ // src/init.ts
19005
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19006
+ import { dirname, join as join3, resolve as resolve2 } from "node:path";
19007
+
19021
19008
  // src/hub-template.ts
19022
19009
  function renderHubAgentsGuide(input) {
19023
- return `# ${input.name}
19010
+ return `<!-- vetta-guide-revision: ${AGENTS_GUIDE_REVISION} -->
19011
+ # ${input.name}
19024
19012
 
19025
19013
  Vetta 能力市场仓库。本仓库索引若干**能力**(plugin / mcp / skill / scene / bundle),
19026
19014
  每个能力是 \`abilities/\` 下的一个自包含目录。
@@ -19054,16 +19042,25 @@ npx @vetta-org/plugin-cli init --id <slug> --name "<Display Name>" abilities/plu
19054
19042
 
19055
19043
  ## 索引由工具对账,不要手改派生字段
19056
19044
 
19057
- \`.vetta/marketplace.json\` 里能力的 \`version\`、\`config.api_version\`、\`config.permissions\`、
19058
- \`config.commands\` 全都是从能力包推导出来的。改完能力后:
19045
+ 改完能力后:
19059
19046
 
19060
19047
  \`\`\`bash
19061
- npx @vetta-org/plugin-cli sync # 回填派生字段,并推进 marketplaceVersion
19048
+ npx @vetta-org/plugin-cli sync # 对账并回填,看输出
19062
19049
  npx @vetta-org/plugin-cli sync --check # 只报不写,非零退出(CI 用)
19063
19050
  \`\`\`
19064
19051
 
19052
+ \`sync\` 到底做什么,分三档看清楚——它不是万能的:
19053
+
19054
+ | 字段 | \`sync\` 的行为 |
19055
+ | --- | --- |
19056
+ | 条目 \`version\` | **回填**成能力包里的版本 |
19057
+ | \`marketplaceVersion\` | 内容有变化时**尝试**推进。只认 semver(\`1.2.3\`)和纯整数;\`YYYY.MM.DD-NN\` 之类的格式推不动,会报出来要你手改 |
19058
+ | \`config.api_version\` / \`config.permissions\` / \`config.commands\` | **不写**。宿主建目录时用 \`plugin.json\` 整个重算 \`config\`,索引里的副本读都不读。已经存在且与真源不符时 \`sync\` 会提醒你删掉或改对 |
19059
+
19060
+ 所以 \`sync\` 跑完要看输出:它报出来的问题(尤其是推不动的 \`marketplaceVersion\`)没人会替你处理。
19061
+
19065
19062
  要**手写**的只有身份与展示:\`slug\`、\`name\`、\`description\`、\`source.path\`、\`category\`、\`tags\`、
19066
- \`detail\`。新能力上架时手动加一条这样的条目,其余字段交给 \`sync\`。
19063
+ \`detail\`。新能力上架时手动加一条这样的条目。
19067
19064
 
19068
19065
  三条容易踩的约束,\`sync --check\` 会替你守住:
19069
19066
 
@@ -19073,7 +19070,7 @@ npx @vetta-org/plugin-cli sync --check # 只报不写,非零退出(CI 用
19073
19070
  | \`plugin.json\` 的 \`entry\` / \`styles\` 必须在已发布目录里真实存在 | 本地能装,市场上装不了 |
19074
19071
  | 改了任何内容必须换 \`marketplaceVersion\` | 客户端不报错、也不更新,用户永远收不到 |
19075
19072
 
19076
- 第三条最阴险——它不报错。
19073
+ 第三条最阴险——它不报错。\`sync\` 只在版本号是 semver 或整数时才推得动,其余格式要你自己换。
19077
19074
 
19078
19075
  ## 为什么插件目录里要提交 \`dist/\`
19079
19076
 
@@ -19241,26 +19238,80 @@ node_modules/
19241
19238
  }
19242
19239
  return { root, pluginId: input.pluginId, files: Object.keys(files).sort() };
19243
19240
  }
19244
- function refreshAgentsGuide(targetDir) {
19241
+ function refreshAgentsGuide(targetDir, options = {}) {
19245
19242
  const root = resolve2(targetDir);
19243
+ const file = join3(root, "AGENTS.md");
19246
19244
  const manifestPath = join3(root, "plugin.json");
19245
+ const hubManifestPath = join3(root, ".vetta", "marketplace.json");
19246
+ let kind;
19247
+ let content;
19247
19248
  if (existsSync(manifestPath)) {
19248
19249
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
19249
19250
  const pluginId = typeof manifest.id === "string" ? manifest.id : undefined;
19250
19251
  if (!pluginId)
19251
19252
  throw new Error(`plugin.json at ${root} has no id`);
19252
- const displayName = typeof manifest.name === "string" && manifest.name.length > 0 ? manifest.name : pluginId;
19253
- writeFileSync(join3(root, "AGENTS.md"), renderAgentsGuide({ pluginId, displayName }), "utf8");
19254
- return { root, kind: "plugin", file: join3(root, "AGENTS.md") };
19253
+ kind = "plugin";
19254
+ content = renderAgentsGuide({
19255
+ pluginId,
19256
+ displayName: resolveManifestText(manifest.name, root, pluginId),
19257
+ scripts: readPackageScripts(root)
19258
+ });
19259
+ } else if (existsSync(hubManifestPath)) {
19260
+ const manifest = JSON.parse(readFileSync(hubManifestPath, "utf8"));
19261
+ kind = "hub";
19262
+ content = renderHubAgentsGuide({
19263
+ name: typeof manifest.name === "string" && manifest.name.length > 0 ? manifest.name : "marketplace"
19264
+ });
19265
+ } else {
19266
+ throw new Error(`Not a plugin project or marketplace repository: ${root}`);
19255
19267
  }
19256
- const hubManifest = join3(root, ".vetta", "marketplace.json");
19257
- if (existsSync(hubManifest)) {
19258
- const manifest = JSON.parse(readFileSync(hubManifest, "utf8"));
19259
- const name = typeof manifest.name === "string" && manifest.name.length > 0 ? manifest.name : "marketplace";
19260
- writeFileSync(join3(root, "AGENTS.md"), renderHubAgentsGuide({ name }), "utf8");
19261
- return { root, kind: "hub", file: join3(root, "AGENTS.md") };
19268
+ if (!options.dryRun)
19269
+ assertSafeToOverwrite(file, options.force === true);
19270
+ if (options.dryRun)
19271
+ return { root, kind, file, content, written: false };
19272
+ writeFileSync(file, content, "utf8");
19273
+ return { root, kind, file, content, written: true };
19274
+ }
19275
+ function assertSafeToOverwrite(file, force) {
19276
+ if (force || !existsSync(file))
19277
+ return;
19278
+ if (readAgentsGuideRevision(readFileSync(file, "utf8")) !== undefined)
19279
+ return;
19280
+ throw new Error(`${file} has no vetta-guide-revision marker, so it looks hand-written rather than scaffolded. ` + "Refusing to overwrite it. Review the new template with `--dry-run`, merge what you want by hand, " + "or pass `--force` to replace the file.");
19281
+ }
19282
+ function resolveManifestText(raw2, root, fallback) {
19283
+ if (typeof raw2 !== "string" || raw2.length === 0)
19284
+ return fallback;
19285
+ const match2 = /^%([^%]+)%$/.exec(raw2);
19286
+ if (!match2)
19287
+ return raw2;
19288
+ const key = match2[1];
19289
+ const pluginManifest = readJsonFile(join3(root, "plugin.json"));
19290
+ const locale = typeof pluginManifest?.defaultLocale === "string" ? pluginManifest.defaultLocale : "zh";
19291
+ for (const candidate of [locale, "zh", "en"]) {
19292
+ const table = readJsonFile(join3(root, "locales", `${candidate}.json`));
19293
+ const value = table?.[key];
19294
+ if (typeof value === "string" && value.length > 0)
19295
+ return value;
19296
+ }
19297
+ return fallback;
19298
+ }
19299
+ function readPackageScripts(root) {
19300
+ const pkg = readJsonFile(join3(root, "package.json"));
19301
+ const scripts = pkg?.scripts;
19302
+ if (typeof scripts !== "object" || scripts === null || Array.isArray(scripts))
19303
+ return [];
19304
+ return Object.keys(scripts);
19305
+ }
19306
+ function readJsonFile(path) {
19307
+ if (!existsSync(path))
19308
+ return;
19309
+ try {
19310
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
19311
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : undefined;
19312
+ } catch {
19313
+ return;
19262
19314
  }
19263
- throw new Error(`Not a plugin project or marketplace repository: ${root}`);
19264
19315
  }
19265
19316
  var HUB_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
19266
19317
  var APP_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -19320,7 +19371,7 @@ function detectJsonIndent(source) {
19320
19371
  const match2 = /\n([ \t]+)\S/.exec(source);
19321
19372
  return match2?.[1] ?? "\t";
19322
19373
  }
19323
- function readJsonFile(path) {
19374
+ function readJsonFile2(path) {
19324
19375
  try {
19325
19376
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
19326
19377
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
@@ -19364,7 +19415,7 @@ function nextMarketplaceVersion(current) {
19364
19415
  return;
19365
19416
  }
19366
19417
  function syncMarketplaceIndex(input) {
19367
- const manifest = readJsonFile(input.manifestPath);
19418
+ const manifest = readJsonFile2(input.manifestPath);
19368
19419
  if (!manifest)
19369
19420
  throw new Error(`Malformed marketplace manifest: ${input.manifestPath}`);
19370
19421
  const abilities = Array.isArray(manifest.abilities) ? manifest.abilities : [];
@@ -19433,7 +19484,7 @@ function syncMarketplaceIndex(input) {
19433
19484
  }
19434
19485
  function reconcilePlugin(context) {
19435
19486
  const { entry, slug, abilityDir, changes, problems } = context;
19436
- const manifest = readJsonFile(join4(abilityDir, "plugin.json"));
19487
+ const manifest = readJsonFile2(join4(abilityDir, "plugin.json"));
19437
19488
  if (!manifest) {
19438
19489
  problems.push({ slug, message: "plugin.json is missing or malformed" });
19439
19490
  return;
@@ -19474,7 +19525,7 @@ function reconcilePlugin(context) {
19474
19525
  }
19475
19526
  function reconcileIdentityFile(context) {
19476
19527
  const { entry, slug, abilityDir, fileName, changes, problems } = context;
19477
- const identity = readJsonFile(join4(abilityDir, fileName));
19528
+ const identity = readJsonFile2(join4(abilityDir, fileName));
19478
19529
  if (!identity) {
19479
19530
  problems.push({ slug, message: `${fileName} is missing or malformed` });
19480
19531
  return;
@@ -19517,7 +19568,7 @@ function findAbilityDirectories(hubRoot) {
19517
19568
  return found.sort();
19518
19569
  }
19519
19570
  function describeIndexDrift(input) {
19520
- const manifest = readJsonFile(input.manifestPath);
19571
+ const manifest = readJsonFile2(input.manifestPath);
19521
19572
  if (!manifest || !Array.isArray(manifest.abilities))
19522
19573
  return;
19523
19574
  const entry = manifest.abilities.find((item) => typeof item === "object" && item !== null && !Array.isArray(item) && item.slug === input.slug);
@@ -19607,7 +19658,7 @@ Usage:
19607
19658
  vetta-plugin-cli reload <plugin-id> [--json]
19608
19659
  vetta-plugin-cli docs [--check-latest] [--json]
19609
19660
  vetta-plugin-cli init --id <plugin-id> [--name <display>] [dir] [--json]
19610
- vetta-plugin-cli init --refresh-guide [dir] [--json]
19661
+ vetta-plugin-cli init --refresh-guide [dir] [--dry-run] [--force] [--json]
19611
19662
  vetta-plugin-cli init hub --name <slug> --repository <url> --min-app-version <x.y.z> [dir]
19612
19663
  vetta-plugin-cli watch [dir] [--stop] [--json]
19613
19664
  vetta-plugin-cli uninstall [plugin-id] [--json]
@@ -19708,7 +19759,9 @@ function parsePluginInitCommand(argv) {
19708
19759
  id: { type: "string" },
19709
19760
  name: { type: "string" },
19710
19761
  json: { type: "boolean" },
19711
- "refresh-guide": { type: "boolean" }
19762
+ "refresh-guide": { type: "boolean" },
19763
+ force: { type: "boolean" },
19764
+ "dry-run": { type: "boolean" }
19712
19765
  }
19713
19766
  });
19714
19767
  } catch (error) {
@@ -19718,7 +19771,13 @@ function parsePluginInitCommand(argv) {
19718
19771
  const [dir, extra] = parsed.positionals;
19719
19772
  if (extra)
19720
19773
  return { type: "error", message: `Unexpected argument: ${extra}` };
19721
- return { type: "refresh-guide", ...dir ? { targetDir: dir } : {}, json: parsed.values.json === true };
19774
+ return {
19775
+ type: "refresh-guide",
19776
+ ...dir ? { targetDir: dir } : {},
19777
+ json: parsed.values.json === true,
19778
+ force: parsed.values.force === true,
19779
+ dryRun: parsed.values["dry-run"] === true
19780
+ };
19722
19781
  }
19723
19782
  const pluginId = parsed.values.id;
19724
19783
  if (typeof pluginId !== "string" || pluginId.length === 0) {
@@ -20061,6 +20120,7 @@ async function runDocsCommand(command, dependencies) {
20061
20120
  const sdkVersion = readManualSdkVersion(manualDir);
20062
20121
  const latestVersion = command.checkLatest ? await dependencies.readLatestSdkVersion?.() : undefined;
20063
20122
  const outdated = sdkVersion !== undefined && latestVersion !== undefined && compareSemver(sdkVersion, latestVersion) < 0;
20123
+ const guide = inspectAgentsGuide(project?.root ?? hub?.root ?? cwd);
20064
20124
  if (command.json) {
20065
20125
  dependencies.writeStdout(`${JSON.stringify({
20066
20126
  ok: true,
@@ -20068,6 +20128,7 @@ async function runDocsCommand(command, dependencies) {
20068
20128
  entry: join6(manualDir, "README.md"),
20069
20129
  sdkVersion,
20070
20130
  refreshCommand: SDK_REFRESH_COMMAND,
20131
+ guide,
20071
20132
  ...command.checkLatest ? { latestVersion, outdated } : {},
20072
20133
  project: project ? { root: project.root, pluginId: project.pluginId, version: project.version } : undefined,
20073
20134
  hub: hub ? {
@@ -20093,6 +20154,11 @@ async function runDocsCommand(command, dependencies) {
20093
20154
  } else {
20094
20155
  lines.push(`Manual follows the installed SDK. To refresh it: ${SDK_REFRESH_COMMAND}`);
20095
20156
  }
20157
+ if (guide.stale) {
20158
+ lines.push(`This brief is stale (AGENTS.md revision ${guide.revision} < ${AGENTS_GUIDE_REVISION}). Refresh it with: ${GUIDE_REFRESH_COMMAND}`);
20159
+ } else if (guide.unstamped) {
20160
+ lines.push(`AGENTS.md has no revision marker, so it looks hand-written. Review the current template with \`${GUIDE_REFRESH_COMMAND} --dry-run\` and merge by hand; do not overwrite it blindly.`);
20161
+ }
20096
20162
  if (hub) {
20097
20163
  lines.push(`Marketplace index: ${hub.manifestPath}`);
20098
20164
  lines.push("After changing version/permissions, run `vetta-plugin-cli sync` at the repository root.");
@@ -20103,6 +20169,24 @@ async function runDocsCommand(command, dependencies) {
20103
20169
  return 0;
20104
20170
  }
20105
20171
  var SDK_REFRESH_COMMAND = "npm i -D @vetta-org/plugin-sdk@latest && npx vetta-plugin-cli docs";
20172
+ var GUIDE_REFRESH_COMMAND = "npx @vetta-org/plugin-cli init --refresh-guide";
20173
+ function inspectAgentsGuide(root) {
20174
+ const path = join6(root, "AGENTS.md");
20175
+ if (!existsSync4(path))
20176
+ return { present: false, stale: false, unstamped: false };
20177
+ let revision;
20178
+ try {
20179
+ revision = readAgentsGuideRevision(readFileSync4(path, "utf8"));
20180
+ } catch {
20181
+ return { present: true, stale: false, unstamped: false };
20182
+ }
20183
+ return {
20184
+ present: true,
20185
+ ...revision === undefined ? {} : { revision },
20186
+ stale: revision !== undefined && revision < AGENTS_GUIDE_REVISION,
20187
+ unstamped: revision === undefined
20188
+ };
20189
+ }
20106
20190
  function compareSemver(left, right) {
20107
20191
  const parse2 = (value) => {
20108
20192
  const match2 = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(value.trim());
@@ -20123,11 +20207,18 @@ function compareSemver(left, right) {
20123
20207
  function runRefreshGuideCommand(command, dependencies) {
20124
20208
  const cwd = dependencies.cwd?.() ?? process.cwd();
20125
20209
  try {
20126
- const result = refreshAgentsGuide(resolve5(cwd, command.targetDir ?? "."));
20127
- dependencies.writeStdout(command.json ? `${JSON.stringify({ ok: true, ...result })}
20128
- ` : `Rewrote ${result.file}
20129
- Next: npx vetta-plugin-cli docs --check-latest
20210
+ const result = refreshAgentsGuide(resolve5(cwd, command.targetDir ?? "."), {
20211
+ force: command.force,
20212
+ dryRun: command.dryRun
20213
+ });
20214
+ if (command.json) {
20215
+ dependencies.writeStdout(`${JSON.stringify({ ok: true, ...result })}
20130
20216
  `);
20217
+ return 0;
20218
+ }
20219
+ dependencies.writeStdout(result.written ? `Rewrote ${result.file}
20220
+ Next: npx vetta-plugin-cli docs --check-latest
20221
+ ` : `${result.content}`);
20131
20222
  return 0;
20132
20223
  } catch (error) {
20133
20224
  const message = error instanceof Error ? error.message : String(error);
package/dist/command.d.ts CHANGED
@@ -44,6 +44,8 @@ export type PluginInitCommand = {
44
44
  type: "refresh-guide";
45
45
  targetDir?: string;
46
46
  json: boolean;
47
+ force: boolean;
48
+ dryRun: boolean;
47
49
  } | {
48
50
  type: "init-hub";
49
51
  targetDir?: string;
@@ -104,5 +106,19 @@ export declare function parsePluginUninstallCommand(argv: string[]): PluginUnins
104
106
  export declare function parsePluginSyncCommand(argv: string[]): PluginSyncCommand | undefined;
105
107
  export declare function runPluginAddCommand(command: PluginAddCommand, dependencies?: PluginAddCommandDependencies): Promise<number>;
106
108
  export declare function runPluginCommand(command: PluginCommand, dependencies?: PluginCommandDependencies): Promise<number>;
109
+ export interface AgentsGuideStatus {
110
+ /** 本工程有没有 AGENTS.md。 */
111
+ readonly present: boolean;
112
+ /** 读到的版本戳;没有戳(模板早于版本戳,或是手写的)时缺省。 */
113
+ readonly revision?: number;
114
+ /**
115
+ * **带戳**且落后于当前 CLI 的模板——只有这一档能安全地一键重写。
116
+ *
117
+ * 没有 AGENTS.md 时为 false(那是「没有」,不是「旧」);没有戳时也为 false,见 {@link unstamped}。
118
+ */
119
+ readonly stale: boolean;
120
+ /** 有文件但没有版本戳:可能是手写的,也可能是版本戳之前的模板,无从区分。 */
121
+ readonly unstamped: boolean;
122
+ }
107
123
  export declare function runPluginCli(argv: string[]): Promise<number>;
108
124
  //# sourceMappingURL=command.d.ts.map