@kb-labs/cli-commands 2.93.0 → 2.96.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.
package/dist/index.js CHANGED
@@ -5,25 +5,20 @@ import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { promises, existsSync } from 'fs';
6
6
  import { createHash } from 'crypto';
7
7
  import path from 'path';
8
+ import { DiagnosticCollector, readMarketplaceLock, computeManifestIntegrity } from '@kb-labs/core-discovery';
8
9
  import { parse } from 'yaml';
9
10
  import { glob } from 'glob';
10
11
  import { defineSystemCommand, defineSystemCommandGroup } from '@kb-labs/shared-command-kit';
11
- import { createRegistry, formatDiagnosticReport } from '@kb-labs/core-registry';
12
- import { readMarketplaceLock, DiagnosticCollector } from '@kb-labs/core-discovery';
12
+ import { createRegistry as createRegistry$1, formatDiagnosticReport } from '@kb-labs/core-registry';
13
+ import { createRequire } from 'module';
13
14
  import { platform, platformSync } from '@kb-labs/core-runtime';
15
+ import { getLogLevel, colors } from '@kb-labs/cli-runtime';
16
+ import { access, readFile } from 'fs/promises';
14
17
  import { CredentialsManager } from '@kb-labs/cli-runtime/gateway';
15
- import Ajv from 'ajv';
16
- import { createRequire } from 'module';
17
- import { colors, getLogLevel } from '@kb-labs/cli-runtime';
18
+ import os from 'os';
18
19
 
19
20
  var __defProp = Object.defineProperty;
20
21
  var __getOwnPropNames = Object.getOwnPropertyNames;
21
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
22
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
23
- }) : x)(function(x) {
24
- if (typeof require !== "undefined") return require.apply(this, arguments);
25
- throw Error('Dynamic require of "' + x + '" is not supported');
26
- });
27
22
  var __esm = (fn, res) => function __init() {
28
23
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
29
24
  };
@@ -43,34 +38,24 @@ function validateManifests(manifests) {
43
38
  if (result.success) {
44
39
  validated.push(result.data);
45
40
  } else {
46
- const errorWithIndex = new z.ZodError([
47
- ...result.error.issues.map((issue) => ({
48
- ...issue,
49
- path: [`[${i}]`, ...issue.path]
50
- }))
51
- ]);
52
- errors.push(errorWithIndex);
41
+ errors.push(
42
+ new z.ZodError(
43
+ result.error.issues.map((issue) => ({ ...issue, path: [`[${i}]`, ...issue.path] }))
44
+ )
45
+ );
53
46
  }
54
47
  }
55
- if (errors.length === 0) {
56
- return { success: true, data: validated };
57
- }
58
- return { success: false, errors };
48
+ return errors.length === 0 ? { success: true, data: validated } : { success: false, errors };
59
49
  }
