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

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,665 @@ function defaultHomeDir() {
75
75
  }
76
76
 
77
77
  // src/core/loader.ts
78
- import fs3 from "fs";
79
- import path4 from "path";
78
+ import fs4 from "fs";
79
+ import path5 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
+ });
558
+ }
559
+ }
560
+ }
561
+ function validateLocalCommand(candidate, options = {}) {
562
+ const errors = [];
563
+ if (!isPlainObject3(candidate)) {
564
+ return { ok: false, errors: ["\u63D2\u4EF6\u5BFC\u51FA\u5FC5\u987B\u662F\u5BF9\u8C61"] };
565
+ }
85
566
  const cmd = candidate;
86
- return typeof cmd.name === "string" && typeof cmd.description === "string" && typeof cmd.register === "function";
567
+ if (cmd.apiVersion !== 1) {
568
+ const got = cmd.apiVersion === void 0 ? "\u7F3A\u5931" : JSON.stringify(cmd.apiVersion);
569
+ errors.push(
570
+ `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`
571
+ );
572
+ }
573
+ if (typeof cmd.name !== "string" || cmd.name.length === 0) {
574
+ errors.push("name \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
575
+ } else {
576
+ if (!NAME_RE.test(cmd.name)) {
577
+ 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`);
578
+ }
579
+ const reserved = [...RESERVED_COMMAND_NAMES, ...OFFICIAL_COMMAND_NAMES];
580
+ if (reserved.includes(cmd.name)) {
581
+ const kind = RESERVED_COMMAND_NAMES.includes(cmd.name) ? "v-cli \u5185\u7F6E\u4FDD\u7559\u547D\u4EE4" : "\u5B98\u65B9\u63D2\u4EF6\u547D\u4EE4";
582
+ errors.push(`\u547D\u4EE4\u540D "${cmd.name}" \u662F${kind}\uFF0C\u672C\u5730\u63D2\u4EF6\u4E0D\u80FD\u5360\u7528`);
583
+ }
584
+ if ((options.existingNames ?? []).includes(cmd.name)) {
585
+ errors.push(`\u91CD\u590D\u7684\u547D\u4EE4\u540D "${cmd.name}"\uFF08\u5DF2\u7531\u5176\u4ED6\u672C\u5730\u63D2\u4EF6\u5B9A\u4E49\uFF09`);
586
+ }
587
+ }
588
+ if (typeof cmd.description !== "string" || cmd.description.length === 0) {
589
+ errors.push("description \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
590
+ }
591
+ if (typeof cmd.register !== "function") {
592
+ errors.push("register \u5FC5\u987B\u662F\u51FD\u6570");
593
+ }
594
+ validateAgentMeta(cmd.agent, errors);
595
+ if (errors.length > 0) return { ok: false, errors };
596
+ return { ok: true, command: candidate, errors: [] };
597
+ }
598
+
599
+ // src/commands/agent.ts
600
+ function buildAgentIndex(builtin, local, official) {
601
+ const rows = [];
602
+ for (const cmd of builtin) {
603
+ rows.push({
604
+ name: cmd.name,
605
+ type: "builtin",
606
+ description: cmd.description,
607
+ metadataStatus: cmd.agent ? "full" : "minimal",
608
+ whenToUse: cmd.agent?.whenToUse,
609
+ globalOptions: cmd.agent?.globalOptions,
610
+ commands: cmd.agent?.commands
611
+ });
612
+ }
613
+ for (const item of local) {
614
+ const cmd = item.command;
615
+ if (!cmd) {
616
+ rows.push({
617
+ name: item.file ? item.file.split(/[\\/]/).pop()?.replace(/\.mjs$/, "") ?? "(\u52A0\u8F7D\u5931\u8D25)" : "(\u52A0\u8F7D\u5931\u8D25)",
618
+ type: "local",
619
+ description: item.error ?? "\u52A0\u8F7D\u5931\u8D25",
620
+ metadataStatus: "minimal",
621
+ error: item.error
622
+ });
623
+ continue;
624
+ }
625
+ rows.push({
626
+ name: cmd.name,
627
+ type: "local",
628
+ description: cmd.description,
629
+ metadataStatus: cmd.agent ? "full" : "minimal",
630
+ whenToUse: cmd.agent?.whenToUse,
631
+ globalOptions: cmd.agent?.globalOptions,
632
+ commands: cmd.agent?.commands
633
+ });
634
+ }
635
+ for (const info of official) {
636
+ rows.push({
637
+ name: info.name,
638
+ type: "official",
639
+ description: info.description,
640
+ metadataStatus: info.status === "available" ? "full" : "minimal",
641
+ package: info.package,
642
+ version: info.version,
643
+ status: info.status,
644
+ platform: info.platform,
645
+ requiredPlatforms: info.requiredPlatforms,
646
+ error: info.error,
647
+ whenToUse: info.whenToUse,
648
+ globalOptions: info.globalOptions,
649
+ // 完整清单元数据全量透传(arguments/options/output/exitCodes/safety)
650
+ commands: info.commands,
651
+ runtime: info.runtime,
652
+ environment: info.environment
653
+ });
654
+ }
655
+ return rows;
656
+ }
657
+ function rowToText(row) {
658
+ const status = row.status ? ` [${row.status}]` : "";
659
+ const meta = row.metadataStatus === "full" ? "\u5B8C\u6574\u5143\u6570\u636E" : "\u6700\u5C0F\u5143\u6570\u636E";
660
+ return `[${row.type}] ${row.name}${status}: ${row.description} (${meta})`;
87
661
  }
662
+ var agent = {
663
+ name: "agent",
664
+ description: "agent \u7D22\u5F15\uFF1Aindex \u5217\u51FA\u5168\u90E8\u547D\u4EE4\u53CA\u5143\u6570\u636E\uFF0Cdescribe \u67E5\u770B\u5355\u4E2A\u547D\u4EE4\u8BE6\u60C5",
665
+ apiVersion: 1,
666
+ register(program, ctx) {
667
+ 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) => {
668
+ const json = ctx.json || opts.json;
669
+ const rows = buildAgentIndex(
670
+ builtinCommands,
671
+ await loadLocalPlugins(ctx),
672
+ discoverAllOfficialPlugins()
673
+ );
674
+ ctx.log.result(json ? rows : rows.map(rowToText).join("\n"));
675
+ });
676
+ 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) => {
677
+ const json = ctx.json || opts.json;
678
+ const row = buildAgentIndex(
679
+ builtinCommands,
680
+ await loadLocalPlugins(ctx),
681
+ discoverAllOfficialPlugins()
682
+ ).find((r) => r.name === name);
683
+ if (!row) {
684
+ ctx.log.error(`\u672A\u627E\u5230\u547D\u4EE4: ${name}`);
685
+ process.exitCode = 1;
686
+ return;
687
+ }
688
+ if (json) {
689
+ ctx.log.result(row);
690
+ return;
691
+ }
692
+ const lines = [
693
+ `\u547D\u4EE4: ${row.name} (${row.type})`,
694
+ `\u63CF\u8FF0: ${row.description}`,
695
+ `\u5143\u6570\u636E: ${row.metadataStatus}`
696
+ ];
697
+ if (row.status) lines.push(`\u72B6\u6001: ${row.status}${row.error ? `\uFF08${row.error}\uFF09` : ""}`);
698
+ if (row.package) lines.push(`\u5305: ${row.package}${row.version ? `@${row.version}` : ""}`);
699
+ if (row.requiredPlatforms) lines.push(`\u652F\u6301\u5E73\u53F0: ${row.requiredPlatforms.join(", ")}`);
700
+ if (row.whenToUse) lines.push(`\u4F55\u65F6\u4F7F\u7528: ${row.whenToUse}`);
701
+ if (row.globalOptions && row.globalOptions.length > 0) {
702
+ lines.push("\u5168\u5C40\u9009\u9879:");
703
+ for (const o of row.globalOptions) lines.push(` ${o.flags} \u2014 ${o.description}`);
704
+ }
705
+ if (row.commands && row.commands.length > 0) {
706
+ lines.push("\u5B50\u547D\u4EE4:");
707
+ for (const c of row.commands) {
708
+ const pathText = Array.isArray(c.path) ? c.path.join(" ") : "";
709
+ const optText = Array.isArray(c.options) && c.options.length > 0 ? ` \u9009\u9879: ${c.options.map((o) => o.flags).join(", ")}` : "";
710
+ const safeText = Array.isArray(c.safety) && c.safety.length > 0 ? ` \u5B89\u5168: ${c.safety.join("\uFF1B")}` : "";
711
+ lines.push(
712
+ ` ${pathText} \u2014 ${c.description}${c.usage ? ` (\u7528\u6CD5: ${c.usage})` : ""}${optText}${safeText}`
713
+ );
714
+ }
715
+ }
716
+ ctx.log.result(lines.join("\n"));
717
+ });
718
+ }
719
+ };
88
720
 
89
721
  // src/commands/doctor.ts
90
- import fs2 from "fs";
722
+ import fs3 from "fs";
91
723
 
92
724
  // src/version.ts
93
- var VERSION = "0.1.1-beta.1";
725
+ var VERSION = "0.2.0-beta.1";
94
726
 
95
727
  // src/commands/doctor.ts
96
728
  var doctor = {
97
729
  name: "doctor",
98
730
  description: "\u4F53\u68C0\uFF1Anode/v-cli \u7248\u672C\u3001homeDir\u3001config\u3001\u63D2\u4EF6\u72B6\u6001",
731
+ apiVersion: 1,
99
732
  register(program, ctx) {
100
- program.action(async () => {
733
+ program.option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").action(async (opts) => {
734
+ const json = ctx.json || opts.json;
101
735
  const homeDir = ctx.homeDir;
102
- const exists = fs2.existsSync(homeDir);
736
+ const exists = fs3.existsSync(homeDir);
103
737
  let configWritable = false;
104
738
  try {
105
739
  ctx.config.set((cfg) => cfg);
@@ -113,6 +747,14 @@ var doctor = {
113
747
  file: p.file,
114
748
  error: p.error ?? null
115
749
  }));
750
+ const officialPlugins = discoverAllOfficialPlugins().map((o) => ({
751
+ package: o.package,
752
+ name: o.name,
753
+ version: o.version,
754
+ status: o.status,
755
+ platform: o.platform,
756
+ error: o.error
757
+ }));
116
758
  const report = {
117
759
  version: VERSION,
118
760
  node: process.version,
@@ -120,9 +762,10 @@ var doctor = {
120
762
  homeDirExists: exists,
121
763
  configWritable,
122
764
  localPluginCount: plugins.length,
123
- localPlugins: list
765
+ localPlugins: list,
766
+ officialPlugins
124
767
  };
125
- if (ctx.json) {
768
+ if (json) {
126
769
  ctx.log.result(report);
127
770
  } else {
128
771
  ctx.log.result(`vcli ${VERSION} (node ${process.version})`);
@@ -132,18 +775,26 @@ var doctor = {
132
775
  for (const p of plugins) {
133
776
  ctx.log.result(` - ${p.file}${p.error ? ` [\u5931\u8D25: ${p.error}]` : ""}`);
134
777
  }
778
+ ctx.log.result(`\u5B98\u65B9\u63D2\u4EF6: ${officialPlugins.length} \u4E2A`);
779
+ for (const o of officialPlugins) {
780
+ ctx.log.result(
781
+ ` - ${o.name} (${o.package}${o.version ? `@${o.version}` : ""}) [${o.status}]${o.error ? ` ${o.error}` : ""}`
782
+ );
783
+ }
135
784
  }
136
785
  });
137
786
  }
138
787
  };
139
788
 
140
789
  // src/commands/plugin.ts
141
- import path3 from "path";
790
+ import path4 from "path";
142
791
  var plugin = {
143
792
  name: "plugin",
144
793
  description: "\u63D2\u4EF6\u7BA1\u7406\uFF1Alist \u5217\u51FA\u547D\u4EE4\uFF0Cpath \u663E\u793A\u672C\u5730\u63D2\u4EF6\u76EE\u5F55",
794
+ apiVersion: 1,
145
795
  register(program, ctx) {
146
- program.command("list").description("\u5217\u51FA\u5185\u7F6E\u547D\u4EE4\u4E0E\u672C\u5730\u63D2\u4EF6").action(async () => {
796
+ 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) => {
797
+ const json = ctx.json || opts.json;
147
798
  const all = await loadAllCommands(ctx);
148
799
  const rows = all.map((item) => ({
149
800
  source: item.source,
@@ -151,17 +802,32 @@ var plugin = {
151
802
  description: item.command?.description ?? item.error ?? "",
152
803
  file: item.file ?? ""
153
804
  }));
154
- if (ctx.json) {
155
- ctx.log.result(rows);
805
+ const official = discoverAllOfficialPlugins().map((o) => ({
806
+ source: "official",
807
+ name: o.name,
808
+ description: o.description,
809
+ package: o.package,
810
+ version: o.version,
811
+ status: o.status,
812
+ platform: o.platform,
813
+ error: o.error
814
+ }));
815
+ if (json) {
816
+ ctx.log.result([...rows, ...official]);
156
817
  } else {
157
818
  for (const row of rows) {
158
819
  ctx.log.result(`[${row.source}] ${row.name}: ${row.description}`);
159
820
  }
821
+ for (const row of official) {
822
+ ctx.log.result(
823
+ `[${row.source}] ${row.name}: ${row.description} [${row.status}]${row.error ? ` ${row.error}` : ""}`
824
+ );
825
+ }
160
826
  }
161
827
  });
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) {
828
+ 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) => {
829
+ const dir = path4.join(ctx.homeDir, "commands");
830
+ if (ctx.json || opts.json) {
165
831
  ctx.log.result({ dir });
166
832
  } else {
167
833
  ctx.log.result(dir);
@@ -192,8 +858,10 @@ function parseInput(input) {
192
858
  var ts = {
193
859
  name: "ts",
194
860
  description: "\u65F6\u95F4\u6233\u5DE5\u5177\uFF1A\u65F6\u95F4\u6233 <-> \u53EF\u8BFB\u65F6\u95F4\u4E92\u8F6C",
861
+ apiVersion: 1,
195
862
  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) => {
863
+ 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) => {
864
+ const json = ctx.json || opts.json;
197
865
  if (!value || value === "now") {
198
866
  const now = Date.now();
199
867
  const report2 = {
@@ -202,7 +870,7 @@ var ts = {
202
870
  iso: new Date(now).toISOString(),
203
871
  local: new Date(now).toString()
204
872
  };
205
- ctx.log.result(ctx.json ? report2 : report2.iso);
873
+ ctx.log.result(json ? report2 : report2.iso);
206
874
  return;
207
875
  }
208
876
  const result = parseInput(value);
@@ -213,36 +881,38 @@ var ts = {
213
881
  iso: new Date(result.milliseconds).toISOString(),
214
882
  local: formatTime(result.milliseconds)
215
883
  };
216
- ctx.log.result(ctx.json ? report : report.local);
884
+ ctx.log.result(json ? report : report.local);
217
885
  });
218
886
  }
219
887
  };
220
888
 
221
889
  // src/commands/index.ts
222
- var builtinCommands = [doctor, plugin, ts];
890
+ var builtinCommands = [doctor, plugin, ts, agent];
223
891
 
224
892
  // src/core/loader.ts
225
893
  async function loadBuiltinCommands() {
226
894
  return builtinCommands.map((command) => ({ command, source: "builtin" }));
227
895
  }
228
896
  async function loadLocalPlugins(ctx) {
229
- const dir = path4.join(ctx.homeDir, "commands");
897
+ const dir = path5.join(ctx.homeDir, "commands");
230
898
  const loaded = [];
231
- if (!fs3.existsSync(dir)) return loaded;
232
- const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".mjs")).sort();
899
+ if (!fs4.existsSync(dir)) return loaded;
900
+ const files = fs4.readdirSync(dir).filter((f) => f.endsWith(".mjs")).sort();
233
901
  for (const file of files) {
234
- const abs = path4.join(dir, file);
902
+ const abs = path5.join(dir, file);
235
903
  try {
236
904
  const mod = await import(
237
905
  /* @vite-ignore */
238
906
  pathToFileURL(abs).href
239
907
  );
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" });
908
+ const candidate = mod.default ?? mod;
909
+ const acceptedNames = loaded.map((l) => l.command?.name).filter((n) => typeof n === "string");
910
+ const validation = validateLocalCommand(candidate, { existingNames: acceptedNames });
911
+ if (!validation.ok || !validation.command) {
912
+ loaded.push({ source: "local", file: abs, error: validation.errors.join("\uFF1B") });
243
913
  continue;
244
914
  }
245
- loaded.push({ command: cmd, source: "local", file: abs });
915
+ loaded.push({ command: validation.command, source: "local", file: abs });
246
916
  } catch (err) {
247
917
  loaded.push({
248
918
  source: "local",
@@ -257,14 +927,120 @@ async function loadAllCommands(ctx) {
257
927
  return [...await loadBuiltinCommands(), ...await loadLocalPlugins(ctx)];
258
928
  }
259
929
 
930
+ // src/core/runner.ts
931
+ import { spawn } from "child_process";
932
+ var PLUGIN_ENV_KEYS = {
933
+ V_CLI_HOST_VERSION: VERSION,
934
+ V_CLI_PLUGIN_API: "1",
935
+ V_CLI_INVOKED_BY: "v-cli"
936
+ };
937
+ var SIGNAL_NUMBERS = {
938
+ SIGHUP: 1,
939
+ SIGINT: 2,
940
+ SIGQUIT: 3,
941
+ SIGILL: 4,
942
+ SIGTRAP: 5,
943
+ SIGABRT: 6,
944
+ SIGBUS: 7,
945
+ SIGFPE: 8,
946
+ SIGKILL: 9,
947
+ SIGUSR1: 10,
948
+ SIGSEGV: 11,
949
+ SIGUSR2: 12,
950
+ SIGPIPE: 13,
951
+ SIGALRM: 14,
952
+ SIGTERM: 15,
953
+ SIGSTKFLT: 16,
954
+ SIGCHLD: 17,
955
+ SIGCONT: 18,
956
+ SIGSTOP: 19,
957
+ SIGTSTP: 20,
958
+ SIGTTIN: 21,
959
+ SIGTTOU: 22,
960
+ SIGURG: 23,
961
+ SIGXCPU: 24,
962
+ SIGXFSZ: 25,
963
+ SIGVTALRM: 26,
964
+ SIGPROF: 27,
965
+ SIGWINCH: 28,
966
+ SIGIO: 29,
967
+ SIGPWR: 30,
968
+ SIGSYS: 31
969
+ };
970
+ function runPluginBin({ bin, argv, env = {}, cwd }) {
971
+ return new Promise((resolve, reject) => {
972
+ const child = spawn(process.execPath, [bin, ...argv], {
973
+ cwd,
974
+ env: { ...process.env, ...PLUGIN_ENV_KEYS, ...env },
975
+ stdio: "inherit",
976
+ shell: false
977
+ });
978
+ const forward = (signal) => {
979
+ try {
980
+ child.kill(signal);
981
+ } catch {
982
+ }
983
+ };
984
+ const onSigint = () => forward("SIGINT");
985
+ const onSigterm = () => forward("SIGTERM");
986
+ process.on("SIGINT", onSigint);
987
+ process.on("SIGTERM", onSigterm);
988
+ const cleanup = () => {
989
+ process.removeListener("SIGINT", onSigint);
990
+ process.removeListener("SIGTERM", onSigterm);
991
+ };
992
+ child.on("error", (err) => {
993
+ cleanup();
994
+ reject(err);
995
+ });
996
+ child.on("close", (code, signal) => {
997
+ cleanup();
998
+ resolve({ code, signal });
999
+ });
1000
+ });
1001
+ }
1002
+ function mapExitCode(result) {
1003
+ if (result.code !== null) return result.code;
1004
+ if (result.signal !== null) {
1005
+ if (process.platform !== "win32") {
1006
+ const signum = SIGNAL_NUMBERS[result.signal];
1007
+ if (signum !== void 0) return 128 + signum;
1008
+ }
1009
+ return 1;
1010
+ }
1011
+ return 1;
1012
+ }
1013
+
260
1014
  // src/cli.ts
1015
+ function scanLeadingJson(rawArgs) {
1016
+ let i = 0;
1017
+ while (i < rawArgs.length && rawArgs[i] === "--json") i++;
1018
+ return { json: i > 0, args: rawArgs.slice(i), commandIndex: i };
1019
+ }
1020
+ async function interceptOfficialCommand(rawArgs, commandIndex) {
1021
+ const token = rawArgs[commandIndex];
1022
+ if (!isOfficialCommand(token)) return void 0;
1023
+ const spec = OFFICIAL_PLUGINS.find((p) => p.command === token);
1024
+ if (!spec) return void 0;
1025
+ const info = discoverOfficialPlugin(spec);
1026
+ if (info.status !== "available" || !info.bin) {
1027
+ process.stderr.write(
1028
+ `[v-cli] \u9519\u8BEF: \u5B98\u65B9\u63D2\u4EF6 ${spec.package}\uFF08\u547D\u4EE4 ${spec.command}\uFF09\u4E0D\u53EF\u7528\uFF1A${info.error ?? info.status}
1029
+ `
1030
+ );
1031
+ return 1;
1032
+ }
1033
+ const result = await runPluginBin({ bin: info.bin, argv: rawArgs.slice(commandIndex + 1) });
1034
+ return mapExitCode(result);
1035
+ }
261
1036
  async function main() {
262
1037
  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);
1038
+ const span = scanLeadingJson(rawArgs);
1039
+ const ctx = createContext(defaultHomeDir(), span.json);
1040
+ const intercepted = await interceptOfficialCommand(rawArgs, span.commandIndex);
1041
+ if (intercepted !== void 0) return intercepted;
266
1042
  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();
1043
+ program.name("v-cli").description("kevlns \u7684\u4E2A\u4EBA\u5DE5\u5177\u7BB1").version(VERSION).showHelpAfterError();
268
1044
  const loaded = await loadAllCommands(ctx);
269
1045
  for (const item of loaded) {
270
1046
  if (!item.command) {
@@ -273,12 +1049,22 @@ async function main() {
273
1049
  }
274
1050
  const cmd = item.command;
275
1051
  if (cmd.hidden) continue;
276
- const sub = program.command(cmd.name).description(cmd.description);
277
- cmd.register(sub, ctx);
1052
+ const sub = new Command(cmd.name).description(cmd.description);
1053
+ try {
1054
+ cmd.register(sub, ctx);
1055
+ program.addCommand(sub);
1056
+ } catch (err) {
1057
+ ctx.log.warn(
1058
+ `${item.file ?? cmd.name} register \u5931\u8D25: ${err instanceof Error ? err.message : String(err)}\uFF1B\u5DF2\u8DF3\u8FC7`
1059
+ );
1060
+ }
1061
+ }
1062
+ for (const spec of OFFICIAL_PLUGINS) {
1063
+ program.command(spec.command).description(`${spec.description}\uFF08\u5B98\u65B9\u63D2\u4EF6\uFF1A\u5B89\u88C5\u540E\u53EF\u7528\uFF09`);
278
1064
  }
279
1065
  try {
280
- await program.parseAsync(args, { from: "user" });
281
- return 0;
1066
+ await program.parseAsync(span.args, { from: "user" });
1067
+ return typeof process.exitCode === "number" ? process.exitCode : 0;
282
1068
  } catch (err) {
283
1069
  if (err instanceof CommanderError) {
284
1070
  return err.exitCode ?? 1;