@kb-labs/cli-commands 2.3.0 → 2.5.0

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.
@@ -0,0 +1,1712 @@
1
+ import { createRequire } from 'module';
2
+ import path2 from 'path';
3
+ import { fileURLToPath, pathToFileURL } from 'url';
4
+ import { existsSync, promises } from 'fs';
5
+ import { createHash } from 'crypto';
6
+ import { parse } from 'yaml';
7
+ import { glob } from 'glob';
8
+ import { toPosixPath } from '@kb-labs/shared-cli-ui';
9
+ import { z } from 'zod';
10
+ import Ajv from 'ajv';
11
+ import { platform } from '@kb-labs/core-runtime';
12
+ import { getLogLevel } from '@kb-labs/cli-runtime';
13
+
14
+ // src/registry/availability.ts
15
+ var req = createRequire(import.meta.url);
16
+ var __filename$1 = fileURLToPath(import.meta.url);
17
+ var __dirname$1 = path2.dirname(__filename$1);
18
+ function resolveFromCwd(spec, cwd) {
19
+ const cliRoot = path2.resolve(__dirname$1, "../../..");
20
+ let monorepoRoot = cwd;
21
+ while (monorepoRoot !== path2.dirname(monorepoRoot)) {
22
+ if (existsSync(path2.join(monorepoRoot, "pnpm-workspace.yaml"))) {
23
+ break;
24
+ }
25
+ monorepoRoot = path2.dirname(monorepoRoot);
26
+ }
27
+ const pathsToTry = [
28
+ cwd,
29
+ // Current working directory (user's project)
30
+ cliRoot,
31
+ // CLI installation directory
32
+ monorepoRoot,
33
+ // Monorepo root (if found)
34
+ path2.join(monorepoRoot, "../kb-labs-cli"),
35
+ // Explicit kb-labs-cli path
36
+ path2.join(monorepoRoot, "../kb-labs-mind")
37
+ // Explicit kb-labs-mind path
38
+ ].filter(Boolean);
39
+ for (const searchPath of pathsToTry) {
40
+ try {
41
+ return req.resolve(spec, { paths: [searchPath] });
42
+ } catch (_e) {
43
+ }
44
+ }
45
+ throw new Error(`Cannot resolve ${spec} from any of: ${pathsToTry.join(", ")}`);
46
+ }
47
+ function checkRequires(manifest, options = {}) {
48
+ const cwd = options.cwd ?? process.cwd();
49
+ if (!manifest.requires || manifest.requires.length === 0) {
50
+ return { available: true };
51
+ }
52
+ let isInMonorepo = false;
53
+ let monorepoRoot = cwd;
54
+ while (monorepoRoot !== path2.dirname(monorepoRoot)) {
55
+ if (existsSync(path2.join(monorepoRoot, "pnpm-workspace.yaml"))) {
56
+ isInMonorepo = true;
57
+ break;
58
+ }
59
+ monorepoRoot = path2.dirname(monorepoRoot);
60
+ }
61
+ for (const dep of manifest.requires) {
62
+ try {
63
+ const resolved = resolveFromCwd(dep, cwd);
64
+ if (!resolved.includes("node_modules")) {
65
+ continue;
66
+ }
67
+ } catch (err) {
68
+ if (isInMonorepo) {
69
+ continue;
70
+ }
71
+ const errorMessage = err.message || "";
72
+ const hasExportsError = errorMessage.includes("exports") || errorMessage.includes("main");
73
+ if (hasExportsError || errorMessage.includes("Cannot resolve")) {
74
+ const parts = dep.startsWith("@") ? dep.slice(1).split("/") : [dep];
75
+ if (parts.length === 0) {
76
+ continue;
77
+ }
78
+ const scope = parts.length >= 2 ? parts[0] : "";
79
+ const packageName = parts.length >= 2 ? parts[1] : parts[0];
80
+ if (!packageName) {
81
+ continue;
82
+ }
83
+ const cliRoot = path2.resolve(__dirname$1, "../../..");
84
+ const packagePath = scope ? path2.join(cliRoot, "node_modules", "@" + scope, packageName, "package.json") : path2.join(cliRoot, "node_modules", packageName, "package.json");
85
+ if (existsSync(packagePath)) {
86
+ continue;
87
+ }
88
+ }
89
+ const isKbLabsPackage = dep.startsWith("@kb-labs/");
90
+ const hint = isKbLabsPackage ? `Run: pnpm add ${dep}` : `Run: npm install ${dep}`;
91
+ return {
92
+ available: false,
93
+ reason: `Missing dependency: ${dep}`,
94
+ hint
95
+ };
96
+ }
97
+ }
98
+ return { available: true };
99
+ }
100
+ var semverPattern = /^[\^~]?[\d]+\.[\d]+(\.[\d]+)?(-[\w\.-]+)?(\+[\w\.-]+)?$/;
101
+ var FlagDefinitionSchema = z.object({
102
+ name: z.string().min(1).regex(/^[a-z0-9-]+$/, "Flag name must be lowercase alphanumeric with hyphens"),
103
+ type: z.enum(["string", "boolean", "number", "array"]),
104
+ alias: z.string().length(1).regex(/^[a-z]$/i, "Alias must be a single letter").optional(),
105
+ default: z.any().optional(),
106
+ description: z.string().optional(),
107
+ choices: z.array(z.string()).optional(),
108
+ required: z.boolean().optional()
109
+ }).refine(
110
+ (data) => {
111
+ if (data.choices && data.type !== "string") {
112
+ return false;
113
+ }
114
+ return true;
115
+ },
116
+ { message: "Choices are only allowed for string type flags" }
117
+ );
118
+ var EngineSchema = z.object({
119
+ node: z.string().optional(),
120
+ // e.g., ">=18", "^18.0.0"
121
+ kbCli: z.string().optional(),
122
+ // e.g., "^1.5.0"
123
+ module: z.enum(["esm", "cjs"]).optional()
124
+ }).optional();
125
+ var CommandManifestSchema = z.object({
126
+ manifestVersion: z.literal("1.0"),
127
+ // Legacy fields (still supported)
128
+ id: z.string().min(1).regex(/^[a-z0-9-]+:[a-z0-9-]+(?:[:a-z0-9-]+)*$/, 'Command ID must be in format "namespace:command"'),
129
+ aliases: z.array(z.string()).optional(),
130
+ group: z.string().min(1),
131
+ describe: z.string().min(1),
132
+ longDescription: z.string().optional(),
133
+ requires: z.array(z.string()).optional(),
134
+ // Package names with optional semver
135
+ flags: z.array(FlagDefinitionSchema).optional(),
136
+ examples: z.array(z.string()).optional(),
137
+ loader: z.any(),
138
+ // Function validation happens at runtime
139
+ // New fields (optional for backward compatibility)
140
+ package: z.string().optional(),
141
+ // Full package name
142
+ namespace: z.string().optional(),
143
+ // Derived from id if not provided
144
+ engine: EngineSchema,
145
+ permissions: z.array(z.string()).optional(),
146
+ // e.g., ["fs.read", "git.read", "net.fetch"]
147
+ telemetry: z.enum(["opt-in", "off"]).optional(),
148
+ manifestV2: z.any().optional(),
149
+ // Full ManifestV3 for sandbox execution
150
+ // Internal flags for auto-generated commands
151
+ isSetup: z.boolean().optional(),
152
+ // Auto-generated setup command
153
+ isSetupRollback: z.boolean().optional(),
154
+ // Auto-generated setup rollback command
155
+ pkgRoot: z.string().optional()
156
+ // Package root path for handler resolution
157
+ }).refine(
158
+ (data) => {
159
+ if (data.namespace && data.group && data.namespace !== data.group) {
160
+ return false;
161
+ }
162
+ return true;
163
+ },
164
+ { message: "namespace must match group" }
165
+ ).refine(
166
+ (data) => {
167
+ if (data.requires) {
168
+ for (const req2 of data.requires) {
169
+ if (req2.includes("@")) {
170
+ const parts = req2.split("@");
171
+ if (parts.length === 2) {
172
+ const version = parts[1];
173
+ if (version && !semverPattern.test(version) && !version.startsWith("^") && !version.startsWith("~") && !version.match(/^[\^~>=<]?[\d\.]+/)) {
174
+ return false;
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ return true;
181
+ },
182
+ { message: "requires entries must use valid semver ranges" }
183
+ );
184
+ function validateManifests(manifests) {
185
+ const errors = [];
186
+ const validated = [];
187
+ for (let i = 0; i < manifests.length; i++) {
188
+ const result = CommandManifestSchema.safeParse(manifests[i]);
189
+ if (result.success) {
190
+ validated.push(result.data);
191
+ } else {
192
+ const errorWithIndex = new z.ZodError([
193
+ ...result.error.issues.map((issue) => ({
194
+ ...issue,
195
+ path: [`[${i}]`, ...issue.path]
196
+ }))
197
+ ]);
198
+ errors.push(errorWithIndex);
199
+ }
200
+ }
201
+ if (errors.length === 0) {
202
+ return { success: true, data: validated };
203
+ }
204
+ return { success: false, errors };
205
+ }
206
+ function normalizeManifest(manifest, packageName, namespace) {
207
+ const normalized = { ...manifest };
208
+ if (!normalized.namespace && normalized.group) {
209
+ normalized.namespace = normalized.group;
210
+ } else if (!normalized.namespace && namespace) {
211
+ normalized.namespace = namespace;
212
+ }
213
+ if (!normalized.group && normalized.namespace) {
214
+ normalized.group = normalized.namespace;
215
+ }
216
+ if (!normalized.package) {
217
+ normalized.package = packageName;
218
+ }
219
+ return normalized;
220
+ }
221
+
222
+ // src/registry/discover.ts
223
+ var DEBUG_MODE = process.env.DEBUG_SANDBOX === "1" || process.env.NODE_ENV === "development";
224
+ var log = (level, message, fields) => {
225
+ if (!DEBUG_MODE) {
226
+ return;
227
+ }
228
+ const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : level === "info" ? "\u2139" : "\u{1F50D}";
229
+ const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
230
+ {
231
+ logFn(`${prefix} [discover] ${message}`);
232
+ }
233
+ };
234
+ function createManifestV3Loader(commandId) {
235
+ return async () => {
236
+ throw new Error(
237
+ `Loader should not be called for ManifestV3 command ${commandId}. Use plugin-adapter-cli executeCommand instead.`
238
+ );
239
+ };
240
+ }
241
+ function ensureManifestLoader(manifest) {
242
+ if (typeof manifest.loader !== "function") {
243
+ const commandId = manifest.id || manifest.group || "unknown";
244
+ log("debug", `[plugins][cache] Rehydrated loader for ${commandId}`);
245
+ manifest.loader = createManifestV3Loader(commandId);
246
+ }
247
+ }
248
+ var __test = {
249
+ ensureManifestLoader,
250
+ createManifestV3Loader
251
+ };
252
+ function createUnavailableManifest(pkgName, error) {
253
+ const rawMsg = (error?.message || String(error) || "").toString();
254
+ let missing = null;
255
+ const m1 = rawMsg.match(/Cannot find (?:module|package) '([^']+)'/);
256
+ const m2 = rawMsg.match(/from ['"]([^'"]+)['"]/);
257
+ if (m1 && m1[1]) {
258
+ missing = m1[1];
259
+ } else if (m2 && m2[1] && m2[1].startsWith("@")) {
260
+ missing = m2[1];
261
+ }
262
+ const seg = pkgName.includes("/") ? pkgName.split("/")[1] : pkgName;
263
+ const group = (seg || pkgName).replace(/-cli$/, "");
264
+ const short = seg || pkgName;
265
+ const requires = missing ? [missing] : [];
266
+ const manifest = {
267
+ manifestVersion: "1.0",
268
+ id: `${group}:manifest:${short}`,
269
+ group,
270
+ describe: `Commands from ${pkgName} are unavailable`,
271
+ requires,
272
+ loader: async () => {
273
+ throw new Error(`Cannot load ${pkgName} CLI manifest. ${rawMsg}`);
274
+ }
275
+ };
276
+ return manifest;
277
+ }
278
+ var PACKAGE_JSON = "package.json";
279
+ var MANIFEST_LOAD_TIMEOUT = 1500;
280
+ var IN_PROC_CACHE_TTL_MS = 6e4;
281
+ var DISK_CACHE_TTL_MS = 5 * 6e4;
282
+ var inProcDiscoveryCache = null;
283
+ async function computeManifestHash(manifestPath) {
284
+ try {
285
+ const content = await promises.readFile(manifestPath, "utf8");
286
+ return createHash("sha256").update(content).digest("hex");
287
+ } catch {
288
+ return "unknown";
289
+ }
290
+ }
291
+ async function computeLockfileHash(cwd) {
292
+ const lockfilePath = path2.join(cwd, "pnpm-lock.yaml");
293
+ try {
294
+ const content = await promises.readFile(lockfilePath, "utf8");
295
+ return createHash("sha256").update(content).digest("hex");
296
+ } catch {
297
+ return "";
298
+ }
299
+ }
300
+ async function computeConfigHash(cwd) {
301
+ const configPath = path2.join(cwd, ".kb", "kb.config.json");
302
+ try {
303
+ const content = await promises.readFile(configPath, "utf8");
304
+ return createHash("sha256").update(content).digest("hex");
305
+ } catch {
306
+ return "";
307
+ }
308
+ }
309
+ async function computePluginsStateHash(cwd) {
310
+ const pluginsPath = path2.join(cwd, ".kb", "plugins.json");
311
+ try {
312
+ const content = await promises.readFile(pluginsPath, "utf8");
313
+ return createHash("sha256").update(content).digest("hex");
314
+ } catch {
315
+ return "";
316
+ }
317
+ }
318
+ async function detectNewWorkspacePackages(cwd, cachedPackages) {
319
+ if (!cachedPackages) {
320
+ return true;
321
+ }
322
+ try {
323
+ const workspaceYaml = path2.join(cwd, "pnpm-workspace.yaml");
324
+ const content = await promises.readFile(workspaceYaml, "utf8");
325
+ const parsed = parse(content);
326
+ if (!Array.isArray(parsed.packages)) {
327
+ return false;
328
+ }
329
+ const knownPackages = new Set(Object.keys(cachedPackages));
330
+ for (const pattern of parsed.packages) {
331
+ const pkgPattern = path2.join(pattern, PACKAGE_JSON);
332
+ const pkgFiles = await glob(pkgPattern, {
333
+ cwd,
334
+ absolute: false,
335
+ ignore: [".kb/**", "node_modules/**", "**/node_modules/**"]
336
+ });
337
+ for (const pkgFile of pkgFiles) {
338
+ const pkgRoot = path2.dirname(path2.join(cwd, pkgFile));
339
+ const pkg = await readPackageJson(path2.join(cwd, pkgFile));
340
+ if (!pkg || !pkg.name) {
341
+ continue;
342
+ }
343
+ if (knownPackages.has(pkg.name)) {
344
+ continue;
345
+ }
346
+ const manifestInfo = await findManifestPath(pkgRoot, pkg);
347
+ if (manifestInfo.path) {
348
+ log("debug", `[plugins][cache] New workspace package detected: ${pkg.name}`);
349
+ return true;
350
+ }
351
+ }
352
+ }
353
+ } catch (error) {
354
+ log("debug", `[plugins][cache] Workspace scan skipped: ${error?.message || "unknown error"}`);
355
+ }
356
+ return false;
357
+ }
358
+ async function loadManifestWithTimeout(manifestPath, pkgName, pkgRoot) {
359
+ const timeout = new Promise((_, reject) => {
360
+ setTimeout(() => reject(new Error("Timeout")), MANIFEST_LOAD_TIMEOUT);
361
+ });
362
+ try {
363
+ return await Promise.race([loadManifest(manifestPath, pkgName, pkgRoot), timeout]);
364
+ } catch (err) {
365
+ if (err.message === "Timeout") {
366
+ log("warn", `Timeout loading manifest from ${pkgName}`);
367
+ return [];
368
+ }
369
+ throw err;
370
+ }
371
+ }
372
+ function getNamespaceFromManifest(manifestV2, packageName) {
373
+ const manifestId = manifestV2?.id;
374
+ if (typeof manifestId === "string" && manifestId.length > 0) {
375
+ const seg = manifestId.split("/").pop() || manifestId;
376
+ return seg.replace(/^@/, "");
377
+ }
378
+ const parts = packageName.split("/");
379
+ const last = parts[parts.length - 1] || packageName;
380
+ return last.replace(/^@/, "");
381
+ }
382
+ async function loadManifest(manifestPath, pkgName, pkgRoot) {
383
+ const fileUrl = pathToFileURL(manifestPath).href;
384
+ const mod = await import(fileUrl);
385
+ const manifest = mod.manifest || mod.default;
386
+ if (!manifest || typeof manifest !== "object" || manifest.schema !== "kb.plugin/3") {
387
+ throw new Error(`Unsupported manifest format in ${pkgName}. Only kb.plugin/3 schema is supported.`);
388
+ }
389
+ const namespace = getNamespaceFromManifest(manifest, pkgName);
390
+ const manifestDir = path2.dirname(manifestPath);
391
+ const baseRoot = pkgRoot || manifestDir;
392
+ const cliCommands = Array.isArray(manifest.cli?.commands) ? manifest.cli.commands : [];
393
+ if (cliCommands.length === 0 && !manifest.setup) {
394
+ log("warn", `ManifestV3 ${manifest.id || pkgName} has no CLI commands or setup entry`);
395
+ }
396
+ const commandManifests = cliCommands.map((cmd) => {
397
+ const commandId = cmd.id;
398
+ const commandManifest = {
399
+ manifestVersion: "1.0",
400
+ id: commandId,
401
+ group: cmd.group || namespace,
402
+ subgroup: cmd.subgroup,
403
+ describe: cmd.describe || "",
404
+ longDescription: cmd.longDescription,
405
+ aliases: cmd.aliases,
406
+ flags: cmd.flags,
407
+ examples: cmd.examples,
408
+ loader: createManifestV3Loader(commandId),
409
+ package: pkgName,
410
+ namespace: cmd.group || namespace
411
+ };
412
+ commandManifest.manifestV2 = manifest;
413
+ commandManifest.pkgRoot = baseRoot;
414
+ return commandManifest;
415
+ });
416
+ const validation = validateManifests(commandManifests);
417
+ if (!validation.success) {
418
+ const errorMessages = validation.errors.map(
419
+ (err) => err.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ")
420
+ ).join("; ");
421
+ log("warn", `ManifestV3 validation warnings for ${pkgName}: ${errorMessages}`);
422
+ }
423
+ return (validation.success ? validation.data : commandManifests).map(
424
+ (m) => normalizeManifest(m, pkgName, namespace)
425
+ );
426
+ }
427
+ async function readPackageJson(pkgPath) {
428
+ try {
429
+ const content = await promises.readFile(pkgPath, "utf8");
430
+ return JSON.parse(content);
431
+ } catch {
432
+ return null;
433
+ }
434
+ }
435
+ async function loadConfig(cwd) {
436
+ const configPath = path2.join(cwd, ".kb", "kb.config.json");
437
+ try {
438
+ const content = await promises.readFile(configPath, "utf8");
439
+ const config = JSON.parse(content);
440
+ return {
441
+ allow: config.plugins?.allow,
442
+ block: config.plugins?.block,
443
+ linked: config.plugins?.linked
444
+ };
445
+ } catch {
446
+ return {};
447
+ }
448
+ }
449
+ async function findManifestPath(pkgRoot, pkg) {
450
+ if (pkg.kb?.manifest) {
451
+ const manifestPath = path2.join(pkgRoot, pkg.kb.manifest);
452
+ try {
453
+ await promises.access(manifestPath);
454
+ return { path: manifestPath, deprecated: false };
455
+ } catch {
456
+ return { path: null, deprecated: false };
457
+ }
458
+ }
459
+ if (pkg.exports?.["./kb/commands"]) {
460
+ const exportPath = pkg.exports["./kb/commands"];
461
+ const manifestPath = typeof exportPath === "string" ? exportPath : exportPath.default || exportPath.import;
462
+ if (manifestPath) {
463
+ const resolved = path2.resolve(pkgRoot, manifestPath);
464
+ try {
465
+ await promises.access(resolved);
466
+ return { path: resolved, deprecated: false };
467
+ } catch {
468
+ const withExt = resolved.endsWith(".js") ? resolved : `${resolved}.js`;
469
+ try {
470
+ await promises.access(withExt);
471
+ return { path: withExt, deprecated: false };
472
+ } catch {
473
+ }
474
+ }
475
+ }
476
+ }
477
+ return { path: null, deprecated: false };
478
+ }
479
+ function isPluginPackage(pkg) {
480
+ if (!pkg) {
481
+ return false;
482
+ }
483
+ if (pkg.kb?.plugin === true) {
484
+ return true;
485
+ }
486
+ const keywords = Array.isArray(pkg.keywords) ? pkg.keywords : [];
487
+ return keywords.includes("kb-cli-plugin");
488
+ }
489
+ function validateUniqueIds(manifests, pkgName) {
490
+ const ids = /* @__PURE__ */ new Set();
491
+ const aliases = /* @__PURE__ */ new Set();
492
+ for (const m of manifests) {
493
+ if (ids.has(m.id)) {
494
+ throw new Error(`Duplicate command ID "${m.id}" in package ${pkgName}`);
495
+ }
496
+ ids.add(m.id);
497
+ if (m.aliases) {
498
+ for (const alias of m.aliases) {
499
+ if (aliases.has(alias) || ids.has(alias)) {
500
+ throw new Error(`Alias collision "${alias}" in package ${pkgName}`);
501
+ }
502
+ aliases.add(alias);
503
+ }
504
+ }
505
+ }
506
+ }
507
+ async function discoverWorkspace(cwd) {
508
+ const workspaceYaml = path2.join(cwd, "pnpm-workspace.yaml");
509
+ const content = await promises.readFile(workspaceYaml, "utf8");
510
+ const parsed = parse(content);
511
+ if (!parsed.packages || !Array.isArray(parsed.packages)) {
512
+ throw new Error("Invalid pnpm-workspace.yaml: missing packages array");
513
+ }
514
+ const packageInfos = [];
515
+ for (const pattern of parsed.packages) {
516
+ const pkgPattern = path2.join(pattern, PACKAGE_JSON);
517
+ const pkgFiles = await glob(pkgPattern, {
518
+ cwd,
519
+ absolute: false,
520
+ ignore: [".kb/**", "node_modules/**", "**/node_modules/**"]
521
+ // Ignore .kb, node_modules
522
+ });
523
+ for (const pkgFile of pkgFiles) {
524
+ const pkgRoot = path2.dirname(path2.join(cwd, pkgFile));
525
+ const pkg = await readPackageJson(path2.join(cwd, pkgFile));
526
+ if (!pkg) {
527
+ continue;
528
+ }
529
+ const manifestInfo = await findManifestPath(pkgRoot, pkg);
530
+ if (manifestInfo.path) {
531
+ if (manifestInfo.deprecated) {
532
+ log("warn", `[DEPRECATED] ${pkg.name} uses legacy manifest path: ${manifestInfo.path}`);
533
+ log("warn", ` \u2192 Migrate to exports["./kb/commands"] or set kb.commandsManifest in package.json`);
534
+ }
535
+ packageInfos.push({ pkgRoot, pkg, manifestPath: manifestInfo.path });
536
+ }
537
+ }
538
+ }
539
+ const loadPromises = packageInfos.map(async ({ pkgRoot, pkg, manifestPath }) => {
540
+ const pkgStart = Date.now();
541
+ try {
542
+ const manifests = await loadManifestWithTimeout(manifestPath, pkg.name, pkgRoot);
543
+ const pkgTime = Date.now() - pkgStart;
544
+ if (pkgTime > 30) {
545
+ log("debug", `[plugins][perf] ${pkg.name} manifest parse: ${pkgTime}ms (budget: 30ms)`);
546
+ }
547
+ if (manifests.length > 0) {
548
+ validateUniqueIds(manifests, pkg.name);
549
+ return {
550
+ manifests,
551
+ source: "workspace",
552
+ packageName: pkg.name,
553
+ manifestPath: toPosixPath(manifestPath),
554
+ pkgRoot: toPosixPath(pkgRoot)
555
+ };
556
+ }
557
+ return null;
558
+ } catch (err) {
559
+ const pkgTime = Date.now() - pkgStart;
560
+ log("debug", `[plugins][perf] ${pkg.name} failed after ${pkgTime}ms`);
561
+ log("warn", JSON.stringify({
562
+ code: "DISCOVERY_MANIFEST_LOAD_FAIL",
563
+ packageName: pkg.name,
564
+ manifestPath: toPosixPath(manifestPath),
565
+ errorCode: err.code || "UNKNOWN",
566
+ errorMessage: err.message,
567
+ hint: err.message.includes("Cannot find package") ? "Run: kb devlink apply && pnpm -w build" : "Check manifest syntax and dependencies"
568
+ }));
569
+ const synthetic = createUnavailableManifest(pkg.name, err);
570
+ return {
571
+ manifests: [synthetic],
572
+ source: "workspace",
573
+ packageName: pkg.name,
574
+ manifestPath: toPosixPath(manifestPath),
575
+ pkgRoot: toPosixPath(pkgRoot)
576
+ };
577
+ }
578
+ });
579
+ const settledResults = await Promise.allSettled(loadPromises);
580
+ const results = [];
581
+ for (const settled of settledResults) {
582
+ if (settled.status === "fulfilled" && settled.value) {
583
+ results.push(settled.value);
584
+ }
585
+ }
586
+ return results;
587
+ }
588
+ async function discoverCurrentPackage(cwd) {
589
+ try {
590
+ const pkg = await readPackageJson(path2.join(cwd, PACKAGE_JSON));
591
+ if (!pkg) {
592
+ return null;
593
+ }
594
+ const manifestInfo = await findManifestPath(cwd, pkg);
595
+ if (manifestInfo.path) {
596
+ if (manifestInfo.deprecated) {
597
+ log("warn", `[DEPRECATED] ${pkg.name} uses legacy manifest path: ${manifestInfo.path}`);
598
+ log("warn", ` \u2192 Migrate to exports["./kb/commands"] or set kb.commandsManifest in package.json`);
599
+ }
600
+ const manifests = await loadManifestWithTimeout(manifestInfo.path, pkg.name, cwd);
601
+ if (manifests.length > 0) {
602
+ validateUniqueIds(manifests, pkg.name);
603
+ return {
604
+ manifests,
605
+ source: "workspace",
606
+ packageName: pkg.name,
607
+ manifestPath: toPosixPath(manifestInfo.path),
608
+ pkgRoot: toPosixPath(cwd)
609
+ };
610
+ }
611
+ }
612
+ } catch (err) {
613
+ log("debug", `No CLI manifest in current package: ${err.message}`);
614
+ }
615
+ return null;
616
+ }
617
+ async function discoverNodeModules(cwd) {
618
+ const nmDir = path2.join(cwd, "node_modules");
619
+ const config = await loadConfig(cwd);
620
+ try {
621
+ const entries = await promises.readdir(nmDir, { withFileTypes: true });
622
+ const packageInfos = [];
623
+ const scanPromises = [];
624
+ for (const entry of entries) {
625
+ if (!entry.isDirectory()) {
626
+ continue;
627
+ }
628
+ const scanEntry = async () => {
629
+ let pkgRoot;
630
+ let pkg;
631
+ if (entry.name.startsWith("@")) {
632
+ const scopeDir = path2.join(nmDir, entry.name);
633
+ try {
634
+ const scopedDirs = await promises.readdir(scopeDir, { withFileTypes: true });
635
+ for (const scopedEntry of scopedDirs.filter((d) => d.isDirectory())) {
636
+ pkgRoot = path2.join(scopeDir, scopedEntry.name);
637
+ pkg = await readPackageJson(path2.join(pkgRoot, PACKAGE_JSON));
638
+ if (!pkg) {
639
+ continue;
640
+ }
641
+ const isPlugin = isPluginPackage(pkg);
642
+ if (pkg.name?.startsWith("@kb-labs/")) {
643
+ const manifestInfo = await findManifestPath(pkgRoot, pkg);
644
+ if (manifestInfo.path) {
645
+ packageInfos.push({ pkgRoot, pkg, manifestPath: manifestInfo.path });
646
+ }
647
+ } else if (isPlugin) {
648
+ if (config.block?.includes(pkg.name)) {
649
+ log("debug", `Plugin ${pkg.name} blocked by config`);
650
+ return;
651
+ }
652
+ const isAllowlisted = config.allow?.includes(pkg.name) || config.linked?.includes(pkg.name);
653
+ if (!isAllowlisted) {
654
+ log("debug", `Plugin ${pkg.name} skipped (not allowlisted). Add to kb-labs.config.json plugins.allow or enable via 'kb marketplace enable'`);
655
+ return;
656
+ }
657
+ const manifestInfo = await findManifestPath(pkgRoot, pkg);
658
+ if (manifestInfo.path) {
659
+ if (manifestInfo.deprecated) {
660
+ log("warn", `[DEPRECATED] ${pkg.name} uses legacy manifest path: ${manifestInfo.path}`);
661
+ log("warn", ` \u2192 Migrate to exports["./kb/commands"] or set kb.commandsManifest in package.json`);
662
+ }
663
+ const isLinked = config.linked?.includes(pkg.name);
664
+ packageInfos.push({ pkgRoot, pkg, manifestPath: manifestInfo.path, isLinked });
665
+ }
666
+ }
667
+ }
668
+ } catch {
669
+ }
670
+ } else {
671
+ pkgRoot = path2.join(nmDir, entry.name);
672
+ pkg = await readPackageJson(path2.join(pkgRoot, PACKAGE_JSON));
673
+ if (!pkg) {
674
+ return;
675
+ }
676
+ const isPlugin = isPluginPackage(pkg);
677
+ if (isPlugin) {
678
+ if (config.block?.includes(pkg.name)) {
679
+ log("debug", `Plugin ${pkg.name} blocked by config`);
680
+ return;
681
+ }
682
+ const isAllowlisted = config.allow?.includes(pkg.name) || config.linked?.includes(pkg.name);
683
+ if (!isAllowlisted) {
684
+ log("debug", `Plugin ${pkg.name} skipped (not allowlisted). Add to kb-labs.config.json plugins.allow or enable via 'kb marketplace enable'`);
685
+ return;
686
+ }
687
+ const manifestInfo = await findManifestPath(pkgRoot, pkg);
688
+ if (manifestInfo.path) {
689
+ if (manifestInfo.deprecated) {
690
+ log("warn", `[DEPRECATED] ${pkg.name} uses legacy manifest path: ${manifestInfo.path}`);
691
+ log("warn", ` \u2192 Migrate to exports["./kb/commands"] or set kb.commandsManifest in package.json`);
692
+ }
693
+ const isLinked = config.linked?.includes(pkg.name);
694
+ packageInfos.push({ pkgRoot, pkg, manifestPath: manifestInfo.path, isLinked });
695
+ }
696
+ }
697
+ }
698
+ };
699
+ scanPromises.push(scanEntry());
700
+ }
701
+ await Promise.allSettled(scanPromises);
702
+ const loadPromises = packageInfos.map(async ({ pkgRoot, pkg, manifestPath, isLinked }) => {
703
+ try {
704
+ const manifests = await loadManifestWithTimeout(manifestPath, pkg.name, pkgRoot);
705
+ if (manifests.length > 0) {
706
+ validateUniqueIds(manifests, pkg.name);
707
+ return {
708
+ manifests,
709
+ source: isLinked ? "linked" : "node_modules",
710
+ packageName: pkg.name,
711
+ manifestPath: toPosixPath(manifestPath),
712
+ pkgRoot: toPosixPath(pkgRoot)
713
+ };
714
+ }
715
+ return null;
716
+ } catch (err) {
717
+ log("warn", JSON.stringify({
718
+ code: "DISCOVERY_MANIFEST_LOAD_FAIL",
719
+ packageName: pkg.name,
720
+ manifestPath: toPosixPath(manifestPath),
721
+ errorCode: err.code || "UNKNOWN",
722
+ errorMessage: err.message,
723
+ hint: err.message.includes("Cannot find package") ? "Run: kb devlink apply && pnpm -w build" : "Check manifest syntax and dependencies"
724
+ }));
725
+ const synthetic = createUnavailableManifest(pkg.name, err);
726
+ return {
727
+ manifests: [synthetic],
728
+ source: isLinked ? "linked" : "node_modules",
729
+ packageName: pkg.name,
730
+ manifestPath: toPosixPath(manifestPath),
731
+ pkgRoot: toPosixPath(pkgRoot)
732
+ };
733
+ }
734
+ });
735
+ const settledResults = await Promise.allSettled(loadPromises);
736
+ const results = [];
737
+ for (const settled of settledResults) {
738
+ if (settled.status === "fulfilled" && settled.value) {
739
+ results.push(settled.value);
740
+ }
741
+ }
742
+ return results;
743
+ } catch (err) {
744
+ log("debug", `Could not scan node_modules: ${err.message}`);
745
+ return [];
746
+ }
747
+ }
748
+ function deduplicateManifests(all) {
749
+ const byPackageName = /* @__PURE__ */ new Map();
750
+ const priority = {
751
+ workspace: 3,
752
+ linked: 2,
753
+ node_modules: 1,
754
+ builtin: 0
755
+ };
756
+ for (const result of all) {
757
+ const existing = byPackageName.get(result.packageName);
758
+ if (!existing) {
759
+ byPackageName.set(result.packageName, result);
760
+ } else {
761
+ const existingPriority = priority[existing.source] || 0;
762
+ const newPriority = priority[result.source] || 0;
763
+ if (newPriority > existingPriority) {
764
+ byPackageName.set(result.packageName, result);
765
+ }
766
+ }
767
+ }
768
+ return Array.from(byPackageName.values());
769
+ }
770
+ async function loadCache(cwd) {
771
+ const cachePath = path2.join(cwd, ".kb", "cache", "cli-manifests.json");
772
+ try {
773
+ const content = await promises.readFile(cachePath, "utf8");
774
+ const cache = JSON.parse(content);
775
+ if (cache.mtimes && cache.results) {
776
+ log("debug", "Old cache format detected, ignoring");
777
+ return null;
778
+ }
779
+ if (!cache.packages) {
780
+ log("debug", "Invalid cache format, ignoring");
781
+ return null;
782
+ }
783
+ if (cache.version !== process.version) {
784
+ log("debug", "Cache invalidated: Node version changed");
785
+ return null;
786
+ }
787
+ const currentCliVersion = process.env.CLI_VERSION || "0.1.0";
788
+ if (cache.cliVersion !== currentCliVersion) {
789
+ log("debug", "Cache invalidated: CLI version changed");
790
+ return null;
791
+ }
792
+ const currentLockfileHash = await computeLockfileHash(cwd);
793
+ if (currentLockfileHash && cache.lockfileHash && cache.lockfileHash !== currentLockfileHash) {
794
+ log("debug", "Cache invalidated: lockfile changed");
795
+ return null;
796
+ }
797
+ const currentConfigHash = await computeConfigHash(cwd);
798
+ if (currentConfigHash && cache.configHash && cache.configHash !== currentConfigHash) {
799
+ log("debug", "Cache invalidated: kb-labs.config.json changed");
800
+ return null;
801
+ }
802
+ const currentPluginsStateHash = await computePluginsStateHash(cwd);
803
+ if (currentPluginsStateHash && cache.pluginsStateHash && cache.pluginsStateHash !== currentPluginsStateHash) {
804
+ log("debug", "Cache invalidated: .kb/plugins.json changed");
805
+ return null;
806
+ }
807
+ const parsedCache = cache;
808
+ parsedCache.ttlMs = parsedCache.ttlMs ?? DISK_CACHE_TTL_MS;
809
+ for (const entry of Object.values(parsedCache.packages)) {
810
+ for (const manifest of entry.result.manifests) {
811
+ ensureManifestLoader(manifest);
812
+ }
813
+ entry.cachedAt = entry.cachedAt ?? parsedCache.timestamp ?? Date.now();
814
+ }
815
+ return parsedCache;
816
+ } catch {
817
+ return null;
818
+ }
819
+ }
820
+ async function isPackageCacheStale(entry, options) {
821
+ const manifestFsPath = entry.manifestPath.split("/").join(path2.sep);
822
+ const pkgJsonPath = path2.join(entry.result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
823
+ try {
824
+ const pkgStat = await promises.stat(pkgJsonPath);
825
+ if (pkgStat.mtimeMs !== entry.pkgJsonMtime) {
826
+ log("debug", `Package cache invalidated: package.json changed for ${entry.result.packageName}`);
827
+ return true;
828
+ }
829
+ } catch (error) {
830
+ log("debug", `Package cache invalidated: missing package.json for ${entry.result.packageName} (${error?.message || "unknown"})`);
831
+ return true;
832
+ }
833
+ let manifestStat;
834
+ try {
835
+ manifestStat = await promises.stat(manifestFsPath);
836
+ if (manifestStat.mtimeMs !== entry.manifestMtime) {
837
+ log("debug", `Package cache invalidated: manifest mtime changed for ${entry.result.packageName}`);
838
+ return true;
839
+ }
840
+ } catch (error) {
841
+ log("debug", `Package cache invalidated: manifest deleted for ${entry.result.packageName} (${error?.message || "unknown"})`);
842
+ return true;
843
+ }
844
+ if (options.validateHash) {
845
+ try {
846
+ const currentHash = await computeManifestHash(manifestFsPath);
847
+ if (currentHash !== entry.manifestHash) {
848
+ log("debug", `Package cache invalidated: manifest hash changed for ${entry.result.packageName}`);
849
+ return true;
850
+ }
851
+ } catch (error) {
852
+ log("debug", `Package cache hash validation failed for ${entry.result.packageName}: ${error?.message || "unknown"}`);
853
+ return true;
854
+ }
855
+ }
856
+ return false;
857
+ }
858
+ async function saveCache(cwd, results) {
859
+ const cachePath = path2.join(cwd, ".kb", "cache", "cli-manifests.json");
860
+ await promises.mkdir(path2.dirname(cachePath), { recursive: true });
861
+ const packages = {};
862
+ const now = Date.now();
863
+ const stateHasher = createHash("sha256");
864
+ for (const result of results) {
865
+ try {
866
+ const manifestHash = await computeManifestHash(result.manifestPath);
867
+ const pkgJsonPath = path2.join(result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
868
+ const pkgStat = await promises.stat(pkgJsonPath);
869
+ const pkg = await readPackageJson(pkgJsonPath);
870
+ const version = pkg?.version || "0.1.0";
871
+ const manifestStat = await promises.stat(result.manifestPath.split("/").join(path2.sep));
872
+ const manifestsForCache = result.manifests.map((manifest) => {
873
+ const manifestCopy = { ...manifest };
874
+ delete manifestCopy.loader;
875
+ return manifestCopy;
876
+ });
877
+ const resultForCache = {
878
+ ...result,
879
+ manifests: manifestsForCache
880
+ };
881
+ packages[result.packageName] = {
882
+ version,
883
+ manifestHash,
884
+ manifestPath: result.manifestPath,
885
+ pkgJsonMtime: pkgStat.mtimeMs,
886
+ manifestMtime: manifestStat.mtimeMs,
887
+ cachedAt: now,
888
+ result: resultForCache
889
+ };
890
+ stateHasher.update(result.packageName);
891
+ stateHasher.update(manifestHash);
892
+ } catch (err) {
893
+ log("debug", `Failed to cache package ${result.packageName}: ${err.message}`);
894
+ }
895
+ }
896
+ const lockfileHash = await computeLockfileHash(cwd);
897
+ const configHash = await computeConfigHash(cwd);
898
+ const pluginsStateHash = await computePluginsStateHash(cwd);
899
+ if (lockfileHash) {
900
+ stateHasher.update(lockfileHash);
901
+ }
902
+ if (configHash) {
903
+ stateHasher.update(configHash);
904
+ }
905
+ if (pluginsStateHash) {
906
+ stateHasher.update(pluginsStateHash);
907
+ }
908
+ const cache = {
909
+ version: process.version,
910
+ cliVersion: process.env.CLI_VERSION || "0.1.0",
911
+ timestamp: now,
912
+ ttlMs: DISK_CACHE_TTL_MS,
913
+ stateHash: stateHasher.digest("hex"),
914
+ lockfileHash: lockfileHash || void 0,
915
+ configHash: configHash || void 0,
916
+ pluginsStateHash: pluginsStateHash || void 0,
917
+ packages
918
+ };
919
+ try {
920
+ await promises.writeFile(cachePath, JSON.stringify(cache), "utf8");
921
+ } catch (err) {
922
+ log("debug", `Failed to save cache: ${err.message}`);
923
+ }
924
+ }
925
+ async function discoverManifests(cwd, noCache = false, options = {}) {
926
+ const startTime = Date.now();
927
+ const timings = {};
928
+ if (noCache) {
929
+ inProcDiscoveryCache = null;
930
+ } else if (inProcDiscoveryCache) {
931
+ const age = Date.now() - inProcDiscoveryCache.timestamp;
932
+ if (age < IN_PROC_CACHE_TTL_MS) {
933
+ const cachedResults = inProcDiscoveryCache.results;
934
+ cachedResults.reduce((acc, r) => {
935
+ acc[r.source] = (acc[r.source] || 0) + 1;
936
+ return acc;
937
+ }, {});
938
+ log("debug", `[plugins][discover] in-proc cache hit (${cachedResults.length} packages, age ${age}ms)`);
939
+ return cachedResults;
940
+ }
941
+ }
942
+ if (!noCache) {
943
+ const cacheStart = Date.now();
944
+ const cached = await loadCache(cwd);
945
+ timings.cacheLoad = Date.now() - cacheStart;
946
+ if (cached) {
947
+ log("debug", "Using cached manifests");
948
+ const freshResults = [];
949
+ const cacheAge = Date.now() - cached.timestamp;
950
+ const ttlMs = cached.ttlMs ?? DISK_CACHE_TTL_MS;
951
+ const enforceHashValidation = cacheAge >= ttlMs;
952
+ log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms validateHash=${enforceHashValidation}`);
953
+ let staleCount = 0;
954
+ for (const entry of Object.values(cached.packages)) {
955
+ const stale = await isPackageCacheStale(entry, { validateHash: enforceHashValidation });
956
+ if (!stale) {
957
+ freshResults.push(entry.result);
958
+ } else {
959
+ staleCount += 1;
960
+ }
961
+ }
962
+ const hasNewWorkspacePackages = await detectNewWorkspacePackages(
963
+ cwd,
964
+ cached.packages
965
+ );
966
+ if (staleCount === 0 && freshResults.length > 0 && !hasNewWorkspacePackages) {
967
+ const totalTime2 = Date.now() - startTime;
968
+ const sourceCounts2 = freshResults.reduce((acc, r) => {
969
+ acc[r.source] = (acc[r.source] || 0) + 1;
970
+ return acc;
971
+ }, {});
972
+ log("info", `[plugins][discover] ${totalTime2}ms (cached: ${Object.entries(sourceCounts2).map(([s, c]) => `${s}:${c}`).join(", ")})`);
973
+ inProcDiscoveryCache = { timestamp: Date.now(), results: freshResults };
974
+ return freshResults;
975
+ }
976
+ if (hasNewWorkspacePackages) {
977
+ log("debug", "[plugins][cache] invalidated: new workspace packages detected");
978
+ }
979
+ if (staleCount > 0) {
980
+ log("debug", `[plugins][cache] invalidated: ${staleCount} stale package(s) detected`);
981
+ }
982
+ }
983
+ }
984
+ let workspace = [];
985
+ try {
986
+ const wsStart = Date.now();
987
+ workspace = await discoverWorkspace(cwd);
988
+ timings.workspace = Date.now() - wsStart;
989
+ log("info", `Discovered ${workspace.length} workspace packages with CLI manifests`);
990
+ } catch (_err) {
991
+ log("info", "No workspace file found, checking current package");
992
+ const currentStart = Date.now();
993
+ const currentPkg = await discoverCurrentPackage(cwd);
994
+ timings.currentPackage = Date.now() - currentStart;
995
+ if (currentPkg) {
996
+ workspace = [currentPkg];
997
+ log("info", `Discovered current package with CLI manifest: ${currentPkg.packageName}`);
998
+ }
999
+ }
1000
+ const nmStart = Date.now();
1001
+ const nodeModulesRoot = options.platformRoot ?? cwd;
1002
+ const installed = await discoverNodeModules(nodeModulesRoot);
1003
+ timings.nodeModules = Date.now() - nmStart;
1004
+ if (installed.length > 0) {
1005
+ log("info", `Discovered ${installed.length} installed packages with CLI manifests`);
1006
+ }
1007
+ const dedupStart = Date.now();
1008
+ const results = deduplicateManifests([...workspace, ...installed]);
1009
+ timings.deduplicate = Date.now() - dedupStart;
1010
+ const saveStart = Date.now();
1011
+ await saveCache(cwd, results);
1012
+ timings.cacheSave = Date.now() - saveStart;
1013
+ const totalTime = Date.now() - startTime;
1014
+ const sourceCounts = results.reduce((acc, r) => {
1015
+ acc[r.source] = (acc[r.source] || 0) + 1;
1016
+ return acc;
1017
+ }, {});
1018
+ const timingDetails = Object.entries(timings).filter(([_, t]) => t > 0).map(([k, v]) => `${k}:${v}ms`).join(", ");
1019
+ log("info", `[plugins][discover] ${totalTime}ms (${Object.entries(sourceCounts).map(([s, c]) => `${s}:${c}`).join(", ")})${timingDetails ? ` | ${timingDetails}` : ""}`);
1020
+ if (totalTime > 150) {
1021
+ log("warn", `[plugins][perf] Discovery took ${totalTime}ms (budget: 150ms)`);
1022
+ }
1023
+ inProcDiscoveryCache = { timestamp: Date.now(), results };
1024
+ return results;
1025
+ }
1026
+ async function discoverManifestsByNamespace(cwd, namespace, noCache = false) {
1027
+ const allResults = await discoverManifests(cwd, noCache);
1028
+ return allResults.filter((result) => {
1029
+ return result.manifests.some((m) => {
1030
+ const manifestNamespace = m.namespace || m.group;
1031
+ return manifestNamespace === namespace;
1032
+ });
1033
+ });
1034
+ }
1035
+ var ajv = new Ajv();
1036
+ var flagSchema = {
1037
+ type: "object",
1038
+ required: ["name", "type"],
1039
+ additionalProperties: false,
1040
+ properties: {
1041
+ name: { type: "string" },
1042
+ type: { enum: ["string", "boolean", "number", "array"] },
1043
+ alias: { type: "string", pattern: "^[a-z]$" },
1044
+ // single letter
1045
+ description: { type: "string" },
1046
+ choices: { type: "array", items: { type: "string" } },
1047
+ required: { type: "boolean" },
1048
+ default: {}
1049
+ // must match type
1050
+ }
1051
+ };
1052
+ var manifestSchema = {
1053
+ type: "object",
1054
+ required: ["manifestVersion", "id", "group", "describe", "loader"],
1055
+ additionalProperties: true,
1056
+ properties: {
1057
+ manifestVersion: { type: "string", enum: ["1.0"] },
1058
+ id: { type: "string", pattern: "^[a-z0-9-]+(?::[a-z0-9-]+)*$" },
1059
+ // Simple or namespaced (colon-separated)
1060
+ aliases: { type: "array", items: { type: "string" } },
1061
+ group: { type: "string" },
1062
+ describe: { type: "string" },
1063
+ longDescription: { type: "string" },
1064
+ requires: { type: "array", items: { type: "string" } },
1065
+ flags: {
1066
+ type: "array",
1067
+ items: flagSchema
1068
+ },
1069
+ examples: { type: "array", items: { type: "string" } }
1070
+ }
1071
+ };
1072
+ var validateManifest = ajv.compile(manifestSchema);
1073
+ var validateFlag = ajv.compile(flagSchema);
1074
+ function validateFlagDef(flag, manifestId) {
1075
+ if (!validateFlag(flag)) {
1076
+ throw new Error(
1077
+ `Invalid flag "${flag.name}" in ${manifestId}: ${ajv.errorsText(validateFlag.errors)}`
1078
+ );
1079
+ }
1080
+ if (flag.choices && flag.type !== "string") {
1081
+ throw new Error(`Flag "${flag.name}" in ${manifestId}: choices allowed only for string type`);
1082
+ }
1083
+ if (flag.default !== void 0) {
1084
+ const typeMatches = flag.type === "string" && typeof flag.default === "string" || flag.type === "boolean" && typeof flag.default === "boolean" || flag.type === "number" && typeof flag.default === "number" || flag.type === "array" && Array.isArray(flag.default);
1085
+ if (!typeMatches) {
1086
+ throw new Error(
1087
+ `Flag "${flag.name}" in ${manifestId}: default value type mismatch (expected ${flag.type})`
1088
+ );
1089
+ }
1090
+ }
1091
+ }
1092
+ function validateManifestStructure(manifest) {
1093
+ if (!validateManifest(manifest)) {
1094
+ throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
1095
+ }
1096
+ if (manifest.manifestVersion !== "1.0") {
1097
+ throw new Error(
1098
+ `Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.id} (expected "1.0")`
1099
+ );
1100
+ }
1101
+ if (manifest.flags && Array.isArray(manifest.flags) && manifest.id) {
1102
+ for (const flag of manifest.flags) {
1103
+ if (flag && typeof flag === "object" && flag.name) {
1104
+ validateFlagDef(flag, manifest.id);
1105
+ }
1106
+ }
1107
+ }
1108
+ }
1109
+ var SOURCE_PRIORITY = {
1110
+ builtin: 4,
1111
+ workspace: 3,
1112
+ linked: 2,
1113
+ node_modules: 1
1114
+ };
1115
+ function normalizeCommandId(id, _namespace) {
1116
+ return id;
1117
+ }
1118
+ function generateWhitespaceAliases(id) {
1119
+ if (!id.includes(":")) {
1120
+ return [];
1121
+ }
1122
+ return [id.replace(":", " ")];
1123
+ }
1124
+ function normalizeAliases(manifest, logger) {
1125
+ const log2 = logger ?? platform.logger;
1126
+ const aliases = /* @__PURE__ */ new Set();
1127
+ manifest.namespace || manifest.group;
1128
+ if (manifest.aliases) {
1129
+ for (const alias of manifest.aliases) {
1130
+ if (!/^[a-z0-9-:]+$/i.test(alias)) {
1131
+ log2.warn(`Invalid alias "${alias}" in ${manifest.id}: aliases must be alphanumeric with hyphens or colons`);
1132
+ continue;
1133
+ }
1134
+ aliases.add(alias);
1135
+ }
1136
+ }
1137
+ const whitespaceAliases = generateWhitespaceAliases(manifest.id);
1138
+ for (const alias of whitespaceAliases) {
1139
+ aliases.add(alias);
1140
+ }
1141
+ return Array.from(aliases);
1142
+ }
1143
+ function checkNamespaceCollision(manifest, existing, namespace) {
1144
+ const existingGroup = existing.manifest.group || "";
1145
+ const currentGroup = manifest.group || "";
1146
+ if (existingGroup === currentGroup && existingGroup === namespace && existing.manifest.id === manifest.id) {
1147
+ throw new Error(
1148
+ `Command collision in group "${namespace}": "${manifest.id}" conflicts with existing "${existing.manifest.id}". Rename one of the commands to use a different ID (e.g., "${manifest.id}2" or use --alias to create a different alias).`
1149
+ );
1150
+ }
1151
+ }
1152
+ function getSourcePriority(source) {
1153
+ return SOURCE_PRIORITY[source] || 0;
1154
+ }
1155
+ function checkCollision(manifest, existing, currentSource, namespace) {
1156
+ const existingPriority = getSourcePriority(existing.source);
1157
+ const currentPriority = getSourcePriority(currentSource);
1158
+ checkNamespaceCollision(manifest, existing, namespace);
1159
+ if (currentSource === "workspace" && existing.source === "workspace") {
1160
+ throw new Error(
1161
+ `Command ID collision: "${manifest.id}" is exported by multiple workspace packages. This is not allowed. Please rename one of the commands to use a different ID.`
1162
+ );
1163
+ }
1164
+ if (currentPriority > existingPriority) {
1165
+ return { shouldShadow: true };
1166
+ } else if (currentPriority < existingPriority) {
1167
+ return { shouldShadow: false };
1168
+ }
1169
+ return { shouldShadow: false };
1170
+ }
1171
+ function preflightManifests(discoveryResults, logger) {
1172
+ const log2 = logger ?? platform.logger;
1173
+ const valid = [];
1174
+ const skipped = [];
1175
+ for (const result of discoveryResults) {
1176
+ const allowed = [];
1177
+ for (const manifest of result.manifests) {
1178
+ try {
1179
+ validateManifestStructure(manifest);
1180
+ allowed.push(manifest);
1181
+ } catch (error) {
1182
+ const reason = error?.message ? String(error.message) : "Validation failed";
1183
+ const manifestId = manifest?.id || manifest?.group || result.packageName || "unknown";
1184
+ skipped.push({
1185
+ id: manifestId,
1186
+ source: result.source,
1187
+ reason
1188
+ });
1189
+ log2.warn(`Preflight skipped manifest ${manifestId}: ${reason}`);
1190
+ }
1191
+ }
1192
+ if (allowed.length > 0) {
1193
+ valid.push({
1194
+ ...result,
1195
+ manifests: allowed
1196
+ });
1197
+ }
1198
+ }
1199
+ return { valid, skipped };
1200
+ }
1201
+ async function registerManifests(discoveryResults, registry2, options = {}) {
1202
+ const log2 = options.logger ?? platform.logger;
1203
+ const registered = [];
1204
+ const skipped = [];
1205
+ const globalIds = /* @__PURE__ */ new Map();
1206
+ const globalAliases = /* @__PURE__ */ new Map();
1207
+ const logLevel = getLogLevel();
1208
+ let collisions = 0;
1209
+ let errors = 0;
1210
+ const sorted = [...discoveryResults].sort((a, b) => {
1211
+ const priorityA = getSourcePriority(a.source);
1212
+ const priorityB = getSourcePriority(b.source);
1213
+ return priorityB - priorityA;
1214
+ });
1215
+ for (const result of sorted) {
1216
+ for (const manifest of result.manifests) {
1217
+ const manifestId = manifest.id || manifest.group || "unknown";
1218
+ const namespace = manifest.namespace || manifest.group;
1219
+ try {
1220
+ try {
1221
+ validateManifestStructure(manifest);
1222
+ } catch (err) {
1223
+ throw new Error(`Validation failed: ${err.message}`);
1224
+ }
1225
+ const normalizedId = normalizeCommandId(manifest.id, namespace);
1226
+ if (normalizedId !== manifest.id) {
1227
+ log2.warn(`Command ID "${manifest.id}" normalized to "${normalizedId}"`);
1228
+ manifest.id = normalizedId;
1229
+ }
1230
+ const normalizedAliases = normalizeAliases(manifest, log2);
1231
+ if (normalizedAliases.length > 0) {
1232
+ manifest.aliases = normalizedAliases;
1233
+ }
1234
+ const availability = checkRequires(manifest, {
1235
+ cwd: options.cwd ?? result.pkgRoot
1236
+ });
1237
+ const cmd = {
1238
+ manifest,
1239
+ v3Manifest: manifest.manifestV2,
1240
+ // Extract V3 manifest from legacy field
1241
+ available: availability.available,
1242
+ unavailableReason: availability.available ? void 0 : availability.reason,
1243
+ hint: availability.available ? void 0 : availability.hint,
1244
+ source: result.source,
1245
+ shadowed: false,
1246
+ pkgRoot: result.pkgRoot,
1247
+ packageName: result.packageName
1248
+ };
1249
+ try {
1250
+ const manifestModule = await import(result.manifestPath);
1251
+ if (manifestModule.init && typeof manifestModule.init === "function") {
1252
+ await manifestModule.init({
1253
+ cwd: result.pkgRoot,
1254
+ package: result.packageName,
1255
+ manifest: cmd.manifest
1256
+ });
1257
+ }
1258
+ if (manifestModule.register && typeof manifestModule.register === "function") {
1259
+ await manifestModule.register({
1260
+ registry: registry2,
1261
+ command: cmd,
1262
+ cwd: result.pkgRoot,
1263
+ package: result.packageName
1264
+ });
1265
+ }
1266
+ if (manifestModule.dispose && typeof manifestModule.dispose === "function") {
1267
+ cmd._disposeHook = manifestModule.dispose;
1268
+ }
1269
+ } catch (hookError) {
1270
+ log2.debug(`Lifecycle hooks unavailable for ${manifest.id}: ${hookError.message}`);
1271
+ }
1272
+ const existing = globalIds.get(manifest.id);
1273
+ if (existing) {
1274
+ const collision = checkCollision(manifest, existing, result.source, namespace);
1275
+ if (collision.shouldShadow) {
1276
+ existing.shadowed = true;
1277
+ globalIds.set(manifest.id, cmd);
1278
+ if (logLevel === "info" || logLevel === "debug") {
1279
+ log2.info(`${manifest.id} from ${result.source} shadows ${existing.source} version`);
1280
+ }
1281
+ } else {
1282
+ cmd.shadowed = true;
1283
+ if (logLevel === "info" || logLevel === "debug") {
1284
+ log2.info(`${manifest.id} from ${result.source} shadowed by ${existing.source} version`);
1285
+ }
1286
+ }
1287
+ } else {
1288
+ globalIds.set(manifest.id, cmd);
1289
+ }
1290
+ const aliasesToCheck = manifest.aliases || [];
1291
+ for (const alias of aliasesToCheck) {
1292
+ const existingAlias = globalAliases.get(alias);
1293
+ if (existingAlias) {
1294
+ if (existingAlias.manifest.id === manifest.id) {
1295
+ continue;
1296
+ }
1297
+ const existingPriority = getSourcePriority(existingAlias.source);
1298
+ const currentPriority = getSourcePriority(result.source);
1299
+ if (currentPriority > existingPriority) {
1300
+ existingAlias.shadowed = true;
1301
+ globalAliases.set(alias, cmd);
1302
+ if (logLevel === "info" || logLevel === "debug") {
1303
+ log2.info(`Alias "${alias}" from ${result.source} shadows ${existingAlias.source} version`);
1304
+ }
1305
+ } else if (currentPriority < existingPriority) {
1306
+ if (logLevel === "info" || logLevel === "debug") {
1307
+ log2.info(`Alias "${alias}" from ${result.source} shadowed by ${existingAlias.source} version`);
1308
+ }
1309
+ continue;
1310
+ } else {
1311
+ collisions++;
1312
+ throw new Error(
1313
+ `Alias collision: "${alias}" used by both ${manifest.id} and ${existingAlias.manifest.id}. Rename one alias or use a different namespace.`
1314
+ );
1315
+ }
1316
+ }
1317
+ if (globalIds.has(alias)) {
1318
+ const conflictingCmd = globalIds.get(alias);
1319
+ const existingPriority = getSourcePriority(conflictingCmd.source);
1320
+ const currentPriority = getSourcePriority(result.source);
1321
+ if (currentPriority > existingPriority) {
1322
+ log2.warn(`Alias "${alias}" conflicts with command ID "${alias}". Alias will shadow command.`);
1323
+ globalAliases.set(alias, cmd);
1324
+ } else {
1325
+ throw new Error(
1326
+ `Alias "${alias}" conflicts with existing command ID "${alias}". Rename the alias or use a different name.`
1327
+ );
1328
+ }
1329
+ } else {
1330
+ globalAliases.set(alias, cmd);
1331
+ }
1332
+ }
1333
+ if (!cmd.shadowed) {
1334
+ registry2.registerManifest(cmd);
1335
+ }
1336
+ registered.push(cmd);
1337
+ } catch (error) {
1338
+ errors++;
1339
+ const reason = error?.message ? String(error.message) : String(error);
1340
+ skipped.push({
1341
+ id: manifestId,
1342
+ source: result.source,
1343
+ reason
1344
+ });
1345
+ log2.error(`Skipped manifest ${manifestId} (${result.source}): ${reason}`);
1346
+ continue;
1347
+ }
1348
+ }
1349
+ }
1350
+ if (skipped.length > 0) {
1351
+ log2.warn(`Skipped ${skipped.length} manifest(s) during registration`);
1352
+ for (const skip of skipped) {
1353
+ log2.warn(` \u2022 ${skip.id} [${skip.source}] \u2192 ${skip.reason}`);
1354
+ }
1355
+ }
1356
+ return {
1357
+ registered,
1358
+ skipped,
1359
+ collisions,
1360
+ errors
1361
+ };
1362
+ }
1363
+ async function disposeAllPlugins(registry2, logger) {
1364
+ const log2 = logger ?? platform.logger;
1365
+ const manifests = registry2.listManifests();
1366
+ const disposePromises = [];
1367
+ for (const cmd of manifests) {
1368
+ const disposeHook = cmd._disposeHook;
1369
+ if (disposeHook && typeof disposeHook === "function") {
1370
+ disposePromises.push(
1371
+ Promise.resolve(disposeHook({
1372
+ registry: registry2,
1373
+ command: cmd
1374
+ })).catch((err) => {
1375
+ log2.warn(`Dispose hook failed for ${cmd.manifest.id}: ${err.message}`);
1376
+ })
1377
+ );
1378
+ }
1379
+ }
1380
+ await Promise.allSettled(disposePromises);
1381
+ }
1382
+
1383
+ // src/registry/run.ts
1384
+ async function runCommand(cmd, ctx, argv, flags) {
1385
+ if (!cmd.available) {
1386
+ if (ctx.presenter?.json) {
1387
+ ctx.presenter.json({
1388
+ ok: false,
1389
+ available: false,
1390
+ command: cmd.manifest.id,
1391
+ reason: cmd.unavailableReason,
1392
+ hint: cmd.hint
1393
+ });
1394
+ }
1395
+ return 2;
1396
+ }
1397
+ if (!cmd.manifest.loader) {
1398
+ return 1;
1399
+ }
1400
+ const implementation = await cmd.manifest.loader();
1401
+ const result = await implementation.run(ctx, argv, flags);
1402
+ return typeof result === "number" ? result : 0;
1403
+ }
1404
+
1405
+ // src/registry/service.ts
1406
+ function manifestToCommand(registered) {
1407
+ return {
1408
+ name: registered.manifest.id,
1409
+ category: registered.manifest.group,
1410
+ describe: registered.manifest.describe,
1411
+ longDescription: registered.manifest.longDescription,
1412
+ aliases: registered.manifest.aliases || [],
1413
+ flags: registered.manifest.flags,
1414
+ examples: registered.manifest.examples,
1415
+ async run() {
1416
+ throw new Error(`Command ${registered.manifest.id} should be executed via plugin-executor, not via legacy run() path.`);
1417
+ }
1418
+ };
1419
+ }
1420
+ var InMemoryRegistry = class {
1421
+ // Separate collections for security isolation
1422
+ systemCommands = /* @__PURE__ */ new Map();
1423
+ // System commands (in-process)
1424
+ pluginCommands = /* @__PURE__ */ new Map();
1425
+ // Plugin commands (subprocess)
1426
+ // Legacy unified collection for backward compatibility
1427
+ byName = /* @__PURE__ */ new Map();
1428
+ groups = /* @__PURE__ */ new Map();
1429
+ manifests = /* @__PURE__ */ new Map();
1430
+ partial = false;
1431
+ register(cmd) {
1432
+ this.systemCommands.set(cmd.name, cmd);
1433
+ this.byName.set(cmd.name, cmd);
1434
+ for (const a of cmd.aliases || []) {
1435
+ this.systemCommands.set(a, cmd);
1436
+ this.byName.set(a, cmd);
1437
+ }
1438
+ }
1439
+ registerGroup(group) {
1440
+ this.groups.set(group.name, group);
1441
+ this.byName.set(group.name, group);
1442
+ for (const cmd of group.commands) {
1443
+ this.systemCommands.set(cmd.name, cmd);
1444
+ const fullName = `${group.name} ${cmd.name}`;
1445
+ this.systemCommands.set(fullName, cmd);
1446
+ this.byName.set(fullName, cmd);
1447
+ for (const alias of cmd.aliases || []) {
1448
+ this.systemCommands.set(alias, cmd);
1449
+ this.byName.set(alias, cmd);
1450
+ }
1451
+ }
1452
+ if (group.subgroups) {
1453
+ for (const sub of group.subgroups) {
1454
+ const subName = `${group.name} ${sub.name}`;
1455
+ this.groups.set(subName, sub);
1456
+ this.byName.set(subName, sub);
1457
+ for (const cmd of sub.commands) {
1458
+ const fullName = `${group.name} ${sub.name} ${cmd.name}`;
1459
+ this.systemCommands.set(fullName, cmd);
1460
+ this.byName.set(fullName, cmd);
1461
+ for (const alias of cmd.aliases || []) {
1462
+ this.systemCommands.set(alias, cmd);
1463
+ this.byName.set(alias, cmd);
1464
+ }
1465
+ }
1466
+ }
1467
+ }
1468
+ }
1469
+ registerManifest(cmd) {
1470
+ const cmdId = cmd.manifest.id;
1471
+ const hasCollision = this.systemCommands.has(cmdId);
1472
+ if (hasCollision) {
1473
+ console.warn(`[registry] Plugin command "${cmdId}" collides with system command. System command takes priority.`);
1474
+ cmd.shadowed = true;
1475
+ }
1476
+ const collisionAliases = /* @__PURE__ */ new Set();
1477
+ for (const alias of cmd.manifest.aliases || []) {
1478
+ if (this.systemCommands.has(alias)) {
1479
+ console.warn(`[registry] Plugin alias "${alias}" collides with system command. System command takes priority.`);
1480
+ collisionAliases.add(alias);
1481
+ }
1482
+ }
1483
+ this.pluginCommands.set(cmdId, cmd);
1484
+ this.manifests.set(cmdId, cmd);
1485
+ if (!hasCollision) {
1486
+ const commandAdapter = manifestToCommand(cmd);
1487
+ this.byName.set(cmdId, commandAdapter);
1488
+ this.byName.set(commandAdapter.name, commandAdapter);
1489
+ if (cmdId.includes(":")) {
1490
+ const spaceSeparated = cmdId.replace(/:/g, " ");
1491
+ this.byName.set(spaceSeparated, commandAdapter);
1492
+ }
1493
+ if (cmd.manifest.group && cmd.manifest.subgroup) {
1494
+ const fullPath = `${cmd.manifest.group} ${cmd.manifest.subgroup} ${cmd.manifest.id}`;
1495
+ const colonPath = `${cmd.manifest.group}:${cmd.manifest.subgroup}:${cmd.manifest.id}`;
1496
+ this.byName.set(fullPath, commandAdapter);
1497
+ this.byName.set(colonPath, commandAdapter);
1498
+ this.manifests.set(fullPath, cmd);
1499
+ this.manifests.set(colonPath, cmd);
1500
+ this.pluginCommands.set(fullPath, cmd);
1501
+ this.pluginCommands.set(colonPath, cmd);
1502
+ const twoPartName = `${cmd.manifest.group} ${cmd.manifest.id}`;
1503
+ if (!this.byName.has(twoPartName)) {
1504
+ this.byName.set(twoPartName, commandAdapter);
1505
+ }
1506
+ const subgroupKey = `${cmd.manifest.group} ${cmd.manifest.subgroup}`;
1507
+ if (!this.groups.has(subgroupKey)) {
1508
+ this.groups.set(subgroupKey, {
1509
+ name: subgroupKey,
1510
+ describe: cmd.manifest.subgroup,
1511
+ commands: []
1512
+ });
1513
+ this.byName.set(subgroupKey, this.groups.get(subgroupKey));
1514
+ }
1515
+ this.groups.get(subgroupKey).commands.push(commandAdapter);
1516
+ } else if (cmd.manifest.group) {
1517
+ const fullName = `${cmd.manifest.group} ${cmd.manifest.id}`;
1518
+ const colonName = `${cmd.manifest.group}:${cmd.manifest.id}`;
1519
+ this.byName.set(fullName, commandAdapter);
1520
+ this.byName.set(colonName, commandAdapter);
1521
+ this.manifests.set(fullName, cmd);
1522
+ this.manifests.set(colonName, cmd);
1523
+ this.pluginCommands.set(fullName, cmd);
1524
+ this.pluginCommands.set(colonName, cmd);
1525
+ }
1526
+ if (cmd.manifest.aliases) {
1527
+ for (const alias of cmd.manifest.aliases) {
1528
+ if (!collisionAliases.has(alias)) {
1529
+ this.byName.set(alias, commandAdapter);
1530
+ }
1531
+ }
1532
+ }
1533
+ }
1534
+ }
1535
+ markPartial(partial) {
1536
+ this.partial = partial;
1537
+ }
1538
+ isPartial() {
1539
+ return this.partial;
1540
+ }
1541
+ getManifest(id) {
1542
+ return this.manifests.get(id);
1543
+ }
1544
+ listManifests() {
1545
+ const unique = /* @__PURE__ */ new Set();
1546
+ for (const cmd of this.manifests.values()) {
1547
+ unique.add(cmd);
1548
+ }
1549
+ return Array.from(unique);
1550
+ }
1551
+ has(name) {
1552
+ return this.byName.has(name);
1553
+ }
1554
+ /**
1555
+ * Get command with type information for secure routing
1556
+ *
1557
+ * Returns type='system' for commands from registerGroup() - execute in-process
1558
+ * Returns type='plugin' for commands from registerManifest() - execute in subprocess
1559
+ *
1560
+ * This separation prevents malicious plugins from escaping the sandbox.
1561
+ */
1562
+ getWithType(nameOrPath) {
1563
+ const cmd = this.get(nameOrPath);
1564
+ if (!cmd) {
1565
+ return void 0;
1566
+ }
1567
+ if ("commands" in cmd) {
1568
+ return { cmd, type: "system" };
1569
+ }
1570
+ const normalizedName = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
1571
+ if (this.systemCommands.has(normalizedName)) {
1572
+ return { cmd, type: "system" };
1573
+ }
1574
+ if (normalizedName.includes(":")) {
1575
+ const spaceVersion = normalizedName.replace(":", " ");
1576
+ if (this.systemCommands.has(spaceVersion)) {
1577
+ return { cmd, type: "system" };
1578
+ }
1579
+ }
1580
+ const manifestCmd = this.getManifestCommand(normalizedName);
1581
+ if (manifestCmd) {
1582
+ return { cmd, type: "plugin" };
1583
+ }
1584
+ return { cmd, type: "system" };
1585
+ }
1586
+ get(nameOrPath) {
1587
+ if (typeof nameOrPath === "string") {
1588
+ if (this.byName.has(nameOrPath)) {
1589
+ return this.byName.get(nameOrPath);
1590
+ }
1591
+ if (nameOrPath.includes(":")) {
1592
+ const parts = nameOrPath.split(":");
1593
+ if (parts.length === 2) {
1594
+ const exactMatch = this.byName.get(nameOrPath);
1595
+ if (exactMatch) {
1596
+ return exactMatch;
1597
+ }
1598
+ const spaceKey = parts.join(" ");
1599
+ if (this.byName.has(spaceKey)) {
1600
+ return this.byName.get(spaceKey);
1601
+ }
1602
+ }
1603
+ }
1604
+ }
1605
+ const key = Array.isArray(nameOrPath) ? nameOrPath.join(" ") : nameOrPath;
1606
+ if (this.byName.has(key)) {
1607
+ return this.byName.get(key);
1608
+ }
1609
+ if (Array.isArray(nameOrPath) && nameOrPath.length === 1 && nameOrPath[0]?.includes(":")) {
1610
+ if (this.byName.has(nameOrPath[0])) {
1611
+ return this.byName.get(nameOrPath[0]);
1612
+ }
1613
+ const [group, command] = nameOrPath[0].split(":");
1614
+ const legacyKey = `${group} ${command}`;
1615
+ if (this.byName.has(legacyKey)) {
1616
+ return this.byName.get(legacyKey);
1617
+ }
1618
+ }
1619
+ if (Array.isArray(nameOrPath)) {
1620
+ const dot = nameOrPath.join(".");
1621
+ if (this.byName.has(dot)) {
1622
+ return this.byName.get(dot);
1623
+ }
1624
+ }
1625
+ if (Array.isArray(nameOrPath) && nameOrPath.length >= 2) {
1626
+ const [groupPrefix, ...cmdParts] = nameOrPath;
1627
+ const cmdName = cmdParts.join(" ");
1628
+ for (const group of this.groups.values()) {
1629
+ if (group.name === groupPrefix || group.name.startsWith(groupPrefix + ":")) {
1630
+ const fullName = `${group.name} ${cmdName}`;
1631
+ if (this.byName.has(fullName)) {
1632
+ return this.byName.get(fullName);
1633
+ }
1634
+ }
1635
+ }
1636
+ }
1637
+ return void 0;
1638
+ }
1639
+ list() {
1640
+ const commands = /* @__PURE__ */ new Set();
1641
+ for (const value of this.byName.values()) {
1642
+ if ("run" in value) {
1643
+ commands.add(value);
1644
+ }
1645
+ }
1646
+ return Array.from(commands);
1647
+ }
1648
+ listGroups() {
1649
+ return Array.from(this.groups.values());
1650
+ }
1651
+ getGroupsByPrefix(prefix) {
1652
+ const result = [];
1653
+ for (const group of this.groups.values()) {
1654
+ if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1655
+ result.push(group);
1656
+ }
1657
+ }
1658
+ return result;
1659
+ }
1660
+ getCommandsByGroupPrefix(prefix) {
1661
+ const result = [];
1662
+ for (const group of this.groups.values()) {
1663
+ if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1664
+ result.push(...group.commands);
1665
+ }
1666
+ }
1667
+ return result;
1668
+ }
1669
+ listProductGroups() {
1670
+ const groups = /* @__PURE__ */ new Map();
1671
+ for (const cmd of this.listManifests()) {
1672
+ const groupName = cmd.manifest.group;
1673
+ if (!groups.has(groupName)) {
1674
+ groups.set(groupName, {
1675
+ name: groupName,
1676
+ describe: cmd.manifest.group,
1677
+ commands: []
1678
+ });
1679
+ }
1680
+ groups.get(groupName).commands.push(cmd);
1681
+ }
1682
+ return Array.from(groups.values());
1683
+ }
1684
+ getCommandsByGroup(group) {
1685
+ return this.listManifests().filter((cmd) => cmd.manifest.group === group).sort((a, b) => a.manifest.id.localeCompare(b.manifest.id));
1686
+ }
1687
+ getManifestCommand(idOrAlias) {
1688
+ if (this.manifests.has(idOrAlias)) {
1689
+ return this.manifests.get(idOrAlias);
1690
+ }
1691
+ for (const cmd of this.manifests.values()) {
1692
+ if (cmd.manifest.aliases?.includes(idOrAlias)) {
1693
+ return cmd;
1694
+ }
1695
+ if (cmd.manifest.id.replace(/:/g, " ") === idOrAlias) {
1696
+ return cmd;
1697
+ }
1698
+ }
1699
+ return void 0;
1700
+ }
1701
+ };
1702
+ var registry = new InMemoryRegistry();
1703
+ function findCommand(nameOrPath) {
1704
+ return registry.get(nameOrPath);
1705
+ }
1706
+ function findCommandWithType(nameOrPath) {
1707
+ return registry.getWithType(nameOrPath);
1708
+ }
1709
+
1710
+ export { __test, checkRequires, discoverManifests, discoverManifestsByNamespace, disposeAllPlugins, findCommand, findCommandWithType, preflightManifests, registerManifests, registry, runCommand };
1711
+ //# sourceMappingURL=index.js.map
1712
+ //# sourceMappingURL=index.js.map