60
- function normalizeManifest(manifest, packageName, namespace) {
61
- const normalized = { ...manifest };
62
- if (!normalized.namespace && normalized.group) {
63
- normalized.namespace = normalized.group;
64
- } else if (!normalized.namespace && namespace) {
65
- normalized.namespace = namespace;
66
- }
67
- if (!normalized.group && normalized.namespace) {
68
- normalized.group = normalized.namespace;
69
- }
70
- if (!normalized.package) {
71
- normalized.package = packageName;
72
- }
73
- return normalized;
50
+ function normalizeManifest(manifest, packageName) {
51
+ const segs = manifest.segments;
52
+ return {
53
+ ...manifest,
54
+ id: segs[segs.length - 1] ?? "",
55
+ group: segs[0] ?? "",
56
+ subgroup: segs.length >= 3 ? segs[1] : void 0,
57
+ package: manifest.package ?? packageName
58
+ };
74
59
  }
75
60
  var semverPattern, FlagDefinitionSchema, EngineSchema, CommandManifestSchema;
76
61
  var init_schema = __esm({
@@ -85,62 +70,37 @@ var init_schema = __esm({
85
70
  choices: z.array(z.string()).optional(),
86
71
  required: z.boolean().optional()
87
72
  }).refine(
88
- (data) => {
89
- if (data.choices && data.type !== "string") {
90
- return false;
91
- }
92
- return true;
93
- },
73
+ (data) => !(data.choices && data.type !== "string"),
94
74
  { message: "Choices are only allowed for string type flags" }
95
75
  );
96
76
  EngineSchema = z.object({
97
77
  node: z.string().optional(),
98
- // e.g., ">=18", "^18.0.0"
99
78
  kbCli: z.string().optional(),
100
- // e.g., "^1.5.0"
101
79
  module: z.enum(["esm", "cjs"]).optional()
102
80
  }).optional();
103
81
  CommandManifestSchema = z.object({
104
82
  manifestVersion: z.literal("1.0"),
105
- // Legacy fields (still supported)
106
- id: z.string().min(1).regex(/^[a-z0-9-]+:[a-z0-9-]+(?:[:a-z0-9-]+)*$/, 'Command ID must be in format "namespace:command"'),
107
- aliases: z.array(z.string()).optional(),
83
+ segments: z.array(z.string().min(1)).min(1),
84
+ id: z.string().min(1),
108
85
  group: z.string().min(1),
86
+ subgroup: z.string().optional(),
87
+ aliases: z.array(z.string()).optional(),
88
+ category: z.string().optional(),
109
89
  describe: z.string().min(1),
110
90
  longDescription: z.string().optional(),
111
91
  requires: z.array(z.string()).optional(),
112
- // Package names with optional semver
113
92
  flags: z.array(FlagDefinitionSchema).optional(),
114
93
  examples: z.array(z.string()).optional(),
115
94
  loader: z.any(),
116
- // Function validation happens at runtime
117
- // New fields (optional for backward compatibility)
118
95
  package: z.string().optional(),
119
- // Full package name
120
- namespace: z.string().optional(),
121
- // Derived from id if not provided
122
96
  engine: EngineSchema,
123
97
  permissions: z.array(z.string()).optional(),
124
- // e.g., ["fs.read", "git.read", "net.fetch"]
125
98
  telemetry: z.enum(["opt-in", "off"]).optional(),
126
99
  manifestV2: z.any().optional(),
127
- // Full ManifestV3 for sandbox execution
128
- // Internal flags for auto-generated commands
129
- isSetup: z.boolean().optional(),
130
- // Auto-generated setup command
131
- isSetupRollback: z.boolean().optional(),
132
- // Auto-generated setup rollback command
133
- pkgRoot: z.string().optional()
134
- // Package root path for handler resolution
100
+ pkgRoot: z.string().optional(),
101
+ _synthetic: z.boolean().optional(),
102
+ operationType: z.enum(["read", "mutate", "execute", "analyze"]).optional()
135
103
  }).refine(
136
- (data) => {
137
- if (data.namespace && data.group && data.namespace !== data.group) {
138
- return false;
139
- }
140
- return true;
141
- },
142
- { message: "namespace must match group" }
143
- ).refine(
144
104
  (data) => {
145
105
  if (data.requires) {
146
106
  for (const req2 of data.requires) {
@@ -168,6 +128,7 @@ __export(discover_exports, {
168
128
  __test: () => __test,
169
129
  discoverManifests: () => discoverManifests,
170
130
  discoverManifestsByNamespace: () => discoverManifestsByNamespace,
131
+ loadConfig: () => loadConfig,
171
132
  resetInProcCache: () => resetInProcCache
172
133
  });
173
134
  function createManifestV3Loader(commandId) {
@@ -198,9 +159,11 @@ function createUnavailableManifest(pkgName, error) {
198
159
  const group = (seg || pkgName).replace(/-cli$/, "");
199
160
  const short = seg || pkgName;
200
161
  const requires = missing ? [missing] : [];
162
+ const unavailableId = `manifest:${short}`;
201
163
  const manifest = {
202
164
  manifestVersion: "1.0",
203
- id: `${group}:manifest:${short}`,
165
+ segments: [group, unavailableId],
166
+ id: unavailableId,
204
167
  group,
205
168
  describe: `Commands from ${pkgName} are unavailable`,
206
169
  requires,
@@ -218,14 +181,6 @@ function createUnavailableManifest(pkgName, error) {
218
181
  function resetInProcCache() {
219
182
  inProcDiscoveryCache = null;
220
183
  }
221
- async function computeManifestHash(manifestPath) {
222
- try {
223
- const content = await promises.readFile(manifestPath, "utf8");
224
- return createHash("sha256").update(content).digest("hex");
225
- } catch {
226
- return "unknown";
227
- }
228
- }
229
184
  async function computeLockfileHash(cwd) {
230
185
  const lockfilePath = path.join(cwd, "pnpm-lock.yaml");
231
186
  try {
@@ -331,8 +286,12 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
331
286
  const mod = await import(fileUrl);
332
287
  const modTyped = mod;
333
288
  const rawManifest = modTyped.manifest || modTyped.default;
334
- if (!rawManifest || typeof rawManifest !== "object" || rawManifest.schema !== "kb.plugin/3") {
335
- throw new Error(`Unsupported manifest format in ${pkgName}. Only kb.plugin/3 schema is supported.`);
289
+ if (!rawManifest || typeof rawManifest !== "object") {
290
+ throw new Error(`No manifest export found in ${pkgName}`);
291
+ }
292
+ const schema = rawManifest.schema;
293
+ if (schema !== "kb.plugin/3") {
294
+ throw new NonPluginManifestError(pkgName, typeof schema === "string" ? schema : String(schema ?? "unknown"));
336
295
  }
337
296
  const manifest = rawManifest;
338
297
  const namespace = getNamespaceFromManifest(manifest, pkgName);
@@ -343,20 +302,23 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
343
302
  log("warn", `ManifestV3 ${manifest.id || pkgName} has no CLI commands or setup entry`);
344
303
  }
345
304
  const commandManifests = cliCommands.map((cmd) => {
346
- const commandId = cmd.id;
305
+ const segments = cmd.path.trim().split(/\s+/).filter(Boolean);
306
+ const commandId = segments[segments.length - 1] ?? namespace;
347
307
  const commandManifest = {
348
308
  manifestVersion: "1.0",
309
+ segments,
349
310
  id: commandId,
350
- group: cmd.group || namespace,
351
- subgroup: cmd.subgroup,
311
+ group: segments[0] ?? namespace,
312
+ subgroup: segments.length >= 3 ? segments[1] : void 0,
313
+ category: cmd.category,
352
314
  describe: cmd.describe || "",
353
315
  longDescription: cmd.longDescription,
354
316
  aliases: cmd.aliases,
355
317
  flags: cmd.flags,
356
318
  examples: cmd.examples,
357
- loader: createManifestV3Loader(commandId),
319
+ loader: createManifestV3Loader(cmd.path),
358
320
  package: pkgName,
359
- namespace: cmd.group || namespace
321
+ operationType: cmd.operationType
360
322
  };
361
323
  commandManifest.manifestV2 = manifest;
362
324
  commandManifest.pkgRoot = baseRoot;
@@ -370,7 +332,7 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
370
332
  log("warn", `ManifestV3 validation warnings for ${pkgName}: ${errorMessages}`);
371
333
  }
372
334
  return (validation.success ? validation.data : commandManifests).map(
373
- (m) => normalizeManifest(m, pkgName, namespace)
335
+ (m) => normalizeManifest(m, pkgName)
374
336
  );
375
337
  }
376
338
  async function readPackageJson(pkgPath) {
@@ -441,10 +403,11 @@ function validateUniqueIds(manifests, pkgName) {
441
403
  const ids = /* @__PURE__ */ new Set();
442
404
  const aliases = /* @__PURE__ */ new Set();
443
405
  for (const m of manifests) {
444
- if (ids.has(m.id)) {
406
+ const key = m.segments.join("/");
407
+ if (ids.has(key)) {
445
408
  throw new Error(`Duplicate command ID "${m.id}" in package ${pkgName}`);
446
409
  }
447
- ids.add(m.id);
410
+ ids.add(key);
448
411
  if (m.aliases) {
449
412
  for (const alias of m.aliases) {
450
413
  if (aliases.has(alias) || ids.has(alias)) {
@@ -537,6 +500,10 @@ async function loadManifestsForPackages(packageInfos, source, scope) {
537
500
  log("debug", `[plugins][perf] ${pkgName} failed after ${pkgTime}ms`);
538
501
  const errMsg = err instanceof Error ? err.message : String(err);
539
502
  const errCode = err.code ?? "UNKNOWN";
503
+ if (err instanceof NonPluginManifestError) {
504
+ log("debug", `[plugins] ${pkgName} skipped: ${err.message}`);
505
+ return null;
506
+ }
540
507
  log("warn", JSON.stringify({
541
508
  code: "DISCOVERY_MANIFEST_LOAD_FAIL",
542
509
  packageName: pkgName,
@@ -707,6 +674,10 @@ async function discoverNodeModules(cwd) {
707
674
  } catch (err) {
708
675
  const errMsg = err instanceof Error ? err.message : String(err);
709
676
  const errCode = err.code ?? "UNKNOWN";
677
+ if (err instanceof NonPluginManifestError) {
678
+ log("debug", `[plugins] ${pkgName} skipped: ${err.message}`);
679
+ return null;
680
+ }
710
681
  log("warn", JSON.stringify({
711
682
  code: "DISCOVERY_MANIFEST_LOAD_FAIL",
712
683
  packageName: pkgName,
@@ -759,14 +730,14 @@ function deduplicateManifests(all) {
759
730
  byPackageName.set(result.packageName, projectEntry);
760
731
  continue;
761
732
  }
762
- const winner = existing.scope === "platform" ? existing : result;
763
- const loser = existing.scope === "platform" ? result : existing;
764
- log("warn", JSON.stringify({
765
- code: "DISCOVERY_SCOPE_COLLISION",
733
+ const winner = existing.scope === "project" ? existing : result;
734
+ const loser = existing.scope === "project" ? result : existing;
735
+ log("debug", JSON.stringify({
736
+ code: "DISCOVERY_SCOPE_OVERRIDE",
766
737
  packageName: result.packageName,
767
- platformPath: winner.pkgRoot,
768
- projectPath: loser.pkgRoot,
769
- message: "Package exists in both platform and project scopes \u2014 platform wins."
738
+ projectPath: winner.pkgRoot,
739
+ platformPath: loser.pkgRoot,
740
+ message: "Package exists in both platform and project scopes \u2014 project wins."
770
741
  }));
771
742
  byPackageName.set(result.packageName, winner);
772
743
  continue;
@@ -796,6 +767,10 @@ async function loadCache(cwd, roots) {
796
767
  log("debug", "Cache invalidated: Node version changed");
797
768
  return null;
798
769
  }
770
+ if ((cache.discoveryVersion ?? 0) !== DISCOVERY_VERSION) {
771
+ log("debug", `Cache invalidated: discovery logic changed (${cache.discoveryVersion ?? 0} \u2192 ${DISCOVERY_VERSION})`);
772
+ return null;
773
+ }
799
774
  const currentCliVersion = process.env.CLI_VERSION || "0.1.0";
800
775
  if (cache.cliVersion !== currentCliVersion) {
801
776
  log("debug", "Cache invalidated: CLI version changed");
@@ -849,7 +824,7 @@ async function loadCache(cwd, roots) {
849
824
  return null;
850
825
  }
851
826
  }
852
- async function isPackageCacheStale(entry, options) {
827
+ async function isPackageCacheStale(entry) {
853
828
  const manifestFsPath = entry.manifestPath.split("/").join(path.sep);
854
829
  const pkgJsonPath = path.join(entry.result.pkgRoot.split("/").join(path.sep), PACKAGE_JSON);
855
830
  try {
@@ -862,26 +837,23 @@ async function isPackageCacheStale(entry, options) {
862
837
  log("debug", `Package cache invalidated: missing package.json for ${entry.result.packageName} (${error instanceof Error ? error.message : "unknown"})`);
863
838
  return true;
864
839
  }
865
- let manifestStat;
840
+ let manifestMtimeChanged = false;
866
841
  try {
867
- manifestStat = await promises.stat(manifestFsPath);
868
- if (manifestStat.mtimeMs !== entry.manifestMtime) {
869
- log("debug", `Package cache invalidated: manifest mtime changed for ${entry.result.packageName}`);
870
- return true;
871
- }
842
+ const manifestStat = await promises.stat(manifestFsPath);
843
+ manifestMtimeChanged = manifestStat.mtimeMs !== entry.manifestMtime;
872
844
  } catch (error) {
873
845
  log("debug", `Package cache invalidated: manifest deleted for ${entry.result.packageName} (${error instanceof Error ? error.message : "unknown"})`);
874
846
  return true;
875
847
  }
876
- if (options.validateHash) {
848
+ if (manifestMtimeChanged) {
877
849
  try {
878
- const currentHash = await computeManifestHash(manifestFsPath);
850
+ const currentHash = await computeManifestIntegrity(manifestFsPath);
879
851
  if (currentHash !== entry.manifestHash) {
880
- log("debug", `Package cache invalidated: manifest hash changed for ${entry.result.packageName}`);
852
+ log("debug", `Package cache invalidated: manifest content changed for ${entry.result.packageName}`);
881
853
  return true;
882
854
  }
883
855
  } catch (error) {
884
- log("debug", `Package cache hash validation failed for ${entry.result.packageName}: ${error instanceof Error ? error.message : "unknown"}`);
856
+ log("debug", `Package cache hash check failed for ${entry.result.packageName}: ${error instanceof Error ? error.message : "unknown"}`);
885
857
  return true;
886
858
  }
887
859
  }
@@ -900,7 +872,7 @@ async function saveCache(cwd, results, roots) {
900
872
  continue;
901
873
  }
902
874
  try {
903
- const manifestHash = await computeManifestHash(result.manifestPath);
875
+ const manifestHash = await computeManifestIntegrity(result.manifestPath);
904
876
  const pkgJsonPath = path.join(result.pkgRoot.split("/").join(path.sep), PACKAGE_JSON);
905
877
  const pkgStat = await promises.stat(pkgJsonPath);
906
878
  const pkg = await readPackageJson(pkgJsonPath);
@@ -955,6 +927,7 @@ async function saveCache(cwd, results, roots) {
955
927
  const cache = {
956
928
  version: process.version,
957
929
  cliVersion: process.env.CLI_VERSION || "0.1.0",
930
+ discoveryVersion: DISCOVERY_VERSION,
958
931
  timestamp: now,
959
932
  ttlMs: DISK_CACHE_TTL_MS,
960
933
  stateHash: stateHasher.digest("hex"),
@@ -1002,11 +975,10 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
1002
975
  const freshResults = [];
1003
976
  const cacheAge = Date.now() - cached.timestamp;
1004
977
  const ttlMs = cached.ttlMs ?? DISK_CACHE_TTL_MS;
1005
- const enforceHashValidation = cacheAge >= ttlMs;
1006
- log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms validateHash=${enforceHashValidation}`);
978
+ log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms`);
1007
979
  let staleCount = 0;
1008
980
  for (const entry of Object.values(cached.packages)) {
1009
- const stale = await isPackageCacheStale(entry, { validateHash: enforceHashValidation });
981
+ const stale = await isPackageCacheStale(entry);
1010
982
  if (!stale) {
1011
983
  freshResults.push(entry.result);
1012
984
  } else {
@@ -1063,8 +1035,33 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
1063
1035
  if (projectLocal.length > 0) {
1064
1036
  log("info", `Discovered ${projectLocal.length} project-local plugins`);
1065
1037
  }
1038
+ let projectWorkspace = [];
1039
+ if (roots.projectRoot !== roots.platformRoot) {
1040
+ try {
1041
+ const pwStart = Date.now();
1042
+ const raw = await discoverWorkspace(roots.projectRoot);
1043
+ projectWorkspace = raw.map((r) => ({ ...r, scope: "project" }));
1044
+ timings.projectWorkspace = Date.now() - pwStart;
1045
+ if (projectWorkspace.length > 0) {
1046
+ log("info", `Discovered ${projectWorkspace.length} project workspace packages with CLI manifests`);
1047
+ }
1048
+ } catch (err) {
1049
+ const msg = err instanceof Error ? err.message : String(err);
1050
+ const isMissingYaml = msg.includes("ENOENT") || msg.includes("no such file");
1051
+ if (isMissingYaml) {
1052
+ log("debug", `[plugins][discover] no pnpm-workspace.yaml at projectRoot=${roots.projectRoot} \u2014 skipping project workspace scan`);
1053
+ } else {
1054
+ log("warn", JSON.stringify({
1055
+ code: "PROJECT_WORKSPACE_DISCOVERY_FAILED",
1056
+ projectRoot: roots.projectRoot,
1057
+ error: msg,
1058
+ hint: "Project workspace plugins may be missing from CLI discovery"
1059
+ }));
1060
+ }
1061
+ }
1062
+ }
1066
1063
  const dedupStart = Date.now();
1067
- const results = deduplicateManifests([...workspace, ...installed, ...projectLocal]);
1064
+ const results = deduplicateManifests([...workspace, ...installed, ...projectLocal, ...projectWorkspace]);
1068
1065
  timings.deduplicate = Date.now() - dedupStart;
1069
1066
  const saveStart = Date.now();
1070
1067
  await saveCache(cwd, results, roots);
@@ -1086,27 +1083,26 @@ async function discoverManifestsByNamespace(cwd, namespace, noCache = false) {
1086
1083
  const allResults = await discoverManifests(cwd, noCache);
1087
1084
  return allResults.filter((result) => {
1088
1085
  return result.manifests.some((m) => {
1089
- const manifestNamespace = m.namespace || m.group;
1090
- return manifestNamespace === namespace;
1086
+ return m.group === namespace;
1091
1087
  });
1092
1088
  });
1093
1089
  }
1094
- var DEBUG_MODE, log, __test, PACKAGE_JSON, MANIFEST_LOAD_TIMEOUT, IN_PROC_CACHE_TTL_MS, DISK_CACHE_TTL_MS, inProcDiscoveryCache;
1090
+ var DISCOVERY_VERSION, DEBUG_MODE, log, __test, NonPluginManifestError, PACKAGE_JSON, MANIFEST_LOAD_TIMEOUT, IN_PROC_CACHE_TTL_MS, DISK_CACHE_TTL_MS, inProcDiscoveryCache;
1095
1091
  var init_discover = __esm({
1096
1092
  "src/registry/discover.ts"() {
1097
1093
  init_path();
1098
1094
  init_schema();
1095
+ DISCOVERY_VERSION = 2;
1099
1096
  DEBUG_MODE = process.env.DEBUG_SANDBOX === "1" || process.env.NODE_ENV === "development";
1100
1097
  log = (level, message, fields) => {
1101
1098
  if (!DEBUG_MODE) {
1102
1099
  return;
1103
1100
  }
1104
1101
  const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : level === "info" ? "\u2139" : "\u{1F50D}";
1105
- const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
1106
1102
  if (fields) {
1107
- logFn(`${prefix} [discover] ${message}`, fields);
1103
+ console.error(`${prefix} [discover] ${message}`, fields);
1108
1104
  } else {
1109
- logFn(`${prefix} [discover] ${message}`);
1105
+ console.error(`${prefix} [discover] ${message}`);
1110
1106
  }
1111
1107
  };
1112
1108
  __test = {
@@ -1114,6 +1110,14 @@ var init_discover = __esm({
1114
1110
  createManifestV3Loader
1115
1111
  // resetInProcCache is exported directly (not via __test) since it's part of the public API
1116
1112
  };
1113
+ NonPluginManifestError = class extends Error {
1114
+ schema;
1115
+ constructor(pkgName, schema) {
1116
+ super(`${pkgName} uses manifest schema "${schema}", not kb.plugin/3 \u2014 skipping`);
1117
+ this.name = "NonPluginManifestError";
1118
+ this.schema = schema;
1119
+ }
1120
+ };
1117
1121
  PACKAGE_JSON = "package.json";
1118
1122
  MANIFEST_LOAD_TIMEOUT = 1500;
1119
1123
  IN_PROC_CACHE_TTL_MS = 6e4;
@@ -1122,420 +1126,511 @@ var init_discover = __esm({
1122
1126
  }
1123
1127
  });
1124
1128
 
1125
- // src/registry/service.ts
1126
- function manifestToCommand(registered) {
1127
- return {
1128
- name: registered.manifest.id,
1129
- category: registered.manifest.group,
1130
- describe: registered.manifest.describe,
1131
- longDescription: registered.manifest.longDescription,
1132
- aliases: registered.manifest.aliases || [],
1133
- flags: registered.manifest.flags,
1134
- examples: registered.manifest.examples,
1135
- async run() {
1136
- throw new Error(`Command ${registered.manifest.id} should be executed via plugin-executor, not via legacy run() path.`);
1137
- }
1138
- };
1129
+ // src/registry/trie-router.ts
1130
+ function makeNode() {
1131
+ return { children: /* @__PURE__ */ new Map() };
1139
1132
  }
1140
- function buildCanonicalId(manifest) {
1141
- const { id, group, subgroup } = manifest;
1142
- if (group && subgroup) {
1143
- return `${group}:${subgroup}:${id}`;
1133
+ function levenshtein(a, b) {
1134
+ const m = a.length;
1135
+ const n = b.length;
1136
+ const dp = Array.from(
1137
+ { length: m + 1 },
1138
+ (_, i) => Array.from({ length: n + 1 }, (__, j) => i === 0 ? j : j === 0 ? i : 0)
1139
+ );
1140
+ for (let i = 1; i <= m; i++) {
1141
+ for (let j = 1; j <= n; j++) {
1142
+ if (a[i - 1] === b[j - 1]) {
1143
+ dp[i][j] = dp[i - 1][j - 1];
1144
+ } else {
1145
+ dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
1146
+ }
1147
+ }
1144
1148
  }
1145
- if (group) {
1146
- return `${group}:${id}`;
1149
+ return dp[m][n];
1150
+ }
1151
+ function findDeep(node, targetSegments, prefix) {
1152
+ const results = [];
1153
+ function dfs(current, path6) {
1154
+ if (current.command) {
1155
+ if (targetSegments.length <= path6.length) {
1156
+ const tail = path6.slice(path6.length - targetSegments.length);
1157
+ const matches = tail.every((seg, i) => seg === targetSegments[i]);
1158
+ if (matches) {
1159
+ results.push(path6.join(" "));
1160
+ }
1161
+ }
1162
+ }
1163
+ for (const [key, child] of current.children) {
1164
+ dfs(child, [...path6, key]);
1165
+ }
1147
1166
  }
1148
- return id;
1167
+ dfs(node, [...prefix]);
1168
+ return results;
1149
1169
  }
1150
- var InMemoryRegistry = class {
1151
- // System commands (in-process): key = any registered name/alias
1152
- systemCommands = /* @__PURE__ */ new Map();
1153
- // Plugin commands: key = canonicalId only
1154
- pluginByCanonical = /* @__PURE__ */ new Map();
1155
- // Alias → canonicalId: covers all user-input variants that resolve to a plugin
1156
- pluginAliases = /* @__PURE__ */ new Map();
1157
- // Legacy unified collection for backward compatibility (display/get)
1158
- byName = /* @__PURE__ */ new Map();
1159
- groups = /* @__PURE__ */ new Map();
1160
- // manifests: any key RegisteredCommand (for listing/lookup; still stores multi-key for compat)
1161
- manifests = /* @__PURE__ */ new Map();
1162
- partial = false;
1163
- // ─── System command registration ────────────────────────────────────────
1164
- register(cmd) {
1165
- this.systemCommands.set(cmd.name, cmd);
1166
- this.byName.set(cmd.name, cmd);
1167
- for (const a of cmd.aliases || []) {
1168
- this.systemCommands.set(a, cmd);
1169
- this.byName.set(a, cmd);
1170
+ var TrieRouter = class {
1171
+ root = makeNode();
1172
+ /**
1173
+ * Insert a plugin command into the trie.
1174
+ *
1175
+ * Enforces 1-manifest-1-namespace: the first packageName to register any command
1176
+ * under a top-level group becomes the owner. Subsequent packages with a different
1177
+ * packageName are rejected (collides: true) the caller is responsible for warning
1178
+ * and marking the command as shadowed.
1179
+ *
1180
+ * Returns { collides: false } on success, or { collides: true, ownerPackage } on conflict.
1181
+ */
1182
+ insertCommand(segments, cmd) {
1183
+ if (segments.length === 0) {
1184
+ return { collides: false };
1185
+ }
1186
+ const incoming = cmd.packageName ?? "__unknown__";
1187
+ const topSeg = segments[0];
1188
+ if (!this.root.children.has(topSeg)) {
1189
+ this.root.children.set(topSeg, makeNode());
1190
+ }
1191
+ const groupNode = this.root.children.get(topSeg);
1192
+ if (groupNode.ownerPackage === void 0) {
1193
+ groupNode.ownerPackage = incoming;
1194
+ } else if (groupNode.ownerPackage !== incoming) {
1195
+ return { collides: true, ownerPackage: groupNode.ownerPackage };
1196
+ }
1197
+ let node = groupNode;
1198
+ for (const seg of segments.slice(1)) {
1199
+ if (!node.children.has(seg)) {
1200
+ node.children.set(seg, makeNode());
1201
+ }
1202
+ node = node.children.get(seg);
1170
1203
  }
1204
+ node.command = cmd;
1205
+ return { collides: false };
1171
1206
  }
1172
- registerGroup(group) {
1173
- this.groups.set(group.name, group);
1174
- this.byName.set(group.name, group);
1207
+ insertSystemCommand(cmd) {
1208
+ const segments = [cmd.name];
1209
+ let node = this.root;
1210
+ for (const seg of segments) {
1211
+ if (!node.children.has(seg)) {
1212
+ node.children.set(seg, makeNode());
1213
+ }
1214
+ node = node.children.get(seg);
1215
+ }
1216
+ node.systemCommand = cmd;
1217
+ for (const alias of cmd.aliases ?? []) {
1218
+ let aliasNode = this.root;
1219
+ if (!aliasNode.children.has(alias)) {
1220
+ aliasNode.children.set(alias, makeNode());
1221
+ }
1222
+ aliasNode = aliasNode.children.get(alias);
1223
+ aliasNode.systemCommand = cmd;
1224
+ }
1225
+ }
1226
+ insertSystemGroup(group) {
1227
+ let groupNode = this.root;
1228
+ if (!groupNode.children.has(group.name)) {
1229
+ groupNode.children.set(group.name, makeNode());
1230
+ }
1231
+ groupNode = groupNode.children.get(group.name);
1232
+ groupNode.systemGroup = group;
1233
+ groupNode.groupDescribe = group.describe;
1175
1234
  for (const cmd of group.commands) {
1176
- this.systemCommands.set(cmd.name, cmd);
1177
- const fullName = `${group.name} ${cmd.name}`;
1178
- this.systemCommands.set(fullName, cmd);
1179
- this.byName.set(fullName, cmd);
1180
- for (const alias of cmd.aliases || []) {
1181
- this.systemCommands.set(alias, cmd);
1182
- this.byName.set(alias, cmd);
1235
+ let cmdNode = groupNode;
1236
+ if (!cmdNode.children.has(cmd.name)) {
1237
+ cmdNode.children.set(cmd.name, makeNode());
1238
+ }
1239
+ cmdNode = cmdNode.children.get(cmd.name);
1240
+ cmdNode.systemCommand = cmd;
1241
+ for (const alias of cmd.aliases ?? []) {
1242
+ let aliasNode = groupNode;
1243
+ if (!aliasNode.children.has(alias)) {
1244
+ aliasNode.children.set(alias, makeNode());
1245
+ }
1246
+ aliasNode = aliasNode.children.get(alias);
1247
+ aliasNode.systemCommand = cmd;
1183
1248
  }
1184
1249
  }
1185
- if (group.subgroups) {
1186
- for (const sub of group.subgroups) {
1187
- const subName = `${group.name} ${sub.name}`;
1188
- this.groups.set(subName, sub);
1189
- this.byName.set(subName, sub);
1190
- for (const cmd of sub.commands) {
1191
- const fullName = `${group.name} ${sub.name} ${cmd.name}`;
1192
- this.systemCommands.set(fullName, cmd);
1193
- this.byName.set(fullName, cmd);
1194
- for (const alias of cmd.aliases || []) {
1195
- this.systemCommands.set(alias, cmd);
1196
- this.byName.set(alias, cmd);
1197
- }
1250
+ for (const sub of group.subgroups ?? []) {
1251
+ let subNode = groupNode;
1252
+ if (!subNode.children.has(sub.name)) {
1253
+ subNode.children.set(sub.name, makeNode());
1254
+ }
1255
+ subNode = subNode.children.get(sub.name);
1256
+ subNode.systemGroup = sub;
1257
+ subNode.groupDescribe = sub.describe;
1258
+ for (const cmd of sub.commands) {
1259
+ let cmdNode = subNode;
1260
+ if (!cmdNode.children.has(cmd.name)) {
1261
+ cmdNode.children.set(cmd.name, makeNode());
1198
1262
  }
1263
+ cmdNode = cmdNode.children.get(cmd.name);
1264
+ cmdNode.systemCommand = cmd;
1199
1265
  }
1200
1266
  }
1201
1267
  }
1202
- // ─── Plugin command registration ────────────────────────────────────────
1203
- registerManifest(cmd) {
1204
- const canonicalId = buildCanonicalId(cmd.manifest);
1205
- const collisionKey = this._findSystemCollision(cmd.manifest, canonicalId);
1206
- if (collisionKey) {
1207
- console.warn(`[registry] Plugin command "${canonicalId}" collides with system command "${collisionKey}". System command takes priority.`);
1208
- cmd.shadowed = true;
1268
+ getGroupDescribe(segments) {
1269
+ let node = this.root;
1270
+ for (const seg of segments) {
1271
+ const child = node.children.get(seg);
1272
+ if (!child) {
1273
+ return void 0;
1274
+ }
1275
+ node = child;
1209
1276
  }
1210
- const collisionAliases = /* @__PURE__ */ new Set();
1211
- for (const alias of cmd.manifest.aliases || []) {
1212
- if (this.systemCommands.has(alias)) {
1213
- console.warn(`[registry] Plugin alias "${alias}" collides with system command. System command takes priority.`);
1214
- collisionAliases.add(alias);
1277
+ return node.groupDescribe;
1278
+ }
1279
+ setGroupDescribe(segments, describe) {
1280
+ let node = this.root;
1281
+ for (const seg of segments) {
1282
+ if (!node.children.has(seg)) {
1283
+ node.children.set(seg, makeNode());
1215
1284
  }
1285
+ node = node.children.get(seg);
1216
1286
  }
1217
- this.pluginByCanonical.set(canonicalId, cmd);
1218
- this.manifests.set(canonicalId, cmd);
1219
- this.manifests.set(cmd.manifest.id, cmd);
1220
- if (cmd.shadowed) {
1221
- return;
1287
+ if (!node.groupDescribe) {
1288
+ node.groupDescribe = describe;
1222
1289
  }
1223
- this._registerPluginAliases(cmd, canonicalId, collisionAliases);
1224
- const commandAdapter = manifestToCommand(cmd);
1225
- this.byName.set(canonicalId, commandAdapter);
1226
- const spaceCanonical = canonicalId.replace(/:/g, " ");
1227
- this.byName.set(spaceCanonical, commandAdapter);
1228
- this._registerSyntheticGroups(cmd, commandAdapter);
1229
1290
  }
1230
- /**
1231
- * Check whether any system command already owns the canonical id or its parts.
1232
- * Returns the colliding key if found, null otherwise.
1233
- */
1234
- _findSystemCollision(manifest, canonicalId) {
1235
- if (this.systemCommands.has(canonicalId)) {
1236
- return canonicalId;
1291
+ resolve(tokens) {
1292
+ if (tokens.length === 0) {
1293
+ return {
1294
+ type: "group",
1295
+ segments: [],
1296
+ childKeys: [...this.root.children.keys()]
1297
+ };
1298
+ }
1299
+ let node = this.root;
1300
+ let i = 0;
1301
+ while (i < tokens.length) {
1302
+ const tok = tokens[i];
1303
+ const child = node.children.get(tok);
1304
+ if (!child) {
1305
+ return this._notFound(node, tokens, i);
1306
+ }
1307
+ node = child;
1308
+ i++;
1309
+ if (node.command && i < tokens.length) {
1310
+ return { type: "command", command: node.command, rest: tokens.slice(i) };
1311
+ }
1312
+ if (node.systemCommand && i < tokens.length) {
1313
+ return { type: "system-cmd", cmd: node.systemCommand, rest: tokens.slice(i) };
1314
+ }
1237
1315
  }
1238
- const space = canonicalId.replace(/:/g, " ");
1239
- if (this.systemCommands.has(space)) {
1240
- return space;
1316
+ if (node.command) {
1317
+ return { type: "command", command: node.command, rest: [] };
1241
1318
  }
1242
- return null;
1319
+ if (node.systemCommand) {
1320
+ return { type: "system-cmd", cmd: node.systemCommand, rest: [] };
1321
+ }
1322
+ if (node.systemGroup) {
1323
+ return { type: "system-group", group: node.systemGroup, rest: [] };
1324
+ }
1325
+ if (node.children.size > 0 || node.groupDescribe !== void 0) {
1326
+ const segs = tokens.slice(0, i);
1327
+ return {
1328
+ type: "group",
1329
+ segments: segs,
1330
+ describe: node.groupDescribe,
1331
+ childKeys: [...node.children.keys()]
1332
+ };
1333
+ }
1334
+ return { type: "not-found", input: tokens, suggestions: [] };
1243
1335
  }
1244
- /**
1245
- * Register all lookup aliases for a plugin command.
1246
- *
1247
- * Priority (what beats what) is handled in resolveToCanonical at query time.
1248
- * Here we just build the mapping.
1249
- *
1250
- * Aliases registered:
1251
- * - canonicalId itself ("marketplace:plugins:list")
1252
- * - bare id ("list") ← low priority, may clash
1253
- * - 2-part shorthand ("marketplace:list", "marketplace list")
1254
- * - manifest.aliases[] (user-defined aliases, except collisions)
1255
- */
1256
- _registerPluginAliases(cmd, canonicalId, collisionAliases) {
1257
- const { id, group, subgroup } = cmd.manifest;
1258
- const register = (key) => {
1259
- if (!this.systemCommands.has(key) && !this.systemCommands.has(key.replace(/:/g, " "))) {
1260
- if (!this.pluginAliases.has(key)) {
1261
- this.pluginAliases.set(key, canonicalId);
1262
- this.manifests.set(key, cmd);
1263
- }
1336
+ complete(tokens) {
1337
+ if (tokens.length === 0) {
1338
+ return [...this.root.children.keys()];
1339
+ }
1340
+ let node = this.root;
1341
+ for (let i = 0; i < tokens.length - 1; i++) {
1342
+ const child = node.children.get(tokens[i]);
1343
+ if (!child) {
1344
+ return [];
1264
1345
  }
1265
- };
1266
- this.pluginAliases.set(canonicalId, canonicalId);
1267
- const spaceCanonical = canonicalId.replace(/:/g, " ");
1268
- this.pluginAliases.set(spaceCanonical, canonicalId);
1269
- this.manifests.set(spaceCanonical, cmd);
1270
- if (group && subgroup) {
1271
- const twoPartColon = `${group}:${id}`;
1272
- const twoPartSpace = `${group} ${id}`;
1273
- register(twoPartColon);
1274
- register(twoPartSpace);
1275
- this.manifests.set(twoPartColon, cmd);
1276
- this.manifests.set(twoPartSpace, cmd);
1277
- const fullPath = `${group} ${subgroup} ${id}`;
1278
- this.pluginAliases.set(fullPath, canonicalId);
1279
- this.manifests.set(fullPath, cmd);
1280
- const colonPath = `${group}:${subgroup}:${id}`;
1281
- this.pluginAliases.set(colonPath, canonicalId);
1282
- this.manifests.set(colonPath, cmd);
1283
- } else if (group) {
1284
- const colonName = `${group}:${id}`;
1285
- const spaceName = `${group} ${id}`;
1286
- this.pluginAliases.set(colonName, canonicalId);
1287
- this.pluginAliases.set(spaceName, canonicalId);
1288
- this.manifests.set(colonName, cmd);
1289
- this.manifests.set(spaceName, cmd);
1290
- }
1291
- register(id);
1292
- for (const alias of cmd.manifest.aliases || []) {
1293
- if (!collisionAliases.has(alias)) {
1294
- register(alias);
1346
+ node = child;
1347
+ }
1348
+ const partial = tokens[tokens.length - 1] ?? "";
1349
+ const results = [];
1350
+ const prefix = tokens.slice(0, tokens.length - 1);
1351
+ for (const [key] of node.children) {
1352
+ if (key.startsWith(partial)) {
1353
+ results.push([...prefix, key].join(" "));
1295
1354
  }
1296
1355
  }
1356
+ return results;
1297
1357
  }
1298
1358
  /**
1299
- * Register synthetic subgroups for help display.
1359
+ * List all leaf plugin commands under a given node path.
1300
1360
  */
1301
- _registerSyntheticGroups(cmd, commandAdapter) {
1302
- const { id, group, subgroup } = cmd.manifest;
1303
- if (group && subgroup) {
1304
- const subgroupKey = `${group} ${subgroup}`;
1305
- if (!this.groups.has(subgroupKey)) {
1306
- this.groups.set(subgroupKey, {
1307
- name: subgroupKey,
1308
- describe: subgroup,
1309
- commands: []
1310
- });
1311
- this.byName.set(subgroupKey, this.groups.get(subgroupKey));
1361
+ listUnder(segments) {
1362
+ let node = this.root;
1363
+ for (const seg of segments) {
1364
+ const child = node.children.get(seg);
1365
+ if (!child) {
1366
+ return [];
1312
1367
  }
1313
- this.groups.get(subgroupKey).commands.push(commandAdapter);
1314
- const twoPartSpace = `${group} ${id}`;
1315
- if (!this.byName.has(twoPartSpace)) {
1316
- this.byName.set(twoPartSpace, commandAdapter);
1368
+ node = child;
1369
+ }
1370
+ const results = [];
1371
+ this._collectCommands(node, results);
1372
+ return results;
1373
+ }
1374
+ /**
1375
+ * Get exact command at path.
1376
+ */
1377
+ getAt(segments) {
1378
+ let node = this.root;
1379
+ for (const seg of segments) {
1380
+ const child = node.children.get(seg);
1381
+ if (!child) {
1382
+ return null;
1317
1383
  }
1318
- const twoPartColon = `${group}:${id}`;
1319
- if (!this.byName.has(twoPartColon)) {
1320
- this.byName.set(twoPartColon, commandAdapter);
1384
+ node = child;
1385
+ }
1386
+ return node.command ?? null;
1387
+ }
1388
+ /**
1389
+ * List top-level system groups and standalone system commands (depth=1 nodes).
1390
+ */
1391
+ listSystemTopLevel() {
1392
+ const results = [];
1393
+ for (const [name, node] of this.root.children) {
1394
+ if (node.systemGroup) {
1395
+ results.push({ name, describe: node.systemGroup.describe, isGroup: true });
1396
+ } else if (node.systemCommand) {
1397
+ results.push({ name, describe: node.systemCommand.describe, isGroup: false });
1321
1398
  }
1322
- const fullPath = `${group} ${subgroup} ${id}`;
1323
- this.byName.set(fullPath, commandAdapter);
1324
- } else if (group) {
1325
- const fullName = `${group} ${id}`;
1326
- const colonName = `${group}:${id}`;
1327
- this.byName.set(fullName, commandAdapter);
1328
- this.byName.set(colonName, commandAdapter);
1329
1399
  }
1400
+ return results;
1330
1401
  }
1331
- // ─── Resolution ──────────────────────────────────────────────────────────
1332
1402
  /**
1333
- * Resolve any user input to its canonical plugin ID.
1334
- *
1335
- * Returns canonicalId if found in plugin aliases, undefined otherwise.
1403
+ * Collect all plugin commands reachable from node.
1336
1404
  */
1337
- resolveToCanonical(input) {
1338
- if (this.pluginAliases.has(input)) {
1339
- return this.pluginAliases.get(input);
1405
+ listAll() {
1406
+ const results = [];
1407
+ this._collectCommands(this.root, results);
1408
+ return results;
1409
+ }
1410
+ _collectCommands(node, out) {
1411
+ if (node.command) {
1412
+ out.push(node.command);
1340
1413
  }
1341
- const converted = input.includes(":") ? input.replace(/:/g, " ") : input.replace(/ /g, ":");
1342
- if (this.pluginAliases.has(converted)) {
1343
- return this.pluginAliases.get(converted);
1414
+ for (const child of node.children.values()) {
1415
+ this._collectCommands(child, out);
1344
1416
  }
1345
- return void 0;
1346
1417
  }
1347
- // ─── Public API ──────────────────────────────────────────────────────────
1348
- markPartial(partial) {
1349
- this.partial = partial;
1418
+ _notFound(node, tokens, failIdx) {
1419
+ const failToken = tokens[failIdx];
1420
+ const prefix = tokens.slice(0, failIdx);
1421
+ const siblings = [...node.children.keys()];
1422
+ const fuzzy = siblings.map((s) => ({ s, d: levenshtein(failToken, s) })).filter(({ d }) => d <= 2).sort((a, b) => a.d - b.d).map(({ s }) => [...prefix, s, ...tokens.slice(failIdx + 1)].join(" "));
1423
+ if (fuzzy.length === 1) {
1424
+ return { type: "not-found", input: tokens, suggestions: fuzzy };
1425
+ }
1426
+ if (fuzzy.length > 1) {
1427
+ return { type: "ambiguous", input: tokens, candidates: fuzzy };
1428
+ }
1429
+ const remaining = tokens.slice(failIdx);
1430
+ const deep = findDeep(node, remaining, prefix);
1431
+ if (deep.length === 0) {
1432
+ return { type: "not-found", input: tokens, suggestions: [] };
1433
+ }
1434
+ if (deep.length === 1) {
1435
+ return { type: "not-found", input: tokens, suggestions: deep };
1436
+ }
1437
+ return { type: "ambiguous", input: tokens, candidates: deep };
1350
1438
  }
1351
- isPartial() {
1352
- return this.partial;
1439
+ };
1440
+
1441
+ // src/registry/archetype-flags.ts
1442
+ var outputFlag = { name: "output", type: "string", choices: ["json", "table", "csv"], description: "Output format" };
1443
+ var dryRunFlag = { name: "dry-run", type: "boolean", description: "Show what would happen without executing" };
1444
+ var yesFlag = { name: "yes", type: "boolean", alias: "y", description: "Skip confirmation prompts" };
1445
+ var waitFlag = { name: "wait", type: "boolean", description: "Block until execution completes" };
1446
+ var watchFlag = { name: "watch", type: "boolean", description: "Stream events as NDJSON" };
1447
+ var timeoutFlag = { name: "timeout", type: "string", description: "Max wait time (e.g. 30s, 5m)" };
1448
+ var limitFlag = { name: "limit", type: "number", description: "Max results to return" };
1449
+ var offsetFlag = { name: "offset", type: "number", description: "Offset for pagination" };
1450
+ var formatFlag = { name: "format", type: "string", choices: ["json", "text", "md"], description: "Output format" };
1451
+ var streamFlag = { name: "stream", type: "boolean", description: "Stream output progressively" };
1452
+ var schemaFlag = {
1453
+ name: "schema",
1454
+ type: "boolean",
1455
+ description: "Output JSON Schema for this command (flags, types, examples)"
1456
+ };
1457
+ var ARCHETYPE_FLAGS = {
1458
+ read: [outputFlag, limitFlag, offsetFlag],
1459
+ mutate: [outputFlag, dryRunFlag, yesFlag],
1460
+ execute: [outputFlag, waitFlag, watchFlag, timeoutFlag, yesFlag],
1461
+ analyze: [outputFlag, formatFlag, streamFlag]
1462
+ };
1463
+ function getArchetypeFlags(operationType, existing = []) {
1464
+ const toInject = ARCHETYPE_FLAGS[operationType] ?? [];
1465
+ const existingNames = new Set(existing.map((f) => f.name));
1466
+ return toInject.filter((f) => !existingNames.has(f.name));
1467
+ }
1468
+
1469
+ // src/registry/service.ts
1470
+ var RESERVED_NAMESPACES = /* @__PURE__ */ new Set(["__complete", "__internal"]);
1471
+ var MAX_PATH_DEPTH = 6;
1472
+ function validateSegments(segs) {
1473
+ if (segs.length === 0) {
1474
+ return "empty path \u2014 segments must not be empty";
1353
1475
  }
1354
- getManifest(id) {
1355
- return this.manifests.get(id);
1476
+ if (segs.length > MAX_PATH_DEPTH) {
1477
+ return `path too deep: ${segs.length} segments (max ${MAX_PATH_DEPTH})`;
1356
1478
  }
1357
- listManifests() {
1358
- const unique = /* @__PURE__ */ new Set();
1359
- for (const cmd of this.pluginByCanonical.values()) {
1360
- unique.add(cmd);
1361
- }
1362
- return Array.from(unique);
1479
+ if (RESERVED_NAMESPACES.has(segs[0])) {
1480
+ return `"${segs[0]}" is a reserved namespace`;
1481
+ }
1482
+ return null;
1483
+ }
1484
+ var noopLogger = {
1485
+ debug: () => {
1486
+ },
1487
+ info: () => {
1488
+ },
1489
+ warn: () => {
1490
+ },
1491
+ error: () => {
1492
+ },
1493
+ child: () => noopLogger
1494
+ };
1495
+ var TrieBackedRegistry = class {
1496
+ systemRouter = new TrieRouter();
1497
+ pluginRouter = new TrieRouter();
1498
+ partial = false;
1499
+ logger = noopLogger;
1500
+ // ─── Logger ────────────────────────────────────────────────────────────────
1501
+ setLogger(logger) {
1502
+ this.logger = logger;
1363
1503
  }
1364
- has(name) {
1365
- return this.byName.has(name) || this.pluginAliases.has(name);
1504
+ // ─── Registration ──────────────────────────────────────────────────────────
1505
+ register(cmd) {
1506
+ this.systemRouter.insertSystemCommand(cmd);
1366
1507
  }
1367
- /**
1368
- * Get command with type information for secure routing.
1369
- *
1370
- * System commands ALWAYS win — checked first before any plugin lookup.
1371
- * Returns type='system' for system commands (in-process execution).
1372
- * Returns type='plugin' for plugin commands (subprocess execution).
1373
- */
1374
- getWithType(nameOrPath) {
1375
- const normalized = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
1376
- if (this.systemCommands.has(normalized)) {
1377
- return { cmd: this.systemCommands.get(normalized), type: "system" };
1378
- }
1379
- const colonVariant = normalized.replace(/ /g, ":");
1380
- if (this.systemCommands.has(colonVariant)) {
1381
- return { cmd: this.systemCommands.get(colonVariant), type: "system" };
1382
- }
1383
- if (this.groups.has(normalized)) {
1384
- return { cmd: this.groups.get(normalized), type: "system" };
1385
- }
1386
- const canonicalId = this.resolveToCanonical(normalized);
1387
- if (canonicalId) {
1388
- const pluginCmd = this.pluginByCanonical.get(canonicalId);
1389
- if (pluginCmd && !pluginCmd.shadowed) {
1390
- const cmd2 = this.byName.get(canonicalId) ?? this.byName.get(normalized);
1391
- if (cmd2) {
1392
- return { cmd: cmd2, type: "plugin" };
1393
- }
1508
+ registerGroup(group) {
1509
+ this.systemRouter.insertSystemGroup(group);
1510
+ if (group.subgroups) {
1511
+ for (const sub of group.subgroups) {
1512
+ this.systemRouter.setGroupDescribe([group.name, sub.name], sub.describe ?? "");
1394
1513
  }
1395
1514
  }
1396
- const cmd = this.get(nameOrPath);
1397
- if (!cmd) {
1398
- return void 0;
1399
- }
1400
- if ("commands" in cmd) {
1401
- return { cmd, type: "system" };
1515
+ }
1516
+ registerManifest(cmd) {
1517
+ if (cmd.manifest._synthetic) {
1518
+ return;
1402
1519
  }
1403
- if (this.systemCommands.has(normalized) || this.systemCommands.has(colonVariant)) {
1404
- return { cmd, type: "system" };
1520
+ const segs = cmd.manifest.segments;
1521
+ const pkg = cmd.packageName ?? "unknown";
1522
+ const validationError = validateSegments(segs);
1523
+ if (validationError) {
1524
+ this.logger.warn(`[registry] Plugin "${pkg}" skipped: ${validationError} (path: "${segs.join(" ")}")`);
1525
+ cmd.shadowed = true;
1526
+ return;
1405
1527
  }
1406
- const manifestCmd = this.getManifestCommand(normalized);
1407
- if (manifestCmd && !manifestCmd.shadowed) {
1408
- return { cmd, type: "plugin" };
1528
+ const sysResult = this.systemRouter.resolve([...segs]);
1529
+ if (sysResult.type === "system-cmd" || sysResult.type === "system-group") {
1530
+ this.logger.warn(`[registry] Plugin "${pkg}" tried to register "${segs.join(" ")}" but it collides with a system command. Command will be shadowed.`);
1531
+ cmd.shadowed = true;
1532
+ return;
1409
1533
  }
1410
- return { cmd, type: "system" };
1411
- }
1412
- get(nameOrPath) {
1413
- if (typeof nameOrPath === "string") {
1414
- if (this.byName.has(nameOrPath)) {
1415
- return this.byName.get(nameOrPath);
1416
- }
1417
- if (nameOrPath.includes(":")) {
1418
- const spaceKey = nameOrPath.replace(/:/g, " ");
1419
- if (this.byName.has(spaceKey)) {
1420
- return this.byName.get(spaceKey);
1421
- }
1422
- const parts = nameOrPath.split(":");
1423
- if (parts.length === 2) {
1424
- const spaceKey2 = parts.join(" ");
1425
- if (this.byName.has(spaceKey2)) {
1426
- return this.byName.get(spaceKey2);
1427
- }
1428
- }
1534
+ if (cmd.manifest.operationType) {
1535
+ const injected = getArchetypeFlags(cmd.manifest.operationType, cmd.manifest.flags ?? []);
1536
+ if (injected.length > 0) {
1537
+ cmd.manifest.flags = [...cmd.manifest.flags ?? [], ...injected];
1429
1538
  }
1430
1539
  }
1431
- const key = Array.isArray(nameOrPath) ? nameOrPath.join(" ") : nameOrPath;
1432
- if (this.byName.has(key)) {
1433
- return this.byName.get(key);
1540
+ if (cmd.manifest.operationType && !cmd.manifest.flags?.some((f) => f.name === "schema")) {
1541
+ cmd.manifest.flags = [...cmd.manifest.flags ?? [], schemaFlag];
1542
+ }
1543
+ const result = this.pluginRouter.insertCommand(segs, cmd);
1544
+ if (result.collides) {
1545
+ this.logger.warn(
1546
+ `[registry] Plugin "${pkg}" tried to register "${segs.join(" ")}" but namespace "${segs[0]}" is owned by "${result.ownerPackage ?? "unknown"}". Command will be shadowed.`
1547
+ );
1548
+ cmd.shadowed = true;
1549
+ return;
1434
1550
  }
1435
- if (Array.isArray(nameOrPath) && nameOrPath.length === 1 && nameOrPath[0]?.includes(":")) {
1436
- if (this.byName.has(nameOrPath[0])) {
1437
- return this.byName.get(nameOrPath[0]);
1551
+ for (const alias of cmd.manifest.aliases ?? []) {
1552
+ const aliasSegs = alias.trim().split(/\s+/).filter(Boolean);
1553
+ if (aliasSegs.length === 0) {
1554
+ continue;
1438
1555
  }
1439
- const [group, command] = nameOrPath[0].split(":");
1440
- const legacyKey = `${group} ${command}`;
1441
- if (this.byName.has(legacyKey)) {
1442
- return this.byName.get(legacyKey);
1556
+ const aliasValidationError = validateSegments(aliasSegs);
1557
+ if (aliasValidationError) {
1558
+ this.logger.warn(`[registry] Plugin "${pkg}" alias "${alias}" skipped: ${aliasValidationError}`);
1559
+ continue;
1443
1560
  }
1444
- }
1445
- if (Array.isArray(nameOrPath)) {
1446
- const dot = nameOrPath.join(".");
1447
- if (this.byName.has(dot)) {
1448
- return this.byName.get(dot);
1561
+ const aliasSysResult = this.systemRouter.resolve([...aliasSegs]);
1562
+ if (aliasSysResult.type === "system-cmd" || aliasSysResult.type === "system-group") {
1563
+ this.logger.warn(`[registry] Plugin "${pkg}" alias "${alias}" collides with a system command and will be skipped.`);
1564
+ continue;
1565
+ }
1566
+ const aliasResult = this.pluginRouter.insertCommand(aliasSegs, cmd);
1567
+ if (aliasResult.collides) {
1568
+ this.logger.warn(
1569
+ `[registry] Plugin "${pkg}" alias "${alias}" conflicts with namespace "${aliasSegs[0]}" owned by "${aliasResult.ownerPackage ?? "unknown"}". Alias skipped.`
1570
+ );
1449
1571
  }
1450
1572
  }
1451
- if (Array.isArray(nameOrPath) && nameOrPath.length >= 2) {
1452
- const [groupPrefix, ...cmdParts] = nameOrPath;
1453
- const cmdName = cmdParts.join(" ");
1454
- for (const group of this.groups.values()) {
1455
- if (group.name === groupPrefix || group.name.startsWith(groupPrefix + ":")) {
1456
- const fullName = `${group.name} ${cmdName}`;
1457
- if (this.byName.has(fullName)) {
1458
- return this.byName.get(fullName);
1459
- }
1460
- }
1573
+ const v3 = cmd.manifest.manifestV2;
1574
+ if (v3?.cli?.groupMeta) {
1575
+ for (const meta of v3.cli.groupMeta) {
1576
+ const metaSegs = meta.path.trim().split(/\s+/).filter(Boolean);
1577
+ this.pluginRouter.setGroupDescribe(metaSegs, meta.describe);
1461
1578
  }
1462
1579
  }
1463
- return void 0;
1464
1580
  }
1465
- list() {
1466
- const commands = /* @__PURE__ */ new Set();
1467
- for (const value of this.byName.values()) {
1468
- if ("run" in value) {
1469
- commands.add(value);
1581
+ // ─── Resolution ────────────────────────────────────────────────────────────
1582
+ resolve(tokens) {
1583
+ if (tokens.length > 0) {
1584
+ const sysResult = this.systemRouter.resolve(tokens);
1585
+ if (sysResult.type !== "not-found") {
1586
+ return sysResult;
1470
1587
  }
1471
1588
  }
1472
- return Array.from(commands);
1589
+ return this.pluginRouter.resolve(tokens);
1473
1590
  }
1474
- listGroups() {
1475
- return Array.from(this.groups.values());
1591
+ // ─── Tab completion ─────────────────────────────────────────────────────────
1592
+ complete(tokens) {
1593
+ const sysCompletions = this.systemRouter.complete(tokens);
1594
+ const pluginCompletions = this.pluginRouter.complete(tokens);
1595
+ return [.../* @__PURE__ */ new Set([...sysCompletions, ...pluginCompletions])];
1476
1596
  }
1477
- getGroupsByPrefix(prefix) {
1478
- const result = [];
1479
- for (const group of this.groups.values()) {
1480
- if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1481
- result.push(group);
1482
- }
1483
- }
1484
- return result;
1597
+ // ─── Query ──────────────────────────────────────────────────────────────────
1598
+ listSystemTopLevel() {
1599
+ return this.systemRouter.listSystemTopLevel();
1485
1600
  }
1486
- getCommandsByGroupPrefix(prefix) {
1487
- const result = [];
1488
- for (const group of this.groups.values()) {
1489
- if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1490
- result.push(...group.commands);
1491
- }
1492
- }
1493
- return result;
1601
+ getPluginGroupDescribe(segments) {
1602
+ return this.pluginRouter.getGroupDescribe(segments);
1494
1603
  }
1495
- listProductGroups() {
1496
- const groups = /* @__PURE__ */ new Map();
1497
- for (const cmd of this.listManifests()) {
1498
- const groupName = cmd.manifest.group;
1499
- if (!groups.has(groupName)) {
1500
- groups.set(groupName, {
1501
- name: groupName,
1502
- describe: cmd.manifest.group,
1503
- commands: []
1504
- });
1505
- }
1506
- groups.get(groupName).commands.push(cmd);
1507
- }
1508
- return Array.from(groups.values());
1604
+ listCommands() {
1605
+ return this.pluginRouter.listAll();
1509
1606
  }
1510
- getCommandsByGroup(group) {
1511
- return this.listManifests().filter((cmd) => cmd.manifest.group === group).sort((a, b) => a.manifest.id.localeCompare(b.manifest.id));
1607
+ listCommandsUnder(segments) {
1608
+ return this.pluginRouter.listUnder(segments);
1512
1609
  }
1513
- getManifestCommand(idOrAlias) {
1514
- if (this.manifests.has(idOrAlias)) {
1515
- return this.manifests.get(idOrAlias);
1516
- }
1517
- const canonicalId = this.resolveToCanonical(idOrAlias);
1518
- if (canonicalId) {
1519
- return this.pluginByCanonical.get(canonicalId);
1520
- }
1521
- for (const cmd of this.pluginByCanonical.values()) {
1522
- if (cmd.manifest.aliases?.includes(idOrAlias)) {
1523
- return cmd;
1524
- }
1525
- if (cmd.manifest.id.replace(/:/g, " ") === idOrAlias) {
1526
- return cmd;
1527
- }
1528
- }
1529
- return void 0;
1610
+ getCommandAt(segments) {
1611
+ return this.pluginRouter.getAt(segments);
1612
+ }
1613
+ // ─── Partial loading state ─────────────────────────────────────────────────
1614
+ markPartial(v) {
1615
+ this.partial = v;
1616
+ }
1617
+ isPartial() {
1618
+ return this.partial;
1619
+ }
1620
+ // ─── Diagnostics ──────────────────────────────────────────────────────────
1621
+ getDiagnostics() {
1622
+ return {
1623
+ systemCommandCount: this.systemRouter.listAll().length,
1624
+ systemGroupCount: 0,
1625
+ pluginCommandCount: this.pluginRouter.listAll().length,
1626
+ partialLoad: this.partial
1627
+ };
1530
1628
  }
1531
1629
  };
1532
- var registry = new InMemoryRegistry();
1533
- function findCommand(nameOrPath) {
1534
- return registry.get(nameOrPath);
1535
- }
1536
- function findCommandWithType(nameOrPath) {
1537
- return registry.getWithType(nameOrPath);
1630
+ function createRegistry() {
1631
+ return new TrieBackedRegistry();
1538
1632
  }
1633
+ var registry = new TrieBackedRegistry();
1539
1634
 
1540
1635
  // src/utils/generate-examples.ts
1541
1636
  function generateExamples(commandName, productName, cases) {
@@ -1650,7 +1745,7 @@ var health = defineSystemCommand({
1650
1745
  finishEvent: "HEALTH_FINISHED"
1651
1746
  },
1652
1747
  async handler(ctx, _argv, _flags) {
1653
- const cliApi = await createRegistry({
1748
+ const cliApi = await createRegistry$1({
1654
1749
  cache: { ttlMs: 5e3 }
1655
1750
  });
1656
1751
  try {
@@ -1688,7 +1783,7 @@ var health = defineSystemCommand({
1688
1783
  status: result.healthStatus,
1689
1784
  error: result.error
1690
1785
  };
1691
- console.log(JSON.stringify(jsonOutput, null, 2));
1786
+ ctx.ui?.json?.(jsonOutput);
1692
1787
  } else {
1693
1788
  if (result.error) {
1694
1789
  ctx.ui.error("System Health", {
@@ -1739,81 +1834,739 @@ var health = defineSystemCommand({
1739
1834
  }
1740
1835
  });
1741
1836
  init_discover();
1742
- var diag = defineSystemCommand({
1743
- name: "diag",
1744
- description: "Comprehensive system diagnostics (plugins, cache, environment, versions)",
1745
- category: "info",
1746
- examples: generateExamples("diag", "kb", [
1747
- { flags: {} },
1748
- { flags: { json: true } }
1749
- ]),
1750
- flags: {
1751
- json: { type: "boolean", description: "Output in JSON format" }
1752
- },
1753
- analytics: {
1754
- command: "diag",
1755
- startEvent: "DIAG_STARTED",
1756
- finishEvent: "DIAG_FINISHED"
1757
- },
1758
- // eslint-disable-next-line sonarjs/cognitive-complexity -- Comprehensive diagnostic checks across multiple system components
1759
- async handler(ctx, _argv, _flags) {
1760
- const cwd = getContextCwd(ctx);
1761
- const diagnostics = [];
1762
- const nodeVersion = process.version;
1763
- const cliVersion = process.env.CLI_VERSION || process.env.CLI_VERSION || "0.1.0";
1764
- const platform11 = process.platform;
1765
- const arch = process.arch;
1766
- diagnostics.push({
1767
- category: "environment",
1768
- status: "ok",
1769
- message: `Node ${nodeVersion}, CLI ${cliVersion}, ${platform11}/${arch}`,
1770
- details: { nodeVersion, cliVersion, platform: platform11, arch }
1771
- });
1772
- try {
1773
- const platformRoot = process.env.KB_PLATFORM_ROOT;
1774
- const projectRoot = process.env.KB_PROJECT_ROOT;
1775
- const discovered = await discoverManifests(cwd, false, { platformRoot, projectRoot });
1776
- const manifests = registry.listManifests();
1777
- const enabled = manifests.filter((m) => m.available && !m.shadowed).length;
1778
- const disabled = manifests.filter((m) => !m.available).length;
1779
- const shadowed = manifests.filter((m) => m.shadowed).length;
1780
- diagnostics.push({
1781
- category: "marketplace",
1782
- status: disabled > 0 ? "warning" : "ok",
1783
- message: `Found ${discovered.length} packages, ${enabled} enabled, ${disabled} unavailable, ${shadowed} shadowed`,
1784
- details: {
1785
- packages: discovered.length,
1786
- enabled,
1787
- disabled,
1788
- shadowed,
1789
- totalCommands: manifests.length
1790
- }
1791
- });
1792
- } catch (err) {
1793
- const errMsg = err instanceof Error ? err.message : String(err);
1794
- diagnostics.push({
1795
- category: "marketplace",
1796
- status: "error",
1797
- message: `Discovery failed: ${errMsg}`,
1798
- details: { error: errMsg }
1799
- });
1837
+ var req = createRequire(import.meta.url);
1838
+ var __filename$1 = fileURLToPath(import.meta.url);
1839
+ var __dirname$1 = path.dirname(__filename$1);
1840
+ function resolveFromCwd(spec, cwd) {
1841
+ const cliRoot = path.resolve(__dirname$1, "../../..");
1842
+ let monorepoRoot = cwd;
1843
+ while (monorepoRoot !== path.dirname(monorepoRoot)) {
1844
+ if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
1845
+ break;
1846
+ }
1847
+ monorepoRoot = path.dirname(monorepoRoot);
1848
+ }
1849
+ const pathsToTry = [
1850
+ cwd,
1851
+ // Current working directory (user's project)
1852
+ cliRoot,
1853
+ // CLI installation directory
1854
+ monorepoRoot,
1855
+ // Monorepo root (if found)
1856
+ path.join(monorepoRoot, "../kb-labs-cli"),
1857
+ // Explicit kb-labs-cli path
1858
+ path.join(monorepoRoot, "../kb-labs-mind")
1859
+ // Explicit kb-labs-mind path
1860
+ ].filter(Boolean);
1861
+ for (const searchPath of pathsToTry) {
1862
+ try {
1863
+ return req.resolve(spec, { paths: [searchPath] });
1864
+ } catch (_e) {
1865
+ }
1866
+ }
1867
+ throw new Error(`Cannot resolve ${spec} from any of: ${pathsToTry.join(", ")}`);
1868
+ }
1869
+ function checkRequires(manifest, options = {}) {
1870
+ const cwd = options.cwd ?? process.cwd();
1871
+ if (!manifest.requires || manifest.requires.length === 0) {
1872
+ return { available: true };
1873
+ }
1874
+ let isInMonorepo = false;
1875
+ let monorepoRoot = cwd;
1876
+ while (monorepoRoot !== path.dirname(monorepoRoot)) {
1877
+ if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
1878
+ isInMonorepo = true;
1879
+ break;
1880
+ }
1881
+ monorepoRoot = path.dirname(monorepoRoot);
1882
+ }
1883
+ for (const dep of manifest.requires) {
1884
+ try {
1885
+ const resolved = resolveFromCwd(dep, cwd);
1886
+ if (!resolved.includes("node_modules")) {
1887
+ continue;
1888
+ }
1889
+ } catch (err) {
1890
+ if (isInMonorepo) {
1891
+ continue;
1892
+ }
1893
+ const errorMessage = (err instanceof Error ? err.message : "") || "";
1894
+ const hasExportsError = errorMessage.includes("exports") || errorMessage.includes("main");
1895
+ if (hasExportsError || errorMessage.includes("Cannot resolve")) {
1896
+ const parts = dep.startsWith("@") ? dep.slice(1).split("/") : [dep];
1897
+ if (parts.length === 0) {
1898
+ continue;
1899
+ }
1900
+ const scope = parts.length >= 2 ? parts[0] : "";
1901
+ const packageName = parts.length >= 2 ? parts[1] : parts[0];
1902
+ if (!packageName) {
1903
+ continue;
1904
+ }
1905
+ const cliRoot = path.resolve(__dirname$1, "../../..");
1906
+ const packagePath = scope ? path.join(cliRoot, "node_modules", "@" + scope, packageName, "package.json") : path.join(cliRoot, "node_modules", packageName, "package.json");
1907
+ if (existsSync(packagePath)) {
1908
+ continue;
1909
+ }
1910
+ }
1911
+ const isKbLabsPackage = dep.startsWith("@kb-labs/");
1912
+ const hint = isKbLabsPackage ? `Run: pnpm add ${dep}` : `Run: npm install ${dep}`;
1913
+ return {
1914
+ available: false,
1915
+ reason: `Missing dependency: ${dep}`,
1916
+ hint
1917
+ };
1918
+ }
1919
+ }
1920
+ return { available: true };
1921
+ }
1922
+ function validateManifestStructure(manifest) {
1923
+ if (manifest.manifestVersion !== "1.0") {
1924
+ throw new Error(
1925
+ `Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.segments?.join(" ") ?? manifest.id} (expected "1.0")`
1926
+ );
1927
+ }
1928
+ if (!Array.isArray(manifest.segments) || manifest.segments.length === 0) {
1929
+ throw new Error(`Missing or empty segments in manifest ${manifest.id}`);
1930
+ }
1931
+ if (!manifest.describe) {
1932
+ throw new Error(`Missing describe in manifest ${manifest.segments.join(" ")}`);
1933
+ }
1934
+ if (!manifest.loader && !manifest.manifestV2) {
1935
+ throw new Error(`Command ${manifest.segments.join(" ")} must have either loader or manifestV2`);
1936
+ }
1937
+ if (manifest.flags) {
1938
+ for (const flag of manifest.flags) {
1939
+ if (!flag.name || !flag.type) {
1940
+ throw new Error(`Invalid flag in ${manifest.segments.join(" ")}: missing name or type`);
1941
+ }
1942
+ if (flag.alias && flag.alias.length !== 1) {
1943
+ throw new Error(`Flag "${flag.name}" in ${manifest.segments.join(" ")}: alias must be a single character`);
1944
+ }
1945
+ if (flag.choices && flag.type !== "string") {
1946
+ throw new Error(
1947
+ `Flag "${flag.name}" in ${manifest.segments.join(" ")}: choices only allowed for string type`
1948
+ );
1949
+ }
1950
+ }
1951
+ }
1952
+ }
1953
+ var SOURCE_PRIORITY = {
1954
+ builtin: 4,
1955
+ workspace: 3,
1956
+ linked: 2,
1957
+ node_modules: 1
1958
+ };
1959
+ function getSourcePriority(source) {
1960
+ return SOURCE_PRIORITY[source] ?? 0;
1961
+ }
1962
+ function preflightManifests(discoveryResults, logger) {
1963
+ const log3 = logger ?? platform.logger;
1964
+ const logLevel = getLogLevel();
1965
+ const valid = [];
1966
+ const skipped = [];
1967
+ for (const result of discoveryResults) {
1968
+ const allowed = [];
1969
+ for (const manifest of result.manifests) {
1970
+ try {
1971
+ validateManifestStructure(manifest);
1972
+ allowed.push(manifest);
1973
+ } catch (error) {
1974
+ const reason = error instanceof Error ? error.message : "Validation failed";
1975
+ const id = manifest?.segments?.join(" ") ?? manifest?.id ?? result.packageName ?? "unknown";
1976
+ skipped.push({ id, source: result.source, reason });
1977
+ log3.warn(`Preflight skipped manifest ${id}: ${reason}`);
1978
+ if (logLevel === "debug") {
1979
+ process.stderr.write(`[debug][preflight] skipped ${id} (${result.source}): ${reason}
1980
+ `);
1981
+ }
1982
+ }
1983
+ }
1984
+ if (allowed.length > 0) {
1985
+ valid.push({ ...result, manifests: allowed });
1986
+ }
1987
+ }
1988
+ return { valid, skipped };
1989
+ }
1990
+ async function registerManifests(discoveryResults, registry2, options = {}) {
1991
+ const log3 = options.logger ?? platform.logger;
1992
+ const registered = [];
1993
+ const skipped = [];
1994
+ const globalIds = /* @__PURE__ */ new Map();
1995
+ const logLevel = getLogLevel();
1996
+ let collisions = 0;
1997
+ let errors = 0;
1998
+ const sorted = [...discoveryResults].sort((a, b) => {
1999
+ return getSourcePriority(b.source) - getSourcePriority(a.source);
2000
+ });
2001
+ for (const result of sorted) {
2002
+ for (const manifest of result.manifests) {
2003
+ const manifestId = manifest.segments?.join(" ") ?? manifest.id ?? "unknown";
2004
+ try {
2005
+ try {
2006
+ validateManifestStructure(manifest);
2007
+ } catch (err) {
2008
+ throw new Error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
2009
+ }
2010
+ const availability = checkRequires(manifest, {
2011
+ cwd: options.cwd ?? result.pkgRoot
2012
+ });
2013
+ const cmd = {
2014
+ manifest,
2015
+ v3Manifest: manifest.manifestV2,
2016
+ available: availability.available,
2017
+ unavailableReason: availability.available ? void 0 : availability.reason,
2018
+ hint: availability.available ? void 0 : availability.hint,
2019
+ source: result.source,
2020
+ shadowed: false,
2021
+ pkgRoot: result.pkgRoot,
2022
+ packageName: result.packageName
2023
+ };
2024
+ try {
2025
+ const manifestModule = await import(result.manifestPath);
2026
+ if (typeof manifestModule.init === "function") {
2027
+ await manifestModule.init({
2028
+ cwd: result.pkgRoot,
2029
+ package: result.packageName,
2030
+ manifest: cmd.manifest
2031
+ });
2032
+ }
2033
+ if (typeof manifestModule.register === "function") {
2034
+ await manifestModule.register({
2035
+ registry: registry2,
2036
+ command: cmd,
2037
+ cwd: result.pkgRoot,
2038
+ package: result.packageName
2039
+ });
2040
+ }
2041
+ if (typeof manifestModule.dispose === "function") {
2042
+ cmd._disposeHook = manifestModule.dispose;
2043
+ }
2044
+ } catch (hookError) {
2045
+ const hookMsg = hookError instanceof Error ? hookError.message : String(hookError);
2046
+ log3.debug(`Lifecycle hooks unavailable for ${manifestId}: ${hookMsg}`);
2047
+ }
2048
+ const canonicalKey = manifest.segments.join(":");
2049
+ const existing = globalIds.get(canonicalKey);
2050
+ if (existing) {
2051
+ if (existing.source === result.source && existing.source === "workspace") {
2052
+ collisions++;
2053
+ throw new Error(
2054
+ `Command path collision: "${manifestId}" exported by multiple workspace packages.`
2055
+ );
2056
+ }
2057
+ const existPri = getSourcePriority(existing.source);
2058
+ const curPri = getSourcePriority(result.source);
2059
+ if (curPri > existPri) {
2060
+ existing.shadowed = true;
2061
+ globalIds.set(canonicalKey, cmd);
2062
+ if (logLevel === "info" || logLevel === "debug") {
2063
+ log3.info(`${canonicalKey} from ${result.source} shadows ${existing.source} version`);
2064
+ }
2065
+ } else {
2066
+ cmd.shadowed = true;
2067
+ if (logLevel === "info" || logLevel === "debug") {
2068
+ log3.info(`${canonicalKey} from ${result.source} shadowed by ${existing.source} version`);
2069
+ }
2070
+ }
2071
+ } else {
2072
+ globalIds.set(canonicalKey, cmd);
2073
+ }
2074
+ if (!cmd.shadowed) {
2075
+ registry2.registerManifest(cmd);
2076
+ registered.push(cmd);
2077
+ }
2078
+ } catch (error) {
2079
+ errors++;
2080
+ const reason = error instanceof Error ? error.message : String(error);
2081
+ skipped.push({ id: manifestId, source: result.source, reason });
2082
+ log3.error(`Skipped manifest ${manifestId} (${result.source}): ${reason}`);
2083
+ }
2084
+ }
2085
+ }
2086
+ if (skipped.length > 0) {
2087
+ log3.warn(`Skipped ${skipped.length} manifest(s) during registration`);
2088
+ for (const skip of skipped) {
2089
+ log3.warn(` \u2022 ${skip.id} [${skip.source}] \u2192 ${skip.reason}`);
2090
+ }
2091
+ }
2092
+ return { registered, skipped, collisions, errors };
2093
+ }
2094
+ async function disposeAllPlugins(registry2, logger) {
2095
+ const log3 = logger ?? platform.logger;
2096
+ const manifests = registry2.listCommands();
2097
+ const disposePromises = [];
2098
+ for (const cmd of manifests) {
2099
+ const disposeHook = cmd._disposeHook;
2100
+ if (typeof disposeHook === "function") {
2101
+ disposePromises.push(
2102
+ Promise.resolve(disposeHook()).catch((err) => {
2103
+ log3.warn(`Dispose hook failed for ${cmd.manifest.segments.join(" ")}: ${err instanceof Error ? err.message : String(err)}`);
2104
+ })
2105
+ );
2106
+ }
2107
+ }
2108
+ await Promise.allSettled(disposePromises);
2109
+ }
2110
+
2111
+ // src/commands/system/diag.ts
2112
+ init_schema();
2113
+ function stageToChainItem(stage) {
2114
+ const statusMap = {
2115
+ ok: "success",
2116
+ error: "error",
2117
+ warning: "warning",
2118
+ info: "info"
2119
+ };
2120
+ const sections = [
2121
+ { items: [stage.message] },
2122
+ ...stage.remediation ? [{ header: "Fix", items: [stage.remediation] }] : []
2123
+ ];
2124
+ return {
2125
+ title: stage.stage,
2126
+ status: statusMap[stage.status],
2127
+ summary: { code: stage.code },
2128
+ sections
2129
+ };
2130
+ }
2131
+ async function tryLoadManifestForTrace(manifestPath, pkgName) {
2132
+ try {
2133
+ const mod = await import(pathToFileURL(manifestPath).href);
2134
+ const raw = mod.manifest ?? mod.default;
2135
+ if (!raw || typeof raw !== "object") {
2136
+ return {
2137
+ code: "NO_EXPORT",
2138
+ message: "No manifest export \u2014 add: export { manifest } or export default manifest"
2139
+ };
2140
+ }
2141
+ const schema = raw.schema;
2142
+ if (schema !== "kb.plugin/3") {
2143
+ return {
2144
+ code: "WRONG_SCHEMA",
2145
+ foundSchema: String(schema ?? "undefined"),
2146
+ message: `schema must be "kb.plugin/3", found "${String(schema ?? "undefined")}"`
2147
+ };
2148
+ }
2149
+ const cliCmds = raw.cli?.commands ?? [];
2150
+ if (!Array.isArray(cliCmds) || cliCmds.length === 0) {
2151
+ return {
2152
+ code: "VALIDATION_FAILED",
2153
+ message: "cli.commands is empty \u2014 no commands to register",
2154
+ validationErrors: [{ field: "cli.commands", message: "Must contain at least one command" }]
2155
+ };
2156
+ }
2157
+ const mapped = cliCmds.map((cmd) => {
2158
+ const segments = (cmd.path ?? "").trim().split(/\s+/).filter(Boolean);
2159
+ return {
2160
+ manifestVersion: "1.0",
2161
+ segments,
2162
+ id: segments[segments.length - 1] ?? "unknown",
2163
+ group: segments[0] ?? "unknown",
2164
+ describe: cmd.describe ?? "",
2165
+ loader: async () => {
2166
+ throw new Error("trace-only");
2167
+ },
2168
+ flags: cmd.flags,
2169
+ aliases: cmd.aliases,
2170
+ operationType: cmd.operationType,
2171
+ package: pkgName
2172
+ };
2173
+ });
2174
+ const result = validateManifests(mapped);
2175
+ if (!result.success) {
2176
+ const errors = result.errors.flatMap(
2177
+ (e) => e.issues.map((i) => ({ field: i.path.join(".") || "(root)", message: i.message }))
2178
+ );
2179
+ return {
2180
+ code: "VALIDATION_FAILED",
2181
+ message: `${errors.length} validation error(s)`,
2182
+ validationErrors: errors
2183
+ };
2184
+ }
2185
+ return null;
2186
+ } catch (err) {
2187
+ return {
2188
+ code: "LOAD_ERROR",
2189
+ message: err instanceof Error ? err.message : String(err)
2190
+ };
2191
+ }
2192
+ }
2193
+ function matchesTopSegment(result, topSegment) {
2194
+ if (result.manifests.some((m) => m.group === topSegment)) {
2195
+ return true;
2196
+ }
2197
+ const last = result.packageName.split("/").pop() ?? "";
2198
+ return last.replace(/-entry$|-cli$/, "") === topSegment;
2199
+ }
2200
+ function findLockEntry(installed, topSegment) {
2201
+ return Object.entries(installed).find(([key]) => {
2202
+ const last = key.split("/").pop() ?? "";
2203
+ return last.replace(/-entry$|-cli$/, "") === topSegment || last === topSegment;
2204
+ });
2205
+ }
2206
+ async function runContextStage(ctx) {
2207
+ return {
2208
+ stage: "context",
2209
+ status: "info",
2210
+ code: "CONTEXT",
2211
+ message: `cwd=${ctx.cwd} KB_PLATFORM_ROOT=${ctx.platformRoot ?? "(not set)"} KB_PROJECT_ROOT=${ctx.projectRoot ?? "(not set)"}`,
2212
+ details: {
2213
+ cwd: ctx.cwd,
2214
+ platformRoot: ctx.platformRoot ?? null,
2215
+ projectRoot: ctx.projectRoot ?? null,
2216
+ execPath: process.execPath
2217
+ }
2218
+ };
2219
+ }
2220
+ async function runRegistryStage(ctx) {
2221
+ const result = registry.resolve(ctx.segments);
2222
+ if (result.type === "command") {
2223
+ const cmd = result.command;
2224
+ if (cmd.shadowed) {
2225
+ const owner = registry.listCommands().find((c) => c.manifest.segments[0] === ctx.segments[0] && !c.shadowed)?.packageName;
2226
+ return {
2227
+ stage: "registry",
2228
+ status: "warning",
2229
+ code: "SHADOWED_BY",
2230
+ message: `Command found but shadowed by ${owner ?? "another package"}`,
2231
+ details: { ownerPackage: owner ?? null, shadowedPackage: cmd.packageName ?? null }
2232
+ };
2233
+ }
2234
+ if (!cmd.available) {
2235
+ return {
2236
+ stage: "registry",
2237
+ status: "error",
2238
+ code: "REQUIRES_MISSING",
2239
+ message: cmd.unavailableReason ?? "Command unavailable",
2240
+ remediation: cmd.hint,
2241
+ details: { package: cmd.packageName ?? null, source: cmd.source }
2242
+ };
2243
+ }
2244
+ return {
2245
+ stage: "registry",
2246
+ status: "ok",
2247
+ code: "REGISTRY_OK",
2248
+ message: `Command registered and available`,
2249
+ details: { package: cmd.packageName ?? null, source: cmd.source },
2250
+ enrich: { registryOk: true }
2251
+ };
2252
+ }
2253
+ if (result.type === "group") {
2254
+ return {
2255
+ stage: "registry",
2256
+ status: "warning",
2257
+ code: "GROUP_EXISTS_LEAF_MISSING",
2258
+ message: `Group "${ctx.segments[0]}" exists but leaf "${ctx.segments.slice(1).join(" ")}" not found`,
2259
+ details: { availableChildren: result.childKeys }
2260
+ };
2261
+ }
2262
+ const shadowedMatch = registry.listCommands().find((c) => c.manifest.segments.join(" ") === ctx.segments.join(" ") && c.shadowed);
2263
+ if (shadowedMatch) {
2264
+ const owner = registry.listCommands().find((c) => c.manifest.segments[0] === ctx.segments[0] && !c.shadowed)?.packageName;
2265
+ return {
2266
+ stage: "registry",
2267
+ status: "warning",
2268
+ code: "SHADOWED_BY",
2269
+ message: `Command is shadowed by ${owner ?? "another package"}`,
2270
+ details: { ownerPackage: owner ?? null, shadowedPackage: shadowedMatch.packageName ?? null }
2271
+ };
2272
+ }
2273
+ return {
2274
+ stage: "registry",
2275
+ status: "info",
2276
+ code: "REGISTRY_NOT_FOUND",
2277
+ message: "Command not in registry"
2278
+ };
2279
+ }
2280
+ async function runDiscoveryStage(ctx) {
2281
+ resetInProcCache();
2282
+ const opts = { platformRoot: ctx.platformRoot, projectRoot: ctx.projectRoot };
2283
+ const discovered = await discoverManifests(ctx.cwd, true, opts);
2284
+ const [topSegment] = ctx.segments;
2285
+ const result = discovered.find((r) => matchesTopSegment(r, topSegment ?? ""));
2286
+ if (!result) {
2287
+ const { block: blocklist = [] } = await loadConfig(ctx.cwd);
2288
+ const blocked = blocklist.some((b) => b === topSegment || b.includes(`/${topSegment ?? ""}`));
2289
+ if (blocked) {
2290
+ return {
2291
+ stage: "discovery",
2292
+ status: "error",
2293
+ code: "PLUGIN_BLOCKLISTED",
2294
+ message: `Plugin "${topSegment}" is in plugins.block in .kb/kb.config.json`,
2295
+ remediation: "Remove it from plugins.block in .kb/kb.config.json"
2296
+ };
2297
+ }
2298
+ return {
2299
+ stage: "discovery",
2300
+ status: "info",
2301
+ code: "NOT_IN_DISCOVERY",
2302
+ message: `No package found for group "${topSegment}" in workspace or node_modules`
2303
+ };
2304
+ }
2305
+ if (result.manifests.every((m) => m._synthetic)) {
2306
+ const distPath = path.join(result.pkgRoot, "dist");
2307
+ const distExists = await access(distPath).then(() => true).catch(() => false);
2308
+ const loadErr = await tryLoadManifestForTrace(result.manifestPath, result.packageName);
2309
+ const stageCode = loadErr?.code === "WRONG_SCHEMA" ? "WRONG_SCHEMA" : loadErr?.code === "VALIDATION_FAILED" ? "MANIFEST_VALIDATION_FAILED" : "MANIFEST_LOAD_FAILED";
2310
+ return {
2311
+ stage: "discovery",
2312
+ status: "error",
2313
+ code: stageCode,
2314
+ message: loadErr ? loadErr.message : "Manifest failed to load (timeout or import error)",
2315
+ remediation: !distExists ? `pnpm --filter ${result.packageName} build` : "Fix the error in your manifest.ts",
2316
+ details: {
2317
+ manifestPath: result.manifestPath,
2318
+ distExists,
2319
+ error: loadErr?.message ?? null,
2320
+ foundSchema: loadErr?.foundSchema ?? null,
2321
+ validationErrors: loadErr?.validationErrors ?? null
2322
+ },
2323
+ enrich: { discoveryResult: result }
2324
+ };
2325
+ }
2326
+ const { skipped } = preflightManifests([result]);
2327
+ if (skipped.length > 0) {
2328
+ return {
2329
+ stage: "discovery",
2330
+ status: "error",
2331
+ code: "MANIFEST_STRUCT_INVALID",
2332
+ message: `Manifest loaded but ${skipped.length} command(s) failed structural validation`,
2333
+ remediation: "Fix the errors in your manifest.ts",
2334
+ details: {
2335
+ failures: skipped.map((s) => ({ command: s.id, reason: s.reason }))
2336
+ }
2337
+ };
2338
+ }
2339
+ const commandPath = ctx.segments.join(" ");
2340
+ const found = result.manifests.filter((m) => !m._synthetic).find((m) => m.segments.join(" ") === commandPath);
2341
+ if (!found) {
2342
+ const availablePaths = result.manifests.filter((m) => !m._synthetic).map((m) => m.segments.join(" ")).sort();
2343
+ return {
2344
+ stage: "discovery",
2345
+ status: "warning",
2346
+ code: "COMMAND_PATH_MISSING",
2347
+ message: `Package found but command path "${commandPath}" is not defined in manifest`,
2348
+ remediation: `Check command path in manifest.ts. Available (${availablePaths.length}): ${availablePaths.slice(0, 5).join(", ")}${availablePaths.length > 5 ? ` \u2026 and ${availablePaths.length - 5} more` : ""}`,
2349
+ details: { availablePaths, packageName: result.packageName }
2350
+ };
2351
+ }
2352
+ return {
2353
+ stage: "discovery",
2354
+ status: "ok",
2355
+ code: "MANIFEST_OK",
2356
+ message: `Manifest loaded and command "${commandPath}" is defined`,
2357
+ details: { manifestPath: result.manifestPath, packageName: result.packageName },
2358
+ enrich: { discoveryResult: result }
2359
+ };
2360
+ }
2361
+ async function runLockStage(ctx) {
2362
+ const collector = new DiagnosticCollector();
2363
+ const lock = await readMarketplaceLock(ctx.cwd, collector);
2364
+ if (!lock) {
2365
+ const status = ctx.registryOk ? "info" : "warning";
2366
+ return {
2367
+ stage: "lock",
2368
+ status,
2369
+ code: "NO_LOCK",
2370
+ message: ctx.registryOk ? "No marketplace.lock \u2014 plugin is available from workspace (normal in dev mode)" : "No marketplace.lock found \u2014 plugin may not be installed"
2371
+ };
2372
+ }
2373
+ const [topSegment] = ctx.segments;
2374
+ const entry = findLockEntry(lock.installed, topSegment ?? "");
2375
+ if (!entry) {
2376
+ if (ctx.registryOk) {
2377
+ return {
2378
+ stage: "lock",
2379
+ status: "info",
2380
+ code: "NOT_IN_MARKETPLACE",
2381
+ message: `"${topSegment}" is not installed via marketplace \u2014 it runs from the workspace (normal in dev mode)`
2382
+ };
2383
+ }
2384
+ return {
2385
+ stage: "lock",
2386
+ status: "warning",
2387
+ code: "NOT_IN_LOCK",
2388
+ message: `"${topSegment}" not found in marketplace.lock`,
2389
+ remediation: `kb marketplace install ${topSegment}`
2390
+ };
2391
+ }
2392
+ const [key, data] = entry;
2393
+ if (data.enabled === false) {
2394
+ return {
2395
+ stage: "lock",
2396
+ status: "error",
2397
+ code: "PLUGIN_DISABLED",
2398
+ message: `Package "${key}" is installed but disabled`,
2399
+ remediation: `kb marketplace plugins enable ${key}`,
2400
+ details: { key, version: data.version }
2401
+ };
2402
+ }
2403
+ return {
2404
+ stage: "lock",
2405
+ status: "ok",
2406
+ code: "PLUGIN_ENABLED",
2407
+ message: `"${key}" is installed and enabled`,
2408
+ details: { key, version: data.version, resolvedPath: data.resolvedPath },
2409
+ enrich: { lockEntry: entry }
2410
+ };
2411
+ }
2412
+ async function runFilesystemStage(ctx) {
2413
+ if (!ctx.lockEntry) {
2414
+ return {
2415
+ stage: "filesystem",
2416
+ status: "info",
2417
+ code: "FS_SKIP",
2418
+ message: "Skipped \u2014 no lock entry to verify"
2419
+ };
2420
+ }
2421
+ const [, data] = ctx.lockEntry;
2422
+ if (!data.resolvedPath) {
2423
+ return {
2424
+ stage: "filesystem",
2425
+ status: "info",
2426
+ code: "FS_NO_PATH",
2427
+ message: "Lock entry has no resolvedPath to check"
2428
+ };
2429
+ }
2430
+ try {
2431
+ await access(data.resolvedPath);
2432
+ return {
2433
+ stage: "filesystem",
2434
+ status: "ok",
2435
+ code: "PATH_EXISTS",
2436
+ message: `Package path exists at ${data.resolvedPath}`
2437
+ };
2438
+ } catch {
2439
+ return {
2440
+ stage: "filesystem",
2441
+ status: "error",
2442
+ code: "PATH_MISSING",
2443
+ message: `Package path does not exist: ${data.resolvedPath}`,
2444
+ remediation: "pnpm install",
2445
+ details: { resolvedPath: data.resolvedPath }
2446
+ };
2447
+ }
2448
+ }
2449
+ var STAGE_RUNNERS = [
2450
+ runContextStage,
2451
+ runRegistryStage,
2452
+ runDiscoveryStage,
2453
+ runLockStage,
2454
+ runFilesystemStage
2455
+ ];
2456
+ async function runTrace(ctx) {
2457
+ const stages = [];
2458
+ for (const run of STAGE_RUNNERS) {
2459
+ const { enrich, ...stage } = await run(ctx);
2460
+ stages.push(stage);
2461
+ if (enrich) {
2462
+ Object.assign(ctx, enrich);
2463
+ }
2464
+ }
2465
+ const failing = stages.find((s) => s.status === "error") ?? stages.find((s) => s.status === "warning");
2466
+ return {
2467
+ ok: !stages.some((s) => s.status === "error" || s.status === "warning"),
2468
+ command: ctx.segments.join(" "),
2469
+ stages,
2470
+ verdict: failing ? { rootCause: failing.code, remediation: failing.remediation } : { rootCause: "NONE" }
2471
+ };
2472
+ }
2473
+ var diag = defineSystemCommand({
2474
+ name: "diag",
2475
+ description: "Comprehensive system diagnostics (plugins, cache, environment, versions)",
2476
+ category: "info",
2477
+ examples: generateExamples("diag", "kb", [
2478
+ { flags: {} },
2479
+ { flags: { json: true } },
2480
+ { flags: { command: "workflow run" } }
2481
+ ]),
2482
+ flags: {
2483
+ json: { type: "boolean", description: "Output in JSON format" },
2484
+ command: { type: "string", description: 'Trace a specific command through the discovery pipeline (e.g. "workflow run")' }
2485
+ },
2486
+ analytics: {
2487
+ command: "diag",
2488
+ startEvent: "DIAG_STARTED",
2489
+ finishEvent: "DIAG_FINISHED"
2490
+ },
2491
+ // eslint-disable-next-line sonarjs/cognitive-complexity
2492
+ async handler(ctx, _argv, flags) {
2493
+ const cwd = getContextCwd(ctx);
2494
+ const platformRoot = process.env.KB_PLATFORM_ROOT;
2495
+ const projectRoot = process.env.KB_PROJECT_ROOT;
2496
+ if (flags.command) {
2497
+ const segments = flags.command.trim().split(/\s+/).filter(Boolean);
2498
+ const traceCtx = { cwd, segments, platformRoot, projectRoot };
2499
+ return runTrace(traceCtx);
2500
+ }
2501
+ const diagnostics = [];
2502
+ diagnostics.push({
2503
+ category: "environment",
2504
+ status: "ok",
2505
+ message: `Node ${process.version}, CLI ${process.env.CLI_VERSION ?? "0.1.0"}, ${process.platform}/${process.arch}`,
2506
+ details: {
2507
+ nodeVersion: process.version,
2508
+ cliVersion: process.env.CLI_VERSION ?? "0.1.0",
2509
+ platform: process.platform,
2510
+ arch: process.arch
2511
+ }
2512
+ });
2513
+ let discovered = [];
2514
+ try {
2515
+ discovered = await discoverManifests(cwd, false, { platformRoot, projectRoot });
2516
+ const allCommands = registry.listCommands();
2517
+ const enabled = allCommands.filter((m) => m.available && !m.shadowed).length;
2518
+ const disabled = allCommands.filter((m) => !m.available && !m.shadowed).length;
2519
+ const shadowed = allCommands.filter((m) => m.shadowed).length;
2520
+ diagnostics.push({
2521
+ category: "marketplace",
2522
+ status: disabled > 0 ? "warning" : "ok",
2523
+ message: `Found ${discovered.length} packages, ${enabled} enabled, ${disabled} unavailable, ${shadowed} shadowed`,
2524
+ details: { packages: discovered.length, enabled, disabled, shadowed, totalCommands: allCommands.length }
2525
+ });
2526
+ for (const cmd of allCommands.filter((m) => !m.available && !m.shadowed)) {
2527
+ diagnostics.push({
2528
+ category: "unavailable-command",
2529
+ status: "warning",
2530
+ message: `${cmd.manifest.segments.join(" ")}: ${cmd.unavailableReason ?? "unavailable"}`,
2531
+ code: "REQUIRES_MISSING",
2532
+ remediation: cmd.hint,
2533
+ details: { source: cmd.source, package: cmd.packageName ?? null }
2534
+ });
2535
+ }
2536
+ for (const result of discovered) {
2537
+ for (const manifest of result.manifests) {
2538
+ if (manifest._synthetic) {
2539
+ diagnostics.push({
2540
+ category: "manifest-load-failure",
2541
+ status: "error",
2542
+ message: `${result.packageName}: manifest failed to load`,
2543
+ code: "MANIFEST_LOAD_FAILED",
2544
+ remediation: `pnpm --filter ${result.packageName} build`,
2545
+ details: { source: result.source, manifestPath: result.manifestPath }
2546
+ });
2547
+ }
2548
+ }
2549
+ }
2550
+ } catch (err) {
2551
+ const errMsg = err instanceof Error ? err.message : String(err);
2552
+ diagnostics.push({
2553
+ category: "marketplace",
2554
+ status: "error",
2555
+ message: `Discovery failed: ${errMsg}`,
2556
+ details: { error: errMsg }
2557
+ });
1800
2558
  }
1801
2559
  try {
1802
2560
  const cachePath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
1803
- const cacheExists = await promises.access(cachePath).then(() => true).catch(() => false);
2561
+ const cacheExists = await access(cachePath).then(() => true).catch(() => false);
1804
2562
  if (cacheExists) {
1805
- const cache = JSON.parse(await promises.readFile(cachePath, "utf8"));
1806
- const age = Date.now() - (cache.timestamp || 0);
1807
- const ageHours = Math.floor(age / (1e3 * 60 * 60));
2563
+ const cache = JSON.parse(await readFile(cachePath, "utf8"));
2564
+ const ageHours = Math.floor((Date.now() - (cache.timestamp ?? 0)) / (1e3 * 60 * 60));
1808
2565
  diagnostics.push({
1809
2566
  category: "cache",
1810
2567
  status: "ok",
1811
- message: `Cache exists, ${ageHours}h old, ${Object.keys(cache.packages || {}).length} packages cached`,
1812
- details: {
1813
- exists: true,
1814
- ageHours,
1815
- packages: Object.keys(cache.packages || {}).length
1816
- }
2568
+ message: `Cache exists, ${ageHours}h old, ${Object.keys(cache.packages ?? {}).length} packages cached`,
2569
+ details: { exists: true, ageHours, packages: Object.keys(cache.packages ?? {}).length }
1817
2570
  });
1818
2571
  } else {
1819
2572
  diagnostics.push({
@@ -1833,7 +2586,20 @@ var diag = defineSystemCommand({
1833
2586
  });
1834
2587
  }
1835
2588
  try {
1836
- const lock = await readMarketplaceLock(cwd, new DiagnosticCollector());
2589
+ const collector = new DiagnosticCollector();
2590
+ const lock = await readMarketplaceLock(cwd, collector);
2591
+ for (const event of collector.getEvents()) {
2592
+ if (event.severity === "error" || event.severity === "warning") {
2593
+ diagnostics.push({
2594
+ category: "lock-event",
2595
+ status: event.severity === "error" ? "error" : "warning",
2596
+ message: event.message,
2597
+ code: event.code,
2598
+ remediation: event.remediation,
2599
+ details: event.context
2600
+ });
2601
+ }
2602
+ }
1837
2603
  if (lock) {
1838
2604
  const entries = Object.entries(lock.installed);
1839
2605
  const enabledCount = entries.filter(([, e]) => e.enabled !== false).length;
@@ -1860,42 +2626,25 @@ var diag = defineSystemCommand({
1860
2626
  details: { error: errMsg }
1861
2627
  });
1862
2628
  }
1863
- const versionIssues = [];
1864
2629
  try {
1865
- const manifests = registry.listManifests();
1866
- for (const cmd of manifests) {
2630
+ const cliVersion = process.env.CLI_VERSION ?? "0.1.0";
2631
+ const versionIssues = [];
2632
+ for (const cmd of registry.listCommands()) {
1867
2633
  const required = cmd.manifest.engine?.kbCli;
1868
2634
  if (required) {
1869
- const requiredParts = required.replace("^", "").split(".");
1870
- const currentParts = cliVersion.split(".");
1871
- if (requiredParts[0] && currentParts[0]) {
1872
- const requiredMajor = parseInt(requiredParts[0], 10);
1873
- const currentMajor = parseInt(currentParts[0], 10);
1874
- if (!isNaN(requiredMajor) && !isNaN(currentMajor) && currentMajor < requiredMajor) {
1875
- versionIssues.push({
1876
- plugin: cmd.manifest.package || cmd.manifest.group,
1877
- required,
1878
- current: cliVersion
1879
- });
1880
- }
2635
+ const requiredMajor = parseInt(required.replace("^", "").split(".")[0] ?? "0", 10);
2636
+ const currentMajor = parseInt(cliVersion.split(".")[0] ?? "0", 10);
2637
+ if (!isNaN(requiredMajor) && !isNaN(currentMajor) && currentMajor < requiredMajor) {
2638
+ versionIssues.push({ plugin: cmd.manifest.package ?? cmd.manifest.group, required, current: cliVersion });
1881
2639
  }
1882
2640
  }
1883
2641
  }
1884
- if (versionIssues.length > 0) {
1885
- diagnostics.push({
1886
- category: "versions",
1887
- status: "warning",
1888
- message: `${versionIssues.length} plugin(s) require newer CLI version`,
1889
- details: { issues: versionIssues }
1890
- });
1891
- } else {
1892
- diagnostics.push({
1893
- category: "versions",
1894
- status: "ok",
1895
- message: "All plugins compatible with current CLI version",
1896
- details: { issues: [] }
1897
- });
1898
- }
2642
+ diagnostics.push({
2643
+ category: "versions",
2644
+ status: versionIssues.length > 0 ? "warning" : "ok",
2645
+ message: versionIssues.length > 0 ? `${versionIssues.length} plugin(s) require newer CLI version` : "All plugins compatible with current CLI version",
2646
+ details: { issues: versionIssues }
2647
+ });
1899
2648
  } catch (err) {
1900
2649
  const errMsg = err instanceof Error ? err.message : String(err);
1901
2650
  diagnostics.push({
@@ -1912,68 +2661,75 @@ var diag = defineSystemCommand({
1912
2661
  errors: diagnostics.filter((d) => d.status === "error").length
1913
2662
  };
1914
2663
  ctx.platform?.logger?.info("Diag command completed", summary);
1915
- return {
1916
- ok: summary.errors === 0,
1917
- diagnostics,
1918
- summary
1919
- };
2664
+ return { ok: summary.errors === 0, diagnostics, summary };
1920
2665
  },
1921
- // eslint-disable-next-line sonarjs/cognitive-complexity -- Complex formatting logic for diagnostic output with multiple display modes
2666
+ // eslint-disable-next-line sonarjs/cognitive-complexity
1922
2667
  formatter(result, ctx, flags) {
1923
2668
  if (flags.json) {
1924
- console.log(JSON.stringify(result, null, 2));
1925
- } else {
1926
- const hasErrors = result.summary.errors > 0;
1927
- const hasWarnings = result.summary.warnings > 0;
1928
- const summaryItems = [
1929
- `${safeColors.bold("Total Checks")}: ${result.summary.total}`,
1930
- `${safeColors.success("OK")}: ${result.summary.ok}`,
1931
- `${safeColors.warning("Warnings")}: ${result.summary.warnings}`,
1932
- `${safeColors.error("Errors")}: ${result.summary.errors}`
1933
- ];
1934
- const diagItems = [];
1935
- for (const diag2 of result.diagnostics) {
1936
- const icon = diag2.status === "ok" ? safeSymbols.success : diag2.status === "warning" ? safeSymbols.warning : safeSymbols.error;
1937
- const colorize = diag2.status === "ok" ? safeColors.success : diag2.status === "warning" ? safeColors.warning : safeColors.error;
1938
- diagItems.push(`${icon} ${colorize(safeColors.bold(diag2.category))}: ${diag2.message}`);
1939
- if (diag2.status === "warning" && Array.isArray(diag2.details?.issues)) {
1940
- for (const issue of diag2.details.issues) {
1941
- diagItems.push(
1942
- ` ${safeColors.warning(`\u2192 ${issue.plugin}: requires ${issue.required}, found ${issue.current}`)}`
1943
- );
1944
- }
2669
+ ctx.ui?.json?.(result);
2670
+ return;
2671
+ }
2672
+ if ("stages" in result) {
2673
+ const trace = result;
2674
+ const verdictStatus = trace.ok ? "success" : "error";
2675
+ const verdictMsg = trace.ok ? "Command is fully operational" : `Root cause: ${trace.verdict.rootCause}`;
2676
+ ctx.ui.chain([
2677
+ ...trace.stages.map(stageToChainItem),
2678
+ {
2679
+ title: "verdict",
2680
+ status: verdictStatus,
2681
+ summary: { rootCause: trace.verdict.rootCause },
2682
+ sections: [
2683
+ { items: [verdictMsg] },
2684
+ ...trace.verdict.remediation ? [{ header: "Fix", items: [trace.verdict.remediation] }] : []
2685
+ ]
1945
2686
  }
2687
+ ]);
2688
+ return;
2689
+ }
2690
+ const base = result;
2691
+ const hasErrors = base.summary.errors > 0;
2692
+ const hasWarnings = base.summary.warnings > 0;
2693
+ const summaryItems = [
2694
+ `${safeColors.bold("Total Checks")}: ${base.summary.total}`,
2695
+ `${safeColors.success("OK")}: ${base.summary.ok}`,
2696
+ `${safeColors.warning("Warnings")}: ${base.summary.warnings}`,
2697
+ `${safeColors.error("Errors")}: ${base.summary.errors}`
2698
+ ];
2699
+ const diagItems = [];
2700
+ for (const diag2 of base.diagnostics) {
2701
+ const icon = diag2.status === "ok" ? safeSymbols.success : diag2.status === "warning" ? safeSymbols.warning : safeSymbols.error;
2702
+ const colorize = diag2.status === "ok" ? safeColors.success : diag2.status === "warning" ? safeColors.warning : safeColors.error;
2703
+ diagItems.push(`${icon} ${colorize(safeColors.bold(diag2.category))}: ${diag2.message}`);
2704
+ if (diag2.remediation) {
2705
+ diagItems.push(` ${safeColors.muted(`\u2192 Fix: ${diag2.remediation}`)}`);
1946
2706
  }
1947
- const nextSteps = [];
1948
- if (hasErrors) {
1949
- nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
1950
- }
1951
- if (hasWarnings) {
1952
- nextSteps.push(`kb marketplace list ${safeColors.muted("List all plugins")}`);
1953
- }
1954
- nextSteps.push(`kb diagnose ${safeColors.muted("Quick environment check")}`);
1955
- const sections = [
1956
- {
1957
- header: "Summary",
1958
- items: summaryItems
1959
- },
1960
- {
1961
- header: "Details",
1962
- items: diagItems
1963
- },
1964
- {
1965
- header: "Next Steps",
1966
- items: nextSteps
2707
+ if (diag2.status === "warning" && Array.isArray(diag2.details?.issues)) {
2708
+ for (const issue of diag2.details.issues) {
2709
+ diagItems.push(` ${safeColors.warning(`\u2192 ${issue.plugin}: requires ${issue.required}, found ${issue.current}`)}`);
1967
2710
  }
1968
- ];
1969
- if (hasErrors) {
1970
- ctx.ui.error("System Diagnostics", { sections });
1971
- } else if (hasWarnings) {
1972
- ctx.ui.warn("System Diagnostics", { sections });
1973
- } else {
1974
- ctx.ui.success("System Diagnostics", { sections });
1975
2711
  }
1976
2712
  }
2713
+ const nextSteps = [];
2714
+ if (hasErrors) {
2715
+ nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
2716
+ }
2717
+ if (hasWarnings) {
2718
+ nextSteps.push(`kb marketplace list ${safeColors.muted("List all plugins")}`);
2719
+ }
2720
+ nextSteps.push(`kb diag --command <name> ${safeColors.muted("Trace a specific command")}`);
2721
+ const sections = [
2722
+ { header: "Summary", items: summaryItems },
2723
+ { header: "Details", items: diagItems },
2724
+ { header: "Next Steps", items: nextSteps }
2725
+ ];
2726
+ if (hasErrors) {
2727
+ ctx.ui.error("System Diagnostics", { sections });
2728
+ } else if (hasWarnings) {
2729
+ ctx.ui.warn("System Diagnostics", { sections });
2730
+ } else {
2731
+ ctx.ui.success("System Diagnostics", { sections });
2732
+ }
1977
2733
  }
1978
2734
  });
1979
2735
  var registryDiagnostics = defineSystemCommand({
@@ -1992,7 +2748,7 @@ var registryDiagnostics = defineSystemCommand({
1992
2748
  },
1993
2749
  async handler(ctx, _argv, flags) {
1994
2750
  const cwd = getContextCwd(ctx);
1995
- const registry2 = await createRegistry({ root: cwd });
2751
+ const registry2 = await createRegistry$1({ root: cwd });
1996
2752
  if (flags.refresh) {
1997
2753
  await registry2.refresh();
1998
2754
  }
@@ -2016,128 +2772,6 @@ var registryDiagnostics = defineSystemCommand({
2016
2772
  }
2017
2773
  }
2018
2774
  });
2019
-
2020
- // src/registry/plugins-state.ts
2021
- init_discover();
2022
- async function clearCache(cwd, options) {
2023
- const cleared = [];
2024
- const cacheDir = path.join(cwd, ".kb", "cache");
2025
- try {
2026
- const entries = await promises.readdir(cacheDir);
2027
- for (const entry of entries) {
2028
- if (entry.includes("manifest") || entry.includes("plugin")) {
2029
- const entryPath = path.join(cacheDir, entry);
2030
- await promises.unlink(entryPath);
2031
- cleared.push(entry);
2032
- }
2033
- }
2034
- } catch {
2035
- }
2036
- const manifestsCachePath = path.join(cwd, ".kb", "marketplace.manifests.json");
2037
- try {
2038
- await promises.unlink(manifestsCachePath);
2039
- cleared.push("marketplace.manifests.json");
2040
- } catch {
2041
- }
2042
- const cliManifestsPath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
2043
- try {
2044
- await promises.unlink(cliManifestsPath);
2045
- if (!cleared.includes("cli-manifests.json")) {
2046
- cleared.push("cli-manifests.json");
2047
- }
2048
- } catch {
2049
- }
2050
- resetInProcCache();
2051
- const modulesCleared = [];
2052
- if (options?.deep) {
2053
- try {
2054
- const cache = __require.cache;
2055
- for (const key in cache) {
2056
- if (key.includes("plugin") || key.includes("manifest") || key.includes("@kb-labs")) {
2057
- delete cache[key];
2058
- modulesCleared.push(key);
2059
- }
2060
- }
2061
- } catch {
2062
- }
2063
- }
2064
- return {
2065
- files: cleared,
2066
- ...options?.deep ? { modules: modulesCleared } : {}
2067
- };
2068
- }
2069
- var pluginsCacheClear = defineSystemCommand({
2070
- name: "clear-cache",
2071
- description: "Clear CLI plugin discovery cache",
2072
- category: "marketplace",
2073
- examples: ["kb marketplace clear-cache", "kb marketplace clear-cache --deep"],
2074
- flags: {
2075
- deep: { type: "boolean", description: "Also clear Node.js module cache" },
2076
- json: { type: "boolean", description: "Output in JSON format" }
2077
- },
2078
- analytics: {
2079
- command: "marketplace:clear-cache",
2080
- startEvent: "MARKETPLACE_CACHE_CLEAR_STARTED",
2081
- finishEvent: "MARKETPLACE_CACHE_CLEAR_FINISHED"
2082
- },
2083
- async handler(ctx, _argv, flags) {
2084
- const deep = flags.deep;
2085
- const cwd = getContextCwd(ctx);
2086
- const result = await clearCache(cwd, { deep });
2087
- ctx.platform?.logger?.info("Cache cleared", {
2088
- filesCount: result.files.length,
2089
- modulesCount: result.modules?.length || 0,
2090
- deep
2091
- });
2092
- if (flags.json) {
2093
- const jsonResult = {
2094
- ok: true,
2095
- action: "cache:clear",
2096
- files: result.files,
2097
- modules: result.modules,
2098
- count: result.files.length,
2099
- modulesCount: result.modules?.length || 0
2100
- };
2101
- console.log(JSON.stringify(jsonResult, null, 2));
2102
- return jsonResult;
2103
- }
2104
- const files = result.files ?? [];
2105
- const modules = result.modules ?? [];
2106
- const sections = [];
2107
- if (files.length > 0) {
2108
- sections.push({
2109
- header: "Cache Files",
2110
- items: files.map((f) => `\u2713 ${f}`)
2111
- });
2112
- } else {
2113
- sections.push({
2114
- items: ["No cache files found"]
2115
- });
2116
- }
2117
- if (deep && modules.length > 0) {
2118
- sections.push({
2119
- header: "Node Modules",
2120
- items: [`Cleared ${modules.length} module(s) from cache`]
2121
- });
2122
- }
2123
- sections.push({
2124
- header: "Summary",
2125
- items: [`Removed ${files.length} file(s)${deep ? ` and ${modules.length} module(s)` : ""}`]
2126
- });
2127
- ctx.ui.success("", {
2128
- title: "\u{1F5D1}\uFE0F Cache Management",
2129
- sections
2130
- });
2131
- return {
2132
- ok: true,
2133
- action: "cache:clear",
2134
- files: result.files,
2135
- modules: result.modules,
2136
- count: result.files.length,
2137
- modulesCount: result.modules?.length || 0
2138
- };
2139
- }
2140
- });
2141
2775
  var docsGenerateCliReference = defineSystemCommand({
2142
2776
  name: "generate-cli-reference",
2143
2777
  description: "Generate CLI reference documentation from command registry",
@@ -2159,21 +2793,20 @@ var docsGenerateCliReference = defineSystemCommand({
2159
2793
  async handler(ctx, argv, flags) {
2160
2794
  const cwd = getContextCwd(ctx);
2161
2795
  const outputPath = flags.output || path.join(cwd, "CLI-REFERENCE.md");
2162
- const commands = registry.list();
2163
- const productGroups = registry.listProductGroups();
2796
+ const registeredCommands2 = registry.listCommands();
2164
2797
  ctx.platform?.logger?.info("Generating CLI reference", {
2165
- commands: commands.length,
2166
- products: productGroups.length,
2798
+ commands: registeredCommands2.length,
2167
2799
  output: outputPath
2168
2800
  });
2169
2801
  const groups = /* @__PURE__ */ new Map();
2170
- for (const cmd of commands) {
2171
- const group = cmd.category || "other";
2802
+ for (const cmd of registeredCommands2) {
2803
+ const group = cmd.manifest.group || "other";
2172
2804
  if (!groups.has(group)) {
2173
2805
  groups.set(group, []);
2174
2806
  }
2175
2807
  groups.get(group).push(cmd);
2176
2808
  }
2809
+ const commands = registeredCommands2;
2177
2810
  const lines = [];
2178
2811
  lines.push("# KB Labs CLI Reference\n");
2179
2812
  lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
@@ -2189,21 +2822,24 @@ var docsGenerateCliReference = defineSystemCommand({
2189
2822
  }
2190
2823
  lines.push("\n---\n");
2191
2824
  for (const groupName of sortedGroups) {
2192
- const cmds = groups.get(groupName).sort((a, b) => a.name.localeCompare(b.name));
2825
+ const cmds = groups.get(groupName).sort(
2826
+ (a, b) => a.manifest.segments.join(" ").localeCompare(b.manifest.segments.join(" "))
2827
+ );
2193
2828
  lines.push(`## ${groupName}
2194
2829
  `);
2195
2830
  for (const cmd of cmds) {
2196
- lines.push(`### \`kb ${cmd.name}\`
2831
+ const displayPath = cmd.manifest.segments.join(" ");
2832
+ lines.push(`### \`kb ${displayPath}\`
2197
2833
  `);
2198
- lines.push(`${cmd.describe || "No description"}
2834
+ lines.push(`${cmd.manifest.describe || "No description"}
2199
2835
  `);
2200
- if (cmd.longDescription) {
2201
- lines.push(`${cmd.longDescription}
2836
+ if (cmd.manifest.longDescription) {
2837
+ lines.push(`${cmd.manifest.longDescription}
2202
2838
  `);
2203
2839
  }
2204
- if (cmd.flags && cmd.flags.length > 0) {
2840
+ if (cmd.manifest.flags && cmd.manifest.flags.length > 0) {
2205
2841
  lines.push("**Flags:**\n");
2206
- for (const flag of cmd.flags) {
2842
+ for (const flag of cmd.manifest.flags) {
2207
2843
  const alias = flag.alias ? ` (-${flag.alias})` : "";
2208
2844
  const required = flag.required ? " **(required)**" : "";
2209
2845
  const defaultVal = flag.default !== void 0 ? ` (default: \`${flag.default}\`)` : "";
@@ -2212,16 +2848,16 @@ var docsGenerateCliReference = defineSystemCommand({
2212
2848
  }
2213
2849
  lines.push("");
2214
2850
  }
2215
- if (cmd.examples && cmd.examples.length > 0) {
2851
+ if (cmd.manifest.examples && cmd.manifest.examples.length > 0) {
2216
2852
  lines.push("**Examples:**\n");
2217
2853
  lines.push("```bash");
2218
- for (const example of cmd.examples) {
2854
+ for (const example of cmd.manifest.examples) {
2219
2855
  lines.push(example);
2220
2856
  }
2221
2857
  lines.push("```\n");
2222
2858
  }
2223
- if (cmd.aliases && cmd.aliases.length > 0) {
2224
- lines.push(`**Aliases:** ${cmd.aliases.map((a) => `\`${a}\``).join(", ")}
2859
+ if (cmd.manifest.aliases && cmd.manifest.aliases.length > 0) {
2860
+ lines.push(`**Aliases:** ${cmd.manifest.aliases.map((a) => `\`${a}\``).join(", ")}
2225
2861
  `);
2226
2862
  }
2227
2863
  lines.push("---\n");
@@ -3165,6 +3801,7 @@ var authLogin = defineSystemCommand({
3165
3801
  "client-secret": { type: "string", description: "Client Secret from service account registration" },
3166
3802
  json: { type: "boolean", description: "Output in JSON format" }
3167
3803
  },
3804
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3168
3805
  async handler(ctx, _argv, flags) {
3169
3806
  const gatewayUrl = flags["gateway-url"];
3170
3807
  const clientId = flags["client-id"];
@@ -3206,23 +3843,43 @@ var authLogin = defineSystemCommand({
3206
3843
  return { ok: false, error: msg, gatewayUrl, expiresIn: 0 };
3207
3844
  }
3208
3845
  const tokenData = await tokenResponse.json();
3846
+ let handle;
3847
+ let namespaceId;
3848
+ try {
3849
+ const meRes = await fetch(`${gatewayUrl}/auth/me`, {
3850
+ headers: { Authorization: `Bearer ${tokenData.accessToken}` }
3851
+ });
3852
+ if (meRes.ok) {
3853
+ const profile = await meRes.json();
3854
+ handle = profile.handle;
3855
+ namespaceId = profile.namespaceId;
3856
+ }
3857
+ } catch {
3858
+ }
3209
3859
  const credentialsManager = new CredentialsManager();
3210
3860
  const expiresAt = Date.now() + tokenData.expiresIn * 1e3;
3211
3861
  await credentialsManager.save({
3212
3862
  gatewayUrl,
3213
3863
  accessToken: tokenData.accessToken,
3214
3864
  refreshToken: tokenData.refreshToken,
3215
- expiresAt
3865
+ expiresAt,
3866
+ handle,
3867
+ namespaceId
3216
3868
  });
3217
3869
  if (flags.json) {
3218
3870
  ctx.ui?.json({
3219
3871
  ok: true,
3220
3872
  gatewayUrl,
3221
- expiresIn: tokenData.expiresIn
3873
+ expiresIn: tokenData.expiresIn,
3874
+ ...handle ? { handle } : {}
3222
3875
  });
3223
3876
  } else {
3224
3877
  ctx.ui?.write?.(`Authenticated with Gateway at ${gatewayUrl}
3225
3878
  `);
3879
+ if (handle) {
3880
+ ctx.ui?.write?.(`Handle: ${handle}
3881
+ `);
3882
+ }
3226
3883
  ctx.ui?.write?.(`Token expires in ${Math.floor(tokenData.expiresIn / 60)} minutes (auto-refresh enabled).
3227
3884
  `);
3228
3885
  }
@@ -3332,6 +3989,7 @@ var authCreateServiceAccount = defineSystemCommand({
3332
3989
  "namespace-id": { type: "string", description: "Namespace ID (1-64 chars)" },
3333
3990
  json: { type: "boolean", description: "Output in JSON format" }
3334
3991
  },
3992
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3335
3993
  async handler(ctx, _argv, flags) {
3336
3994
  const gatewayUrl = flags["gateway-url"];
3337
3995
  const name = flags.name;
@@ -3459,9 +4117,10 @@ var platformSyncCommand = defineSystemCommand({
3459
4117
  sync: result
3460
4118
  };
3461
4119
  },
4120
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3462
4121
  formatter(result, ctx, flags) {
3463
4122
  if (flags.json) {
3464
- console.log(JSON.stringify(result.sync ?? result, null, 2));
4123
+ ctx.ui?.json?.(result.sync ?? result);
3465
4124
  return;
3466
4125
  }
3467
4126
  const sync = result.sync;
@@ -3505,35 +4164,308 @@ var platformSyncCommand = defineSystemCommand({
3505
4164
  items: sync.missing.map((id) => `\u2022 ${id}`)
3506
4165
  });
3507
4166
  }
3508
- if (sync.mismatched.length) {
3509
- sections.push({
3510
- header: "Integrity mismatch",
3511
- items: sync.mismatched.map((id) => `\u2022 ${id}`)
3512
- });
4167
+ if (sync.mismatched.length) {
4168
+ sections.push({
4169
+ header: "Integrity mismatch",
4170
+ items: sync.mismatched.map((id) => `\u2022 ${id}`)
4171
+ });
4172
+ }
4173
+ if (sync.errors.length) {
4174
+ sections.push({
4175
+ header: "Errors",
4176
+ items: sync.errors.map((e) => `\u2022 ${e.packageId}: ${e.message}`)
4177
+ });
4178
+ }
4179
+ const nextSteps = [];
4180
+ if (!sync.ok) {
4181
+ if (sync.missing.length && sync.mode === "validate") {
4182
+ nextSteps.push("kb platform provision --mode reconcile Install missing packages");
4183
+ }
4184
+ if (sync.mismatched.length) {
4185
+ nextSteps.push("kb marketplace install <pkg> Re-install mismatched packages");
4186
+ }
4187
+ }
4188
+ if (nextSteps.length) {
4189
+ sections.push({ header: "Next Steps", items: nextSteps });
4190
+ }
4191
+ if (sync.ok) {
4192
+ ctx.ui.success("Platform Sync", { sections });
4193
+ } else {
4194
+ ctx.ui.error("Platform Sync", { sections });
4195
+ }
4196
+ }
4197
+ });
4198
+ var webhookProvision = defineSystemCommand({
4199
+ name: "provision",
4200
+ description: "Provision (or rotate) a webhook secret",
4201
+ longDescription: "Generates a new webhook secret for a plugin+event combination and registers it with the Gateway. On first call: creates the secret. On subsequent calls: rotates it (old secret valid for 24h). The secret is shown once \u2014 save it immediately.",
4202
+ category: "webhook",
4203
+ examples: [
4204
+ "kb webhook provision --plugin @kb-labs/rollout --event alert",
4205
+ "kb webhook provision --plugin @kb-labs/rollout --event alert --instance prod",
4206
+ "kb webhook provision --plugin @kb-labs/rollout --event alert --json"
4207
+ ],
4208
+ flags: {
4209
+ plugin: { type: "string", description: "Plugin ID (e.g. @kb-labs/rollout)" },
4210
+ event: { type: "string", description: "Webhook event name declared in the plugin manifest" },
4211
+ instance: { type: "string", description: "Instance ID for multi-instance webhooks (optional)" },
4212
+ json: { type: "boolean", description: "Output in JSON format" }
4213
+ },
4214
+ async handler(ctx, _argv, flags) {
4215
+ const pluginId = flags.plugin;
4216
+ const event = flags.event;
4217
+ if (!pluginId) {
4218
+ const msg = "--plugin is required";
4219
+ if (flags.json) {
4220
+ ctx.ui?.json({ ok: false, error: msg });
4221
+ } else {
4222
+ ctx.ui?.error?.(msg);
4223
+ }
4224
+ return { ok: false, error: msg };
4225
+ }
4226
+ if (!event) {
4227
+ const msg = "--event is required";
4228
+ if (flags.json) {
4229
+ ctx.ui?.json({ ok: false, error: msg });
4230
+ } else {
4231
+ ctx.ui?.error?.(msg);
4232
+ }
4233
+ return { ok: false, error: msg };
4234
+ }
4235
+ const credentialsManager = new CredentialsManager();
4236
+ const credentials = await credentialsManager.load();
4237
+ if (!credentials) {
4238
+ const msg = 'Not authenticated. Run "kb auth login" first.';
4239
+ if (flags.json) {
4240
+ ctx.ui?.json({ ok: false, error: msg });
4241
+ } else {
4242
+ ctx.ui?.error?.(msg);
4243
+ }
4244
+ return { ok: false, error: msg };
4245
+ }
4246
+ const url = `${credentials.gatewayUrl}/api/v1/webhooks/provision`;
4247
+ let response;
4248
+ try {
4249
+ response = await fetch(url, {
4250
+ method: "POST",
4251
+ headers: {
4252
+ "Content-Type": "application/json",
4253
+ Authorization: `Bearer ${credentials.accessToken}`
4254
+ },
4255
+ body: JSON.stringify({
4256
+ pluginId,
4257
+ event,
4258
+ ...flags.instance ? { instanceId: flags.instance } : {}
4259
+ })
4260
+ });
4261
+ } catch (err) {
4262
+ const msg = `Cannot reach Gateway at ${credentials.gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`;
4263
+ if (flags.json) {
4264
+ ctx.ui?.json({ ok: false, error: msg });
4265
+ } else {
4266
+ ctx.ui?.error?.(msg);
4267
+ }
4268
+ return { ok: false, error: msg };
4269
+ }
4270
+ if (!response.ok) {
4271
+ const body = await response.text().catch(() => "");
4272
+ let detail = "";
4273
+ try {
4274
+ detail = JSON.parse(body)?.message ?? body;
4275
+ } catch {
4276
+ detail = body;
4277
+ }
4278
+ const msg = `Provision failed (HTTP ${response.status})${detail ? ": " + detail : ""}`;
4279
+ if (flags.json) {
4280
+ ctx.ui?.json({ ok: false, error: msg });
4281
+ } else {
4282
+ ctx.ui?.error?.(msg);
4283
+ }
4284
+ return { ok: false, error: msg };
4285
+ }
4286
+ const data = await response.json();
4287
+ if (flags.json) {
4288
+ ctx.ui?.json({ ok: true, url: data.url, secret: data.secret, rotated: data.rotated });
4289
+ } else {
4290
+ ctx.ui?.write?.(data.rotated ? "\u27F3 Secret rotated (old secret valid for 24h grace period)\n" : "\u2713 Webhook provisioned\n");
4291
+ ctx.ui?.write?.(`URL: ${data.url}
4292
+ `);
4293
+ ctx.ui?.write?.(`Secret: ${data.secret}
4294
+ `);
4295
+ ctx.ui?.write?.("\n\u26A0 Save the secret now \u2014 it will not be shown again.\n");
4296
+ }
4297
+ return { ok: true, url: data.url, rotated: data.rotated };
4298
+ }
4299
+ });
4300
+ var webhookList = defineSystemCommand({
4301
+ name: "list",
4302
+ description: "List declared webhook endpoints",
4303
+ longDescription: "Lists all webhook endpoints declared in installed plugin manifests. Shows provisioned status (whether a secret has been set) for each endpoint.",
4304
+ category: "webhook",
4305
+ examples: [
4306
+ "kb webhook list",
4307
+ "kb webhook list --plugin @kb-labs/rollout",
4308
+ "kb webhook list --json"
4309
+ ],
4310
+ flags: {
4311
+ plugin: { type: "string", description: "Filter by plugin ID" },
4312
+ json: { type: "boolean", description: "Output in JSON format" }
4313
+ },
4314
+ async handler(ctx, _argv, flags) {
4315
+ const credentialsManager = new CredentialsManager();
4316
+ const credentials = await credentialsManager.load();
4317
+ if (!credentials) {
4318
+ const msg = 'Not authenticated. Run "kb auth login" first.';
4319
+ if (flags.json) {
4320
+ ctx.ui?.json({ ok: false, error: msg });
4321
+ } else {
4322
+ ctx.ui?.error?.(msg);
4323
+ }
4324
+ return { ok: false, error: msg };
4325
+ }
4326
+ const qs = flags.plugin ? `?pluginId=${encodeURIComponent(flags.plugin)}` : "";
4327
+ const url = `${credentials.gatewayUrl}/api/v1/webhooks${qs}`;
4328
+ let response;
4329
+ try {
4330
+ response = await fetch(url, {
4331
+ headers: { Authorization: `Bearer ${credentials.accessToken}` }
4332
+ });
4333
+ } catch (err) {
4334
+ const msg = `Cannot reach Gateway at ${credentials.gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`;
4335
+ if (flags.json) {
4336
+ ctx.ui?.json({ ok: false, error: msg });
4337
+ } else {
4338
+ ctx.ui?.error?.(msg);
4339
+ }
4340
+ return { ok: false, error: msg };
4341
+ }
4342
+ if (!response.ok) {
4343
+ const body = await response.text().catch(() => "");
4344
+ const msg = `List failed (HTTP ${response.status}): ${body}`;
4345
+ if (flags.json) {
4346
+ ctx.ui?.json({ ok: false, error: msg });
4347
+ } else {
4348
+ ctx.ui?.error?.(msg);
4349
+ }
4350
+ return { ok: false, error: msg };
4351
+ }
4352
+ const data = await response.json();
4353
+ const webhooks = data.webhooks ?? [];
4354
+ if (flags.json) {
4355
+ ctx.ui?.json({ ok: true, webhooks });
4356
+ } else {
4357
+ if (webhooks.length === 0) {
4358
+ ctx.ui?.write?.("No webhook endpoints found.\n");
4359
+ } else {
4360
+ const pluginWidth = Math.max(6, ...webhooks.map((w) => w.pluginId.length));
4361
+ const eventWidth = Math.max(5, ...webhooks.map((w) => w.event.length));
4362
+ ctx.ui?.write?.(
4363
+ `${"PLUGIN".padEnd(pluginWidth)} ${"EVENT".padEnd(eventWidth)} MULTI PROVISIONED
4364
+ `
4365
+ );
4366
+ ctx.ui?.write?.(`${"\u2500".repeat(pluginWidth)} ${"\u2500".repeat(eventWidth)} \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
4367
+ `);
4368
+ for (const w of webhooks) {
4369
+ ctx.ui?.write?.(
4370
+ `${w.pluginId.padEnd(pluginWidth)} ${w.event.padEnd(eventWidth)} ${w.multi ? "yes " : "no "} ${w.provisioned ? "yes" : "no"}
4371
+ `
4372
+ );
4373
+ }
4374
+ ctx.ui?.write?.(`
4375
+ ${webhooks.length} webhook(s)
4376
+ `);
4377
+ }
4378
+ }
4379
+ return { ok: true, webhooks };
4380
+ }
4381
+ });
4382
+ var webhookRevoke = defineSystemCommand({
4383
+ name: "revoke",
4384
+ description: "Revoke a provisioned webhook secret",
4385
+ longDescription: 'Deletes the webhook secret for a plugin+event combination. Any subsequent deliveries with the old secret are immediately rejected (no grace period). Use "kb webhook provision" to create a new secret afterwards.',
4386
+ category: "webhook",
4387
+ examples: [
4388
+ "kb webhook revoke --plugin @kb-labs/rollout --event alert",
4389
+ "kb webhook revoke --plugin @kb-labs/rollout --event alert --instance prod",
4390
+ "kb webhook revoke --plugin @kb-labs/rollout --event alert --json"
4391
+ ],
4392
+ flags: {
4393
+ plugin: { type: "string", description: "Plugin ID (e.g. @kb-labs/rollout)" },
4394
+ event: { type: "string", description: "Webhook event name" },
4395
+ instance: { type: "string", description: "Instance ID for multi-instance webhooks (optional)" },
4396
+ json: { type: "boolean", description: "Output in JSON format" }
4397
+ },
4398
+ async handler(ctx, _argv, flags) {
4399
+ const pluginId = flags.plugin;
4400
+ const event = flags.event;
4401
+ if (!pluginId) {
4402
+ const msg = "--plugin is required";
4403
+ if (flags.json) {
4404
+ ctx.ui?.json({ ok: false, error: msg });
4405
+ } else {
4406
+ ctx.ui?.error?.(msg);
4407
+ }
4408
+ return { ok: false, error: msg };
3513
4409
  }
3514
- if (sync.errors.length) {
3515
- sections.push({
3516
- header: "Errors",
3517
- items: sync.errors.map((e) => `\u2022 ${e.packageId}: ${e.message}`)
3518
- });
4410
+ if (!event) {
4411
+ const msg = "--event is required";
4412
+ if (flags.json) {
4413
+ ctx.ui?.json({ ok: false, error: msg });
4414
+ } else {
4415
+ ctx.ui?.error?.(msg);
4416
+ }
4417
+ return { ok: false, error: msg };
3519
4418
  }
3520
- const nextSteps = [];
3521
- if (!sync.ok) {
3522
- if (sync.missing.length && sync.mode === "validate") {
3523
- nextSteps.push("kb platform provision --mode reconcile Install missing packages");
4419
+ const credentialsManager = new CredentialsManager();
4420
+ const credentials = await credentialsManager.load();
4421
+ if (!credentials) {
4422
+ const msg = 'Not authenticated. Run "kb auth login" first.';
4423
+ if (flags.json) {
4424
+ ctx.ui?.json({ ok: false, error: msg });
4425
+ } else {
4426
+ ctx.ui?.error?.(msg);
3524
4427
  }
3525
- if (sync.mismatched.length) {
3526
- nextSteps.push("kb marketplace install <pkg> Re-install mismatched packages");
4428
+ return { ok: false, error: msg };
4429
+ }
4430
+ const segments = [
4431
+ encodeURIComponent(pluginId),
4432
+ encodeURIComponent(event),
4433
+ ...flags.instance ? [encodeURIComponent(flags.instance)] : []
4434
+ ].join("/");
4435
+ const url = `${credentials.gatewayUrl}/api/v1/webhooks/${segments}`;
4436
+ let response;
4437
+ try {
4438
+ response = await fetch(url, {
4439
+ method: "DELETE",
4440
+ headers: { Authorization: `Bearer ${credentials.accessToken}` }
4441
+ });
4442
+ } catch (err) {
4443
+ const msg = `Cannot reach Gateway at ${credentials.gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`;
4444
+ if (flags.json) {
4445
+ ctx.ui?.json({ ok: false, error: msg });
4446
+ } else {
4447
+ ctx.ui?.error?.(msg);
3527
4448
  }
4449
+ return { ok: false, error: msg };
3528
4450
  }
3529
- if (nextSteps.length) {
3530
- sections.push({ header: "Next Steps", items: nextSteps });
4451
+ if (!response.ok && response.status !== 404) {
4452
+ const body = await response.text().catch(() => "");
4453
+ const msg = `Revoke failed (HTTP ${response.status}): ${body}`;
4454
+ if (flags.json) {
4455
+ ctx.ui?.json({ ok: false, error: msg });
4456
+ } else {
4457
+ ctx.ui?.error?.(msg);
4458
+ }
4459
+ return { ok: false, error: msg };
3531
4460
  }
3532
- if (sync.ok) {
3533
- ctx.ui.success("Platform Sync", { sections });
4461
+ const label = flags.instance ? `${pluginId}/${event}/${flags.instance}` : `${pluginId}/${event}`;
4462
+ if (flags.json) {
4463
+ ctx.ui?.json({ ok: true, revoked: label });
3534
4464
  } else {
3535
- ctx.ui.error("Platform Sync", { sections });
4465
+ ctx.ui?.write?.(`\u2713 Webhook revoked: ${label}
4466
+ `);
3536
4467
  }
4468
+ return { ok: true };
3537
4469
  }
3538
4470
  });
3539
4471
 
@@ -3544,9 +4476,6 @@ var infoGroup = defineSystemCommandGroup("info", "System information commands",
3544
4476
  health,
3545
4477
  diag
3546
4478
  ]);
3547
- var marketplaceGroup = defineSystemCommandGroup("marketplace", "Marketplace management commands", [
3548
- pluginsCacheClear
3549
- ]);
3550
4479
  var docsGroup = defineSystemCommandGroup("docs", "Documentation generation commands", [
3551
4480
  docsGenerateCliReference
3552
4481
  ]);
@@ -3571,442 +4500,260 @@ var authGroup = defineSystemCommandGroup("auth", "Gateway authentication command
3571
4500
  var platformGroup = defineSystemCommandGroup("platform", "Platform lifecycle commands", [
3572
4501
  platformSyncCommand
3573
4502
  ]);
3574
- var req = createRequire(import.meta.url);
3575
- var __filename$1 = fileURLToPath(import.meta.url);
3576
- var __dirname$1 = path.dirname(__filename$1);
3577
- function resolveFromCwd(spec, cwd) {
3578
- const cliRoot = path.resolve(__dirname$1, "../../..");
3579
- let monorepoRoot = cwd;
3580
- while (monorepoRoot !== path.dirname(monorepoRoot)) {
3581
- if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
3582
- break;
3583
- }
3584
- monorepoRoot = path.dirname(monorepoRoot);
3585
- }
3586
- const pathsToTry = [
3587
- cwd,
3588
- // Current working directory (user's project)
3589
- cliRoot,
3590
- // CLI installation directory
3591
- monorepoRoot,
3592
- // Monorepo root (if found)
3593
- path.join(monorepoRoot, "../kb-labs-cli"),
3594
- // Explicit kb-labs-cli path
3595
- path.join(monorepoRoot, "../kb-labs-mind")
3596
- // Explicit kb-labs-mind path
3597
- ].filter(Boolean);
3598
- for (const searchPath of pathsToTry) {
3599
- try {
3600
- return req.resolve(spec, { paths: [searchPath] });
3601
- } catch (_e) {
3602
- }
4503
+ var webhookGroup = defineSystemCommandGroup("webhook", "Webhook management commands", [
4504
+ webhookProvision,
4505
+ webhookList,
4506
+ webhookRevoke
4507
+ ]);
4508
+ var MARKER = "# kb completion";
4509
+ var ALIAS_MARKER = "# kb alias";
4510
+ var STATIC_FILE = path.join(os.homedir(), ".config", "kb", "completion.zsh");
4511
+ function dynamicScript(shell) {
4512
+ switch (shell) {
4513
+ case "bash":
4514
+ return [
4515
+ "_kb_completion() {",
4516
+ " local words cur",
4517
+ ' cur="${COMP_WORDS[COMP_CWORD]}"',
4518
+ ' words="${COMP_WORDS[*]:1:$((COMP_CWORD - 1))}"',
4519
+ ' COMPREPLY=($(kb __complete $words "$cur" 2>/dev/null))',
4520
+ " return 0",
4521
+ "}",
4522
+ "complete -o nospace -F _kb_completion kb"
4523
+ ].join("\n");
4524
+ case "zsh":
4525
+ return [
4526
+ "#compdef kb",
4527
+ "_kb_completion() {",
4528
+ " local completions",
4529
+ " completions=($(kb __complete ${words[2,-1]} 2>/dev/null))",
4530
+ " compadd -a completions",
4531
+ "}",
4532
+ "compdef _kb_completion kb"
4533
+ ].join("\n");
4534
+ case "fish":
4535
+ return [
4536
+ "function __kb_complete",
4537
+ " set -l tokens (commandline -opc)",
4538
+ " kb __complete $tokens[2..] (commandline -ct) 2>/dev/null",
4539
+ "end",
4540
+ 'complete -c kb -f -a "(__kb_complete)"'
4541
+ ].join("\n");
4542
+ default:
4543
+ return null;
3603
4544
  }
3604
- throw new Error(`Cannot resolve ${spec} from any of: ${pathsToTry.join(", ")}`);
3605
4545
  }
3606
- function checkRequires(manifest, options = {}) {
3607
- const cwd = options.cwd ?? process.cwd();
3608
- if (!manifest.requires || manifest.requires.length === 0) {
3609
- return { available: true };
3610
- }
3611
- let isInMonorepo = false;
3612
- let monorepoRoot = cwd;
3613
- while (monorepoRoot !== path.dirname(monorepoRoot)) {
3614
- if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
3615
- isInMonorepo = true;
3616
- break;
3617
- }
3618
- monorepoRoot = path.dirname(monorepoRoot);
3619
- }
3620
- for (const dep of manifest.requires) {
3621
- try {
3622
- const resolved = resolveFromCwd(dep, cwd);
3623
- if (!resolved.includes("node_modules")) {
3624
- continue;
3625
- }
3626
- } catch (err) {
3627
- if (isInMonorepo) {
3628
- continue;
3629
- }
3630
- const errorMessage = (err instanceof Error ? err.message : "") || "";
3631
- const hasExportsError = errorMessage.includes("exports") || errorMessage.includes("main");
3632
- if (hasExportsError || errorMessage.includes("Cannot resolve")) {
3633
- const parts = dep.startsWith("@") ? dep.slice(1).split("/") : [dep];
3634
- if (parts.length === 0) {
3635
- continue;
3636
- }
3637
- const scope = parts.length >= 2 ? parts[0] : "";
3638
- const packageName = parts.length >= 2 ? parts[1] : parts[0];
3639
- if (!packageName) {
3640
- continue;
3641
- }
3642
- const cliRoot = path.resolve(__dirname$1, "../../..");
3643
- const packagePath = scope ? path.join(cliRoot, "node_modules", "@" + scope, packageName, "package.json") : path.join(cliRoot, "node_modules", packageName, "package.json");
3644
- if (existsSync(packagePath)) {
3645
- continue;
3646
- }
4546
+ function buildStaticZsh(tree) {
4547
+ const lines = ["#compdef kb", "_kb() {"];
4548
+ const l1 = [...tree.keys()];
4549
+ lines.push(` if [[ $CURRENT -eq 2 ]]; then`);
4550
+ lines.push(` compadd -- ${l1.join(" ")}`);
4551
+ lines.push(` return`);
4552
+ lines.push(` fi`);
4553
+ lines.push(` if [[ $CURRENT -eq 3 ]]; then`);
4554
+ lines.push(` case $words[2] in`);
4555
+ for (const [cmd, subMap] of tree) {
4556
+ const subs = [...subMap.keys()];
4557
+ if (subs.length > 0) {
4558
+ lines.push(` ${cmd}) compadd -- ${subs.join(" ")} ;;`);
4559
+ }
4560
+ }
4561
+ lines.push(` esac`);
4562
+ lines.push(` return`);
4563
+ lines.push(` fi`);
4564
+ lines.push(` if [[ $CURRENT -eq 4 ]]; then`);
4565
+ lines.push(` case "$words[2] $words[3]" in`);
4566
+ for (const [cmd, subMap] of tree) {
4567
+ for (const [sub, leaves] of subMap) {
4568
+ if (leaves.length > 0) {
4569
+ const bare = leaves.map((l) => l.split(" ").pop() ?? l);
4570
+ lines.push(` "${cmd} ${sub}") compadd -- ${bare.join(" ")} ;;`);
3647
4571
  }
3648
- const isKbLabsPackage = dep.startsWith("@kb-labs/");
3649
- const hint = isKbLabsPackage ? `Run: pnpm add ${dep}` : `Run: npm install ${dep}`;
3650
- return {
3651
- available: false,
3652
- reason: `Missing dependency: ${dep}`,
3653
- hint
3654
- };
3655
- }
3656
- }
3657
- return { available: true };
3658
- }
3659
- var ajv = new Ajv();
3660
- var flagSchema = {
3661
- type: "object",
3662
- required: ["name", "type"],
3663
- additionalProperties: false,
3664
- properties: {
3665
- name: { type: "string" },
3666
- type: { enum: ["string", "boolean", "number", "array"] },
3667
- alias: { type: "string", pattern: "^[a-z]$" },
3668
- // single letter
3669
- description: { type: "string" },
3670
- choices: { type: "array", items: { type: "string" } },
3671
- required: { type: "boolean" },
3672
- default: {}
3673
- // must match type
3674
- }
3675
- };
3676
- var manifestSchema = {
3677
- type: "object",
3678
- required: ["manifestVersion", "id", "group", "describe", "loader"],
3679
- additionalProperties: true,
3680
- properties: {
3681
- manifestVersion: { type: "string", enum: ["1.0"] },
3682
- id: { type: "string", pattern: "^[a-z0-9-]+(?::[a-z0-9-]+)*$" },
3683
- // Simple or namespaced (colon-separated)
3684
- aliases: { type: "array", items: { type: "string" } },
3685
- group: { type: "string" },
3686
- describe: { type: "string" },
3687
- longDescription: { type: "string" },
3688
- requires: { type: "array", items: { type: "string" } },
3689
- flags: {
3690
- type: "array",
3691
- items: flagSchema
3692
- },
3693
- examples: { type: "array", items: { type: "string" } }
3694
- }
3695
- };
3696
- var validateManifest = ajv.compile(manifestSchema);
3697
- var validateFlag = ajv.compile(flagSchema);
3698
- function validateFlagDef(flag, manifestId) {
3699
- if (!validateFlag(flag)) {
3700
- throw new Error(
3701
- `Invalid flag "${flag.name}" in ${manifestId}: ${ajv.errorsText(validateFlag.errors)}`
3702
- );
3703
- }
3704
- if (flag.choices && flag.type !== "string") {
3705
- throw new Error(`Flag "${flag.name}" in ${manifestId}: choices allowed only for string type`);
3706
- }
3707
- if (flag.default !== void 0) {
3708
- 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);
3709
- if (!typeMatches) {
3710
- throw new Error(
3711
- `Flag "${flag.name}" in ${manifestId}: default value type mismatch (expected ${flag.type})`
3712
- );
3713
4572
  }
3714
4573
  }
4574
+ lines.push(` esac`);
4575
+ lines.push(` return`);
4576
+ lines.push(` fi`);
4577
+ lines.push(`}`, `compdef _kb kb`);
4578
+ lines.push(``, `# pnpm kb \u2014 same completions, shifted by 1 position`);
4579
+ lines.push(`_kb_pnpm_wrap() {`);
4580
+ lines.push(` if [[ \${words[2]-} == kb ]]; then`);
4581
+ lines.push(` local CURRENT=$((CURRENT - 1))`);
4582
+ lines.push(` local -a words=("kb" "\${words[3,-1]}")`);
4583
+ lines.push(` [[ $CURRENT -lt 1 ]] && CURRENT=1`);
4584
+ lines.push(` _kb`);
4585
+ lines.push(` return 0`);
4586
+ lines.push(` fi`);
4587
+ lines.push(` (( $+functions[_pnpm_orig] )) && { _pnpm_orig; return; }`);
4588
+ lines.push(` return 1`);
4589
+ lines.push(`}`);
4590
+ lines.push(`if (( $+functions[_pnpm] )) && ! (( $+functions[_pnpm_orig] )); then`);
4591
+ lines.push(` functions[_pnpm_orig]=$functions[_pnpm]`);
4592
+ lines.push(` _pnpm() { _kb_pnpm_wrap; }`);
4593
+ lines.push(`else`);
4594
+ lines.push(` compdef _kb_pnpm_wrap pnpm`);
4595
+ lines.push(`fi`);
4596
+ return lines.join("\n");
3715
4597
  }
3716
- function validateManifestStructure(manifest) {
3717
- if (!validateManifest(manifest)) {
3718
- throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
3719
- }
3720
- if (manifest.manifestVersion !== "1.0") {
3721
- throw new Error(
3722
- `Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.id} (expected "1.0")`
3723
- );
3724
- }
3725
- if (manifest.flags && Array.isArray(manifest.flags) && manifest.id) {
3726
- for (const flag of manifest.flags) {
3727
- if (flag && typeof flag === "object" && flag.name) {
3728
- validateFlagDef(flag, manifest.id);
3729
- }
3730
- }
3731
- }
4598
+ async function collectTree(registry2) {
4599
+ const tree = /* @__PURE__ */ new Map();
4600
+ const l1 = registry2.complete([""]);
4601
+ for (const cmd of l1) {
4602
+ const subMap = /* @__PURE__ */ new Map();
4603
+ const l2 = registry2.complete([cmd, ""]);
4604
+ for (const full2 of l2) {
4605
+ const sub = full2.split(" ").pop() ?? full2;
4606
+ const l3 = registry2.complete([cmd, sub, ""]);
4607
+ subMap.set(sub, l3);
4608
+ }
4609
+ tree.set(cmd, subMap);
4610
+ }
4611
+ return tree;
3732
4612
  }
3733
- var SOURCE_PRIORITY = {
3734
- builtin: 4,
3735
- workspace: 3,
3736
- linked: 2,
3737
- node_modules: 1
3738
- };
3739
- function normalizeCommandId(id, _namespace) {
3740
- return id;
4613
+ function detectShell() {
4614
+ const name = path.basename(process.env["SHELL"] ?? "");
4615
+ return ["bash", "zsh", "fish"].includes(name) ? name : "zsh";
3741
4616
  }
3742
- function generateWhitespaceAliases(id) {
3743
- if (!id.includes(":")) {
3744
- return [];
4617
+ function profilePath(shell) {
4618
+ const home = os.homedir();
4619
+ if (shell === "bash") {
4620
+ return path.join(home, ".bashrc");
4621
+ }
4622
+ if (shell === "fish") {
4623
+ return path.join(home, ".config", "fish", "config.fish");
3745
4624
  }
3746
- return [id.replace(":", " ")];
4625
+ return path.join(home, ".zshrc");
3747
4626
  }
3748
- function normalizeAliases(manifest, logger) {
3749
- const log3 = logger ?? platform.logger;
3750
- const aliases = /* @__PURE__ */ new Set();
3751
- manifest.namespace || manifest.group;
3752
- if (manifest.aliases) {
3753
- for (const alias of manifest.aliases) {
3754
- if (!/^[a-z0-9-:]+$/i.test(alias)) {
3755
- log3.warn(`Invalid alias "${alias}" in ${manifest.id}: aliases must be alphanumeric with hyphens or colons`);
3756
- continue;
3757
- }
3758
- aliases.add(alias);
3759
- }
4627
+ function profileLine(shell) {
4628
+ if (shell === "zsh") {
4629
+ return `${MARKER}
4630
+ [ -f "${STATIC_FILE}" ] && source "${STATIC_FILE}"`;
3760
4631
  }
3761
- const whitespaceAliases = generateWhitespaceAliases(manifest.id);
3762
- for (const alias of whitespaceAliases) {
3763
- aliases.add(alias);
4632
+ if (shell === "fish") {
4633
+ return `${MARKER}
4634
+ test -f "${STATIC_FILE}" && source "${STATIC_FILE}"`;
3764
4635
  }
3765
- return Array.from(aliases);
4636
+ return `${MARKER}
4637
+ command -v kb &>/dev/null && eval "$(kb completion bash 2>/dev/null)" || true`;
3766
4638
  }
3767
- function checkNamespaceCollision(manifest, existing, namespace) {
3768
- const existingGroup = existing.manifest.group || "";
3769
- const currentGroup = manifest.group || "";
3770
- if (existingGroup === currentGroup && existingGroup === namespace && existing.manifest.id === manifest.id) {
3771
- throw new Error(
3772
- `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).`
3773
- );
3774
- }
4639
+ function aliasLine(shell) {
4640
+ if (shell === "fish") {
4641
+ return [
4642
+ ALIAS_MARKER,
4643
+ `alias kb='pnpm --silent kb'`,
4644
+ `function pnpm`,
4645
+ ` if test "$argv[1]" = "kb"`,
4646
+ ` command pnpm --silent $argv`,
4647
+ ` else`,
4648
+ ` command pnpm $argv`,
4649
+ ` end`,
4650
+ `end`
4651
+ ].join("\n");
4652
+ }
4653
+ return [
4654
+ ALIAS_MARKER,
4655
+ `alias kb='pnpm --silent kb'`,
4656
+ `pnpm() { if [[ "$1" == "kb" ]]; then command pnpm --silent "$@"; else command pnpm "$@"; fi }`
4657
+ ].join("\n");
3775
4658
  }
3776
- function getSourcePriority(source) {
3777
- return SOURCE_PRIORITY[source] || 0;
4659
+ function commandsHash(registry2) {
4660
+ const keys = registry2.complete([""]).sort().join(",");
4661
+ return createHash("sha256").update(keys).digest("hex").slice(0, 12);
3778
4662
  }
3779
- function checkCollision(manifest, existing, currentSource, namespace) {
3780
- const existingPriority = getSourcePriority(existing.source);
3781
- const currentPriority = getSourcePriority(currentSource);
3782
- checkNamespaceCollision(manifest, existing, namespace);
3783
- if (currentSource === "workspace" && existing.source === "workspace") {
3784
- throw new Error(
3785
- `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.`
3786
- );
3787
- }
3788
- if (currentPriority > existingPriority) {
3789
- return { shouldShadow: true };
3790
- } else if (currentPriority < existingPriority) {
3791
- return { shouldShadow: false };
3792
- }
3793
- return { shouldShadow: false };
4663
+ function hashFromFile(content) {
4664
+ const m = content.match(/^# hash: ([a-f0-9]+)/m);
4665
+ return m?.[1] ?? null;
3794
4666
  }
3795
- function preflightManifests(discoveryResults, logger) {
3796
- const log3 = logger ?? platform.logger;
3797
- const logLevel = getLogLevel();
3798
- const valid = [];
3799
- const skipped = [];
3800
- for (const result of discoveryResults) {
3801
- const allowed = [];
3802
- for (const manifest of result.manifests) {
3803
- try {
3804
- validateManifestStructure(manifest);
3805
- allowed.push(manifest);
3806
- } catch (error) {
3807
- const reason = error instanceof Error ? error.message : "Validation failed";
3808
- const manifestId = manifest?.id || manifest?.group || result.packageName || "unknown";
3809
- skipped.push({
3810
- id: manifestId,
3811
- source: result.source,
3812
- reason
3813
- });
3814
- log3.warn(`Preflight skipped manifest ${manifestId}: ${reason}`);
3815
- if (logLevel === "debug") {
3816
- process.stderr.write(`[debug][preflight] skipped ${manifestId} (${result.source}): ${reason}
3817
- `);
3818
- }
3819
- }
3820
- }
3821
- if (allowed.length > 0) {
3822
- valid.push({
3823
- ...result,
3824
- manifests: allowed
3825
- });
3826
- }
4667
+ async function autoUpdateCompletion(registry2) {
4668
+ try {
4669
+ await promises.access(STATIC_FILE);
4670
+ } catch {
4671
+ return;
3827
4672
  }
3828
- return { valid, skipped };
3829
- }
3830
- async function registerManifests(discoveryResults, registry2, options = {}) {
3831
- const log3 = options.logger ?? platform.logger;
3832
- const registered = [];
3833
- const skipped = [];
3834
- const globalIds = /* @__PURE__ */ new Map();
3835
- const globalAliases = /* @__PURE__ */ new Map();
3836
- const logLevel = getLogLevel();
3837
- let collisions = 0;
3838
- let errors = 0;
3839
- const sorted = [...discoveryResults].sort((a, b) => {
3840
- const priorityA = getSourcePriority(a.source);
3841
- const priorityB = getSourcePriority(b.source);
3842
- return priorityB - priorityA;
3843
- });
3844
- for (const result of sorted) {
3845
- for (const manifest of result.manifests) {
3846
- const manifestId = manifest.id || manifest.group || "unknown";
3847
- const namespace = manifest.namespace || manifest.group;
3848
- try {
3849
- try {
3850
- validateManifestStructure(manifest);
3851
- } catch (err) {
3852
- throw new Error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
3853
- }
3854
- const normalizedId = normalizeCommandId(manifest.id, namespace);
3855
- if (normalizedId !== manifest.id) {
3856
- log3.warn(`Command ID "${manifest.id}" normalized to "${normalizedId}"`);
3857
- manifest.id = normalizedId;
3858
- }
3859
- const normalizedAliases = normalizeAliases(manifest, log3);
3860
- if (normalizedAliases.length > 0) {
3861
- manifest.aliases = normalizedAliases;
4673
+ const current = commandsHash(registry2);
4674
+ let existing = "";
4675
+ try {
4676
+ existing = await promises.readFile(STATIC_FILE, "utf8");
4677
+ } catch {
4678
+ return;
4679
+ }
4680
+ if (hashFromFile(existing) === current) {
4681
+ return;
4682
+ }
4683
+ const tree = await collectTree(registry2);
4684
+ const script = `# hash: ${current}
4685
+ ` + buildStaticZsh(tree);
4686
+ await promises.writeFile(STATIC_FILE, script, "utf8");
4687
+ }
4688
+ function createCompletionCommand(registry2) {
4689
+ return defineSystemCommand({
4690
+ name: "completion",
4691
+ description: "Generate or install shell completion",
4692
+ longDescription: "Print a shell completion script or bake a static one into your shell profile.\n\nUsage:\n kb completion install # auto-detect shell, write static file, add to profile\n kb completion zsh # print dynamic zsh script\n kb completion bash # print dynamic bash script\n kb completion fish # print dynamic fish script",
4693
+ flags: {},
4694
+ async handler(_ctx, argv) {
4695
+ const arg = argv[0];
4696
+ if (!arg || arg === "install") {
4697
+ const shell = argv[1] ?? detectShell();
4698
+ if (shell === "zsh") {
4699
+ const tree = await collectTree(registry2);
4700
+ const hash = commandsHash(registry2);
4701
+ const script2 = `# hash: ${hash}
4702
+ ` + buildStaticZsh(tree);
4703
+ await promises.mkdir(path.dirname(STATIC_FILE), { recursive: true });
4704
+ await promises.writeFile(STATIC_FILE, script2, "utf8");
3862
4705
  }
3863
- const availability = checkRequires(manifest, {
3864
- cwd: options.cwd ?? result.pkgRoot
3865
- });
3866
- const cmd = {
3867
- manifest,
3868
- v3Manifest: manifest.manifestV2,
3869
- // Extract V3 manifest from legacy field
3870
- available: availability.available,
3871
- unavailableReason: availability.available ? void 0 : availability.reason,
3872
- hint: availability.available ? void 0 : availability.hint,
3873
- source: result.source,
3874
- shadowed: false,
3875
- pkgRoot: result.pkgRoot,
3876
- packageName: result.packageName
3877
- };
4706
+ const rcPath = profilePath(shell);
4707
+ let existing = "";
3878
4708
  try {
3879
- const manifestModule = await import(result.manifestPath);
3880
- if (manifestModule.init && typeof manifestModule.init === "function") {
3881
- await manifestModule.init({
3882
- cwd: result.pkgRoot,
3883
- package: result.packageName,
3884
- manifest: cmd.manifest
3885
- });
3886
- }
3887
- if (manifestModule.register && typeof manifestModule.register === "function") {
3888
- await manifestModule.register({
3889
- registry: registry2,
3890
- command: cmd,
3891
- cwd: result.pkgRoot,
3892
- package: result.packageName
3893
- });
3894
- }
3895
- if (manifestModule.dispose && typeof manifestModule.dispose === "function") {
3896
- cmd._disposeHook = manifestModule.dispose;
3897
- }
3898
- } catch (hookError) {
3899
- const hookMsg = hookError instanceof Error ? hookError.message : String(hookError);
3900
- log3.debug(`Lifecycle hooks unavailable for ${manifest.id}: ${hookMsg}`);
3901
- }
3902
- const canonicalKey = manifest.group ? `${manifest.group}:${manifest.id}` : manifest.id;
3903
- const existing = globalIds.get(canonicalKey);
3904
- if (existing) {
3905
- const collision = checkCollision(manifest, existing, result.source, namespace);
3906
- if (collision.shouldShadow) {
3907
- existing.shadowed = true;
3908
- globalIds.set(canonicalKey, cmd);
3909
- if (logLevel === "info" || logLevel === "debug") {
3910
- log3.info(`${canonicalKey} from ${result.source} shadows ${existing.source} version`);
3911
- }
3912
- } else {
3913
- cmd.shadowed = true;
3914
- if (logLevel === "info" || logLevel === "debug") {
3915
- log3.info(`${canonicalKey} from ${result.source} shadowed by ${existing.source} version`);
3916
- }
3917
- }
3918
- } else {
3919
- globalIds.set(canonicalKey, cmd);
4709
+ existing = await promises.readFile(rcPath, "utf8");
4710
+ } catch {
3920
4711
  }
3921
- const aliasesToCheck = manifest.aliases || [];
3922
- for (const alias of aliasesToCheck) {
3923
- const existingAlias = globalAliases.get(alias);
3924
- if (existingAlias) {
3925
- if (existingAlias.manifest.id === manifest.id) {
3926
- continue;
3927
- }
3928
- const existingPriority = getSourcePriority(existingAlias.source);
3929
- const currentPriority = getSourcePriority(result.source);
3930
- if (currentPriority > existingPriority) {
3931
- existingAlias.shadowed = true;
3932
- globalAliases.set(alias, cmd);
3933
- if (logLevel === "info" || logLevel === "debug") {
3934
- log3.info(`Alias "${alias}" from ${result.source} shadows ${existingAlias.source} version`);
3935
- }
3936
- } else if (currentPriority < existingPriority) {
3937
- if (logLevel === "info" || logLevel === "debug") {
3938
- log3.info(`Alias "${alias}" from ${result.source} shadowed by ${existingAlias.source} version`);
3939
- }
3940
- continue;
3941
- } else {
3942
- collisions++;
3943
- throw new Error(
3944
- `Alias collision: "${alias}" used by both ${manifest.id} and ${existingAlias.manifest.id}. Rename one alias or use a different namespace.`
3945
- );
3946
- }
3947
- }
3948
- if (globalIds.has(alias)) {
3949
- const conflictingCmd = globalIds.get(alias);
3950
- const existingPriority = getSourcePriority(conflictingCmd.source);
3951
- const currentPriority = getSourcePriority(result.source);
3952
- if (currentPriority > existingPriority) {
3953
- log3.warn(`Alias "${alias}" conflicts with command ID "${alias}". Alias will shadow command.`);
3954
- globalAliases.set(alias, cmd);
3955
- } else {
3956
- throw new Error(
3957
- `Alias "${alias}" conflicts with existing command ID "${alias}". Rename the alias or use a different name.`
3958
- );
3959
- }
3960
- } else {
3961
- globalAliases.set(alias, cmd);
3962
- }
4712
+ const alreadyInstalled = existing.includes(MARKER);
4713
+ if (!alreadyInstalled) {
4714
+ await promises.appendFile(rcPath, `
4715
+ ${profileLine(shell)}
4716
+ `);
3963
4717
  }
3964
- if (!cmd.shadowed) {
3965
- registry2.registerManifest(cmd);
4718
+ const aliasAlreadyInstalled = existing.includes(ALIAS_MARKER);
4719
+ if (!aliasAlreadyInstalled) {
4720
+ await promises.appendFile(rcPath, `
4721
+ ${aliasLine(shell)}
4722
+ `);
3966
4723
  }
3967
- registered.push(cmd);
3968
- } catch (error) {
3969
- errors++;
3970
- const reason = error instanceof Error ? error.message : String(error);
3971
- skipped.push({
3972
- id: manifestId,
3973
- source: result.source,
3974
- reason
3975
- });
3976
- log3.error(`Skipped manifest ${manifestId} (${result.source}): ${reason}`);
3977
- continue;
4724
+ return { ok: true, status: "success", shell, profilePath: rcPath, alreadyInstalled, aliasInstalled: !aliasAlreadyInstalled };
3978
4725
  }
4726
+ const script = dynamicScript(arg);
4727
+ if (!script) {
4728
+ throw new Error(`Unsupported shell: ${arg}. Supported: bash, zsh, fish`);
4729
+ }
4730
+ return { ok: true, status: "success", script, shell: arg };
4731
+ },
4732
+ formatter(result) {
4733
+ if ("script" in result) {
4734
+ process.stdout.write(result.script + "\n");
4735
+ return;
4736
+ }
4737
+ const r = result;
4738
+ if (r.shell === "zsh") {
4739
+ process.stdout.write(`\u2713 Static completions \u2192 ${STATIC_FILE}
4740
+ `);
4741
+ }
4742
+ if (r.alreadyInstalled) {
4743
+ process.stdout.write(`Profile already configured: ${r.profilePath}
4744
+ `);
4745
+ } else {
4746
+ process.stdout.write(`\u2713 Added to ${r.profilePath}
4747
+ `);
4748
+ }
4749
+ if (r.aliasInstalled) {
4750
+ process.stdout.write(`\u2713 Alias kb='pnpm --silent kb' added
4751
+ `);
4752
+ }
4753
+ process.stdout.write(`Run: source ${r.profilePath}
4754
+ `);
3979
4755
  }
3980
- }
3981
- if (skipped.length > 0) {
3982
- log3.warn(`Skipped ${skipped.length} manifest(s) during registration`);
3983
- for (const skip of skipped) {
3984
- log3.warn(` \u2022 ${skip.id} [${skip.source}] \u2192 ${skip.reason}`);
3985
- }
3986
- }
3987
- return {
3988
- registered,
3989
- skipped,
3990
- collisions,
3991
- errors
3992
- };
3993
- }
3994
- async function disposeAllPlugins(registry2, logger) {
3995
- const log3 = logger ?? platform.logger;
3996
- const manifests = registry2.listManifests();
3997
- const disposePromises = [];
3998
- for (const cmd of manifests) {
3999
- const disposeHook = cmd._disposeHook;
4000
- if (disposeHook && typeof disposeHook === "function") {
4001
- disposePromises.push(
4002
- Promise.resolve(disposeHook()).catch((err) => {
4003
- const errMsg = err instanceof Error ? err.message : String(err);
4004
- log3.warn(`Dispose hook failed for ${cmd.manifest.id}: ${errMsg}`);
4005
- })
4006
- );
4007
- }
4008
- }
4009
- await Promise.allSettled(disposePromises);
4756
+ });
4010
4757
  }
4011
4758
  var log2 = platform.logger.child({ module: "cli:shutdown" });
4012
4759
  var hooks = /* @__PURE__ */ new Set();
@@ -4051,15 +4798,18 @@ async function registerBuiltinCommands(input = {}) {
4051
4798
  return;
4052
4799
  }
4053
4800
  _registered = true;
4801
+ registry.setLogger(log3);
4054
4802
  registry.markPartial(true);
4055
4803
  registeredCommands.length = 0;
4056
4804
  registry.registerGroup(infoGroup);
4057
- registry.registerGroup(marketplaceGroup);
4058
4805
  registry.registerGroup(registryGroup);
4059
4806
  registry.registerGroup(docsGroup);
4060
4807
  registry.registerGroup(logsGroup);
4061
4808
  registry.registerGroup(authGroup);
4062
4809
  registry.registerGroup(platformGroup);
4810
+ registry.registerGroup(webhookGroup);
4811
+ registry.register(createCompletionCommand(registry));
4812
+ registry.register(diag);
4063
4813
  try {
4064
4814
  const cwd = getContextCwd({ cwd: input.cwd });
4065
4815
  const env = input.env ?? process.env;
@@ -4111,402 +4861,217 @@ async function registerBuiltinCommands(input = {}) {
4111
4861
  _registered = false;
4112
4862
  return;
4113
4863
  }
4864
+ await autoUpdateCompletion(registry).catch(() => {
4865
+ });
4114
4866
  registerShutdownHook(async () => {
4115
4867
  await disposeAllPlugins(registry, log3);
4116
4868
  });
4117
4869
  }
4118
- function renderGroupHelp(group) {
4870
+ function renderGroupHelp(opts) {
4119
4871
  const tracker = new TimingTracker();
4120
- const sortedCommands = [...group.commands].sort(
4121
- (a, b) => a.name.localeCompare(b.name)
4122
- );
4872
+ const groupName = opts.segments.join(" ");
4123
4873
  const sections = [];
4124
- sections.push({
4125
- items: [
4126
- `${safeColors.bold("Description")}: ${group.describe}`,
4127
- `${safeColors.bold("Commands")}: ${sortedCommands.length}`
4128
- ]
4129
- });
4130
- const commandDisplayNames = sortedCommands.map((cmd) => {
4131
- return cmd.name.replace(/:/g, " ");
4132
- });
4133
- const maxNameLength = Math.max(...commandDisplayNames.map((name) => name.length));
4134
- const commandItems = [];
4135
- for (let i = 0; i < sortedCommands.length; i++) {
4136
- const cmd = sortedCommands[i];
4137
- const displayName = commandDisplayNames[i];
4138
- if (!cmd || !displayName) {
4139
- continue;
4140
- }
4141
- const paddedName = displayName.padEnd(maxNameLength);
4142
- const description = cmd.describe || "No description";
4143
- commandItems.push(`${safeColors.primary(paddedName)} ${safeColors.muted(description)}`);
4144
- if (cmd.examples && cmd.examples.length > 0) {
4145
- for (const example of cmd.examples.slice(0, 2)) {
4146
- commandItems.push(` ${safeColors.muted(example)}`);
4147
- }
4874
+ if (opts.commands && opts.commands.length > 0) {
4875
+ const available = opts.commands.filter((c) => c.available && !c.shadowed);
4876
+ const unavailable = opts.commands.filter((c) => !c.available || c.shadowed);
4877
+ const maxLength = Math.max(
4878
+ ...opts.commands.map((c) => c.manifest.segments.join(" ").length),
4879
+ 20
4880
+ );
4881
+ if (available.length > 0) {
4882
+ const items = available.sort((a, b) => a.manifest.segments.join(" ").localeCompare(b.manifest.segments.join(" "))).map((cmd) => {
4883
+ const displayPath = cmd.manifest.segments.join(" ");
4884
+ return `${safeColors.primary(displayPath.padEnd(maxLength))} ${safeColors.muted(cmd.manifest.describe)}`;
4885
+ });
4886
+ sections.push({ header: "Commands", items });
4887
+ }
4888
+ if (unavailable.length > 0) {
4889
+ const items = unavailable.map((cmd) => {
4890
+ const displayPath = cmd.manifest.segments.join(" ");
4891
+ let line = `${safeColors.muted(displayPath.padEnd(maxLength))} ${safeColors.muted(cmd.manifest.describe)}`;
4892
+ if (cmd.unavailableReason) {
4893
+ line += `
4894
+ ${safeColors.muted(`Reason: ${cmd.unavailableReason}`)}`;
4895
+ }
4896
+ return line;
4897
+ });
4898
+ sections.push({ header: "Unavailable", items });
4148
4899
  }
4900
+ } else {
4901
+ const sorted = [...opts.childKeys].sort();
4902
+ sections.push({
4903
+ header: "Available",
4904
+ items: sorted.map((k) => safeColors.primary(` ${k}`))
4905
+ });
4149
4906
  }
4150
4907
  sections.push({
4151
- header: "Available commands",
4152
- items: commandItems
4153
- });
4154
- sections.push({
4155
- header: "Next Steps",
4156
- items: [
4157
- `kb ${group.name} <command> --help ${safeColors.muted("Get help for a specific command")}`
4158
- ]
4908
+ items: [safeColors.muted(`kb ${groupName} <command> --help`)]
4159
4909
  });
4160
4910
  return sideBorderBox({
4161
- title: `\u{1F4E6} ${group.name}`,
4911
+ title: groupName,
4162
4912
  sections,
4163
4913
  status: "success",
4164
4914
  timing: tracker.total()
4165
4915
  });
4166
4916
  }
4167
- function collectManifestVersions(commands) {
4168
- const versions = /* @__PURE__ */ new Set();
4169
- for (const cmd of commands) {
4170
- const schema = cmd.manifest.manifestV2?.schema;
4171
- if (typeof schema === "string") {
4172
- const tail = schema.split("/").pop() ?? schema;
4173
- const match = tail.match(/\d+/);
4174
- versions.add(match ? `v${match[0]}` : tail);
4175
- } else {
4176
- versions.add("v2");
4177
- }
4917
+ function truncate(s, max) {
4918
+ if (s.length <= max) {
4919
+ return s;
4178
4920
  }
4179
- return Array.from(versions).sort();
4921
+ return s.slice(0, max - 1) + "\u2026";
4180
4922
  }
4181
- function renderGlobalHelp(groups, standalone) {
4182
- const lines = [];
4183
- lines.push(
4184
- colors.cyan(colors.bold("KB Labs CLI")) + " - Project management and automation tool"
4185
- );
4186
- lines.push("");
4187
- lines.push(colors.bold("Usage:") + " kb [command] [options]");
4188
- lines.push("");
4189
- if (groups.length > 0) {
4190
- lines.push(colors.bold("Product Commands:"));
4191
- lines.push("");
4192
- for (const group of groups.sort((a, b) => a.name.localeCompare(b.name))) {
4193
- lines.push(
4194
- ` ${colors.cyan(group.name.padEnd(12))} ${colors.dim(
4195
- group.describe ?? ""
4196
- )}`
4197
- );
4198
- }
4199
- lines.push("");
4200
- }
4201
- if (standalone.length > 0) {
4202
- lines.push(colors.bold("System Commands:"));
4203
- lines.push("");
4204
- for (const cmd of standalone.sort((a, b) => a.name.localeCompare(b.name))) {
4205
- lines.push(
4206
- ` ${colors.cyan(cmd.name.padEnd(12))} ${colors.dim(cmd.describe)}`
4207
- );
4923
+ function renderGlobalHelp(registry2) {
4924
+ const cols = typeof process !== "undefined" && process.stdout?.columns || 80;
4925
+ const sections = [];
4926
+ const systemEntries = registry2.listSystemTopLevel().sort((a, b) => a.name.localeCompare(b.name));
4927
+ if (systemEntries.length > 0) {
4928
+ const nameLen = Math.max(...systemEntries.map((e) => e.name.length), 8);
4929
+ const items = systemEntries.map(
4930
+ ({ name, describe }) => `${colors.cyan(name.padEnd(nameLen))} ${colors.dim(describe)}`
4931
+ );
4932
+ sections.push({ header: "Commands", items });
4933
+ }
4934
+ const allCommands = registry2.listCommands();
4935
+ const groupNames = [...new Set(allCommands.map((c) => c.manifest.group))].sort();
4936
+ if (groupNames.length > 0) {
4937
+ const nameLen = Math.max(...groupNames.map((g) => g.length), 8);
4938
+ const entries = groupNames.map((name) => ({
4939
+ name,
4940
+ describe: registry2.getPluginGroupDescribe([name]) ?? ""
4941
+ }));
4942
+ const GAP = 4;
4943
+ const minDescLen = 24;
4944
+ const twoColMinWidth = (nameLen + 2 + minDescLen) * 2 + GAP + 4;
4945
+ const useTwoCols = cols >= twoColMinWidth;
4946
+ let items;
4947
+ if (useTwoCols) {
4948
+ const descMax = Math.max(minDescLen, Math.floor((cols - 4 - (nameLen + 2) * 2 - GAP) / 2));
4949
+ const half = Math.ceil(entries.length / 2);
4950
+ const left = entries.slice(0, half);
4951
+ const right = entries.slice(half);
4952
+ items = left.map((l, i) => {
4953
+ const r = right[i];
4954
+ const lName = colors.cyan(l.name.padEnd(nameLen));
4955
+ const lDesc = l.describe ? colors.dim(truncate(l.describe, descMax).padEnd(descMax)) : " ".repeat(descMax);
4956
+ if (!r) {
4957
+ return `${lName} ${lDesc.trimEnd()}`;
4958
+ }
4959
+ const rName = colors.cyan(r.name.padEnd(nameLen));
4960
+ const rDesc = r.describe ? colors.dim(truncate(r.describe, descMax)) : "";
4961
+ return `${lName} ${lDesc} ${" ".repeat(GAP - 2)}${rName} ${rDesc}`;
4962
+ });
4963
+ } else {
4964
+ const descMax = Math.max(24, cols - 4 - nameLen - 2);
4965
+ items = entries.map(({ name, describe }) => {
4966
+ const n = colors.cyan(name.padEnd(nameLen));
4967
+ return describe ? `${n} ${colors.dim(truncate(describe, descMax))}` : n;
4968
+ });
4208
4969
  }
4209
- lines.push("");
4970
+ sections.push({ header: "Plugins", items });
4210
4971
  }
4211
- lines.push(colors.bold("Global Options:"));
4212
- lines.push("");
4213
- lines.push(
4214
- ` ${colors.cyan("--help".padEnd(12))} ${colors.dim("Show help information")}`
4215
- );
4216
- lines.push(
4217
- ` ${colors.cyan("--version".padEnd(12))} ${colors.dim("Show CLI version")}`
4218
- );
4219
- lines.push(
4220
- ` ${colors.cyan("--json".padEnd(12))} ${colors.dim("Output in JSON format")}`
4221
- );
4222
- lines.push(
4223
- ` ${colors.cyan("--quiet".padEnd(12))} ${colors.dim("Suppress detailed output")}`
4224
- );
4225
- lines.push("");
4226
- lines.push(
4227
- colors.dim(
4228
- "Use 'kb <group> --help' to see commands for a specific product."
4229
- )
4230
- );
4231
- return lines.join("\n");
4972
+ sections.push({ items: [`kb ${colors.dim("<command> [subcommand] [flags]")} ${colors.dim("\xB7")} ${colors.dim("kb <command> --help for details")}`] });
4973
+ return sideBorderBox({ title: "KB Labs CLI", sections, status: "info" });
4232
4974
  }
4233
- function renderGlobalHelpNew(registry2) {
4234
- const products = registry2.listProductGroups();
4235
- const systemGroups = registry2.listGroups?.() || [];
4236
- const commandsInGroups = /* @__PURE__ */ new Set();
4237
- for (const group of systemGroups) {
4238
- for (const cmd of group.commands) {
4239
- commandsInGroups.add(cmd.name);
4240
- for (const alias of cmd.aliases || []) {
4241
- commandsInGroups.add(alias);
4242
- }
4243
- }
4244
- }
4245
- const standalone = registry2.list().filter((cmd) => {
4246
- if (commandsInGroups.has(cmd.name)) {
4247
- return false;
4248
- }
4249
- return !cmd.category || cmd.category === "system";
4250
- });
4975
+ function renderPluginsHelp(registry2) {
4976
+ const commands = registry2.listCommandsUnder(["marketplace"]);
4251
4977
  const sections = [];
4252
- if (products.length > 0) {
4253
- const maxProductNameLength = Math.max(
4254
- ...products.map((p) => p.name.length),
4255
- 12
4256
- );
4257
- const productItems = products.sort((a, b) => a.name.localeCompare(b.name)).map((product) => {
4258
- const availableCount = product.commands.filter(
4259
- (c) => c.available && !c.shadowed
4260
- ).length;
4261
- const badge = colors.green(`(${availableCount})`);
4262
- return `${colors.cyan(
4263
- product.name.padEnd(maxProductNameLength)
4264
- )} ${badge}`;
4265
- });
4266
- sections.push({
4267
- header: "Products",
4268
- items: productItems
4269
- });
4270
- }
4271
- if (systemGroups.length > 0) {
4272
- const maxGroupNameLength = Math.max(
4273
- ...systemGroups.map((g) => g.name.length),
4274
- 20
4275
- );
4276
- const groupItems = systemGroups.sort((a, b) => a.name.localeCompare(b.name)).map((group) => {
4277
- const commandCount = group.commands.length;
4278
- const badge = colors.green(`(${commandCount})`);
4279
- return `${colors.cyan(group.name.padEnd(maxGroupNameLength))} ${badge}`;
4280
- });
4281
- sections.push({
4282
- header: "System Commands",
4283
- items: groupItems
4284
- });
4285
- }
4286
- if (standalone.length > 0) {
4287
- const maxCommandNameLength = Math.max(
4288
- ...standalone.map((c) => c.name.length),
4289
- 12
4290
- );
4291
- const standaloneItems = standalone.sort((a, b) => a.name.localeCompare(b.name)).map(
4292
- (cmd) => `${colors.cyan(cmd.name.padEnd(maxCommandNameLength))} ${colors.dim(
4293
- cmd.describe
4294
- )}`
4295
- );
4296
- sections.push({
4297
- header: "Other Commands",
4298
- items: standaloneItems
4978
+ if (commands.length > 0) {
4979
+ const maxLength = Math.max(...commands.map((c) => c.manifest.segments.join(" ").length), 20);
4980
+ const items = commands.filter((c) => c.available && !c.shadowed).sort((a, b) => a.manifest.segments.join(" ").localeCompare(b.manifest.segments.join(" "))).map((cmd) => {
4981
+ const displayPath = cmd.manifest.segments.join(" ");
4982
+ return `${colors.cyan(displayPath.padEnd(maxLength))} ${colors.dim(cmd.manifest.describe)}`;
4299
4983
  });
4984
+ sections.push({ header: "Plugin Management", items });
4300
4985
  }
4301
- const globalOptions = [
4302
- { name: "--help", desc: "Show help information" },
4303
- { name: "--version", desc: "Show CLI version" },
4304
- { name: "--json", desc: "Output in JSON format" },
4305
- { name: "--quiet", desc: "Suppress detailed output" }
4306
- ];
4307
- const maxOptionNameLength = Math.max(
4308
- ...globalOptions.map((o) => o.name.length),
4309
- 12
4310
- );
4311
- const optionItems = globalOptions.map(
4312
- (option) => `${colors.cyan(option.name.padEnd(maxOptionNameLength))} ${colors.dim(
4313
- option.desc
4314
- )}`
4315
- );
4316
- sections.push({
4317
- header: "Global Options",
4318
- items: optionItems
4319
- });
4320
- const nextStepsItems = [];
4321
- const firstProduct = products[0];
4322
- if (firstProduct) {
4323
- nextStepsItems.push(
4324
- `${colors.cyan(
4325
- `kb ${firstProduct.name} --help`
4326
- )} ${colors.dim("Explore product commands")}`
4327
- );
4328
- }
4329
- const marketplaceGroup2 = systemGroups.find((g) => g.name === "marketplace");
4330
- if (marketplaceGroup2) {
4331
- nextStepsItems.push(
4332
- `${colors.cyan("kb marketplace")} ${colors.dim("Open marketplace commands")}`
4333
- );
4334
- }
4335
- nextStepsItems.push("");
4336
- nextStepsItems.push(
4337
- colors.dim("Use 'kb <product> --help' or 'kb <group> --help' to see commands for a specific product or group.")
4338
- );
4339
4986
  sections.push({
4340
- header: "Next Steps",
4341
- items: nextStepsItems
4342
- });
4343
- return sideBorderBox({
4344
- title: "KB Labs CLI",
4345
- sections,
4346
- status: "info"
4987
+ items: [colors.dim("kb marketplace <command> --help")]
4347
4988
  });
4989
+ return sideBorderBox({ title: "\u{1F9E9} Plugin Management", sections, status: "info" });
4348
4990
  }
4349
- function renderPluginsHelp(registry2) {
4350
- const tracker = new TimingTracker();
4351
- registry2.list().filter(
4352
- (cmd) => cmd.name?.startsWith("marketplace:") || cmd.category === "system"
4353
- );
4354
- const sections = [];
4355
- const commandMap = {
4356
- "marketplace:install": "Install package(s) from marketplace",
4357
- "marketplace:update": "Update package(s) from marketplace",
4358
- "marketplace:list": "List all discovered plugins",
4359
- "marketplace:enable": "Enable a plugin",
4360
- "marketplace:disable": "Disable a plugin",
4361
- "marketplace:link": "Link a local plugin for development",
4362
- "marketplace:unlink": "Unlink a local plugin",
4363
- "marketplace:doctor": "Diagnose plugin issues",
4364
- "marketplace:scaffold": "Generate a new plugin template",
4365
- "marketplace:clear-cache": "Clear plugin discovery cache"
4366
- };
4367
- const maxLength = Math.max(
4368
- ...Object.keys(commandMap).map((c) => c.length),
4369
- 20
4370
- );
4371
- const commandItems = Object.entries(commandMap).map(
4372
- ([cmdName, desc]) => `${colors.cyan(cmdName.padEnd(maxLength))} ${colors.dim(desc)}`
4373
- );
4374
- sections.push({
4375
- header: "Plugin Management Commands",
4376
- items: commandItems
4377
- });
4378
- const examples = [
4379
- `kb marketplace list ${colors.dim("List all plugins")}`,
4380
- `kb marketplace enable @kb-labs/devlink-cli ${colors.dim("Enable a plugin")}`,
4381
- `kb marketplace doctor ${colors.dim("Diagnose plugin issues")}`,
4382
- `kb marketplace scaffold my-plugin ${colors.dim("Generate plugin template")}`
4383
- ];
4384
- sections.push({
4385
- header: "Examples",
4386
- items: examples.map((ex) => colors.dim(ex))
4387
- });
4388
- sections.push({
4389
- header: "Next Steps",
4390
- items: [colors.dim("Use 'kb marketplace <command> --help' for detailed help")]
4391
- });
4392
- return sideBorderBox({
4393
- title: "\u{1F9E9} Plugin Management",
4394
- sections,
4395
- status: "info",
4396
- timing: tracker.total()
4397
- });
4991
+ var BOX_OVERHEAD = 3;
4992
+ var PREFIX_LEN = 2;
4993
+ var GAP_LEN = 2;
4994
+ function truncateDesc(desc, maxLength) {
4995
+ const cols = typeof process !== "undefined" && process.stdout?.columns || 80;
4996
+ const avail = cols - BOX_OVERHEAD - PREFIX_LEN - maxLength - GAP_LEN;
4997
+ if (avail < 10 || desc.length <= avail) {
4998
+ return desc;
4999
+ }
5000
+ return desc.slice(0, avail - 1) + "\u2026";
4398
5001
  }
4399
5002
  function renderProductHelp(groupName, commands) {
4400
- const tracker = new TimingTracker();
4401
5003
  const sections = [];
4402
- const manifestVersions = collectManifestVersions(commands);
4403
- if (manifestVersions.length > 0) {
4404
- sections.push({
4405
- header: "Manifest",
4406
- items: [colors.cyan(manifestVersions.join(" + "))]
4407
- });
4408
- }
4409
5004
  const availableMap = /* @__PURE__ */ new Map();
4410
5005
  const unavailableMap = /* @__PURE__ */ new Map();
4411
5006
  for (const cmd of commands) {
5007
+ const key = cmd.manifest.segments.join(" ");
4412
5008
  if (cmd.available && !cmd.shadowed) {
4413
- if (!availableMap.has(cmd.manifest.id)) {
4414
- availableMap.set(cmd.manifest.id, cmd);
5009
+ if (!availableMap.has(key)) {
5010
+ availableMap.set(key, cmd);
4415
5011
  }
4416
- } else if (!unavailableMap.has(cmd.manifest.id)) {
4417
- unavailableMap.set(cmd.manifest.id, cmd);
5012
+ } else if (!unavailableMap.has(key)) {
5013
+ unavailableMap.set(key, cmd);
4418
5014
  }
4419
5015
  }
4420
- const available = Array.from(availableMap.values()).sort(
4421
- (a, b) => a.manifest.id.localeCompare(b.manifest.id)
4422
- );
5016
+ const available = Array.from(availableMap.values());
4423
5017
  const unavailable = Array.from(unavailableMap.values()).sort(
4424
- (a, b) => a.manifest.id.localeCompare(b.manifest.id)
5018
+ (a, b) => a.manifest.segments.join(" ").localeCompare(b.manifest.segments.join(" "))
4425
5019
  );
5020
+ const hasUnavailable = unavailable.length > 0;
5021
+ const displayPath = (cmd) => cmd.manifest.segments.join(" ");
4426
5022
  const maxLength = Math.max(
4427
- ...[...available, ...unavailable].map((c) => c.manifest.id.replace(/:/g, " ").length),
5023
+ ...[...available, ...unavailable].map((c) => displayPath(c).length),
4428
5024
  20
4429
5025
  );
4430
- const availableItems = [];
4431
- for (const cmd of available) {
4432
- const status = colors.green("\u2713");
4433
- const displayId = cmd.manifest.id.replace(/:/g, " ");
4434
- const paddedId = displayId.padEnd(maxLength);
4435
- availableItems.push(
4436
- `${status} ${colors.cyan(paddedId)} ${colors.dim(
4437
- cmd.manifest.describe
4438
- )}`
4439
- );
4440
- if (cmd.manifest.examples && cmd.manifest.examples.length > 0) {
4441
- for (const example of cmd.manifest.examples.slice(0, 2)) {
4442
- availableItems.push(` ${colors.dim(example)}`);
5026
+ const hasCategories = available.some((c) => c.manifest.category);
5027
+ const prefix = hasUnavailable ? `${colors.green("\u2713")} ` : " ";
5028
+ if (hasCategories) {
5029
+ const categoryOrder = [];
5030
+ const categoryMap = /* @__PURE__ */ new Map();
5031
+ for (const cmd of available) {
5032
+ const cat = cmd.manifest.category ?? "";
5033
+ if (!categoryMap.has(cat)) {
5034
+ categoryOrder.push(cat);
5035
+ categoryMap.set(cat, []);
4443
5036
  }
5037
+ categoryMap.get(cat).push(cmd);
5038
+ }
5039
+ for (const cat of categoryOrder) {
5040
+ const cmds = categoryMap.get(cat).sort((a, b) => displayPath(a).localeCompare(displayPath(b)));
5041
+ const items = cmds.map((cmd) => {
5042
+ const paddedPath = displayPath(cmd).padEnd(maxLength);
5043
+ return `${prefix}${colors.cyan(paddedPath)} ${colors.dim(truncateDesc(cmd.manifest.describe, maxLength))}`;
5044
+ });
5045
+ sections.push({ header: cat || "Commands", items });
4444
5046
  }
5047
+ } else {
5048
+ const availableItems = [...available].sort((a, b) => displayPath(a).localeCompare(displayPath(b))).map((cmd) => {
5049
+ const paddedPath = displayPath(cmd).padEnd(maxLength);
5050
+ return `${prefix}${colors.cyan(paddedPath)} ${colors.dim(cmd.manifest.describe)}`;
5051
+ });
5052
+ sections.push({ header: "Commands", items: availableItems });
4445
5053
  }
4446
- sections.push({
4447
- header: "Available Commands",
4448
- items: availableItems
4449
- });
4450
- if (unavailable.length > 0) {
5054
+ if (hasUnavailable) {
4451
5055
  const unavailableItems = [];
4452
5056
  for (const cmd of unavailable) {
4453
- const status = colors.red("\u2717");
4454
- const displayId = cmd.manifest.id.replace(/:/g, " ");
4455
- const paddedId = displayId.padEnd(maxLength);
4456
- unavailableItems.push(
4457
- `${status} ${colors.dim(paddedId)} ${colors.dim(
4458
- cmd.manifest.describe
4459
- )}`
4460
- );
5057
+ const paddedPath = displayPath(cmd).padEnd(maxLength);
5058
+ unavailableItems.push(`${colors.red("\u2717")} ${colors.dim(paddedPath)} ${colors.dim(cmd.manifest.describe)}`);
4461
5059
  if (cmd.unavailableReason) {
4462
- unavailableItems.push(
4463
- ` ${colors.red(`Reason: ${cmd.unavailableReason}`)}`
4464
- );
5060
+ unavailableItems.push(` ${colors.red(`Reason: ${cmd.unavailableReason}`)}`);
4465
5061
  }
4466
5062
  if (cmd.hint) {
4467
5063
  unavailableItems.push(` ${colors.yellow(`Hint: ${cmd.hint}`)}`);
4468
5064
  }
4469
5065
  }
4470
- sections.push({
4471
- header: "Unavailable",
4472
- items: unavailableItems
4473
- });
4474
- }
4475
- const uniqueCommands = /* @__PURE__ */ new Map();
4476
- for (const cmd of available) {
4477
- if (!uniqueCommands.has(cmd.manifest.id)) {
4478
- uniqueCommands.set(cmd.manifest.id, cmd);
4479
- }
4480
- }
4481
- const nextStepsItems = Array.from(uniqueCommands.values()).slice(0, 3).map((cmd) => {
4482
- const displayId = cmd.manifest.id.replace(/:/g, " ");
4483
- return `${colors.cyan(`kb ${displayId}`)} ${colors.dim(
4484
- cmd.manifest.describe
4485
- )}`;
4486
- });
4487
- if (nextStepsItems.length === 0) {
4488
- nextStepsItems.push(colors.dim("No available commands in this product"));
4489
- } else {
4490
- nextStepsItems.push("");
4491
- nextStepsItems.push(
4492
- colors.dim(
4493
- `Use 'kb ${groupName} <command> --help' for detailed help`
4494
- )
4495
- );
5066
+ sections.push({ header: "Unavailable", items: unavailableItems });
4496
5067
  }
4497
- nextStepsItems.push("");
4498
- nextStepsItems.push(
4499
- colors.dim("Use 'kb --help' to see all products and system commands.")
4500
- );
4501
5068
  sections.push({
4502
- header: "Next Steps",
4503
- items: nextStepsItems
5069
+ items: [colors.dim(`kb ${groupName} <command> --help`)]
4504
5070
  });
4505
5071
  return sideBorderBox({
4506
5072
  title: groupName,
4507
5073
  sections,
4508
- status: "info",
4509
- timing: tracker.total()
5074
+ status: "info"
4510
5075
  });
4511
5076
  }
4512
5077
  function renderManifestCommandHelp(registered) {
@@ -4596,9 +5161,47 @@ function groupCommands(commands, onlyAvailable) {
4596
5161
  );
4597
5162
  }
4598
5163
 
5164
+ // src/presentation/schema-generator.ts
5165
+ function flagToProperty(flag) {
5166
+ const prop = {
5167
+ type: flag.type === "array" ? "array" : flag.type,
5168
+ description: flag.description ?? flag.describe
5169
+ };
5170
+ if (flag.default !== void 0) {
5171
+ prop.default = flag.default;
5172
+ }
5173
+ if (flag.choices?.length) {
5174
+ prop.enum = flag.choices;
5175
+ }
5176
+ if (flag.type === "array") {
5177
+ prop.items = { type: "string" };
5178
+ }
5179
+ return prop;
5180
+ }
5181
+ function generateCommandSchema(manifest) {
5182
+ const properties = {};
5183
+ const required = [];
5184
+ for (const flag of manifest.flags ?? []) {
5185
+ properties[flag.name] = flagToProperty(flag);
5186
+ if (flag.required) {
5187
+ required.push(flag.name);
5188
+ }
5189
+ }
5190
+ return {
5191
+ $schema: "https://json-schema.org/draft/2020-12/schema",
5192
+ title: manifest.segments.join(" "),
5193
+ description: manifest.describe,
5194
+ ...manifest.operationType ? { operationType: manifest.operationType } : {},
5195
+ type: "object",
5196
+ properties,
5197
+ ...required.length ? { required } : {},
5198
+ examples: manifest.examples ?? []
5199
+ };
5200
+ }
5201
+
4599
5202
  // src/index.ts
4600
5203
  init_discover();
4601
5204
 
4602
- export { discoverManifests, discoverManifestsByNamespace, findCommand, findCommandWithType, generateExamples, health, hello, logsContext, logsDiagnose, logsGet, logsQuery, logsSearch, logsStats, logsSummarize, registerBuiltinCommands, registry, renderGlobalHelp, renderGlobalHelpNew, renderGroupHelp, renderHelp, renderManifestCommandHelp, renderPluginsHelp, renderProductHelp, version };
5205
+ export { TrieBackedRegistry, createCompletionCommand, createRegistry, discoverManifests, discoverManifestsByNamespace, generateCommandSchema, generateExamples, health, hello, logsContext, logsDiagnose, logsGet, logsQuery, logsSearch, logsStats, logsSummarize, registerBuiltinCommands, registry, renderGlobalHelp, renderGroupHelp, renderHelp, renderManifestCommandHelp, renderPluginsHelp, renderProductHelp, version };
4603
5206
  //# sourceMappingURL=index.js.map
4604
5207
  //# sourceMappingURL=index.js.map