@kevlns/v-cli 0.1.1-beta.1 → 0.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -75,31 +75,1071 @@ function defaultHomeDir() {
75
75
  }
76
76
 
77
77
  // src/core/loader.ts
78
- import fs3 from "fs";
79
- import path4 from "path";
78
+ import fs5 from "fs";
79
+ import path7 from "path";
80
80
  import { pathToFileURL } from "url";
81
81
 
82
+ // src/core/official.ts
83
+ import { createRequire } from "module";
84
+ import { fileURLToPath } from "url";
85
+ import path3 from "path";
86
+ import fs2 from "fs";
87
+
88
+ // src/core/manifest.ts
89
+ var RESERVED_MANIFEST_COMMAND_NAMES = ["doctor", "plugin", "ts", "agent", "help"];
90
+ var KNOWN_PLATFORMS = ["darwin", "linux", "win32"];
91
+ var OUTPUT_FORMATS = ["json", "stdout", "text"];
92
+ var COMMAND_NAME_RE = /^[a-z][a-z0-9-]*$/;
93
+ var PACKAGE_NAME_RE = /^@[a-zA-Z0-9][a-zA-Z0-9._~-]*\/[a-zA-Z0-9][a-zA-Z0-9._~-]*$/;
94
+ function isPlainObject(v) {
95
+ return typeof v === "object" && v !== null && !Array.isArray(v);
96
+ }
97
+ function isStringArray(v) {
98
+ return Array.isArray(v) && v.every((x) => typeof x === "string");
99
+ }
100
+ function validateManifest(input) {
101
+ if (!isPlainObject(input)) {
102
+ return { ok: false, errors: ["\u6E05\u5355\u5FC5\u987B\u662F JSON \u5BF9\u8C61"] };
103
+ }
104
+ const errors = [];
105
+ const m = input;
106
+ if (typeof m.schemaVersion !== "number" || !Number.isInteger(m.schemaVersion)) {
107
+ errors.push("schemaVersion \u5FC5\u987B\u662F\u6574\u6570");
108
+ } else if (m.schemaVersion === 1) {
109
+ } else if (m.schemaVersion >= 2) {
110
+ errors.push(
111
+ `schemaVersion ${m.schemaVersion} \u4E0D\u53D7\u652F\u6301\uFF1Av-cli 0.2 \u4EC5\u652F\u6301\u5951\u7EA6\u7248\u672C 1 \u7684\u6E05\u5355\uFF0C\u8BF7\u5347\u7EA7\u63D2\u4EF6\u6216 v-cli`
112
+ );
113
+ } else {
114
+ errors.push("schemaVersion \u5FC5\u987B\u4E3A 1");
115
+ }
116
+ const requireString = (key) => {
117
+ const v = m[key];
118
+ if (typeof v !== "string" || v.length === 0) {
119
+ errors.push(`${key} \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
120
+ return void 0;
121
+ }
122
+ return v;
123
+ };
124
+ const packageName = requireString("package");
125
+ if (typeof packageName === "string" && !PACKAGE_NAME_RE.test(packageName)) {
126
+ errors.push("package \u5FC5\u987B\u662F @scope/name \u5F62\u5F0F\u7684 npm \u5305\u540D");
127
+ }
128
+ const command = requireString("command");
129
+ if (typeof command === "string") {
130
+ if (!COMMAND_NAME_RE.test(command)) {
131
+ errors.push(`command "${command}" \u4E0D\u5408\u6CD5\uFF1A\u53EA\u80FD\u662F\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\u7684 [a-z0-9-] \u5B57\u7B26\u4E32`);
132
+ }
133
+ if (RESERVED_MANIFEST_COMMAND_NAMES.includes(command)) {
134
+ errors.push(
135
+ `command "${command}" \u662F v-cli \u4FDD\u7559\u547D\u4EE4\u540D\uFF08${RESERVED_MANIFEST_COMMAND_NAMES.join("/")}\uFF09\uFF0C\u63D2\u4EF6\u4E0D\u80FD\u5360\u7528`
136
+ );
137
+ }
138
+ }
139
+ requireString("bin");
140
+ requireString("description");
141
+ if (!Array.isArray(m.platforms)) {
142
+ errors.push("platforms \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4");
143
+ } else if (m.platforms.length === 0) {
144
+ errors.push("platforms \u81F3\u5C11\u8981\u58F0\u660E\u4E00\u4E2A\u5E73\u53F0");
145
+ } else if (!isStringArray(m.platforms)) {
146
+ errors.push("platforms \u7684\u5143\u7D20\u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
147
+ } else {
148
+ const unknown = m.platforms.filter((p) => !KNOWN_PLATFORMS.includes(p));
149
+ if (unknown.length > 0) {
150
+ errors.push(`platforms \u542B\u672A\u77E5\u5E73\u53F0: ${unknown.join(", ")}\uFF08\u4EC5\u652F\u6301 ${KNOWN_PLATFORMS.join("/")}\uFF09`);
151
+ }
152
+ }
153
+ if (!isPlainObject(m.runtime)) {
154
+ errors.push("runtime \u5FC5\u987B\u662F\u5BF9\u8C61");
155
+ }
156
+ if (!Array.isArray(m.environment)) {
157
+ errors.push("environment \u5FC5\u987B\u662F\u6570\u7EC4");
158
+ } else {
159
+ m.environment.forEach((entry, i) => {
160
+ if (!isPlainObject(entry)) {
161
+ errors.push(`environment[${i}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
162
+ } else {
163
+ if (typeof entry.name !== "string" || entry.name.length === 0) {
164
+ errors.push(`environment[${i}].name \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
165
+ }
166
+ if (typeof entry.description !== "string" || entry.description.length === 0) {
167
+ errors.push(`environment[${i}].description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
168
+ }
169
+ }
170
+ });
171
+ }
172
+ const agent2 = m.agent;
173
+ if (!isPlainObject(agent2)) {
174
+ errors.push("agent \u5FC5\u987B\u662F\u5BF9\u8C61");
175
+ } else {
176
+ if (typeof agent2.whenToUse !== "string") {
177
+ errors.push("agent.whenToUse \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
178
+ }
179
+ if (!Array.isArray(agent2.globalOptions)) {
180
+ errors.push("agent.globalOptions \u5FC5\u987B\u662F\u6570\u7EC4");
181
+ } else {
182
+ agent2.globalOptions.forEach((o, i) => {
183
+ if (!isPlainObject(o)) {
184
+ errors.push(`agent.globalOptions[${i}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
185
+ } else {
186
+ if (typeof o.flags !== "string" || o.flags.length === 0) {
187
+ errors.push(`agent.globalOptions[${i}].flags \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
188
+ }
189
+ if (typeof o.description !== "string" || o.description.length === 0) {
190
+ errors.push(`agent.globalOptions[${i}].description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
191
+ }
192
+ }
193
+ });
194
+ }
195
+ if (!Array.isArray(agent2.commands)) {
196
+ errors.push("agent.commands \u5FC5\u987B\u662F\u6570\u7EC4");
197
+ } else {
198
+ const seenPaths = /* @__PURE__ */ new Set();
199
+ agent2.commands.forEach((c, i) => {
200
+ const at = `agent.commands[${i}]`;
201
+ if (!isPlainObject(c)) {
202
+ errors.push(`${at} \u5FC5\u987B\u662F\u5BF9\u8C61`);
203
+ return;
204
+ }
205
+ if (!Array.isArray(c.path) || !isStringArray(c.path) || c.path.length === 0) {
206
+ errors.push(`${at}.path \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6570\u7EC4`);
207
+ } else {
208
+ const badSegment = c.path.find((seg) => !COMMAND_NAME_RE.test(seg));
209
+ if (badSegment !== void 0) {
210
+ errors.push(`${at}.path \u542B\u975E\u6CD5\u6BB5 "${badSegment}"\uFF08\u6BCF\u6BB5\u987B\u5339\u914D ^[a-z][a-z0-9-]*$\uFF09`);
211
+ }
212
+ const key = c.path.join(" ");
213
+ if (seenPaths.has(key)) {
214
+ errors.push(`${at}.path ${key} \u4E0E\u524D\u9762\u7684\u547D\u4EE4\u91CD\u590D`);
215
+ }
216
+ seenPaths.add(key);
217
+ }
218
+ if (typeof c.usage !== "string" || c.usage.length === 0) {
219
+ errors.push(`${at}.usage \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
220
+ }
221
+ if (typeof c.description !== "string" || c.description.length === 0) {
222
+ errors.push(`${at}.description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
223
+ }
224
+ if (!Array.isArray(c.arguments)) {
225
+ errors.push(`${at}.arguments \u5FC5\u987B\u662F\u6570\u7EC4`);
226
+ } else {
227
+ c.arguments.forEach((a, j) => {
228
+ if (!isPlainObject(a)) {
229
+ errors.push(`${at}.arguments[${j}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
230
+ } else {
231
+ if (typeof a.name !== "string" || a.name.length === 0) {
232
+ errors.push(`${at}.arguments[${j}].name \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
233
+ }
234
+ if (typeof a.required !== "boolean") {
235
+ errors.push(`${at}.arguments[${j}].required \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
236
+ }
237
+ if (typeof a.description !== "string" || a.description.length === 0) {
238
+ errors.push(`${at}.arguments[${j}].description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
239
+ }
240
+ }
241
+ });
242
+ }
243
+ if (!Array.isArray(c.options)) {
244
+ errors.push(`${at}.options \u5FC5\u987B\u662F\u6570\u7EC4`);
245
+ } else {
246
+ c.options.forEach((o, j) => {
247
+ if (!isPlainObject(o)) {
248
+ errors.push(`${at}.options[${j}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
249
+ } else {
250
+ if (typeof o.flags !== "string" || o.flags.length === 0) {
251
+ errors.push(`${at}.options[${j}].flags \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
252
+ }
253
+ if (typeof o.description !== "string" || o.description.length === 0) {
254
+ errors.push(`${at}.options[${j}].description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
255
+ }
256
+ }
257
+ });
258
+ }
259
+ if (!isPlainObject(c.output)) {
260
+ errors.push(`${at}.output \u5FC5\u987B\u662F\u5BF9\u8C61`);
261
+ } else {
262
+ if (typeof c.output.format !== "string" || c.output.format.length === 0 || !OUTPUT_FORMATS.includes(c.output.format)) {
263
+ errors.push(
264
+ `${at}.output.format \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u4E14\u5C5E\u4E8E [${OUTPUT_FORMATS.join(", ")}]`
265
+ );
266
+ }
267
+ if (typeof c.output.description !== "string" || c.output.description.length === 0) {
268
+ errors.push(`${at}.output.description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
269
+ }
270
+ }
271
+ if (!isPlainObject(c.exitCodes)) {
272
+ errors.push(`${at}.exitCodes \u5FC5\u987B\u662F\u5BF9\u8C61`);
273
+ } else {
274
+ const badExit = Object.values(c.exitCodes).find((v) => typeof v !== "string");
275
+ if (badExit !== void 0) {
276
+ errors.push(`${at}.exitCodes \u7684\u503C\u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
277
+ }
278
+ }
279
+ if (!Array.isArray(c.safety) || !isStringArray(c.safety)) {
280
+ errors.push(`${at}.safety \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4`);
281
+ }
282
+ });
283
+ }
284
+ }
285
+ if (errors.length > 0) {
286
+ return { ok: false, errors };
287
+ }
288
+ return { ok: true, manifest: input };
289
+ }
290
+ function validatePluginIdentity(manifest, pkgJson) {
291
+ const errors = [];
292
+ if (manifest.package !== pkgJson.name) {
293
+ errors.push(
294
+ `\u6E05\u5355 package "${manifest.package}" \u4E0E package.json name "${String(pkgJson.name)}" \u4E0D\u4E00\u81F4`
295
+ );
296
+ }
297
+ const binKeys = isPlainObject(pkgJson.bin) ? Object.keys(pkgJson.bin) : [];
298
+ if (!binKeys.includes(manifest.bin)) {
299
+ errors.push(`\u6E05\u5355 bin "${manifest.bin}" \u4E0D\u5728 package.json bin (${binKeys.join(", ") || "\u7A7A"}) \u4E2D`);
300
+ }
301
+ return errors;
302
+ }
303
+
304
+ // src/core/official.ts
305
+ var OFFICIAL_PLUGINS = [
306
+ {
307
+ package: "@kevlns/xlmerge",
308
+ command: "xlmerge",
309
+ description: "Git \u4E2D .xlsx/.xlsm \u51B2\u7A81\u53EF\u89C6\u5316\u89E3\u51B3\u65B9\u6848\uFF08\u4E09\u5411 diff\u3001\u672C\u5730 UI\u3001\u5199\u56DE\u4E0E\u63D0\u4EA4\uFF09"
310
+ },
311
+ {
312
+ package: "@kevlns/u-cli-mod",
313
+ command: "unity",
314
+ description: "Unity \u5DE5\u7A0B\u7CBE\u786E\u7248\u672C\u8DEF\u7531 + \u5B98\u65B9 CLI \u4E0B\u8F7D + com.unity.pipeline \u9002\u914D\u5305\uFF08Windows-first\uFF09"
315
+ }
316
+ ];
317
+ var OFFICIAL_COMMAND_NAMES = new Set(
318
+ OFFICIAL_PLUGINS.map((p) => p.command)
319
+ );
320
+ function defaultResolvePackage(pkg, opts = {}) {
321
+ const baseFile = opts.base ?? fileURLToPath(import.meta.url);
322
+ const cwd = opts.cwd ?? process.cwd();
323
+ const resolveFrom = opts.resolveFrom ?? process.env.V_CLI_PLUGIN_RESOLVE_FROM;
324
+ const candidates = [];
325
+ if (resolveFrom) {
326
+ candidates.push(path3.join(path3.resolve(cwd, resolveFrom), "node_modules", pkg, "package.json"));
327
+ }
328
+ try {
329
+ candidates.push(createRequire(baseFile).resolve(`${pkg}/package.json`));
330
+ } catch {
331
+ }
332
+ try {
333
+ const reqCwd = createRequire(path3.join(cwd, "__v_cli_resolve__.cjs"));
334
+ candidates.push(reqCwd.resolve(`${pkg}/package.json`, { paths: [cwd] }));
335
+ } catch {
336
+ }
337
+ for (const candidate of candidates) {
338
+ try {
339
+ if (fs2.existsSync(candidate)) return candidate;
340
+ } catch {
341
+ }
342
+ }
343
+ return void 0;
344
+ }
345
+ function defaultExists(file) {
346
+ try {
347
+ return fs2.existsSync(file);
348
+ } catch {
349
+ return false;
350
+ }
351
+ }
352
+ function defaultRead(file) {
353
+ return fs2.readFileSync(file, "utf-8");
354
+ }
355
+ function formatErrors(errors) {
356
+ return errors.join("\uFF1B");
357
+ }
358
+ function isPlainObject2(v) {
359
+ return typeof v === "object" && v !== null && !Array.isArray(v);
360
+ }
361
+ function discoverOfficialPlugin(spec, io = {}) {
362
+ const platform = io.platform ?? process.platform;
363
+ const exists = io.existsFile ?? defaultExists;
364
+ const read = io.readFile ?? defaultRead;
365
+ const resolvePackage = io.resolvePackage ?? defaultResolvePackage;
366
+ const base = {
367
+ source: "official",
368
+ package: spec.package,
369
+ name: spec.command,
370
+ description: spec.description,
371
+ status: "missing",
372
+ platform
373
+ };
374
+ let pkgJsonPath;
375
+ try {
376
+ pkgJsonPath = resolvePackage(spec.package);
377
+ } catch {
378
+ pkgJsonPath = void 0;
379
+ }
380
+ if (!pkgJsonPath || !exists(pkgJsonPath)) {
381
+ return {
382
+ ...base,
383
+ error: `\u672A\u627E\u5230\u5B98\u65B9\u63D2\u4EF6\u5305 ${spec.package}\uFF08\u672A\u5B89\u88C5\u6216\u4E0D\u5728\u53EF\u89E3\u6790\u8DEF\u5F84\uFF1B\u8BF7\u5B89\u88C5\u540E\u91CD\u8BD5\uFF09`
384
+ };
385
+ }
386
+ let pkgJson;
387
+ try {
388
+ pkgJson = JSON.parse(read(pkgJsonPath));
389
+ } catch (err) {
390
+ return {
391
+ ...base,
392
+ status: "invalid",
393
+ error: `package.json \u4E0D\u662F\u5408\u6CD5 JSON: ${err instanceof Error ? err.message : String(err)}`
394
+ };
395
+ }
396
+ const vCliMeta = pkgJson.vCli && isPlainObject2(pkgJson.vCli) ? pkgJson.vCli : {};
397
+ const manifestRel = typeof vCliMeta.manifest === "string" ? vCliMeta.manifest : "v-cli.plugin.json";
398
+ const pkgRoot = path3.dirname(pkgJsonPath);
399
+ const manifestPath = path3.join(pkgRoot, manifestRel);
400
+ if (!exists(manifestPath)) {
401
+ return {
402
+ ...base,
403
+ error: `\u5305\u5185\u7F3A\u5C11\u63D2\u4EF6\u6E05\u5355 ${manifestRel}\uFF08package.json \u7684 vCli.manifest \u6307\u5411\u5B83\uFF09`
404
+ };
405
+ }
406
+ let manifestRaw;
407
+ try {
408
+ manifestRaw = read(manifestPath);
409
+ } catch (err) {
410
+ return {
411
+ ...base,
412
+ error: `\u8BFB\u53D6\u6E05\u5355\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
413
+ };
414
+ }
415
+ let parsed;
416
+ try {
417
+ parsed = JSON.parse(manifestRaw);
418
+ } catch (err) {
419
+ return {
420
+ ...base,
421
+ status: "invalid",
422
+ error: `\u6E05\u5355 ${manifestRel} \u4E0D\u662F\u5408\u6CD5 JSON: ${err instanceof Error ? err.message : String(err)}`
423
+ };
424
+ }
425
+ const validated = validateManifest(parsed);
426
+ if (!validated.ok) {
427
+ return { ...base, status: "invalid", error: `\u6E05\u5355\u6821\u9A8C\u5931\u8D25: ${formatErrors(validated.errors)}` };
428
+ }
429
+ const manifest = validated.manifest;
430
+ const identityErrors = validatePluginIdentity(manifest, pkgJson);
431
+ if (identityErrors.length > 0) {
432
+ return {
433
+ ...base,
434
+ status: "invalid",
435
+ error: `\u8EAB\u4EFD\u6821\u9A8C\u5931\u8D25: ${formatErrors(identityErrors)}`
436
+ };
437
+ }
438
+ if (manifest.command !== spec.command) {
439
+ return {
440
+ ...base,
441
+ status: "invalid",
442
+ error: `\u6E05\u5355 command "${manifest.command}" \u4E0E\u5B98\u65B9\u767D\u540D\u5355\u547D\u4EE4 "${spec.command}" \u4E0D\u4E00\u81F4`
443
+ };
444
+ }
445
+ const binMap = pkgJson.bin;
446
+ const binRel = isPlainObject2(binMap) ? binMap[manifest.bin] : void 0;
447
+ if (typeof binRel !== "string" || binRel.length === 0) {
448
+ return {
449
+ ...base,
450
+ status: "invalid",
451
+ error: `bin "${manifest.bin}" \u7684\u542F\u52A8\u76EE\u6807\u4E0D\u662F\u5B57\u7B26\u4E32\u8DEF\u5F84`
452
+ };
453
+ }
454
+ const binPath = path3.resolve(pkgRoot, binRel);
455
+ const rootPrefix = pkgRoot.endsWith(path3.sep) ? pkgRoot : pkgRoot + path3.sep;
456
+ if (!binPath.startsWith(rootPrefix)) {
457
+ return {
458
+ ...base,
459
+ status: "invalid",
460
+ error: `bin "${manifest.bin}" \u7684\u76EE\u6807 ${binRel} \u9003\u9038\u4E86\u5305\u76EE\u5F55`
461
+ };
462
+ }
463
+ const requiredPlatforms = manifest.platforms;
464
+ if (!requiredPlatforms.includes(platform)) {
465
+ return {
466
+ ...base,
467
+ status: "platform-mismatch",
468
+ version: typeof pkgJson.version === "string" ? pkgJson.version : void 0,
469
+ requiredPlatforms,
470
+ error: `\u8BE5\u63D2\u4EF6\u4EC5\u652F\u6301\u5E73\u53F0 [${requiredPlatforms.join(", ")}]\uFF0C\u5F53\u524D\u5E73\u53F0\u4E3A ${platform}`
471
+ };
472
+ }
473
+ if (!exists(binPath)) {
474
+ return {
475
+ ...base,
476
+ version: typeof pkgJson.version === "string" ? pkgJson.version : void 0,
477
+ requiredPlatforms,
478
+ error: `bin \u6587\u4EF6\u4E0D\u5B58\u5728: ${binRel}\uFF08manifest.bin=${manifest.bin}\uFF09`
479
+ };
480
+ }
481
+ return {
482
+ ...base,
483
+ status: "available",
484
+ version: typeof pkgJson.version === "string" ? pkgJson.version : void 0,
485
+ requiredPlatforms,
486
+ bin: binPath,
487
+ whenToUse: manifest.agent.whenToUse,
488
+ globalOptions: manifest.agent.globalOptions,
489
+ commands: manifest.agent.commands.map((c) => ({
490
+ path: c.path,
491
+ usage: c.usage,
492
+ description: c.description,
493
+ arguments: c.arguments,
494
+ options: c.options,
495
+ output: c.output,
496
+ exitCodes: c.exitCodes,
497
+ safety: c.safety
498
+ })),
499
+ runtime: manifest.runtime,
500
+ environment: manifest.environment
501
+ };
502
+ }
503
+ function discoverAllOfficialPlugins(io = {}) {
504
+ return OFFICIAL_PLUGINS.map((spec) => discoverOfficialPlugin(spec, io));
505
+ }
506
+ function isOfficialCommand(token) {
507
+ return OFFICIAL_COMMAND_NAMES.has(token);
508
+ }
509
+
82
510
  // src/core/command.ts
83
- function isCliCommand(candidate) {
84
- if (typeof candidate !== "object" || candidate === null) return false;
511
+ var RESERVED_COMMAND_NAMES = ["doctor", "plugin", "ts", "agent", "help"];
512
+ var NAME_RE = /^[a-z][a-z0-9-]*$/;
513
+ function isStringArray2(v) {
514
+ return Array.isArray(v) && v.every((x) => typeof x === "string");
515
+ }
516
+ function isPlainObject3(v) {
517
+ return typeof v === "object" && v !== null && !Array.isArray(v);
518
+ }
519
+ function validateAgentMeta(agent2, errors) {
520
+ if (agent2 === void 0 || agent2 === null) return;
521
+ if (!isPlainObject3(agent2)) {
522
+ errors.push("agent \u5FC5\u987B\u662F\u5BF9\u8C61");
523
+ return;
524
+ }
525
+ if (agent2.whenToUse !== void 0 && typeof agent2.whenToUse !== "string") {
526
+ errors.push("agent.whenToUse \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
527
+ }
528
+ if (agent2.globalOptions !== void 0) {
529
+ if (!Array.isArray(agent2.globalOptions)) {
530
+ errors.push("agent.globalOptions \u5FC5\u987B\u662F\u6570\u7EC4");
531
+ } else {
532
+ agent2.globalOptions.forEach((o, i) => {
533
+ if (!isPlainObject3(o) || typeof o.flags !== "string" || typeof o.description !== "string") {
534
+ errors.push(`agent.globalOptions[${i}] \u5FC5\u987B\u662F {flags: string, description: string}`);
535
+ }
536
+ });
537
+ }
538
+ }
539
+ if (agent2.commands !== void 0) {
540
+ if (!Array.isArray(agent2.commands)) {
541
+ errors.push("agent.commands \u5FC5\u987B\u662F\u6570\u7EC4");
542
+ } else {
543
+ agent2.commands.forEach((c, i) => {
544
+ const at = `agent.commands[${i}]`;
545
+ if (!isPlainObject3(c) || typeof c.description !== "string") {
546
+ errors.push(`${at} \u5FC5\u987B\u662F\u542B\u5B57\u7B26\u4E32 description \u7684\u5BF9\u8C61`);
547
+ return;
548
+ }
549
+ if (!Array.isArray(c.path) || !isStringArray2(c.path) || c.path.length === 0) {
550
+ errors.push(`${at}.path \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u6BCF\u6BB5\u5339\u914D ^[a-z][a-z0-9-]*$\uFF09`);
551
+ } else if (c.path.some((seg) => !NAME_RE.test(seg))) {
552
+ errors.push(`${at}.path \u542B\u975E\u6CD5\u6BB5\uFF08\u6BCF\u6BB5\u987B\u5339\u914D ^[a-z][a-z0-9-]*$\uFF09`);
553
+ }
554
+ if (c.usage !== void 0 && typeof c.usage !== "string") {
555
+ errors.push(`${at}.usage \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
556
+ }
557
+ if (c.arguments !== void 0) {
558
+ const bad = !Array.isArray(c.arguments) || c.arguments.some(
559
+ (a) => !isPlainObject3(a) || typeof a.name !== "string" || typeof a.required !== "boolean" || typeof a.description !== "string"
560
+ );
561
+ if (bad) errors.push(`${at}.arguments \u5FC5\u987B\u662F { name: string; required: boolean; description: string }[]`);
562
+ }
563
+ if (c.options !== void 0) {
564
+ const bad = !Array.isArray(c.options) || c.options.some((o) => !isPlainObject3(o) || typeof o.flags !== "string" || typeof o.description !== "string");
565
+ if (bad) errors.push(`${at}.options \u5FC5\u987B\u662F { flags: string; description: string }[]`);
566
+ }
567
+ if (c.output !== void 0) {
568
+ const bad = !isPlainObject3(c.output) || typeof c.output.format !== "string" || typeof c.output.description !== "string";
569
+ if (bad) errors.push(`${at}.output \u5FC5\u987B\u662F { format: string; description: string }`);
570
+ }
571
+ if (c.exitCodes !== void 0) {
572
+ const bad = !isPlainObject3(c.exitCodes) || Object.entries(c.exitCodes).some(([code, desc]) => typeof code !== "string" || typeof desc !== "string");
573
+ if (bad) errors.push(`${at}.exitCodes \u5FC5\u987B\u662F { [\u9000\u51FA\u7801: string]: string }`);
574
+ }
575
+ if (c.safety !== void 0 && !isStringArray2(c.safety)) {
576
+ errors.push(`${at}.safety \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4`);
577
+ }
578
+ });
579
+ }
580
+ }
581
+ }
582
+ function validateLocalCommand(candidate, options = {}) {
583
+ const errors = [];
584
+ if (!isPlainObject3(candidate)) {
585
+ return { ok: false, errors: ["\u63D2\u4EF6\u5BFC\u51FA\u5FC5\u987B\u662F\u5BF9\u8C61"] };
586
+ }
85
587
  const cmd = candidate;
86
- return typeof cmd.name === "string" && typeof cmd.description === "string" && typeof cmd.register === "function";
588
+ if (cmd.apiVersion !== 1) {
589
+ const got = cmd.apiVersion === void 0 ? "\u7F3A\u5931" : JSON.stringify(cmd.apiVersion);
590
+ errors.push(
591
+ `apiVersion \u5FC5\u987B\u4E3A 1\uFF08\u5F53\u524D: ${got}\uFF09\u3002v-cli 0.2 \u8D77\u91C7\u7528 apiVersion 1 \u63D2\u4EF6\u5951\u7EA6\uFF0C\u65E7\u7248\u63D2\u4EF6\u8BF7\u8865\u4E0A apiVersion: 1 \u540E\u91CD\u65B0\u52A0\u8F7D`
592
+ );
593
+ }
594
+ if (typeof cmd.name !== "string" || cmd.name.length === 0) {
595
+ errors.push("name \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
596
+ } else {
597
+ if (!NAME_RE.test(cmd.name)) {
598
+ errors.push(`\u547D\u4EE4\u540D "${cmd.name}" \u4E0D\u5408\u6CD5\uFF1A\u53EA\u80FD\u662F\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\u7684 [a-z0-9-] \u5B57\u7B26\u4E32`);
599
+ }
600
+ const reserved = [...RESERVED_COMMAND_NAMES, ...OFFICIAL_COMMAND_NAMES];
601
+ if (reserved.includes(cmd.name)) {
602
+ const kind = RESERVED_COMMAND_NAMES.includes(cmd.name) ? "v-cli \u5185\u7F6E\u4FDD\u7559\u547D\u4EE4" : "\u5B98\u65B9\u63D2\u4EF6\u547D\u4EE4";
603
+ errors.push(`\u547D\u4EE4\u540D "${cmd.name}" \u662F${kind}\uFF0C\u672C\u5730\u63D2\u4EF6\u4E0D\u80FD\u5360\u7528`);
604
+ }
605
+ if ((options.existingNames ?? []).includes(cmd.name)) {
606
+ errors.push(`\u91CD\u590D\u7684\u547D\u4EE4\u540D "${cmd.name}"\uFF08\u5DF2\u7531\u5176\u4ED6\u672C\u5730\u63D2\u4EF6\u5B9A\u4E49\uFF09`);
607
+ }
608
+ }
609
+ if (typeof cmd.description !== "string" || cmd.description.length === 0) {
610
+ errors.push("description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
611
+ }
612
+ if (typeof cmd.register !== "function") {
613
+ errors.push("register \u5FC5\u987B\u662F\u51FD\u6570");
614
+ }
615
+ validateAgentMeta(cmd.agent, errors);
616
+ if (errors.length > 0) return { ok: false, errors };
617
+ return { ok: true, command: candidate, errors: [] };
87
618
  }
88
619
 
89
- // src/commands/doctor.ts
90
- import fs2 from "fs";
620
+ // src/commands/agent.ts
621
+ import path5 from "path";
622
+
623
+ // src/core/agent-docs.ts
624
+ import fs3 from "fs";
625
+ import path4 from "path";
626
+ import { createHash, randomBytes } from "crypto";
627
+ import { fileURLToPath as fileURLToPath2 } from "url";
91
628
 
92
629
  // src/version.ts
93
- var VERSION = "0.1.1-beta.1";
630
+ var VERSION = "0.2.0-beta.2";
631
+
632
+ // src/core/agent-docs.ts
633
+ var BUNDLED_DOCS_FILE = "AGENTS.md";
634
+ var BUNDLED_DOCS_PACKAGE = "@kevlns/v-cli";
635
+ function bundledCandidatePaths(moduleFile) {
636
+ const candidates = [];
637
+ const push = (p) => {
638
+ if (!candidates.includes(p)) candidates.push(p);
639
+ };
640
+ if (path4.basename(path4.dirname(moduleFile)) === "dist") {
641
+ push(path4.join(path4.dirname(path4.dirname(moduleFile)), BUNDLED_DOCS_FILE));
642
+ }
643
+ let dir = path4.dirname(moduleFile);
644
+ for (; ; ) {
645
+ if (pkgName(path4.join(dir, "package.json")) === BUNDLED_DOCS_PACKAGE) {
646
+ push(path4.join(dir, BUNDLED_DOCS_FILE));
647
+ break;
648
+ }
649
+ const nestedPkg = path4.join(dir, "node_modules", "@kevlns", "v-cli", "package.json");
650
+ if (pkgName(nestedPkg) === BUNDLED_DOCS_PACKAGE) {
651
+ push(path4.join(dir, "node_modules", "@kevlns", "v-cli", BUNDLED_DOCS_FILE));
652
+ break;
653
+ }
654
+ const parent = path4.dirname(dir);
655
+ if (parent === dir) break;
656
+ dir = parent;
657
+ }
658
+ return candidates;
659
+ }
660
+ function pkgName(pkgJsonPath) {
661
+ try {
662
+ const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
663
+ return typeof pkg.name === "string" ? pkg.name : void 0;
664
+ } catch {
665
+ return void 0;
666
+ }
667
+ }
668
+ function resolveBundledAgentsMd(opts = {}) {
669
+ const moduleFile = opts.base ?? fileURLToPath2(import.meta.url);
670
+ for (const candidate of bundledCandidatePaths(moduleFile)) {
671
+ try {
672
+ if (fs3.lstatSync(candidate).isFile()) return candidate;
673
+ } catch {
674
+ }
675
+ }
676
+ return void 0;
677
+ }
678
+ function readBundledAgentsMd(opts = {}) {
679
+ const file = resolveBundledAgentsMd(opts);
680
+ if (!file) {
681
+ const tried = bundledCandidatePaths(opts.base ?? fileURLToPath2(import.meta.url));
682
+ const lastTried = tried.length > 0 ? tried[tried.length - 1] : "\uFF08\u65E0\u5019\u9009\u4F4D\u7F6E\uFF09";
683
+ throw new Error(
684
+ `\u65E0\u6CD5\u5B9A\u4F4D ${BUNDLED_DOCS_PACKAGE} \u5185\u7F6E ${BUNDLED_DOCS_FILE}\uFF1A\u5DF2\u68C0\u67E5 ${tried.length} \u4E2A\u5019\u9009\u4F4D\u7F6E\u5747\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BFB\uFF08\u6700\u540E\u68C0\u67E5: ${lastTried}\uFF09\u3002\u8BF7\u786E\u8BA4\u5B89\u88C5\u7684\u5305\u5185\u5305\u542B AGENTS.md\uFF08\u91CD\u65B0\u5B89\u88C5 @kevlns/v-cli \u540E\u91CD\u8BD5\uFF09\u3002`
685
+ );
686
+ }
687
+ let content;
688
+ try {
689
+ content = fs3.readFileSync(file, "utf-8");
690
+ } catch (err) {
691
+ throw new Error(
692
+ `\u65E0\u6CD5\u8BFB\u53D6 ${BUNDLED_DOCS_PACKAGE} \u5185\u7F6E ${BUNDLED_DOCS_FILE}\uFF08${file}\uFF09: ${err instanceof Error ? err.message : String(err)}`
693
+ );
694
+ }
695
+ return {
696
+ file,
697
+ content,
698
+ sha256: sha256Hex(content),
699
+ bytes: Buffer.byteLength(content, "utf-8")
700
+ };
701
+ }
702
+ function sha256Hex(content) {
703
+ return createHash("sha256").update(content, "utf-8").digest("hex");
704
+ }
705
+ function performAgentInit(opts) {
706
+ const { directory, docs, force = false, dryRun = false, explicitDirectory = false } = opts;
707
+ const target = path4.join(directory, BUNDLED_DOCS_FILE);
708
+ const base = {
709
+ ok: false,
710
+ dryRun,
711
+ action: "refused",
712
+ directory,
713
+ target,
714
+ package: BUNDLED_DOCS_PACKAGE,
715
+ version: VERSION,
716
+ sha256: docs.sha256,
717
+ bytes: docs.bytes
718
+ };
719
+ const refuse = (reason) => ({ ...base, ok: false, action: "refused", reason });
720
+ let dirStat;
721
+ try {
722
+ dirStat = fs3.statSync(directory);
723
+ } catch {
724
+ return refuse(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE: ${directory}`);
725
+ }
726
+ if (!dirStat.isDirectory()) {
727
+ return refuse(`\u76EE\u6807\u4E0D\u662F\u76EE\u5F55: ${directory}`);
728
+ }
729
+ if (explicitDirectory) {
730
+ let dirLstat;
731
+ try {
732
+ dirLstat = fs3.lstatSync(directory);
733
+ } catch {
734
+ }
735
+ if (dirLstat && dirLstat.isSymbolicLink()) {
736
+ return refuse(
737
+ `\u62D2\u7EDD\u5199\u5165\uFF1A\u76EE\u6807\u76EE\u5F55 ${directory} \u662F\u7B26\u53F7\u94FE\u63A5/\u8054\u63A5\u76EE\u5F55\uFF08fail-closed\uFF0C\u4E0D\u8DDF\u968F\uFF09\u3002\u8BF7\u4F20\u5165\u771F\u5B9E\u76EE\u5F55\u8DEF\u5F84\uFF0C\u6216\u7701\u7565\u53C2\u6570\u8BA9 v-cli \u4F7F\u7528\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55`
738
+ );
739
+ }
740
+ }
741
+ let targetStat;
742
+ try {
743
+ targetStat = fs3.lstatSync(target);
744
+ } catch {
745
+ targetStat = void 0;
746
+ }
747
+ if (targetStat) {
748
+ if (targetStat.isSymbolicLink()) {
749
+ return refuse(
750
+ `\u62D2\u7EDD\u5199\u5165 ${target}\uFF1A\u76EE\u6807\u662F\u7B26\u53F7\u94FE\u63A5\uFF08fail-closed\uFF0C\u4E0D\u8DDF\u968F\u3001\u4E0D\u8986\u76D6\u94FE\u63A5\u76EE\u6807\uFF09\u3002\u8BF7\u5148\u79FB\u9664\u94FE\u63A5\u6216\u9009\u62E9\u5176\u4ED6\u76EE\u5F55`
751
+ );
752
+ }
753
+ if (targetStat.isDirectory()) {
754
+ return refuse(`\u62D2\u7EDD\u5199\u5165 ${target}\uFF1A\u76EE\u6807\u662F\u4E00\u4E2A\u76EE\u5F55\uFF08\u65E0\u6CD5\u4EE5\u6587\u4EF6\u8986\u76D6\uFF09`);
755
+ }
756
+ if (!force) {
757
+ return refuse(
758
+ `${target} \u5DF2\u5B58\u5728\uFF0C\u9ED8\u8BA4\u62D2\u7EDD\u8986\u76D6\uFF08\u4E0D\u505A\u4EFB\u4F55\u6539\u52A8\uFF09\u3002\u5982\u9700\u8986\u76D6\u8BF7\u4F7F\u7528 --force\uFF1B\u5982\u9700\u9884\u89C8\u8BF7\u4F7F\u7528 --dry-run\uFF1B\u5982\u9700\u5148\u67E5\u770B\u5185\u5BB9\u8BF7\u8FD0\u884C v-cli agent docs`
759
+ );
760
+ }
761
+ }
762
+ const existed = targetStat !== void 0;
763
+ const action = existed ? dryRun ? "overwrite" : "overwritten" : dryRun ? "write" : "written";
764
+ if (dryRun) {
765
+ return { ...base, ok: true, action };
766
+ }
767
+ try {
768
+ atomicWrite(target, docs.content);
769
+ } catch (err) {
770
+ return refuse(`\u5199\u5165\u5931\u8D25 ${target}: ${err instanceof Error ? err.message : String(err)}`);
771
+ }
772
+ return { ...base, ok: true, action };
773
+ }
774
+ function isEexist(err) {
775
+ return typeof err === "object" && err !== null && err.code === "EEXIST";
776
+ }
777
+ var MAX_TMP_RETRIES = 5;
778
+ function atomicWrite(target, content) {
779
+ let lastError;
780
+ for (let attempt = 0; attempt < MAX_TMP_RETRIES; attempt++) {
781
+ const tmp = path4.join(
782
+ path4.dirname(target),
783
+ `.${BUNDLED_DOCS_FILE}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`
784
+ );
785
+ let created = false;
786
+ try {
787
+ fs3.writeFileSync(tmp, content, { encoding: "utf-8", flag: "wx", mode: 420 });
788
+ created = true;
789
+ fs3.renameSync(tmp, target);
790
+ return;
791
+ } catch (err) {
792
+ lastError = err;
793
+ if (created) {
794
+ try {
795
+ fs3.rmSync(tmp, { force: true });
796
+ } catch {
797
+ }
798
+ }
799
+ if (isEexist(err)) continue;
800
+ throw err;
801
+ }
802
+ }
803
+ const detail = lastError instanceof Error ? lastError.message : String(lastError ?? "\u672A\u77E5\u9519\u8BEF");
804
+ throw new Error(`\u539F\u5B50\u5199\u5165\u5931\u8D25\uFF08\u4E34\u65F6\u6587\u4EF6\u547D\u540D\u78B0\u649E\u91CD\u8BD5 ${MAX_TMP_RETRIES} \u6B21\u672A\u679C\uFF09: ${detail}`);
805
+ }
806
+
807
+ // src/commands/agent.ts
808
+ function buildAgentIndex(builtin, local, official) {
809
+ const rows = [];
810
+ for (const cmd of builtin) {
811
+ rows.push({
812
+ name: cmd.name,
813
+ type: "builtin",
814
+ description: cmd.description,
815
+ metadataStatus: cmd.agent ? "full" : "minimal",
816
+ whenToUse: cmd.agent?.whenToUse,
817
+ globalOptions: cmd.agent?.globalOptions,
818
+ commands: cmd.agent?.commands
819
+ });
820
+ }
821
+ for (const item of local) {
822
+ const cmd = item.command;
823
+ if (!cmd) {
824
+ rows.push({
825
+ name: item.file ? item.file.split(/[\\/]/).pop()?.replace(/\.mjs$/, "") ?? "(\u52A0\u8F7D\u5931\u8D25)" : "(\u52A0\u8F7D\u5931\u8D25)",
826
+ type: "local",
827
+ description: item.error ?? "\u52A0\u8F7D\u5931\u8D25",
828
+ metadataStatus: "minimal",
829
+ error: item.error
830
+ });
831
+ continue;
832
+ }
833
+ rows.push({
834
+ name: cmd.name,
835
+ type: "local",
836
+ description: cmd.description,
837
+ metadataStatus: cmd.agent ? "full" : "minimal",
838
+ whenToUse: cmd.agent?.whenToUse,
839
+ globalOptions: cmd.agent?.globalOptions,
840
+ commands: cmd.agent?.commands
841
+ });
842
+ }
843
+ for (const info of official) {
844
+ rows.push({
845
+ name: info.name,
846
+ type: "official",
847
+ description: info.description,
848
+ metadataStatus: info.status === "available" ? "full" : "minimal",
849
+ package: info.package,
850
+ version: info.version,
851
+ status: info.status,
852
+ platform: info.platform,
853
+ requiredPlatforms: info.requiredPlatforms,
854
+ error: info.error,
855
+ whenToUse: info.whenToUse,
856
+ globalOptions: info.globalOptions,
857
+ // 完整清单元数据全量透传(arguments/options/output/exitCodes/safety)
858
+ commands: info.commands,
859
+ runtime: info.runtime,
860
+ environment: info.environment
861
+ });
862
+ }
863
+ return rows;
864
+ }
865
+ function rowToText(row) {
866
+ const status = row.status ? ` [${row.status}]` : "";
867
+ const meta = row.metadataStatus === "full" ? "\u5B8C\u6574\u5143\u6570\u636E" : "\u6700\u5C0F\u5143\u6570\u636E";
868
+ return `[${row.type}] ${row.name}${status}: ${row.description} (${meta})`;
869
+ }
870
+ function describeCommandLine(c) {
871
+ const pathText = Array.isArray(c.path) ? c.path.join(" ") : "";
872
+ const argText = Array.isArray(c.arguments) && c.arguments.length > 0 ? ` \u53C2\u6570: ${c.arguments.map((a) => `${a.required ? `<${a.name}>` : `[${a.name}]`}`).join(", ")}` : "";
873
+ const optText = Array.isArray(c.options) && c.options.length > 0 ? ` \u9009\u9879: ${c.options.map((o) => o.flags).join(", ")}` : "";
874
+ const outputText = c.output && typeof c.output === "object" && typeof c.output.format === "string" ? ` \u8F93\u51FA: ${c.output.format}${c.output.description ? `\uFF08${c.output.description}\uFF09` : ""}` : "";
875
+ const exitText = c.exitCodes && typeof c.exitCodes === "object" && Object.keys(c.exitCodes).length > 0 ? ` \u9000\u51FA\u7801: ${Object.entries(c.exitCodes).map(([code, desc]) => `${code}=${desc}`).join(", ")}` : "";
876
+ const safeText = Array.isArray(c.safety) && c.safety.length > 0 ? ` \u5B89\u5168: ${c.safety.join("\uFF1B")}` : "";
877
+ return ` ${pathText} \u2014 ${c.description}${c.usage ? ` (\u7528\u6CD5: ${c.usage})` : ""}${argText}${optText}${outputText}${exitText}${safeText}`;
878
+ }
879
+ var AGENT_HELP_TEXT = [
880
+ "agent \u5B50\u547D\u4EE4\u4E00\u89C8\uFF1A",
881
+ " index \u5217\u51FA\u5168\u90E8\u547D\u4EE4\uFF08builtin/local/official\uFF09\u4E0E agent \u5143\u6570\u636E\uFF1B--json \u8F93\u51FA\u7A33\u5B9A JSON \u6570\u7EC4",
882
+ " describe \u67E5\u770B\u5355\u4E2A\u547D\u4EE4\u7684\u5B8C\u6574\u8BB0\u5F55\uFF08\u7528\u6CD5/\u53C2\u6570/\u9009\u9879/\u8F93\u51FA/\u9000\u51FA\u7801/\u5B89\u5168\u6807\u7B7E\uFF09\uFF1B--json \u8F93\u51FA\u5355\u6761\u8BB0\u5F55",
883
+ " docs \u8F93\u51FA\u5F53\u524D @kevlns/v-cli \u5305\u5185\u7F6E AGENTS.md \u539F\u6587\uFF1B--json \u8F93\u51FA { package, version, sha256, content }",
884
+ " init \u628A\u5185\u7F6E AGENTS.md \u5199\u5165\u5DE5\u4F5C\u533A\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09\uFF1B\u5DF2\u5B58\u5728\u9ED8\u8BA4\u62D2\u7EDD\uFF0C--force \u8986\u76D6\uFF0C--dry-run \u9884\u89C8",
885
+ "",
886
+ "JSON\uFF1A\u6240\u6709\u5B50\u547D\u4EE4\u652F\u6301 --json\uFF08\u6216\u524D\u7F6E\u5168\u5C40 --json\uFF0C\u5982 v-cli --json agent index\uFF09",
887
+ "",
888
+ "AI Agent \u5FEB\u901F\u5F00\u59CB\uFF08\u63A8\u8350\u987A\u5E8F\uFF09\uFF1A",
889
+ " 1. v-cli agent docs",
890
+ " 2. v-cli agent index --json",
891
+ " 3. v-cli agent describe <name> --json",
892
+ " 4. v-cli agent init . \uFF08\u53EF\u9009\uFF1A\u5199\u5165\u5DE5\u4F5C\u533A AGENTS.md\uFF0CAI Agent \u81EA\u52A8\u8BFB\u53D6\uFF09",
893
+ "",
894
+ "\u793A\u4F8B\uFF1A",
895
+ " v-cli agent index --json # \u5168\u91CF\u547D\u4EE4\u7D22\u5F15",
896
+ " v-cli agent describe ts --json # \u5355\u547D\u4EE4\u8BE6\u60C5",
897
+ " v-cli agent docs # \u5185\u7F6E\u5F15\u5BFC\u6587\u6863\u539F\u6587",
898
+ " v-cli agent init . --dry-run # \u9884\u89C8\u521D\u59CB\u5316\u76EE\u6807"
899
+ ].join("\n");
900
+ var DOCS_HELP_TEXT = [
901
+ "\u8F93\u51FA\u5F53\u524D\u5B89\u88C5\u7684 @kevlns/v-cli \u5305\u5185\u7F6E AGENTS.md \u539F\u6587\uFF08AI Agent \u5F15\u5BFC\u6587\u6863\uFF09\uFF0C\u9010\u5B57\u8282\u539F\u6837\u8F93\u51FA\u3001\u4E0D\u8FFD\u52A0\u6362\u884C\u3002",
902
+ "",
903
+ "JSON \u884C\u4E3A\uFF08--json \u6216\u524D\u7F6E\u5168\u5C40 --json\uFF09\uFF1A",
904
+ " \u6210\u529F\uFF1Astdout \u8F93\u51FA\u7A33\u5B9A\u5BF9\u8C61 { package, version, sha256, content }",
905
+ " package \u5305\u540D @kevlns/v-cli",
906
+ " version \u5F53\u524D\u5305\u7248\u672C",
907
+ " sha256 content \u7684 SHA-256\uFF08hex\uFF09\uFF0C\u53EF\u6821\u9A8C\u5F15\u5BFC\u6587\u6863\u672A\u88AB\u7BE1\u6539",
908
+ " content AGENTS.md \u539F\u6587",
909
+ " \u5931\u8D25\uFF1Astdout \u8F93\u51FA\u7A00\u758F\u5BF9\u8C61 { ok: false, error }\uFF0Cstderr \u518D\u8F93\u51FA\u540C\u4E00 error\uFF0C\u9000\u51FA\u7801 1",
910
+ "",
911
+ "\u9000\u51FA\u7801\uFF1A0 \u6210\u529F\uFF1B1 \u5185\u7F6E AGENTS.md \u7F3A\u5931/\u4E0D\u53EF\u8BFB",
912
+ "",
913
+ "\u793A\u4F8B\uFF1A",
914
+ " v-cli agent docs",
915
+ " v-cli agent docs --json"
916
+ ].join("\n");
917
+ var INIT_HELP_TEXT = [
918
+ "\u628A\u5F53\u524D @kevlns/v-cli \u5305\u5185\u7F6E AGENTS.md \u539F\u6837\u5199\u5165 <\u76EE\u5F55>/AGENTS.md\uFF0CAI Agent \u4F1A\u81EA\u52A8\u8BFB\u53D6\u5DE5\u4F5C\u533A\u6587\u6863\u3002",
919
+ "",
920
+ "\u53C2\u6570\uFF1A",
921
+ " [directory] \u76EE\u6807\u76EE\u5F55\uFF0C\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1B\u5FC5\u987B\u5DF2\u5B58\u5728\u4E14\u4E3A\u76EE\u5F55",
922
+ "",
923
+ "\u9ED8\u8BA4\u884C\u4E3A\uFF08\u65E0 --force\uFF09\uFF1A\u76EE\u6807\u5DF2\u5B58\u5728 AGENTS.md \u65F6\u62D2\u7EDD\u5E76\u9000\u51FA 1\uFF0C\u7EDD\u4E0D\u6539\u52A8\u73B0\u6709\u6587\u4EF6\u3002",
924
+ " --force \u8986\u76D6\u5DF2\u5B58\u5728\u7684 AGENTS.md\uFF08\u539F\u5B50\u5199\u5165\uFF1A\u540C\u76EE\u5F55\u4E34\u65F6\u6587\u4EF6 + rename\uFF09",
925
+ " --dry-run \u53EA\u62A5\u544A\u76EE\u6807\u4E0E\u5C06\u6267\u884C\u7684\u52A8\u4F5C\uFF0C\u4E0D\u5199\u5165\u4EFB\u4F55\u6587\u4EF6",
926
+ " --json \u6210\u529F/\u5E72\u8DD1\u8F93\u51FA\u7A33\u5B9A JSON { ok, dryRun, action, directory, target, package, version, sha256, bytes }\uFF1B",
927
+ ' \u62D2\u7EDD\uFF08\u5982\u5DF2\u5B58\u5728\u672A\u52A0 --force\uFF09\u8F93\u51FA { ok: false, action: "refused", reason, \u2026 } \u4E14\u9000\u51FA 1\uFF1B',
928
+ " \u5185\u7F6E\u6587\u6863\u7F3A\u5931\u65F6\u8F93\u51FA\u7A00\u758F { ok: false, error } \u4E14\u9000\u51FA 1",
929
+ " \u7B26\u53F7\u94FE\u63A5\u89C4\u5219\uFF1A\u76EE\u6807 AGENTS.md \u662F\u7B26\u53F7\u94FE\u63A5\u4E00\u5F8B\u62D2\u7EDD\uFF08fail-closed\uFF1A\u4E0D\u8DDF\u968F\u3001\u4E0D\u8986\u76D6\u94FE\u63A5\u76EE\u6807\uFF09\uFF1B",
930
+ " \u663E\u5F0F\u4F20\u5165\u7684\u76EE\u5F55\u672C\u8EAB\u662F\u7B26\u53F7\u94FE\u63A5/\u8054\u63A5\u76EE\u5F55\uFF08junction\uFF09\u4E5F\u62D2\u7EDD\uFF1B\u7701\u7565\u76EE\u5F55\u53C2\u6570\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\u4E0D\u53D7\u6B64\u9650\u5236",
931
+ "",
932
+ "\u9000\u51FA\u7801\uFF1A0 \u6210\u529F\u6216\u5E72\u8DD1\uFF1B1 \u76EE\u5F55\u65E0\u6548 / \u5DF2\u5B58\u5728\u672A\u52A0 --force / \u7B26\u53F7\u94FE\u63A5\u6216\u8054\u63A5\u76EE\u6807 / \u5185\u7F6E\u6587\u6863\u7F3A\u5931 / \u5199\u5165\u5931\u8D25",
933
+ "",
934
+ "\u793A\u4F8B\uFF1A",
935
+ " v-cli agent init",
936
+ " v-cli agent init ./myproj",
937
+ " v-cli agent init --dry-run",
938
+ " v-cli agent init --force",
939
+ " v-cli agent init --json"
940
+ ].join("\n");
941
+ var agent = {
942
+ name: "agent",
943
+ description: "agent\uFF1Aindex \u547D\u4EE4\u7D22\u5F15\uFF0Cdescribe \u8BE6\u60C5\uFF0Cdocs \u5185\u7F6E AGENTS.md\uFF0Cinit \u5DE5\u4F5C\u533A\u5F15\u5BFC",
944
+ apiVersion: 1,
945
+ agent: {
946
+ whenToUse: "\u5F53\u7528\u6237\u9700\u8981 AGENTS \u5F15\u5BFC\u6587\u6863\u3001\u547D\u4EE4\u53D1\u73B0\u6216\u4E3A AI Agent \u521D\u59CB\u5316\u5DE5\u4F5C\u533A\u65F6\uFF1A\u5148 `docs` \u8BFB\u5185\u7F6E AGENTS.md\uFF0C\u518D `index --json` \u679A\u4E3E\u5168\u90E8\u547D\u4EE4\u4E0E\u5143\u6570\u636E\uFF0C\u7528 `describe <name> --json` \u770B\u5355\u547D\u4EE4\u8BE6\u60C5\uFF0C\u6700\u540E\u53EF `init` \u628A AGENTS.md \u5199\u5165\u5DE5\u4F5C\u533A",
947
+ globalOptions: [
948
+ { flags: "-h, --help", description: "\u663E\u793A\u547D\u4EE4\u5E2E\u52A9" }
949
+ ],
950
+ commands: [
951
+ {
952
+ path: ["index"],
953
+ usage: "v-cli agent index [--json]",
954
+ description: "\u5217\u51FA\u5168\u90E8\u547D\u4EE4\uFF08builtin/local/official\uFF09\u4E0E agent \u5143\u6570\u636E",
955
+ arguments: [],
956
+ options: [{ flags: "--json", description: "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON" }],
957
+ output: {
958
+ format: "stdout",
959
+ description: "\u6B63\u6587\u8F93\u51FA\u5230 stdout\uFF1B--json \u65F6\u8F93\u51FA\u7A33\u5B9A JSON \u6570\u7EC4\uFF08name/type/description/metadataStatus/commands/\u2026\uFF09"
960
+ },
961
+ exitCodes: { "0": "\u6210\u529F" },
962
+ safety: ["read-only", "no-network"]
963
+ },
964
+ {
965
+ path: ["describe"],
966
+ usage: "v-cli agent describe <name> [--json]",
967
+ description: "\u67E5\u770B\u5355\u4E2A\u547D\u4EE4\u7684\u5B8C\u6574\u8BB0\u5F55",
968
+ arguments: [{ name: "name", required: true, description: "\u547D\u4EE4\u540D\uFF08builtin/local/official\uFF09" }],
969
+ options: [{ flags: "--json", description: "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON" }],
970
+ output: { format: "stdout", description: "\u6B63\u6587\u8F93\u51FA\u5230 stdout\uFF1B--json \u65F6\u8F93\u51FA\u5355\u6761\u7A33\u5B9A JSON \u5BF9\u8C61" },
971
+ exitCodes: { "0": "\u6210\u529F", "1": "\u672A\u627E\u5230\u547D\u4EE4\uFF08stderr \u8BF4\u660E\uFF09" },
972
+ safety: ["read-only"]
973
+ },
974
+ {
975
+ path: ["docs"],
976
+ usage: "v-cli agent docs [--json]",
977
+ description: "\u8F93\u51FA\u5F53\u524D @kevlns/v-cli \u5305\u5185\u7F6E AGENTS.md \u539F\u6587",
978
+ arguments: [],
979
+ options: [{ flags: "--json", description: "\u8F93\u51FA { package, version, sha256, content }" }],
980
+ output: {
981
+ format: "stdout",
982
+ description: "AGENTS.md \u539F\u6587\u9010\u5B57\u8282\u8F93\u51FA\u5230 stdout\uFF1B--json \u65F6\u8F93\u51FA\u542B sha256/content \u7684\u7A33\u5B9A JSON"
983
+ },
984
+ exitCodes: { "0": "\u6210\u529F", "1": "\u5185\u7F6E AGENTS.md \u7F3A\u5931/\u4E0D\u53EF\u8BFB" },
985
+ safety: ["read-only", "no-network"]
986
+ },
987
+ {
988
+ path: ["init"],
989
+ usage: "v-cli agent init [directory] [--force] [--dry-run] [--json]",
990
+ description: "\u628A\u5185\u7F6E AGENTS.md \u5199\u5165 <\u76EE\u5F55>/AGENTS.md\uFF1B\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF1B\u5DF2\u5B58\u5728\u9ED8\u8BA4\u62D2\u7EDD",
991
+ arguments: [
992
+ { name: "directory", required: false, description: "\u76EE\u6807\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1B\u987B\u5DF2\u5B58\u5728\u4E14\u4E3A\u76EE\u5F55\uFF09" }
993
+ ],
994
+ options: [
995
+ { flags: "--force", description: "\u8986\u76D6\u5DF2\u5B58\u5728 AGENTS.md\uFF08\u539F\u5B50\u5199\u5165\uFF09" },
996
+ { flags: "--dry-run", description: "\u53EA\u62A5\u544A\u76EE\u6807\u4E0E\u52A8\u4F5C\uFF0C\u4E0D\u5199\u5165" },
997
+ { flags: "--json", description: "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB\u7ED3\u679C" }
998
+ ],
999
+ output: {
1000
+ format: "stdout",
1001
+ description: "\u7ED3\u679C\u6587\u672C\u8F93\u51FA\u5230 stdout\uFF1B--json \u65F6\u8F93\u51FA\u7A33\u5B9A JSON\uFF08ok/dryRun/action/directory/target/package/version/sha256/bytes\uFF09"
1002
+ },
1003
+ exitCodes: {
1004
+ "0": "\u6210\u529F\u6216\u5E72\u8DD1",
1005
+ "1": "\u76EE\u5F55\u65E0\u6548 / \u5DF2\u5B58\u5728\u672A\u8986\u76D6 / \u7B26\u53F7\u94FE\u63A5\u6216\u8054\u63A5\u76EE\u6807 / \u6587\u6863\u7F3A\u5931 / \u5199\u5165\u5931\u8D25"
1006
+ },
1007
+ safety: [
1008
+ "writes-target-atomic",
1009
+ "refuses-existing",
1010
+ "fail-closed-symlink",
1011
+ "dry-run-supported",
1012
+ "no-commit"
1013
+ ]
1014
+ }
1015
+ ]
1016
+ },
1017
+ register(program, ctx) {
1018
+ program.command("index").description("\u5217\u51FA\u5168\u90E8\u547D\u4EE4\uFF08builtin/local/official\uFF09\u4E0E agent \u5143\u6570\u636E").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action(async (opts) => {
1019
+ const json = ctx.json || opts.json;
1020
+ const rows = buildAgentIndex(
1021
+ builtinCommands,
1022
+ await loadLocalPlugins(ctx),
1023
+ discoverAllOfficialPlugins()
1024
+ );
1025
+ ctx.log.result(json ? rows : rows.map(rowToText).join("\n"));
1026
+ });
1027
+ program.command("describe").description("\u67E5\u770B\u5355\u4E2A\u547D\u4EE4\u7684\u5B8C\u6574\u8BB0\u5F55\uFF08builtin/local/official\uFF09").argument("<name>", "\u547D\u4EE4\u540D").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action(async (name, opts) => {
1028
+ const json = ctx.json || opts.json;
1029
+ const row = buildAgentIndex(
1030
+ builtinCommands,
1031
+ await loadLocalPlugins(ctx),
1032
+ discoverAllOfficialPlugins()
1033
+ ).find((r) => r.name === name);
1034
+ if (!row) {
1035
+ ctx.log.error(`\u672A\u627E\u5230\u547D\u4EE4: ${name}`);
1036
+ process.exitCode = 1;
1037
+ return;
1038
+ }
1039
+ if (json) {
1040
+ ctx.log.result(row);
1041
+ return;
1042
+ }
1043
+ const lines = [
1044
+ `\u547D\u4EE4: ${row.name} (${row.type})`,
1045
+ `\u63CF\u8FF0: ${row.description}`,
1046
+ `\u5143\u6570\u636E: ${row.metadataStatus}`
1047
+ ];
1048
+ if (row.status) lines.push(`\u72B6\u6001: ${row.status}${row.error ? `\uFF08${row.error}\uFF09` : ""}`);
1049
+ if (row.package) lines.push(`\u5305: ${row.package}${row.version ? `@${row.version}` : ""}`);
1050
+ if (row.requiredPlatforms) lines.push(`\u652F\u6301\u5E73\u53F0: ${row.requiredPlatforms.join(", ")}`);
1051
+ if (row.whenToUse) lines.push(`\u4F55\u65F6\u4F7F\u7528: ${row.whenToUse}`);
1052
+ if (row.globalOptions && row.globalOptions.length > 0) {
1053
+ lines.push("\u5168\u5C40\u9009\u9879:");
1054
+ for (const o of row.globalOptions) lines.push(` ${o.flags} \u2014 ${o.description}`);
1055
+ }
1056
+ if (row.commands && row.commands.length > 0) {
1057
+ lines.push("\u5B50\u547D\u4EE4:");
1058
+ for (const c of row.commands) lines.push(describeCommandLine(c));
1059
+ }
1060
+ ctx.log.result(lines.join("\n"));
1061
+ });
1062
+ program.command("docs").description("\u8F93\u51FA\u5F53\u524D @kevlns/v-cli \u5305\u5185\u7F6E AGENTS.md \u539F\u6587\uFF08AI Agent \u5F15\u5BFC\u6587\u6863\uFF09").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON\uFF08\u542B sha256/content\uFF09").addHelpText("after", DOCS_HELP_TEXT).action((opts) => {
1063
+ const json = ctx.json || opts.json;
1064
+ let docs;
1065
+ try {
1066
+ docs = readBundledAgentsMd();
1067
+ } catch (err) {
1068
+ const message = err instanceof Error ? err.message : String(err);
1069
+ if (json) ctx.log.result({ ok: false, error: message });
1070
+ ctx.log.error(message);
1071
+ process.exitCode = 1;
1072
+ return;
1073
+ }
1074
+ if (json) {
1075
+ ctx.log.result({
1076
+ package: BUNDLED_DOCS_PACKAGE,
1077
+ version: VERSION,
1078
+ sha256: docs.sha256,
1079
+ content: docs.content
1080
+ });
1081
+ return;
1082
+ }
1083
+ process.stdout.write(docs.content);
1084
+ });
1085
+ program.command("init").description("\u521D\u59CB\u5316 <\u76EE\u5F55>/AGENTS.md\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09\uFF1B\u5DF2\u5B58\u5728\u9ED8\u8BA4\u62D2\u7EDD\uFF0C--force \u8986\u76D6\uFF0C--dry-run \u9884\u89C8").argument("[directory]", "\u76EE\u6807\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1B\u5FC5\u987B\u5DF2\u5B58\u5728\u4E14\u4E3A\u76EE\u5F55\uFF09").option("--force", "\u8986\u76D6\u5DF2\u5B58\u5728\u7684 AGENTS.md\uFF08\u539F\u5B50\u5199\u5165\uFF09").option("--dry-run", "\u53EA\u62A5\u544A\u76EE\u6807\u4E0E\u52A8\u4F5C\uFF0C\u4E0D\u5199\u5165\u4EFB\u4F55\u6587\u4EF6").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB\u7ED3\u679C").addHelpText("after", INIT_HELP_TEXT).action(
1086
+ (directory, opts) => {
1087
+ const json = ctx.json || opts.json;
1088
+ let docs;
1089
+ try {
1090
+ docs = readBundledAgentsMd();
1091
+ } catch (err) {
1092
+ const message = err instanceof Error ? err.message : String(err);
1093
+ if (json) ctx.log.result({ ok: false, error: message });
1094
+ ctx.log.error(message);
1095
+ process.exitCode = 1;
1096
+ return;
1097
+ }
1098
+ const result = performAgentInit({
1099
+ directory: path5.resolve(directory ?? process.cwd()),
1100
+ docs,
1101
+ force: opts.force,
1102
+ dryRun: opts.dryRun,
1103
+ // 显式传入目录才做目录符号链接/联接 fail-closed;默认 cwd 不受限
1104
+ explicitDirectory: directory !== void 0
1105
+ });
1106
+ if (json) {
1107
+ ctx.log.result(result);
1108
+ if (!result.ok && result.reason) ctx.log.error(result.reason);
1109
+ if (!result.ok) process.exitCode = 1;
1110
+ return;
1111
+ }
1112
+ if (!result.ok) {
1113
+ if (result.reason) ctx.log.error(result.reason);
1114
+ process.exitCode = 1;
1115
+ return;
1116
+ }
1117
+ if (result.dryRun) {
1118
+ ctx.log.result(
1119
+ `[dry-run] \u5C06${result.action === "overwrite" ? "\u8986\u76D6" : "\u5199\u5165"} ${result.target}\uFF08${result.bytes} \u5B57\u8282\uFF09`
1120
+ );
1121
+ } else {
1122
+ ctx.log.result(
1123
+ `\u5DF2${result.action === "overwritten" ? "\u8986\u76D6" : "\u5199\u5165"} ${result.target}\uFF08${result.bytes} \u5B57\u8282\uFF0CSHA-256 ${result.sha256.slice(0, 12)}\u2026\uFF09`
1124
+ );
1125
+ }
1126
+ }
1127
+ );
1128
+ program.addHelpText("after", AGENT_HELP_TEXT);
1129
+ }
1130
+ };
94
1131
 
95
1132
  // src/commands/doctor.ts
1133
+ import fs4 from "fs";
96
1134
  var doctor = {
97
1135
  name: "doctor",
98
1136
  description: "\u4F53\u68C0\uFF1Anode/v-cli \u7248\u672C\u3001homeDir\u3001config\u3001\u63D2\u4EF6\u72B6\u6001",
1137
+ apiVersion: 1,
99
1138
  register(program, ctx) {
100
- program.action(async () => {
1139
+ program.option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action(async (opts) => {
1140
+ const json = ctx.json || opts.json;
101
1141
  const homeDir = ctx.homeDir;
102
- const exists = fs2.existsSync(homeDir);
1142
+ const exists = fs4.existsSync(homeDir);
103
1143
  let configWritable = false;
104
1144
  try {
105
1145
  ctx.config.set((cfg) => cfg);
@@ -113,6 +1153,14 @@ var doctor = {
113
1153
  file: p.file,
114
1154
  error: p.error ?? null
115
1155
  }));
1156
+ const officialPlugins = discoverAllOfficialPlugins().map((o) => ({
1157
+ package: o.package,
1158
+ name: o.name,
1159
+ version: o.version,
1160
+ status: o.status,
1161
+ platform: o.platform,
1162
+ error: o.error
1163
+ }));
116
1164
  const report = {
117
1165
  version: VERSION,
118
1166
  node: process.version,
@@ -120,9 +1168,10 @@ var doctor = {
120
1168
  homeDirExists: exists,
121
1169
  configWritable,
122
1170
  localPluginCount: plugins.length,
123
- localPlugins: list
1171
+ localPlugins: list,
1172
+ officialPlugins
124
1173
  };
125
- if (ctx.json) {
1174
+ if (json) {
126
1175
  ctx.log.result(report);
127
1176
  } else {
128
1177
  ctx.log.result(`vcli ${VERSION} (node ${process.version})`);
@@ -132,18 +1181,26 @@ var doctor = {
132
1181
  for (const p of plugins) {
133
1182
  ctx.log.result(` - ${p.file}${p.error ? ` [\u5931\u8D25: ${p.error}]` : ""}`);
134
1183
  }
1184
+ ctx.log.result(`\u5B98\u65B9\u63D2\u4EF6: ${officialPlugins.length} \u4E2A`);
1185
+ for (const o of officialPlugins) {
1186
+ ctx.log.result(
1187
+ ` - ${o.name} (${o.package}${o.version ? `@${o.version}` : ""}) [${o.status}]${o.error ? ` ${o.error}` : ""}`
1188
+ );
1189
+ }
135
1190
  }
136
1191
  });
137
1192
  }
138
1193
  };
139
1194
 
140
1195
  // src/commands/plugin.ts
141
- import path3 from "path";
1196
+ import path6 from "path";
142
1197
  var plugin = {
143
1198
  name: "plugin",
144
1199
  description: "\u63D2\u4EF6\u7BA1\u7406\uFF1Alist \u5217\u51FA\u547D\u4EE4\uFF0Cpath \u663E\u793A\u672C\u5730\u63D2\u4EF6\u76EE\u5F55",
1200
+ apiVersion: 1,
145
1201
  register(program, ctx) {
146
- program.command("list").description("\u5217\u51FA\u5185\u7F6E\u547D\u4EE4\u4E0E\u672C\u5730\u63D2\u4EF6").action(async () => {
1202
+ program.command("list").description("\u5217\u51FA\u5185\u7F6E\u547D\u4EE4\u3001\u672C\u5730\u63D2\u4EF6\u4E0E\u5B98\u65B9\u63D2\u4EF6\u72B6\u6001").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action(async (opts) => {
1203
+ const json = ctx.json || opts.json;
147
1204
  const all = await loadAllCommands(ctx);
148
1205
  const rows = all.map((item) => ({
149
1206
  source: item.source,
@@ -151,17 +1208,32 @@ var plugin = {
151
1208
  description: item.command?.description ?? item.error ?? "",
152
1209
  file: item.file ?? ""
153
1210
  }));
154
- if (ctx.json) {
155
- ctx.log.result(rows);
1211
+ const official = discoverAllOfficialPlugins().map((o) => ({
1212
+ source: "official",
1213
+ name: o.name,
1214
+ description: o.description,
1215
+ package: o.package,
1216
+ version: o.version,
1217
+ status: o.status,
1218
+ platform: o.platform,
1219
+ error: o.error
1220
+ }));
1221
+ if (json) {
1222
+ ctx.log.result([...rows, ...official]);
156
1223
  } else {
157
1224
  for (const row of rows) {
158
1225
  ctx.log.result(`[${row.source}] ${row.name}: ${row.description}`);
159
1226
  }
1227
+ for (const row of official) {
1228
+ ctx.log.result(
1229
+ `[${row.source}] ${row.name}: ${row.description} [${row.status}]${row.error ? ` ${row.error}` : ""}`
1230
+ );
1231
+ }
160
1232
  }
161
1233
  });
162
- program.command("path").description("\u6253\u5370\u672C\u5730\u63D2\u4EF6\u76EE\u5F55\uFF08\u653E\u5165 .mjs \u6587\u4EF6\u5373\u751F\u6548\uFF09").action(() => {
163
- const dir = path3.join(ctx.homeDir, "commands");
164
- if (ctx.json) {
1234
+ program.command("path").description("\u6253\u5370\u672C\u5730\u63D2\u4EF6\u76EE\u5F55\uFF08\u653E\u5165 .mjs \u6587\u4EF6\u5373\u751F\u6548\uFF09").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action((opts) => {
1235
+ const dir = path6.join(ctx.homeDir, "commands");
1236
+ if (ctx.json || opts.json) {
165
1237
  ctx.log.result({ dir });
166
1238
  } else {
167
1239
  ctx.log.result(dir);
@@ -192,8 +1264,10 @@ function parseInput(input) {
192
1264
  var ts = {
193
1265
  name: "ts",
194
1266
  description: "\u65F6\u95F4\u6233\u5DE5\u5177\uFF1A\u65F6\u95F4\u6233 <-> \u53EF\u8BFB\u65F6\u95F4\u4E92\u8F6C",
1267
+ apiVersion: 1,
195
1268
  register(program, ctx) {
196
- program.argument("[value]", "\u6570\u5B57\u65F6\u95F4\u6233\uFF08\u79D2\u6216\u6BEB\u79D2\uFF0C\u81EA\u52A8\u8BC6\u522B\uFF09\u6216\u65E5\u671F\u5B57\u7B26\u4E32").action((value) => {
1269
+ program.argument("[value]", "\u6570\u5B57\u65F6\u95F4\u6233\uFF08\u79D2\u6216\u6BEB\u79D2\uFF0C\u81EA\u52A8\u8BC6\u522B\uFF09\u6216\u65E5\u671F\u5B57\u7B26\u4E32").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action((value, opts) => {
1270
+ const json = ctx.json || opts.json;
197
1271
  if (!value || value === "now") {
198
1272
  const now = Date.now();
199
1273
  const report2 = {
@@ -202,7 +1276,7 @@ var ts = {
202
1276
  iso: new Date(now).toISOString(),
203
1277
  local: new Date(now).toString()
204
1278
  };
205
- ctx.log.result(ctx.json ? report2 : report2.iso);
1279
+ ctx.log.result(json ? report2 : report2.iso);
206
1280
  return;
207
1281
  }
208
1282
  const result = parseInput(value);
@@ -213,36 +1287,38 @@ var ts = {
213
1287
  iso: new Date(result.milliseconds).toISOString(),
214
1288
  local: formatTime(result.milliseconds)
215
1289
  };
216
- ctx.log.result(ctx.json ? report : report.local);
1290
+ ctx.log.result(json ? report : report.local);
217
1291
  });
218
1292
  }
219
1293
  };
220
1294
 
221
1295
  // src/commands/index.ts
222
- var builtinCommands = [doctor, plugin, ts];
1296
+ var builtinCommands = [doctor, plugin, ts, agent];
223
1297
 
224
1298
  // src/core/loader.ts
225
1299
  async function loadBuiltinCommands() {
226
1300
  return builtinCommands.map((command) => ({ command, source: "builtin" }));
227
1301
  }
228
1302
  async function loadLocalPlugins(ctx) {
229
- const dir = path4.join(ctx.homeDir, "commands");
1303
+ const dir = path7.join(ctx.homeDir, "commands");
230
1304
  const loaded = [];
231
- if (!fs3.existsSync(dir)) return loaded;
232
- const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".mjs")).sort();
1305
+ if (!fs5.existsSync(dir)) return loaded;
1306
+ const files = fs5.readdirSync(dir).filter((f) => f.endsWith(".mjs")).sort();
233
1307
  for (const file of files) {
234
- const abs = path4.join(dir, file);
1308
+ const abs = path7.join(dir, file);
235
1309
  try {
236
1310
  const mod = await import(
237
1311
  /* @vite-ignore */
238
1312
  pathToFileURL(abs).href
239
1313
  );
240
- const cmd = mod.default ?? mod;
241
- if (!isCliCommand(cmd)) {
242
- loaded.push({ source: "local", file: abs, error: "\u9ED8\u8BA4\u5BFC\u51FA\u4E0D\u7B26\u5408 CliCommand \u5951\u7EA6" });
1314
+ const candidate = mod.default ?? mod;
1315
+ const acceptedNames = loaded.map((l) => l.command?.name).filter((n) => typeof n === "string");
1316
+ const validation = validateLocalCommand(candidate, { existingNames: acceptedNames });
1317
+ if (!validation.ok || !validation.command) {
1318
+ loaded.push({ source: "local", file: abs, error: validation.errors.join("\uFF1B") });
243
1319
  continue;
244
1320
  }
245
- loaded.push({ command: cmd, source: "local", file: abs });
1321
+ loaded.push({ command: validation.command, source: "local", file: abs });
246
1322
  } catch (err) {
247
1323
  loaded.push({
248
1324
  source: "local",
@@ -257,14 +1333,132 @@ async function loadAllCommands(ctx) {
257
1333
  return [...await loadBuiltinCommands(), ...await loadLocalPlugins(ctx)];
258
1334
  }
259
1335
 
1336
+ // src/core/runner.ts
1337
+ import { spawn } from "child_process";
1338
+ var PLUGIN_ENV_KEYS = {
1339
+ V_CLI_HOST_VERSION: VERSION,
1340
+ V_CLI_PLUGIN_API: "1",
1341
+ V_CLI_INVOKED_BY: "v-cli"
1342
+ };
1343
+ var SIGNAL_NUMBERS = {
1344
+ SIGHUP: 1,
1345
+ SIGINT: 2,
1346
+ SIGQUIT: 3,
1347
+ SIGILL: 4,
1348
+ SIGTRAP: 5,
1349
+ SIGABRT: 6,
1350
+ SIGBUS: 7,
1351
+ SIGFPE: 8,
1352
+ SIGKILL: 9,
1353
+ SIGUSR1: 10,
1354
+ SIGSEGV: 11,
1355
+ SIGUSR2: 12,
1356
+ SIGPIPE: 13,
1357
+ SIGALRM: 14,
1358
+ SIGTERM: 15,
1359
+ SIGSTKFLT: 16,
1360
+ SIGCHLD: 17,
1361
+ SIGCONT: 18,
1362
+ SIGSTOP: 19,
1363
+ SIGTSTP: 20,
1364
+ SIGTTIN: 21,
1365
+ SIGTTOU: 22,
1366
+ SIGURG: 23,
1367
+ SIGXCPU: 24,
1368
+ SIGXFSZ: 25,
1369
+ SIGVTALRM: 26,
1370
+ SIGPROF: 27,
1371
+ SIGWINCH: 28,
1372
+ SIGIO: 29,
1373
+ SIGPWR: 30,
1374
+ SIGSYS: 31
1375
+ };
1376
+ function runPluginBin({ bin, argv, env = {}, cwd }) {
1377
+ return new Promise((resolve, reject) => {
1378
+ const child = spawn(process.execPath, [bin, ...argv], {
1379
+ cwd,
1380
+ env: { ...process.env, ...PLUGIN_ENV_KEYS, ...env },
1381
+ stdio: "inherit",
1382
+ shell: false
1383
+ });
1384
+ const forward = (signal) => {
1385
+ try {
1386
+ child.kill(signal);
1387
+ } catch {
1388
+ }
1389
+ };
1390
+ const onSigint = () => forward("SIGINT");
1391
+ const onSigterm = () => forward("SIGTERM");
1392
+ process.on("SIGINT", onSigint);
1393
+ process.on("SIGTERM", onSigterm);
1394
+ const cleanup = () => {
1395
+ process.removeListener("SIGINT", onSigint);
1396
+ process.removeListener("SIGTERM", onSigterm);
1397
+ };
1398
+ child.on("error", (err) => {
1399
+ cleanup();
1400
+ reject(err);
1401
+ });
1402
+ child.on("close", (code, signal) => {
1403
+ cleanup();
1404
+ resolve({ code, signal });
1405
+ });
1406
+ });
1407
+ }
1408
+ function mapExitCode(result) {
1409
+ if (result.code !== null) return result.code;
1410
+ if (result.signal !== null) {
1411
+ if (process.platform !== "win32") {
1412
+ const signum = SIGNAL_NUMBERS[result.signal];
1413
+ if (signum !== void 0) return 128 + signum;
1414
+ }
1415
+ return 1;
1416
+ }
1417
+ return 1;
1418
+ }
1419
+
260
1420
  // src/cli.ts
1421
+ function scanLeadingJson(rawArgs) {
1422
+ let i = 0;
1423
+ while (i < rawArgs.length && rawArgs[i] === "--json") i++;
1424
+ return { json: i > 0, args: rawArgs.slice(i), commandIndex: i };
1425
+ }
1426
+ async function interceptOfficialCommand(rawArgs, commandIndex) {
1427
+ const token = rawArgs[commandIndex];
1428
+ if (!isOfficialCommand(token)) return void 0;
1429
+ const spec = OFFICIAL_PLUGINS.find((p) => p.command === token);
1430
+ if (!spec) return void 0;
1431
+ const info = discoverOfficialPlugin(spec);
1432
+ if (info.status !== "available" || !info.bin) {
1433
+ process.stderr.write(
1434
+ `[v-cli] \u9519\u8BEF: \u5B98\u65B9\u63D2\u4EF6 ${spec.package}\uFF08\u547D\u4EE4 ${spec.command}\uFF09\u4E0D\u53EF\u7528\uFF1A${info.error ?? info.status}
1435
+ `
1436
+ );
1437
+ return 1;
1438
+ }
1439
+ const result = await runPluginBin({ bin: info.bin, argv: rawArgs.slice(commandIndex + 1) });
1440
+ return mapExitCode(result);
1441
+ }
261
1442
  async function main() {
262
1443
  const rawArgs = process.argv.slice(2);
263
- const json = rawArgs.includes("--json");
264
- const args = rawArgs.filter((a) => a !== "--json");
265
- const ctx = createContext(defaultHomeDir(), json);
1444
+ const span = scanLeadingJson(rawArgs);
1445
+ const ctx = createContext(defaultHomeDir(), span.json);
1446
+ const intercepted = await interceptOfficialCommand(rawArgs, span.commandIndex);
1447
+ if (intercepted !== void 0) return intercepted;
266
1448
  const program = new Command();
267
- program.name("v-cli").description("kevlns \u7684\u4E2A\u4EBA\u5DE5\u5177\u7BB1").version(VERSION).option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").showHelpAfterError();
1449
+ program.name("v-cli").description("kevlns \u7684\u4E2A\u4EBA\u5DE5\u5177\u7BB1").version(VERSION).showHelpAfterError();
1450
+ program.addHelpText(
1451
+ "after",
1452
+ [
1453
+ "AI Agent \u5FEB\u901F\u5F00\u59CB\uFF08\u63A8\u8350\u987A\u5E8F\uFF09\uFF1A",
1454
+ " 1. v-cli agent docs",
1455
+ " 2. v-cli agent index --json",
1456
+ " 3. v-cli agent describe <name> --json",
1457
+ " 4. v-cli agent init . \uFF08\u53EF\u9009\uFF1A\u5199\u5165\u5DE5\u4F5C\u533A AGENTS.md\uFF0CAI Agent \u81EA\u52A8\u8BFB\u53D6\uFF09",
1458
+ "",
1459
+ "\u8BE6\u60C5\u89C1 `v-cli agent --help`\uFF1B\u5355\u547D\u4EE4\u8BF4\u660E\u4E66\uFF1A`v-cli agent docs --help` / `v-cli agent init --help`\u3002"
1460
+ ].join("\n")
1461
+ );
268
1462
  const loaded = await loadAllCommands(ctx);
269
1463
  for (const item of loaded) {
270
1464
  if (!item.command) {
@@ -273,12 +1467,22 @@ async function main() {
273
1467
  }
274
1468
  const cmd = item.command;
275
1469
  if (cmd.hidden) continue;
276
- const sub = program.command(cmd.name).description(cmd.description);
277
- cmd.register(sub, ctx);
1470
+ const sub = new Command(cmd.name).description(cmd.description);
1471
+ try {
1472
+ cmd.register(sub, ctx);
1473
+ program.addCommand(sub);
1474
+ } catch (err) {
1475
+ ctx.log.warn(
1476
+ `${item.file ?? cmd.name} register \u5931\u8D25: ${err instanceof Error ? err.message : String(err)}\uFF1B\u5DF2\u8DF3\u8FC7`
1477
+ );
1478
+ }
1479
+ }
1480
+ for (const spec of OFFICIAL_PLUGINS) {
1481
+ program.command(spec.command).description(`${spec.description}\uFF08\u5B98\u65B9\u63D2\u4EF6\uFF1A\u5B89\u88C5\u540E\u53EF\u7528\uFF09`);
278
1482
  }
279
1483
  try {
280
- await program.parseAsync(args, { from: "user" });
281
- return 0;
1484
+ await program.parseAsync(span.args, { from: "user" });
1485
+ return typeof process.exitCode === "number" ? process.exitCode : 0;
282
1486
  } catch (err) {
283
1487
  if (err instanceof CommanderError) {
284
1488
  return err.exitCode ?? 1;