@kb-labs/cli-commands 2.94.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,
@@ -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 {
@@ -1073,7 +1045,19 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
1073
1045
  if (projectWorkspace.length > 0) {
1074
1046
  log("info", `Discovered ${projectWorkspace.length} project workspace packages with CLI manifests`);
1075
1047
  }
1076
- } catch {
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
+ }
1077
1061
  }
1078
1062
  }
1079
1063
  const dedupStart = Date.now();
@@ -1099,27 +1083,26 @@ async function discoverManifestsByNamespace(cwd, namespace, noCache = false) {
1099
1083
  const allResults = await discoverManifests(cwd, noCache);
1100
1084
  return allResults.filter((result) => {
1101
1085
  return result.manifests.some((m) => {
1102
- const manifestNamespace = m.namespace || m.group;
1103
- return manifestNamespace === namespace;
1086
+ return m.group === namespace;
1104
1087
  });
1105
1088
  });
1106
1089
  }
1107
- 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;
1108
1091
  var init_discover = __esm({
1109
1092
  "src/registry/discover.ts"() {
1110
1093
  init_path();
1111
1094
  init_schema();
1095
+ DISCOVERY_VERSION = 2;
1112
1096
  DEBUG_MODE = process.env.DEBUG_SANDBOX === "1" || process.env.NODE_ENV === "development";
1113
1097
  log = (level, message, fields) => {
1114
1098
  if (!DEBUG_MODE) {
1115
1099
  return;
1116
1100
  }
1117
1101
  const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : level === "info" ? "\u2139" : "\u{1F50D}";
1118
- const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
1119
1102
  if (fields) {
1120
- logFn(`${prefix} [discover] ${message}`, fields);
1103
+ console.error(`${prefix} [discover] ${message}`, fields);
1121
1104
  } else {
1122
- logFn(`${prefix} [discover] ${message}`);
1105
+ console.error(`${prefix} [discover] ${message}`);
1123
1106
  }
1124
1107
  };
1125
1108
  __test = {
@@ -1127,6 +1110,14 @@ var init_discover = __esm({
1127
1110
  createManifestV3Loader
1128
1111
  // resetInProcCache is exported directly (not via __test) since it's part of the public API
1129
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
+ };
1130
1121
  PACKAGE_JSON = "package.json";
1131
1122
  MANIFEST_LOAD_TIMEOUT = 1500;
1132
1123
  IN_PROC_CACHE_TTL_MS = 6e4;
@@ -1135,420 +1126,511 @@ var init_discover = __esm({
1135
1126
  }
1136
1127
  });
1137
1128
 
1138
- // src/registry/service.ts
1139
- function manifestToCommand(registered) {
1140
- return {
1141
- name: registered.manifest.id,
1142
- category: registered.manifest.group,
1143
- describe: registered.manifest.describe,
1144
- longDescription: registered.manifest.longDescription,
1145
- aliases: registered.manifest.aliases || [],
1146
- flags: registered.manifest.flags,
1147
- examples: registered.manifest.examples,
1148
- async run() {
1149
- throw new Error(`Command ${registered.manifest.id} should be executed via plugin-executor, not via legacy run() path.`);
1150
- }
1151
- };
1129
+ // src/registry/trie-router.ts
1130
+ function makeNode() {
1131
+ return { children: /* @__PURE__ */ new Map() };
1152
1132
  }
1153
- function buildCanonicalId(manifest) {
1154
- const { id, group, subgroup } = manifest;
1155
- if (group && subgroup) {
1156
- 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
+ }
1157
1148
  }
1158
- if (group) {
1159
- 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
+ }
1160
1166
  }
1161
- return id;
1167
+ dfs(node, [...prefix]);
1168
+ return results;
1162
1169
  }
1163
- var InMemoryRegistry = class {
1164
- // System commands (in-process): key = any registered name/alias
1165
- systemCommands = /* @__PURE__ */ new Map();
1166
- // Plugin commands: key = canonicalId only
1167
- pluginByCanonical = /* @__PURE__ */ new Map();
1168
- // Alias → canonicalId: covers all user-input variants that resolve to a plugin
1169
- pluginAliases = /* @__PURE__ */ new Map();
1170
- // Legacy unified collection for backward compatibility (display/get)
1171
- byName = /* @__PURE__ */ new Map();
1172
- groups = /* @__PURE__ */ new Map();
1173
- // manifests: any key RegisteredCommand (for listing/lookup; still stores multi-key for compat)
1174
- manifests = /* @__PURE__ */ new Map();
1175
- partial = false;
1176
- // ─── System command registration ────────────────────────────────────────
1177
- register(cmd) {
1178
- this.systemCommands.set(cmd.name, cmd);
1179
- this.byName.set(cmd.name, cmd);
1180
- for (const a of cmd.aliases || []) {
1181
- this.systemCommands.set(a, cmd);
1182
- 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);
1183
1203
  }
1204
+ node.command = cmd;
1205
+ return { collides: false };
1184
1206
  }
1185
- registerGroup(group) {
1186
- this.groups.set(group.name, group);
1187
- 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;
1188
1234
  for (const cmd of group.commands) {
1189
- this.systemCommands.set(cmd.name, cmd);
1190
- const fullName = `${group.name} ${cmd.name}`;
1191
- this.systemCommands.set(fullName, cmd);
1192
- this.byName.set(fullName, cmd);
1193
- for (const alias of cmd.aliases || []) {
1194
- this.systemCommands.set(alias, cmd);
1195
- 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;
1196
1248
  }
1197
1249
  }
1198
- if (group.subgroups) {
1199
- for (const sub of group.subgroups) {
1200
- const subName = `${group.name} ${sub.name}`;
1201
- this.groups.set(subName, sub);
1202
- this.byName.set(subName, sub);
1203
- for (const cmd of sub.commands) {
1204
- const fullName = `${group.name} ${sub.name} ${cmd.name}`;
1205
- this.systemCommands.set(fullName, cmd);
1206
- this.byName.set(fullName, cmd);
1207
- for (const alias of cmd.aliases || []) {
1208
- this.systemCommands.set(alias, cmd);
1209
- this.byName.set(alias, cmd);
1210
- }
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());
1211
1262
  }
1263
+ cmdNode = cmdNode.children.get(cmd.name);
1264
+ cmdNode.systemCommand = cmd;
1212
1265
  }
1213
1266
  }
1214
1267
  }
1215
- // ─── Plugin command registration ────────────────────────────────────────
1216
- registerManifest(cmd) {
1217
- const canonicalId = buildCanonicalId(cmd.manifest);
1218
- const collisionKey = this._findSystemCollision(cmd.manifest, canonicalId);
1219
- if (collisionKey) {
1220
- console.warn(`[registry] Plugin command "${canonicalId}" collides with system command "${collisionKey}". System command takes priority.`);
1221
- 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;
1222
1276
  }
1223
- const collisionAliases = /* @__PURE__ */ new Set();
1224
- for (const alias of cmd.manifest.aliases || []) {
1225
- if (this.systemCommands.has(alias)) {
1226
- console.warn(`[registry] Plugin alias "${alias}" collides with system command. System command takes priority.`);
1227
- 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());
1228
1284
  }
1285
+ node = node.children.get(seg);
1229
1286
  }
1230
- this.pluginByCanonical.set(canonicalId, cmd);
1231
- this.manifests.set(canonicalId, cmd);
1232
- this.manifests.set(cmd.manifest.id, cmd);
1233
- if (cmd.shadowed) {
1234
- return;
1287
+ if (!node.groupDescribe) {
1288
+ node.groupDescribe = describe;
1235
1289
  }
1236
- this._registerPluginAliases(cmd, canonicalId, collisionAliases);
1237
- const commandAdapter = manifestToCommand(cmd);
1238
- this.byName.set(canonicalId, commandAdapter);
1239
- const spaceCanonical = canonicalId.replace(/:/g, " ");
1240
- this.byName.set(spaceCanonical, commandAdapter);
1241
- this._registerSyntheticGroups(cmd, commandAdapter);
1242
1290
  }
1243
- /**
1244
- * Check whether any system command already owns the canonical id or its parts.
1245
- * Returns the colliding key if found, null otherwise.
1246
- */
1247
- _findSystemCollision(manifest, canonicalId) {
1248
- if (this.systemCommands.has(canonicalId)) {
1249
- 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
+ }
1250
1315
  }
1251
- const space = canonicalId.replace(/:/g, " ");
1252
- if (this.systemCommands.has(space)) {
1253
- return space;
1316
+ if (node.command) {
1317
+ return { type: "command", command: node.command, rest: [] };
1254
1318
  }
1255
- 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: [] };
1256
1335
  }
1257
- /**
1258
- * Register all lookup aliases for a plugin command.
1259
- *
1260
- * Priority (what beats what) is handled in resolveToCanonical at query time.
1261
- * Here we just build the mapping.
1262
- *
1263
- * Aliases registered:
1264
- * - canonicalId itself ("marketplace:plugins:list")
1265
- * - bare id ("list") ← low priority, may clash
1266
- * - 2-part shorthand ("marketplace:list", "marketplace list")
1267
- * - manifest.aliases[] (user-defined aliases, except collisions)
1268
- */
1269
- _registerPluginAliases(cmd, canonicalId, collisionAliases) {
1270
- const { id, group, subgroup } = cmd.manifest;
1271
- const register = (key) => {
1272
- if (!this.systemCommands.has(key) && !this.systemCommands.has(key.replace(/:/g, " "))) {
1273
- if (!this.pluginAliases.has(key)) {
1274
- this.pluginAliases.set(key, canonicalId);
1275
- this.manifests.set(key, cmd);
1276
- }
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 [];
1277
1345
  }
1278
- };
1279
- this.pluginAliases.set(canonicalId, canonicalId);
1280
- const spaceCanonical = canonicalId.replace(/:/g, " ");
1281
- this.pluginAliases.set(spaceCanonical, canonicalId);
1282
- this.manifests.set(spaceCanonical, cmd);
1283
- if (group && subgroup) {
1284
- const twoPartColon = `${group}:${id}`;
1285
- const twoPartSpace = `${group} ${id}`;
1286
- register(twoPartColon);
1287
- register(twoPartSpace);
1288
- this.manifests.set(twoPartColon, cmd);
1289
- this.manifests.set(twoPartSpace, cmd);
1290
- const fullPath = `${group} ${subgroup} ${id}`;
1291
- this.pluginAliases.set(fullPath, canonicalId);
1292
- this.manifests.set(fullPath, cmd);
1293
- const colonPath = `${group}:${subgroup}:${id}`;
1294
- this.pluginAliases.set(colonPath, canonicalId);
1295
- this.manifests.set(colonPath, cmd);
1296
- } else if (group) {
1297
- const colonName = `${group}:${id}`;
1298
- const spaceName = `${group} ${id}`;
1299
- this.pluginAliases.set(colonName, canonicalId);
1300
- this.pluginAliases.set(spaceName, canonicalId);
1301
- this.manifests.set(colonName, cmd);
1302
- this.manifests.set(spaceName, cmd);
1303
- }
1304
- register(id);
1305
- for (const alias of cmd.manifest.aliases || []) {
1306
- if (!collisionAliases.has(alias)) {
1307
- 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(" "));
1308
1354
  }
1309
1355
  }
1356
+ return results;
1310
1357
  }
1311
1358
  /**
1312
- * Register synthetic subgroups for help display.
1359
+ * List all leaf plugin commands under a given node path.
1313
1360
  */
1314
- _registerSyntheticGroups(cmd, commandAdapter) {
1315
- const { id, group, subgroup } = cmd.manifest;
1316
- if (group && subgroup) {
1317
- const subgroupKey = `${group} ${subgroup}`;
1318
- if (!this.groups.has(subgroupKey)) {
1319
- this.groups.set(subgroupKey, {
1320
- name: subgroupKey,
1321
- describe: subgroup,
1322
- commands: []
1323
- });
1324
- 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 [];
1325
1367
  }
1326
- this.groups.get(subgroupKey).commands.push(commandAdapter);
1327
- const twoPartSpace = `${group} ${id}`;
1328
- if (!this.byName.has(twoPartSpace)) {
1329
- 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;
1330
1383
  }
1331
- const twoPartColon = `${group}:${id}`;
1332
- if (!this.byName.has(twoPartColon)) {
1333
- 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 });
1334
1398
  }
1335
- const fullPath = `${group} ${subgroup} ${id}`;
1336
- this.byName.set(fullPath, commandAdapter);
1337
- } else if (group) {
1338
- const fullName = `${group} ${id}`;
1339
- const colonName = `${group}:${id}`;
1340
- this.byName.set(fullName, commandAdapter);
1341
- this.byName.set(colonName, commandAdapter);
1342
1399
  }
1400
+ return results;
1343
1401
  }
1344
- // ─── Resolution ──────────────────────────────────────────────────────────
1345
1402
  /**
1346
- * Resolve any user input to its canonical plugin ID.
1347
- *
1348
- * Returns canonicalId if found in plugin aliases, undefined otherwise.
1403
+ * Collect all plugin commands reachable from node.
1349
1404
  */
1350
- resolveToCanonical(input) {
1351
- if (this.pluginAliases.has(input)) {
1352
- 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);
1413
+ }
1414
+ for (const child of node.children.values()) {
1415
+ this._collectCommands(child, out);
1353
1416
  }
1354
- const converted = input.includes(":") ? input.replace(/:/g, " ") : input.replace(/ /g, ":");
1355
- if (this.pluginAliases.has(converted)) {
1356
- return this.pluginAliases.get(converted);
1417
+ }
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 };
1357
1436
  }
1358
- return void 0;
1437
+ return { type: "ambiguous", input: tokens, candidates: deep };
1359
1438
  }
1360
- // ─── Public API ──────────────────────────────────────────────────────────
1361
- markPartial(partial) {
1362
- this.partial = 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";
1363
1475
  }
1364
- isPartial() {
1365
- return this.partial;
1476
+ if (segs.length > MAX_PATH_DEPTH) {
1477
+ return `path too deep: ${segs.length} segments (max ${MAX_PATH_DEPTH})`;
1366
1478
  }
1367
- getManifest(id) {
1368
- return this.manifests.get(id);
1479
+ if (RESERVED_NAMESPACES.has(segs[0])) {
1480
+ return `"${segs[0]}" is a reserved namespace`;
1369
1481
  }
1370
- listManifests() {
1371
- const unique = /* @__PURE__ */ new Set();
1372
- for (const cmd of this.pluginByCanonical.values()) {
1373
- unique.add(cmd);
1374
- }
1375
- return Array.from(unique);
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;
1376
1503
  }
1377
- has(name) {
1378
- return this.byName.has(name) || this.pluginAliases.has(name);
1504
+ // ─── Registration ──────────────────────────────────────────────────────────
1505
+ register(cmd) {
1506
+ this.systemRouter.insertSystemCommand(cmd);
1379
1507
  }
1380
- /**
1381
- * Get command with type information for secure routing.
1382
- *
1383
- * System commands ALWAYS win — checked first before any plugin lookup.
1384
- * Returns type='system' for system commands (in-process execution).
1385
- * Returns type='plugin' for plugin commands (subprocess execution).
1386
- */
1387
- getWithType(nameOrPath) {
1388
- const normalized = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
1389
- if (this.systemCommands.has(normalized)) {
1390
- return { cmd: this.systemCommands.get(normalized), type: "system" };
1391
- }
1392
- const colonVariant = normalized.replace(/ /g, ":");
1393
- if (this.systemCommands.has(colonVariant)) {
1394
- return { cmd: this.systemCommands.get(colonVariant), type: "system" };
1395
- }
1396
- if (this.groups.has(normalized)) {
1397
- return { cmd: this.groups.get(normalized), type: "system" };
1398
- }
1399
- const canonicalId = this.resolveToCanonical(normalized);
1400
- if (canonicalId) {
1401
- const pluginCmd = this.pluginByCanonical.get(canonicalId);
1402
- if (pluginCmd && !pluginCmd.shadowed) {
1403
- const cmd2 = this.byName.get(canonicalId) ?? this.byName.get(normalized);
1404
- if (cmd2) {
1405
- return { cmd: cmd2, type: "plugin" };
1406
- }
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 ?? "");
1407
1513
  }
1408
1514
  }
1409
- const cmd = this.get(nameOrPath);
1410
- if (!cmd) {
1411
- return void 0;
1412
- }
1413
- if ("commands" in cmd) {
1414
- return { cmd, type: "system" };
1515
+ }
1516
+ registerManifest(cmd) {
1517
+ if (cmd.manifest._synthetic) {
1518
+ return;
1415
1519
  }
1416
- if (this.systemCommands.has(normalized) || this.systemCommands.has(colonVariant)) {
1417
- 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;
1418
1527
  }
1419
- const manifestCmd = this.getManifestCommand(normalized);
1420
- if (manifestCmd && !manifestCmd.shadowed) {
1421
- 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;
1422
1533
  }
1423
- return { cmd, type: "system" };
1424
- }
1425
- get(nameOrPath) {
1426
- if (typeof nameOrPath === "string") {
1427
- if (this.byName.has(nameOrPath)) {
1428
- return this.byName.get(nameOrPath);
1429
- }
1430
- if (nameOrPath.includes(":")) {
1431
- const spaceKey = nameOrPath.replace(/:/g, " ");
1432
- if (this.byName.has(spaceKey)) {
1433
- return this.byName.get(spaceKey);
1434
- }
1435
- const parts = nameOrPath.split(":");
1436
- if (parts.length === 2) {
1437
- const spaceKey2 = parts.join(" ");
1438
- if (this.byName.has(spaceKey2)) {
1439
- return this.byName.get(spaceKey2);
1440
- }
1441
- }
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];
1442
1538
  }
1443
1539
  }
1444
- const key = Array.isArray(nameOrPath) ? nameOrPath.join(" ") : nameOrPath;
1445
- if (this.byName.has(key)) {
1446
- 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;
1447
1550
  }
1448
- if (Array.isArray(nameOrPath) && nameOrPath.length === 1 && nameOrPath[0]?.includes(":")) {
1449
- if (this.byName.has(nameOrPath[0])) {
1450
- 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;
1451
1555
  }
1452
- const [group, command] = nameOrPath[0].split(":");
1453
- const legacyKey = `${group} ${command}`;
1454
- if (this.byName.has(legacyKey)) {
1455
- 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;
1456
1560
  }
1457
- }
1458
- if (Array.isArray(nameOrPath)) {
1459
- const dot = nameOrPath.join(".");
1460
- if (this.byName.has(dot)) {
1461
- 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
+ );
1462
1571
  }
1463
1572
  }
1464
- if (Array.isArray(nameOrPath) && nameOrPath.length >= 2) {
1465
- const [groupPrefix, ...cmdParts] = nameOrPath;
1466
- const cmdName = cmdParts.join(" ");
1467
- for (const group of this.groups.values()) {
1468
- if (group.name === groupPrefix || group.name.startsWith(groupPrefix + ":")) {
1469
- const fullName = `${group.name} ${cmdName}`;
1470
- if (this.byName.has(fullName)) {
1471
- return this.byName.get(fullName);
1472
- }
1473
- }
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);
1474
1578
  }
1475
1579
  }
1476
- return void 0;
1477
1580
  }
1478
- list() {
1479
- const commands = /* @__PURE__ */ new Set();
1480
- for (const value of this.byName.values()) {
1481
- if ("run" in value) {
1482
- 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;
1483
1587
  }
1484
1588
  }
1485
- return Array.from(commands);
1589
+ return this.pluginRouter.resolve(tokens);
1486
1590
  }
1487
- listGroups() {
1488
- 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])];
1489
1596
  }
1490
- getGroupsByPrefix(prefix) {
1491
- const result = [];
1492
- for (const group of this.groups.values()) {
1493
- if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1494
- result.push(group);
1495
- }
1496
- }
1497
- return result;
1597
+ // ─── Query ──────────────────────────────────────────────────────────────────
1598
+ listSystemTopLevel() {
1599
+ return this.systemRouter.listSystemTopLevel();
1498
1600
  }
1499
- getCommandsByGroupPrefix(prefix) {
1500
- const result = [];
1501
- for (const group of this.groups.values()) {
1502
- if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1503
- result.push(...group.commands);
1504
- }
1505
- }
1506
- return result;
1601
+ getPluginGroupDescribe(segments) {
1602
+ return this.pluginRouter.getGroupDescribe(segments);
1507
1603
  }
1508
- listProductGroups() {
1509
- const groups = /* @__PURE__ */ new Map();
1510
- for (const cmd of this.listManifests()) {
1511
- const groupName = cmd.manifest.group;
1512
- if (!groups.has(groupName)) {
1513
- groups.set(groupName, {
1514
- name: groupName,
1515
- describe: cmd.manifest.group,
1516
- commands: []
1517
- });
1518
- }
1519
- groups.get(groupName).commands.push(cmd);
1520
- }
1521
- return Array.from(groups.values());
1604
+ listCommands() {
1605
+ return this.pluginRouter.listAll();
1522
1606
  }
1523
- getCommandsByGroup(group) {
1524
- 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);
1525
1609
  }
1526
- getManifestCommand(idOrAlias) {
1527
- if (this.manifests.has(idOrAlias)) {
1528
- return this.manifests.get(idOrAlias);
1529
- }
1530
- const canonicalId = this.resolveToCanonical(idOrAlias);
1531
- if (canonicalId) {
1532
- return this.pluginByCanonical.get(canonicalId);
1533
- }
1534
- for (const cmd of this.pluginByCanonical.values()) {
1535
- if (cmd.manifest.aliases?.includes(idOrAlias)) {
1536
- return cmd;
1537
- }
1538
- if (cmd.manifest.id.replace(/:/g, " ") === idOrAlias) {
1539
- return cmd;
1540
- }
1541
- }
1542
- 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
+ };
1543
1628
  }
1544
1629
  };
1545
- var registry = new InMemoryRegistry();
1546
- function findCommand(nameOrPath) {
1547
- return registry.get(nameOrPath);
1548
- }
1549
- function findCommandWithType(nameOrPath) {
1550
- return registry.getWithType(nameOrPath);
1630
+ function createRegistry() {
1631
+ return new TrieBackedRegistry();
1551
1632
  }
1633
+ var registry = new TrieBackedRegistry();
1552
1634
 
1553
1635
  // src/utils/generate-examples.ts
1554
1636
  function generateExamples(commandName, productName, cases) {
@@ -1663,7 +1745,7 @@ var health = defineSystemCommand({
1663
1745
  finishEvent: "HEALTH_FINISHED"
1664
1746
  },
1665
1747
  async handler(ctx, _argv, _flags) {
1666
- const cliApi = await createRegistry({
1748
+ const cliApi = await createRegistry$1({
1667
1749
  cache: { ttlMs: 5e3 }
1668
1750
  });
1669
1751
  try {
@@ -1701,7 +1783,7 @@ var health = defineSystemCommand({
1701
1783
  status: result.healthStatus,
1702
1784
  error: result.error
1703
1785
  };
1704
- console.log(JSON.stringify(jsonOutput, null, 2));
1786
+ ctx.ui?.json?.(jsonOutput);
1705
1787
  } else {
1706
1788
  if (result.error) {
1707
1789
  ctx.ui.error("System Health", {
@@ -1752,81 +1834,739 @@ var health = defineSystemCommand({
1752
1834
  }
1753
1835
  });
1754
1836
  init_discover();
1755
- var diag = defineSystemCommand({
1756
- name: "diag",
1757
- description: "Comprehensive system diagnostics (plugins, cache, environment, versions)",
1758
- category: "info",
1759
- examples: generateExamples("diag", "kb", [
1760
- { flags: {} },
1761
- { flags: { json: true } }
1762
- ]),
1763
- flags: {
1764
- json: { type: "boolean", description: "Output in JSON format" }
1765
- },
1766
- analytics: {
1767
- command: "diag",
1768
- startEvent: "DIAG_STARTED",
1769
- finishEvent: "DIAG_FINISHED"
1770
- },
1771
- // eslint-disable-next-line sonarjs/cognitive-complexity -- Comprehensive diagnostic checks across multiple system components
1772
- async handler(ctx, _argv, _flags) {
1773
- const cwd = getContextCwd(ctx);
1774
- const diagnostics = [];
1775
- const nodeVersion = process.version;
1776
- const cliVersion = process.env.CLI_VERSION || process.env.CLI_VERSION || "0.1.0";
1777
- const platform11 = process.platform;
1778
- const arch = process.arch;
1779
- diagnostics.push({
1780
- category: "environment",
1781
- status: "ok",
1782
- message: `Node ${nodeVersion}, CLI ${cliVersion}, ${platform11}/${arch}`,
1783
- details: { nodeVersion, cliVersion, platform: platform11, arch }
1784
- });
1785
- try {
1786
- const platformRoot = process.env.KB_PLATFORM_ROOT;
1787
- const projectRoot = process.env.KB_PROJECT_ROOT;
1788
- const discovered = await discoverManifests(cwd, false, { platformRoot, projectRoot });
1789
- const manifests = registry.listManifests();
1790
- const enabled = manifests.filter((m) => m.available && !m.shadowed).length;
1791
- const disabled = manifests.filter((m) => !m.available).length;
1792
- const shadowed = manifests.filter((m) => m.shadowed).length;
1793
- diagnostics.push({
1794
- category: "marketplace",
1795
- status: disabled > 0 ? "warning" : "ok",
1796
- message: `Found ${discovered.length} packages, ${enabled} enabled, ${disabled} unavailable, ${shadowed} shadowed`,
1797
- details: {
1798
- packages: discovered.length,
1799
- enabled,
1800
- disabled,
1801
- shadowed,
1802
- totalCommands: manifests.length
1803
- }
1804
- });
1805
- } catch (err) {
1806
- const errMsg = err instanceof Error ? err.message : String(err);
1807
- diagnostics.push({
1808
- category: "marketplace",
1809
- status: "error",
1810
- message: `Discovery failed: ${errMsg}`,
1811
- details: { error: errMsg }
1812
- });
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;
1813
1846
  }
1814
- try {
1815
- const cachePath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
1816
- const cacheExists = await promises.access(cachePath).then(() => true).catch(() => false);
1817
- if (cacheExists) {
1818
- const cache = JSON.parse(await promises.readFile(cachePath, "utf8"));
1819
- const age = Date.now() - (cache.timestamp || 0);
1820
- const ageHours = Math.floor(age / (1e3 * 60 * 60));
1821
- diagnostics.push({
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
+ });
2558
+ }
2559
+ try {
2560
+ const cachePath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
2561
+ const cacheExists = await access(cachePath).then(() => true).catch(() => false);
2562
+ if (cacheExists) {
2563
+ const cache = JSON.parse(await readFile(cachePath, "utf8"));
2564
+ const ageHours = Math.floor((Date.now() - (cache.timestamp ?? 0)) / (1e3 * 60 * 60));
2565
+ diagnostics.push({
1822
2566
  category: "cache",
1823
2567
  status: "ok",
1824
- message: `Cache exists, ${ageHours}h old, ${Object.keys(cache.packages || {}).length} packages cached`,
1825
- details: {
1826
- exists: true,
1827
- ageHours,
1828
- packages: Object.keys(cache.packages || {}).length
1829
- }
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 }
1830
2570
  });
1831
2571
  } else {
1832
2572
  diagnostics.push({
@@ -1846,7 +2586,20 @@ var diag = defineSystemCommand({
1846
2586
  });
1847
2587
  }
1848
2588
  try {
1849
- 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
+ }
1850
2603
  if (lock) {
1851
2604
  const entries = Object.entries(lock.installed);
1852
2605
  const enabledCount = entries.filter(([, e]) => e.enabled !== false).length;
@@ -1873,42 +2626,25 @@ var diag = defineSystemCommand({
1873
2626
  details: { error: errMsg }
1874
2627
  });
1875
2628
  }
1876
- const versionIssues = [];
1877
2629
  try {
1878
- const manifests = registry.listManifests();
1879
- 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()) {
1880
2633
  const required = cmd.manifest.engine?.kbCli;
1881
2634
  if (required) {
1882
- const requiredParts = required.replace("^", "").split(".");
1883
- const currentParts = cliVersion.split(".");
1884
- if (requiredParts[0] && currentParts[0]) {
1885
- const requiredMajor = parseInt(requiredParts[0], 10);
1886
- const currentMajor = parseInt(currentParts[0], 10);
1887
- if (!isNaN(requiredMajor) && !isNaN(currentMajor) && currentMajor < requiredMajor) {
1888
- versionIssues.push({
1889
- plugin: cmd.manifest.package || cmd.manifest.group,
1890
- required,
1891
- current: cliVersion
1892
- });
1893
- }
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 });
1894
2639
  }
1895
2640
  }
1896
2641
  }
1897
- if (versionIssues.length > 0) {
1898
- diagnostics.push({
1899
- category: "versions",
1900
- status: "warning",
1901
- message: `${versionIssues.length} plugin(s) require newer CLI version`,
1902
- details: { issues: versionIssues }
1903
- });
1904
- } else {
1905
- diagnostics.push({
1906
- category: "versions",
1907
- status: "ok",
1908
- message: "All plugins compatible with current CLI version",
1909
- details: { issues: [] }
1910
- });
1911
- }
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
+ });
1912
2648
  } catch (err) {
1913
2649
  const errMsg = err instanceof Error ? err.message : String(err);
1914
2650
  diagnostics.push({
@@ -1925,68 +2661,75 @@ var diag = defineSystemCommand({
1925
2661
  errors: diagnostics.filter((d) => d.status === "error").length
1926
2662
  };
1927
2663
  ctx.platform?.logger?.info("Diag command completed", summary);
1928
- return {
1929
- ok: summary.errors === 0,
1930
- diagnostics,
1931
- summary
1932
- };
2664
+ return { ok: summary.errors === 0, diagnostics, summary };
1933
2665
  },
1934
- // 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
1935
2667
  formatter(result, ctx, flags) {
1936
2668
  if (flags.json) {
1937
- console.log(JSON.stringify(result, null, 2));
1938
- } else {
1939
- const hasErrors = result.summary.errors > 0;
1940
- const hasWarnings = result.summary.warnings > 0;
1941
- const summaryItems = [
1942
- `${safeColors.bold("Total Checks")}: ${result.summary.total}`,
1943
- `${safeColors.success("OK")}: ${result.summary.ok}`,
1944
- `${safeColors.warning("Warnings")}: ${result.summary.warnings}`,
1945
- `${safeColors.error("Errors")}: ${result.summary.errors}`
1946
- ];
1947
- const diagItems = [];
1948
- for (const diag2 of result.diagnostics) {
1949
- const icon = diag2.status === "ok" ? safeSymbols.success : diag2.status === "warning" ? safeSymbols.warning : safeSymbols.error;
1950
- const colorize = diag2.status === "ok" ? safeColors.success : diag2.status === "warning" ? safeColors.warning : safeColors.error;
1951
- diagItems.push(`${icon} ${colorize(safeColors.bold(diag2.category))}: ${diag2.message}`);
1952
- if (diag2.status === "warning" && Array.isArray(diag2.details?.issues)) {
1953
- for (const issue of diag2.details.issues) {
1954
- diagItems.push(
1955
- ` ${safeColors.warning(`\u2192 ${issue.plugin}: requires ${issue.required}, found ${issue.current}`)}`
1956
- );
1957
- }
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
+ ]
1958
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}`)}`);
1959
2706
  }
1960
- const nextSteps = [];
1961
- if (hasErrors) {
1962
- nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
1963
- }
1964
- if (hasWarnings) {
1965
- nextSteps.push(`kb marketplace list ${safeColors.muted("List all plugins")}`);
1966
- }
1967
- nextSteps.push(`kb diagnose ${safeColors.muted("Quick environment check")}`);
1968
- const sections = [
1969
- {
1970
- header: "Summary",
1971
- items: summaryItems
1972
- },
1973
- {
1974
- header: "Details",
1975
- items: diagItems
1976
- },
1977
- {
1978
- header: "Next Steps",
1979
- 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}`)}`);
1980
2710
  }
1981
- ];
1982
- if (hasErrors) {
1983
- ctx.ui.error("System Diagnostics", { sections });
1984
- } else if (hasWarnings) {
1985
- ctx.ui.warn("System Diagnostics", { sections });
1986
- } else {
1987
- ctx.ui.success("System Diagnostics", { sections });
1988
2711
  }
1989
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
+ }
1990
2733
  }
1991
2734
  });
1992
2735
  var registryDiagnostics = defineSystemCommand({
@@ -2005,7 +2748,7 @@ var registryDiagnostics = defineSystemCommand({
2005
2748
  },
2006
2749
  async handler(ctx, _argv, flags) {
2007
2750
  const cwd = getContextCwd(ctx);
2008
- const registry2 = await createRegistry({ root: cwd });
2751
+ const registry2 = await createRegistry$1({ root: cwd });
2009
2752
  if (flags.refresh) {
2010
2753
  await registry2.refresh();
2011
2754
  }
@@ -2029,128 +2772,6 @@ var registryDiagnostics = defineSystemCommand({
2029
2772
  }
2030
2773
  }
2031
2774
  });
2032
-
2033
- // src/registry/plugins-state.ts
2034
- init_discover();
2035
- async function clearCache(cwd, options) {
2036
- const cleared = [];
2037
- const cacheDir = path.join(cwd, ".kb", "cache");
2038
- try {
2039
- const entries = await promises.readdir(cacheDir);
2040
- for (const entry of entries) {
2041
- if (entry.includes("manifest") || entry.includes("plugin")) {
2042
- const entryPath = path.join(cacheDir, entry);
2043
- await promises.unlink(entryPath);
2044
- cleared.push(entry);
2045
- }
2046
- }
2047
- } catch {
2048
- }
2049
- const manifestsCachePath = path.join(cwd, ".kb", "marketplace.manifests.json");
2050
- try {
2051
- await promises.unlink(manifestsCachePath);
2052
- cleared.push("marketplace.manifests.json");
2053
- } catch {
2054
- }
2055
- const cliManifestsPath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
2056
- try {
2057
- await promises.unlink(cliManifestsPath);
2058
- if (!cleared.includes("cli-manifests.json")) {
2059
- cleared.push("cli-manifests.json");
2060
- }
2061
- } catch {
2062
- }
2063
- resetInProcCache();
2064
- const modulesCleared = [];
2065
- if (options?.deep) {
2066
- try {
2067
- const cache = __require.cache;
2068
- for (const key in cache) {
2069
- if (key.includes("plugin") || key.includes("manifest") || key.includes("@kb-labs")) {
2070
- delete cache[key];
2071
- modulesCleared.push(key);
2072
- }
2073
- }
2074
- } catch {
2075
- }
2076
- }
2077
- return {
2078
- files: cleared,
2079
- ...options?.deep ? { modules: modulesCleared } : {}
2080
- };
2081
- }
2082
- var pluginsCacheClear = defineSystemCommand({
2083
- name: "clear-cache",
2084
- description: "Clear CLI plugin discovery cache",
2085
- category: "marketplace",
2086
- examples: ["kb marketplace clear-cache", "kb marketplace clear-cache --deep"],
2087
- flags: {
2088
- deep: { type: "boolean", description: "Also clear Node.js module cache" },
2089
- json: { type: "boolean", description: "Output in JSON format" }
2090
- },
2091
- analytics: {
2092
- command: "marketplace:clear-cache",
2093
- startEvent: "MARKETPLACE_CACHE_CLEAR_STARTED",
2094
- finishEvent: "MARKETPLACE_CACHE_CLEAR_FINISHED"
2095
- },
2096
- async handler(ctx, _argv, flags) {
2097
- const deep = flags.deep;
2098
- const cwd = getContextCwd(ctx);
2099
- const result = await clearCache(cwd, { deep });
2100
- ctx.platform?.logger?.info("Cache cleared", {
2101
- filesCount: result.files.length,
2102
- modulesCount: result.modules?.length || 0,
2103
- deep
2104
- });
2105
- if (flags.json) {
2106
- const jsonResult = {
2107
- ok: true,
2108
- action: "cache:clear",
2109
- files: result.files,
2110
- modules: result.modules,
2111
- count: result.files.length,
2112
- modulesCount: result.modules?.length || 0
2113
- };
2114
- console.log(JSON.stringify(jsonResult, null, 2));
2115
- return jsonResult;
2116
- }
2117
- const files = result.files ?? [];
2118
- const modules = result.modules ?? [];
2119
- const sections = [];
2120
- if (files.length > 0) {
2121
- sections.push({
2122
- header: "Cache Files",
2123
- items: files.map((f) => `\u2713 ${f}`)
2124
- });
2125
- } else {
2126
- sections.push({
2127
- items: ["No cache files found"]
2128
- });
2129
- }
2130
- if (deep && modules.length > 0) {
2131
- sections.push({
2132
- header: "Node Modules",
2133
- items: [`Cleared ${modules.length} module(s) from cache`]
2134
- });
2135
- }
2136
- sections.push({
2137
- header: "Summary",
2138
- items: [`Removed ${files.length} file(s)${deep ? ` and ${modules.length} module(s)` : ""}`]
2139
- });
2140
- ctx.ui.success("", {
2141
- title: "\u{1F5D1}\uFE0F Cache Management",
2142
- sections
2143
- });
2144
- return {
2145
- ok: true,
2146
- action: "cache:clear",
2147
- files: result.files,
2148
- modules: result.modules,
2149
- count: result.files.length,
2150
- modulesCount: result.modules?.length || 0
2151
- };
2152
- }
2153
- });
2154
2775
  var docsGenerateCliReference = defineSystemCommand({
2155
2776
  name: "generate-cli-reference",
2156
2777
  description: "Generate CLI reference documentation from command registry",
@@ -2172,21 +2793,20 @@ var docsGenerateCliReference = defineSystemCommand({
2172
2793
  async handler(ctx, argv, flags) {
2173
2794
  const cwd = getContextCwd(ctx);
2174
2795
  const outputPath = flags.output || path.join(cwd, "CLI-REFERENCE.md");
2175
- const commands = registry.list();
2176
- const productGroups = registry.listProductGroups();
2796
+ const registeredCommands2 = registry.listCommands();
2177
2797
  ctx.platform?.logger?.info("Generating CLI reference", {
2178
- commands: commands.length,
2179
- products: productGroups.length,
2798
+ commands: registeredCommands2.length,
2180
2799
  output: outputPath
2181
2800
  });
2182
2801
  const groups = /* @__PURE__ */ new Map();
2183
- for (const cmd of commands) {
2184
- const group = cmd.category || "other";
2802
+ for (const cmd of registeredCommands2) {
2803
+ const group = cmd.manifest.group || "other";
2185
2804
  if (!groups.has(group)) {
2186
2805
  groups.set(group, []);
2187
2806
  }
2188
2807
  groups.get(group).push(cmd);
2189
2808
  }
2809
+ const commands = registeredCommands2;
2190
2810
  const lines = [];
2191
2811
  lines.push("# KB Labs CLI Reference\n");
2192
2812
  lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
@@ -2202,21 +2822,24 @@ var docsGenerateCliReference = defineSystemCommand({
2202
2822
  }
2203
2823
  lines.push("\n---\n");
2204
2824
  for (const groupName of sortedGroups) {
2205
- 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
+ );
2206
2828
  lines.push(`## ${groupName}
2207
2829
  `);
2208
2830
  for (const cmd of cmds) {
2209
- lines.push(`### \`kb ${cmd.name}\`
2831
+ const displayPath = cmd.manifest.segments.join(" ");
2832
+ lines.push(`### \`kb ${displayPath}\`
2210
2833
  `);
2211
- lines.push(`${cmd.describe || "No description"}
2834
+ lines.push(`${cmd.manifest.describe || "No description"}
2212
2835
  `);
2213
- if (cmd.longDescription) {
2214
- lines.push(`${cmd.longDescription}
2836
+ if (cmd.manifest.longDescription) {
2837
+ lines.push(`${cmd.manifest.longDescription}
2215
2838
  `);
2216
2839
  }
2217
- if (cmd.flags && cmd.flags.length > 0) {
2840
+ if (cmd.manifest.flags && cmd.manifest.flags.length > 0) {
2218
2841
  lines.push("**Flags:**\n");
2219
- for (const flag of cmd.flags) {
2842
+ for (const flag of cmd.manifest.flags) {
2220
2843
  const alias = flag.alias ? ` (-${flag.alias})` : "";
2221
2844
  const required = flag.required ? " **(required)**" : "";
2222
2845
  const defaultVal = flag.default !== void 0 ? ` (default: \`${flag.default}\`)` : "";
@@ -2225,16 +2848,16 @@ var docsGenerateCliReference = defineSystemCommand({
2225
2848
  }
2226
2849
  lines.push("");
2227
2850
  }
2228
- if (cmd.examples && cmd.examples.length > 0) {
2851
+ if (cmd.manifest.examples && cmd.manifest.examples.length > 0) {
2229
2852
  lines.push("**Examples:**\n");
2230
2853
  lines.push("```bash");
2231
- for (const example of cmd.examples) {
2854
+ for (const example of cmd.manifest.examples) {
2232
2855
  lines.push(example);
2233
2856
  }
2234
2857
  lines.push("```\n");
2235
2858
  }
2236
- if (cmd.aliases && cmd.aliases.length > 0) {
2237
- 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(", ")}
2238
2861
  `);
2239
2862
  }
2240
2863
  lines.push("---\n");
@@ -3178,6 +3801,7 @@ var authLogin = defineSystemCommand({
3178
3801
  "client-secret": { type: "string", description: "Client Secret from service account registration" },
3179
3802
  json: { type: "boolean", description: "Output in JSON format" }
3180
3803
  },
3804
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3181
3805
  async handler(ctx, _argv, flags) {
3182
3806
  const gatewayUrl = flags["gateway-url"];
3183
3807
  const clientId = flags["client-id"];
@@ -3219,23 +3843,43 @@ var authLogin = defineSystemCommand({
3219
3843
  return { ok: false, error: msg, gatewayUrl, expiresIn: 0 };
3220
3844
  }
3221
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
+ }
3222
3859
  const credentialsManager = new CredentialsManager();
3223
3860
  const expiresAt = Date.now() + tokenData.expiresIn * 1e3;
3224
3861
  await credentialsManager.save({
3225
3862
  gatewayUrl,
3226
3863
  accessToken: tokenData.accessToken,
3227
3864
  refreshToken: tokenData.refreshToken,
3228
- expiresAt
3865
+ expiresAt,
3866
+ handle,
3867
+ namespaceId
3229
3868
  });
3230
3869
  if (flags.json) {
3231
3870
  ctx.ui?.json({
3232
3871
  ok: true,
3233
3872
  gatewayUrl,
3234
- expiresIn: tokenData.expiresIn
3873
+ expiresIn: tokenData.expiresIn,
3874
+ ...handle ? { handle } : {}
3235
3875
  });
3236
3876
  } else {
3237
3877
  ctx.ui?.write?.(`Authenticated with Gateway at ${gatewayUrl}
3238
3878
  `);
3879
+ if (handle) {
3880
+ ctx.ui?.write?.(`Handle: ${handle}
3881
+ `);
3882
+ }
3239
3883
  ctx.ui?.write?.(`Token expires in ${Math.floor(tokenData.expiresIn / 60)} minutes (auto-refresh enabled).
3240
3884
  `);
3241
3885
  }
@@ -3345,6 +3989,7 @@ var authCreateServiceAccount = defineSystemCommand({
3345
3989
  "namespace-id": { type: "string", description: "Namespace ID (1-64 chars)" },
3346
3990
  json: { type: "boolean", description: "Output in JSON format" }
3347
3991
  },
3992
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3348
3993
  async handler(ctx, _argv, flags) {
3349
3994
  const gatewayUrl = flags["gateway-url"];
3350
3995
  const name = flags.name;
@@ -3472,9 +4117,10 @@ var platformSyncCommand = defineSystemCommand({
3472
4117
  sync: result
3473
4118
  };
3474
4119
  },
4120
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3475
4121
  formatter(result, ctx, flags) {
3476
4122
  if (flags.json) {
3477
- console.log(JSON.stringify(result.sync ?? result, null, 2));
4123
+ ctx.ui?.json?.(result.sync ?? result);
3478
4124
  return;
3479
4125
  }
3480
4126
  const sync = result.sync;
@@ -3518,35 +4164,308 @@ var platformSyncCommand = defineSystemCommand({
3518
4164
  items: sync.missing.map((id) => `\u2022 ${id}`)
3519
4165
  });
3520
4166
  }
3521
- if (sync.mismatched.length) {
3522
- sections.push({
3523
- header: "Integrity mismatch",
3524
- items: sync.mismatched.map((id) => `\u2022 ${id}`)
3525
- });
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 };
3526
4409
  }
3527
- if (sync.errors.length) {
3528
- sections.push({
3529
- header: "Errors",
3530
- items: sync.errors.map((e) => `\u2022 ${e.packageId}: ${e.message}`)
3531
- });
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 };
3532
4418
  }
3533
- const nextSteps = [];
3534
- if (!sync.ok) {
3535
- if (sync.missing.length && sync.mode === "validate") {
3536
- 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);
3537
4427
  }
3538
- if (sync.mismatched.length) {
3539
- 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);
3540
4448
  }
4449
+ return { ok: false, error: msg };
3541
4450
  }
3542
- if (nextSteps.length) {
3543
- 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 };
3544
4460
  }
3545
- if (sync.ok) {
3546
- 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 });
3547
4464
  } else {
3548
- ctx.ui.error("Platform Sync", { sections });
4465
+ ctx.ui?.write?.(`\u2713 Webhook revoked: ${label}
4466
+ `);
3549
4467
  }
4468
+ return { ok: true };
3550
4469
  }
3551
4470
  });
3552
4471
 
@@ -3557,9 +4476,6 @@ var infoGroup = defineSystemCommandGroup("info", "System information commands",
3557
4476
  health,
3558
4477
  diag
3559
4478
  ]);
3560
- var marketplaceGroup = defineSystemCommandGroup("marketplace", "Marketplace management commands", [
3561
- pluginsCacheClear
3562
- ]);
3563
4479
  var docsGroup = defineSystemCommandGroup("docs", "Documentation generation commands", [
3564
4480
  docsGenerateCliReference
3565
4481
  ]);
@@ -3584,442 +4500,260 @@ var authGroup = defineSystemCommandGroup("auth", "Gateway authentication command
3584
4500
  var platformGroup = defineSystemCommandGroup("platform", "Platform lifecycle commands", [
3585
4501
  platformSyncCommand
3586
4502
  ]);
3587
- var req = createRequire(import.meta.url);
3588
- var __filename$1 = fileURLToPath(import.meta.url);
3589
- var __dirname$1 = path.dirname(__filename$1);
3590
- function resolveFromCwd(spec, cwd) {
3591
- const cliRoot = path.resolve(__dirname$1, "../../..");
3592
- let monorepoRoot = cwd;
3593
- while (monorepoRoot !== path.dirname(monorepoRoot)) {
3594
- if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
3595
- break;
3596
- }
3597
- monorepoRoot = path.dirname(monorepoRoot);
3598
- }
3599
- const pathsToTry = [
3600
- cwd,
3601
- // Current working directory (user's project)
3602
- cliRoot,
3603
- // CLI installation directory
3604
- monorepoRoot,
3605
- // Monorepo root (if found)
3606
- path.join(monorepoRoot, "../kb-labs-cli"),
3607
- // Explicit kb-labs-cli path
3608
- path.join(monorepoRoot, "../kb-labs-mind")
3609
- // Explicit kb-labs-mind path
3610
- ].filter(Boolean);
3611
- for (const searchPath of pathsToTry) {
3612
- try {
3613
- return req.resolve(spec, { paths: [searchPath] });
3614
- } catch (_e) {
3615
- }
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;
3616
4544
  }
3617
- throw new Error(`Cannot resolve ${spec} from any of: ${pathsToTry.join(", ")}`);
3618
4545
  }
3619
- function checkRequires(manifest, options = {}) {
3620
- const cwd = options.cwd ?? process.cwd();
3621
- if (!manifest.requires || manifest.requires.length === 0) {
3622
- return { available: true };
3623
- }
3624
- let isInMonorepo = false;
3625
- let monorepoRoot = cwd;
3626
- while (monorepoRoot !== path.dirname(monorepoRoot)) {
3627
- if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
3628
- isInMonorepo = true;
3629
- break;
3630
- }
3631
- monorepoRoot = path.dirname(monorepoRoot);
3632
- }
3633
- for (const dep of manifest.requires) {
3634
- try {
3635
- const resolved = resolveFromCwd(dep, cwd);
3636
- if (!resolved.includes("node_modules")) {
3637
- continue;
3638
- }
3639
- } catch (err) {
3640
- if (isInMonorepo) {
3641
- continue;
3642
- }
3643
- const errorMessage = (err instanceof Error ? err.message : "") || "";
3644
- const hasExportsError = errorMessage.includes("exports") || errorMessage.includes("main");
3645
- if (hasExportsError || errorMessage.includes("Cannot resolve")) {
3646
- const parts = dep.startsWith("@") ? dep.slice(1).split("/") : [dep];
3647
- if (parts.length === 0) {
3648
- continue;
3649
- }
3650
- const scope = parts.length >= 2 ? parts[0] : "";
3651
- const packageName = parts.length >= 2 ? parts[1] : parts[0];
3652
- if (!packageName) {
3653
- continue;
3654
- }
3655
- const cliRoot = path.resolve(__dirname$1, "../../..");
3656
- const packagePath = scope ? path.join(cliRoot, "node_modules", "@" + scope, packageName, "package.json") : path.join(cliRoot, "node_modules", packageName, "package.json");
3657
- if (existsSync(packagePath)) {
3658
- continue;
3659
- }
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(" ")} ;;`);
3660
4571
  }
3661
- const isKbLabsPackage = dep.startsWith("@kb-labs/");
3662
- const hint = isKbLabsPackage ? `Run: pnpm add ${dep}` : `Run: npm install ${dep}`;
3663
- return {
3664
- available: false,
3665
- reason: `Missing dependency: ${dep}`,
3666
- hint
3667
- };
3668
- }
3669
- }
3670
- return { available: true };
3671
- }
3672
- var ajv = new Ajv();
3673
- var flagSchema = {
3674
- type: "object",
3675
- required: ["name", "type"],
3676
- additionalProperties: false,
3677
- properties: {
3678
- name: { type: "string" },
3679
- type: { enum: ["string", "boolean", "number", "array"] },
3680
- alias: { type: "string", pattern: "^[a-z]$" },
3681
- // single letter
3682
- description: { type: "string" },
3683
- choices: { type: "array", items: { type: "string" } },
3684
- required: { type: "boolean" },
3685
- default: {}
3686
- // must match type
3687
- }
3688
- };
3689
- var manifestSchema = {
3690
- type: "object",
3691
- required: ["manifestVersion", "id", "group", "describe", "loader"],
3692
- additionalProperties: true,
3693
- properties: {
3694
- manifestVersion: { type: "string", enum: ["1.0"] },
3695
- id: { type: "string", pattern: "^[a-z0-9-]+(?::[a-z0-9-]+)*$" },
3696
- // Simple or namespaced (colon-separated)
3697
- aliases: { type: "array", items: { type: "string" } },
3698
- group: { type: "string" },
3699
- describe: { type: "string" },
3700
- longDescription: { type: "string" },
3701
- requires: { type: "array", items: { type: "string" } },
3702
- flags: {
3703
- type: "array",
3704
- items: flagSchema
3705
- },
3706
- examples: { type: "array", items: { type: "string" } }
3707
- }
3708
- };
3709
- var validateManifest = ajv.compile(manifestSchema);
3710
- var validateFlag = ajv.compile(flagSchema);
3711
- function validateFlagDef(flag, manifestId) {
3712
- if (!validateFlag(flag)) {
3713
- throw new Error(
3714
- `Invalid flag "${flag.name}" in ${manifestId}: ${ajv.errorsText(validateFlag.errors)}`
3715
- );
3716
- }
3717
- if (flag.choices && flag.type !== "string") {
3718
- throw new Error(`Flag "${flag.name}" in ${manifestId}: choices allowed only for string type`);
3719
- }
3720
- if (flag.default !== void 0) {
3721
- 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);
3722
- if (!typeMatches) {
3723
- throw new Error(
3724
- `Flag "${flag.name}" in ${manifestId}: default value type mismatch (expected ${flag.type})`
3725
- );
3726
4572
  }
3727
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");
3728
4597
  }
3729
- function validateManifestStructure(manifest) {
3730
- if (!validateManifest(manifest)) {
3731
- throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
3732
- }
3733
- if (manifest.manifestVersion !== "1.0") {
3734
- throw new Error(
3735
- `Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.id} (expected "1.0")`
3736
- );
3737
- }
3738
- if (manifest.flags && Array.isArray(manifest.flags) && manifest.id) {
3739
- for (const flag of manifest.flags) {
3740
- if (flag && typeof flag === "object" && flag.name) {
3741
- validateFlagDef(flag, manifest.id);
3742
- }
3743
- }
3744
- }
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;
3745
4612
  }
3746
- var SOURCE_PRIORITY = {
3747
- builtin: 4,
3748
- workspace: 3,
3749
- linked: 2,
3750
- node_modules: 1
3751
- };
3752
- function normalizeCommandId(id, _namespace) {
3753
- return id;
4613
+ function detectShell() {
4614
+ const name = path.basename(process.env["SHELL"] ?? "");
4615
+ return ["bash", "zsh", "fish"].includes(name) ? name : "zsh";
3754
4616
  }
3755
- function generateWhitespaceAliases(id) {
3756
- if (!id.includes(":")) {
3757
- 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");
3758
4624
  }
3759
- return [id.replace(":", " ")];
4625
+ return path.join(home, ".zshrc");
3760
4626
  }
3761
- function normalizeAliases(manifest, logger) {
3762
- const log3 = logger ?? platform.logger;
3763
- const aliases = /* @__PURE__ */ new Set();
3764
- manifest.namespace || manifest.group;
3765
- if (manifest.aliases) {
3766
- for (const alias of manifest.aliases) {
3767
- if (!/^[a-z0-9-:]+$/i.test(alias)) {
3768
- log3.warn(`Invalid alias "${alias}" in ${manifest.id}: aliases must be alphanumeric with hyphens or colons`);
3769
- continue;
3770
- }
3771
- aliases.add(alias);
3772
- }
4627
+ function profileLine(shell) {
4628
+ if (shell === "zsh") {
4629
+ return `${MARKER}
4630
+ [ -f "${STATIC_FILE}" ] && source "${STATIC_FILE}"`;
3773
4631
  }
3774
- const whitespaceAliases = generateWhitespaceAliases(manifest.id);
3775
- for (const alias of whitespaceAliases) {
3776
- aliases.add(alias);
4632
+ if (shell === "fish") {
4633
+ return `${MARKER}
4634
+ test -f "${STATIC_FILE}" && source "${STATIC_FILE}"`;
3777
4635
  }
3778
- return Array.from(aliases);
4636
+ return `${MARKER}
4637
+ command -v kb &>/dev/null && eval "$(kb completion bash 2>/dev/null)" || true`;
3779
4638
  }
3780
- function checkNamespaceCollision(manifest, existing, namespace) {
3781
- const existingGroup = existing.manifest.group || "";
3782
- const currentGroup = manifest.group || "";
3783
- if (existingGroup === currentGroup && existingGroup === namespace && existing.manifest.id === manifest.id) {
3784
- throw new Error(
3785
- `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).`
3786
- );
3787
- }
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");
3788
4658
  }
3789
- function getSourcePriority(source) {
3790
- 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);
3791
4662
  }
3792
- function checkCollision(manifest, existing, currentSource, namespace) {
3793
- const existingPriority = getSourcePriority(existing.source);
3794
- const currentPriority = getSourcePriority(currentSource);
3795
- checkNamespaceCollision(manifest, existing, namespace);
3796
- if (currentSource === "workspace" && existing.source === "workspace") {
3797
- throw new Error(
3798
- `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.`
3799
- );
3800
- }
3801
- if (currentPriority > existingPriority) {
3802
- return { shouldShadow: true };
3803
- } else if (currentPriority < existingPriority) {
3804
- return { shouldShadow: false };
3805
- }
3806
- return { shouldShadow: false };
4663
+ function hashFromFile(content) {
4664
+ const m = content.match(/^# hash: ([a-f0-9]+)/m);
4665
+ return m?.[1] ?? null;
3807
4666
  }
3808
- function preflightManifests(discoveryResults, logger) {
3809
- const log3 = logger ?? platform.logger;
3810
- const logLevel = getLogLevel();
3811
- const valid = [];
3812
- const skipped = [];
3813
- for (const result of discoveryResults) {
3814
- const allowed = [];
3815
- for (const manifest of result.manifests) {
3816
- try {
3817
- validateManifestStructure(manifest);
3818
- allowed.push(manifest);
3819
- } catch (error) {
3820
- const reason = error instanceof Error ? error.message : "Validation failed";
3821
- const manifestId = manifest?.id || manifest?.group || result.packageName || "unknown";
3822
- skipped.push({
3823
- id: manifestId,
3824
- source: result.source,
3825
- reason
3826
- });
3827
- log3.warn(`Preflight skipped manifest ${manifestId}: ${reason}`);
3828
- if (logLevel === "debug") {
3829
- process.stderr.write(`[debug][preflight] skipped ${manifestId} (${result.source}): ${reason}
3830
- `);
3831
- }
3832
- }
3833
- }
3834
- if (allowed.length > 0) {
3835
- valid.push({
3836
- ...result,
3837
- manifests: allowed
3838
- });
3839
- }
4667
+ async function autoUpdateCompletion(registry2) {
4668
+ try {
4669
+ await promises.access(STATIC_FILE);
4670
+ } catch {
4671
+ return;
3840
4672
  }
3841
- return { valid, skipped };
3842
- }
3843
- async function registerManifests(discoveryResults, registry2, options = {}) {
3844
- const log3 = options.logger ?? platform.logger;
3845
- const registered = [];
3846
- const skipped = [];
3847
- const globalIds = /* @__PURE__ */ new Map();
3848
- const globalAliases = /* @__PURE__ */ new Map();
3849
- const logLevel = getLogLevel();
3850
- let collisions = 0;
3851
- let errors = 0;
3852
- const sorted = [...discoveryResults].sort((a, b) => {
3853
- const priorityA = getSourcePriority(a.source);
3854
- const priorityB = getSourcePriority(b.source);
3855
- return priorityB - priorityA;
3856
- });
3857
- for (const result of sorted) {
3858
- for (const manifest of result.manifests) {
3859
- const manifestId = manifest.id || manifest.group || "unknown";
3860
- const namespace = manifest.namespace || manifest.group;
3861
- try {
3862
- try {
3863
- validateManifestStructure(manifest);
3864
- } catch (err) {
3865
- throw new Error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
3866
- }
3867
- const normalizedId = normalizeCommandId(manifest.id, namespace);
3868
- if (normalizedId !== manifest.id) {
3869
- log3.warn(`Command ID "${manifest.id}" normalized to "${normalizedId}"`);
3870
- manifest.id = normalizedId;
3871
- }
3872
- const normalizedAliases = normalizeAliases(manifest, log3);
3873
- if (normalizedAliases.length > 0) {
3874
- 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");
3875
4705
  }
3876
- const availability = checkRequires(manifest, {
3877
- cwd: options.cwd ?? result.pkgRoot
3878
- });
3879
- const cmd = {
3880
- manifest,
3881
- v3Manifest: manifest.manifestV2,
3882
- // Extract V3 manifest from legacy field
3883
- available: availability.available,
3884
- unavailableReason: availability.available ? void 0 : availability.reason,
3885
- hint: availability.available ? void 0 : availability.hint,
3886
- source: result.source,
3887
- shadowed: false,
3888
- pkgRoot: result.pkgRoot,
3889
- packageName: result.packageName
3890
- };
4706
+ const rcPath = profilePath(shell);
4707
+ let existing = "";
3891
4708
  try {
3892
- const manifestModule = await import(result.manifestPath);
3893
- if (manifestModule.init && typeof manifestModule.init === "function") {
3894
- await manifestModule.init({
3895
- cwd: result.pkgRoot,
3896
- package: result.packageName,
3897
- manifest: cmd.manifest
3898
- });
3899
- }
3900
- if (manifestModule.register && typeof manifestModule.register === "function") {
3901
- await manifestModule.register({
3902
- registry: registry2,
3903
- command: cmd,
3904
- cwd: result.pkgRoot,
3905
- package: result.packageName
3906
- });
3907
- }
3908
- if (manifestModule.dispose && typeof manifestModule.dispose === "function") {
3909
- cmd._disposeHook = manifestModule.dispose;
3910
- }
3911
- } catch (hookError) {
3912
- const hookMsg = hookError instanceof Error ? hookError.message : String(hookError);
3913
- log3.debug(`Lifecycle hooks unavailable for ${manifest.id}: ${hookMsg}`);
3914
- }
3915
- const canonicalKey = manifest.group ? `${manifest.group}:${manifest.id}` : manifest.id;
3916
- const existing = globalIds.get(canonicalKey);
3917
- if (existing) {
3918
- const collision = checkCollision(manifest, existing, result.source, namespace);
3919
- if (collision.shouldShadow) {
3920
- existing.shadowed = true;
3921
- globalIds.set(canonicalKey, cmd);
3922
- if (logLevel === "info" || logLevel === "debug") {
3923
- log3.info(`${canonicalKey} from ${result.source} shadows ${existing.source} version`);
3924
- }
3925
- } else {
3926
- cmd.shadowed = true;
3927
- if (logLevel === "info" || logLevel === "debug") {
3928
- log3.info(`${canonicalKey} from ${result.source} shadowed by ${existing.source} version`);
3929
- }
3930
- }
3931
- } else {
3932
- globalIds.set(canonicalKey, cmd);
4709
+ existing = await promises.readFile(rcPath, "utf8");
4710
+ } catch {
3933
4711
  }
3934
- const aliasesToCheck = manifest.aliases || [];
3935
- for (const alias of aliasesToCheck) {
3936
- const existingAlias = globalAliases.get(alias);
3937
- if (existingAlias) {
3938
- if (existingAlias.manifest.id === manifest.id) {
3939
- continue;
3940
- }
3941
- const existingPriority = getSourcePriority(existingAlias.source);
3942
- const currentPriority = getSourcePriority(result.source);
3943
- if (currentPriority > existingPriority) {
3944
- existingAlias.shadowed = true;
3945
- globalAliases.set(alias, cmd);
3946
- if (logLevel === "info" || logLevel === "debug") {
3947
- log3.info(`Alias "${alias}" from ${result.source} shadows ${existingAlias.source} version`);
3948
- }
3949
- } else if (currentPriority < existingPriority) {
3950
- if (logLevel === "info" || logLevel === "debug") {
3951
- log3.info(`Alias "${alias}" from ${result.source} shadowed by ${existingAlias.source} version`);
3952
- }
3953
- continue;
3954
- } else {
3955
- collisions++;
3956
- throw new Error(
3957
- `Alias collision: "${alias}" used by both ${manifest.id} and ${existingAlias.manifest.id}. Rename one alias or use a different namespace.`
3958
- );
3959
- }
3960
- }
3961
- if (globalIds.has(alias)) {
3962
- const conflictingCmd = globalIds.get(alias);
3963
- const existingPriority = getSourcePriority(conflictingCmd.source);
3964
- const currentPriority = getSourcePriority(result.source);
3965
- if (currentPriority > existingPriority) {
3966
- log3.warn(`Alias "${alias}" conflicts with command ID "${alias}". Alias will shadow command.`);
3967
- globalAliases.set(alias, cmd);
3968
- } else {
3969
- throw new Error(
3970
- `Alias "${alias}" conflicts with existing command ID "${alias}". Rename the alias or use a different name.`
3971
- );
3972
- }
3973
- } else {
3974
- globalAliases.set(alias, cmd);
3975
- }
4712
+ const alreadyInstalled = existing.includes(MARKER);
4713
+ if (!alreadyInstalled) {
4714
+ await promises.appendFile(rcPath, `
4715
+ ${profileLine(shell)}
4716
+ `);
3976
4717
  }
3977
- if (!cmd.shadowed) {
3978
- registry2.registerManifest(cmd);
4718
+ const aliasAlreadyInstalled = existing.includes(ALIAS_MARKER);
4719
+ if (!aliasAlreadyInstalled) {
4720
+ await promises.appendFile(rcPath, `
4721
+ ${aliasLine(shell)}
4722
+ `);
3979
4723
  }
3980
- registered.push(cmd);
3981
- } catch (error) {
3982
- errors++;
3983
- const reason = error instanceof Error ? error.message : String(error);
3984
- skipped.push({
3985
- id: manifestId,
3986
- source: result.source,
3987
- reason
3988
- });
3989
- log3.error(`Skipped manifest ${manifestId} (${result.source}): ${reason}`);
3990
- continue;
4724
+ return { ok: true, status: "success", shell, profilePath: rcPath, alreadyInstalled, aliasInstalled: !aliasAlreadyInstalled };
3991
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
+ `);
3992
4755
  }
3993
- }
3994
- if (skipped.length > 0) {
3995
- log3.warn(`Skipped ${skipped.length} manifest(s) during registration`);
3996
- for (const skip of skipped) {
3997
- log3.warn(` \u2022 ${skip.id} [${skip.source}] \u2192 ${skip.reason}`);
3998
- }
3999
- }
4000
- return {
4001
- registered,
4002
- skipped,
4003
- collisions,
4004
- errors
4005
- };
4006
- }
4007
- async function disposeAllPlugins(registry2, logger) {
4008
- const log3 = logger ?? platform.logger;
4009
- const manifests = registry2.listManifests();
4010
- const disposePromises = [];
4011
- for (const cmd of manifests) {
4012
- const disposeHook = cmd._disposeHook;
4013
- if (disposeHook && typeof disposeHook === "function") {
4014
- disposePromises.push(
4015
- Promise.resolve(disposeHook()).catch((err) => {
4016
- const errMsg = err instanceof Error ? err.message : String(err);
4017
- log3.warn(`Dispose hook failed for ${cmd.manifest.id}: ${errMsg}`);
4018
- })
4019
- );
4020
- }
4021
- }
4022
- await Promise.allSettled(disposePromises);
4756
+ });
4023
4757
  }
4024
4758
  var log2 = platform.logger.child({ module: "cli:shutdown" });
4025
4759
  var hooks = /* @__PURE__ */ new Set();
@@ -4064,15 +4798,18 @@ async function registerBuiltinCommands(input = {}) {
4064
4798
  return;
4065
4799
  }
4066
4800
  _registered = true;
4801
+ registry.setLogger(log3);
4067
4802
  registry.markPartial(true);
4068
4803
  registeredCommands.length = 0;
4069
4804
  registry.registerGroup(infoGroup);
4070
- registry.registerGroup(marketplaceGroup);
4071
4805
  registry.registerGroup(registryGroup);
4072
4806
  registry.registerGroup(docsGroup);
4073
4807
  registry.registerGroup(logsGroup);
4074
4808
  registry.registerGroup(authGroup);
4075
4809
  registry.registerGroup(platformGroup);
4810
+ registry.registerGroup(webhookGroup);
4811
+ registry.register(createCompletionCommand(registry));
4812
+ registry.register(diag);
4076
4813
  try {
4077
4814
  const cwd = getContextCwd({ cwd: input.cwd });
4078
4815
  const env = input.env ?? process.env;
@@ -4124,402 +4861,217 @@ async function registerBuiltinCommands(input = {}) {
4124
4861
  _registered = false;
4125
4862
  return;
4126
4863
  }
4864
+ await autoUpdateCompletion(registry).catch(() => {
4865
+ });
4127
4866
  registerShutdownHook(async () => {
4128
4867
  await disposeAllPlugins(registry, log3);
4129
4868
  });
4130
4869
  }
4131
- function renderGroupHelp(group) {
4870
+ function renderGroupHelp(opts) {
4132
4871
  const tracker = new TimingTracker();
4133
- const sortedCommands = [...group.commands].sort(
4134
- (a, b) => a.name.localeCompare(b.name)
4135
- );
4872
+ const groupName = opts.segments.join(" ");
4136
4873
  const sections = [];
4137
- sections.push({
4138
- items: [
4139
- `${safeColors.bold("Description")}: ${group.describe}`,
4140
- `${safeColors.bold("Commands")}: ${sortedCommands.length}`
4141
- ]
4142
- });
4143
- const commandDisplayNames = sortedCommands.map((cmd) => {
4144
- return cmd.name.replace(/:/g, " ");
4145
- });
4146
- const maxNameLength = Math.max(...commandDisplayNames.map((name) => name.length));
4147
- const commandItems = [];
4148
- for (let i = 0; i < sortedCommands.length; i++) {
4149
- const cmd = sortedCommands[i];
4150
- const displayName = commandDisplayNames[i];
4151
- if (!cmd || !displayName) {
4152
- continue;
4153
- }
4154
- const paddedName = displayName.padEnd(maxNameLength);
4155
- const description = cmd.describe || "No description";
4156
- commandItems.push(`${safeColors.primary(paddedName)} ${safeColors.muted(description)}`);
4157
- if (cmd.examples && cmd.examples.length > 0) {
4158
- for (const example of cmd.examples.slice(0, 2)) {
4159
- commandItems.push(` ${safeColors.muted(example)}`);
4160
- }
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 });
4161
4899
  }
4900
+ } else {
4901
+ const sorted = [...opts.childKeys].sort();
4902
+ sections.push({
4903
+ header: "Available",
4904
+ items: sorted.map((k) => safeColors.primary(` ${k}`))
4905
+ });
4162
4906
  }
4163
4907
  sections.push({
4164
- header: "Available commands",
4165
- items: commandItems
4166
- });
4167
- sections.push({
4168
- header: "Next Steps",
4169
- items: [
4170
- `kb ${group.name} <command> --help ${safeColors.muted("Get help for a specific command")}`
4171
- ]
4908
+ items: [safeColors.muted(`kb ${groupName} <command> --help`)]
4172
4909
  });
4173
4910
  return sideBorderBox({
4174
- title: `\u{1F4E6} ${group.name}`,
4911
+ title: groupName,
4175
4912
  sections,
4176
4913
  status: "success",
4177
4914
  timing: tracker.total()
4178
4915
  });
4179
4916
  }
4180
- function collectManifestVersions(commands) {
4181
- const versions = /* @__PURE__ */ new Set();
4182
- for (const cmd of commands) {
4183
- const schema = cmd.manifest.manifestV2?.schema;
4184
- if (typeof schema === "string") {
4185
- const tail = schema.split("/").pop() ?? schema;
4186
- const match = tail.match(/\d+/);
4187
- versions.add(match ? `v${match[0]}` : tail);
4188
- } else {
4189
- versions.add("v2");
4190
- }
4917
+ function truncate(s, max) {
4918
+ if (s.length <= max) {
4919
+ return s;
4191
4920
  }
4192
- return Array.from(versions).sort();
4921
+ return s.slice(0, max - 1) + "\u2026";
4193
4922
  }
4194
- function renderGlobalHelp(groups, standalone) {
4195
- const lines = [];
4196
- lines.push(
4197
- colors.cyan(colors.bold("KB Labs CLI")) + " - Project management and automation tool"
4198
- );
4199
- lines.push("");
4200
- lines.push(colors.bold("Usage:") + " kb [command] [options]");
4201
- lines.push("");
4202
- if (groups.length > 0) {
4203
- lines.push(colors.bold("Product Commands:"));
4204
- lines.push("");
4205
- for (const group of groups.sort((a, b) => a.name.localeCompare(b.name))) {
4206
- lines.push(
4207
- ` ${colors.cyan(group.name.padEnd(12))} ${colors.dim(
4208
- group.describe ?? ""
4209
- )}`
4210
- );
4211
- }
4212
- lines.push("");
4213
- }
4214
- if (standalone.length > 0) {
4215
- lines.push(colors.bold("System Commands:"));
4216
- lines.push("");
4217
- for (const cmd of standalone.sort((a, b) => a.name.localeCompare(b.name))) {
4218
- lines.push(
4219
- ` ${colors.cyan(cmd.name.padEnd(12))} ${colors.dim(cmd.describe)}`
4220
- );
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
+ });
4221
4969
  }
4222
- lines.push("");
4970
+ sections.push({ header: "Plugins", items });
4223
4971
  }
4224
- lines.push(colors.bold("Global Options:"));
4225
- lines.push("");
4226
- lines.push(
4227
- ` ${colors.cyan("--help".padEnd(12))} ${colors.dim("Show help information")}`
4228
- );
4229
- lines.push(
4230
- ` ${colors.cyan("--version".padEnd(12))} ${colors.dim("Show CLI version")}`
4231
- );
4232
- lines.push(
4233
- ` ${colors.cyan("--json".padEnd(12))} ${colors.dim("Output in JSON format")}`
4234
- );
4235
- lines.push(
4236
- ` ${colors.cyan("--quiet".padEnd(12))} ${colors.dim("Suppress detailed output")}`
4237
- );
4238
- lines.push("");
4239
- lines.push(
4240
- colors.dim(
4241
- "Use 'kb <group> --help' to see commands for a specific product."
4242
- )
4243
- );
4244
- 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" });
4245
4974
  }
4246
- function renderGlobalHelpNew(registry2) {
4247
- const products = registry2.listProductGroups();
4248
- const systemGroups = registry2.listGroups?.() || [];
4249
- const commandsInGroups = /* @__PURE__ */ new Set();
4250
- for (const group of systemGroups) {
4251
- for (const cmd of group.commands) {
4252
- commandsInGroups.add(cmd.name);
4253
- for (const alias of cmd.aliases || []) {
4254
- commandsInGroups.add(alias);
4255
- }
4256
- }
4257
- }
4258
- const standalone = registry2.list().filter((cmd) => {
4259
- if (commandsInGroups.has(cmd.name)) {
4260
- return false;
4261
- }
4262
- return !cmd.category || cmd.category === "system";
4263
- });
4975
+ function renderPluginsHelp(registry2) {
4976
+ const commands = registry2.listCommandsUnder(["marketplace"]);
4264
4977
  const sections = [];
4265
- if (products.length > 0) {
4266
- const maxProductNameLength = Math.max(
4267
- ...products.map((p) => p.name.length),
4268
- 12
4269
- );
4270
- const productItems = products.sort((a, b) => a.name.localeCompare(b.name)).map((product) => {
4271
- const availableCount = product.commands.filter(
4272
- (c) => c.available && !c.shadowed
4273
- ).length;
4274
- const badge = colors.green(`(${availableCount})`);
4275
- return `${colors.cyan(
4276
- product.name.padEnd(maxProductNameLength)
4277
- )} ${badge}`;
4278
- });
4279
- sections.push({
4280
- header: "Products",
4281
- items: productItems
4282
- });
4283
- }
4284
- if (systemGroups.length > 0) {
4285
- const maxGroupNameLength = Math.max(
4286
- ...systemGroups.map((g) => g.name.length),
4287
- 20
4288
- );
4289
- const groupItems = systemGroups.sort((a, b) => a.name.localeCompare(b.name)).map((group) => {
4290
- const commandCount = group.commands.length;
4291
- const badge = colors.green(`(${commandCount})`);
4292
- return `${colors.cyan(group.name.padEnd(maxGroupNameLength))} ${badge}`;
4293
- });
4294
- sections.push({
4295
- header: "System Commands",
4296
- items: groupItems
4297
- });
4298
- }
4299
- if (standalone.length > 0) {
4300
- const maxCommandNameLength = Math.max(
4301
- ...standalone.map((c) => c.name.length),
4302
- 12
4303
- );
4304
- const standaloneItems = standalone.sort((a, b) => a.name.localeCompare(b.name)).map(
4305
- (cmd) => `${colors.cyan(cmd.name.padEnd(maxCommandNameLength))} ${colors.dim(
4306
- cmd.describe
4307
- )}`
4308
- );
4309
- sections.push({
4310
- header: "Other Commands",
4311
- 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)}`;
4312
4983
  });
4984
+ sections.push({ header: "Plugin Management", items });
4313
4985
  }
4314
- const globalOptions = [
4315
- { name: "--help", desc: "Show help information" },
4316
- { name: "--version", desc: "Show CLI version" },
4317
- { name: "--json", desc: "Output in JSON format" },
4318
- { name: "--quiet", desc: "Suppress detailed output" }
4319
- ];
4320
- const maxOptionNameLength = Math.max(
4321
- ...globalOptions.map((o) => o.name.length),
4322
- 12
4323
- );
4324
- const optionItems = globalOptions.map(
4325
- (option) => `${colors.cyan(option.name.padEnd(maxOptionNameLength))} ${colors.dim(
4326
- option.desc
4327
- )}`
4328
- );
4329
- sections.push({
4330
- header: "Global Options",
4331
- items: optionItems
4332
- });
4333
- const nextStepsItems = [];
4334
- const firstProduct = products[0];
4335
- if (firstProduct) {
4336
- nextStepsItems.push(
4337
- `${colors.cyan(
4338
- `kb ${firstProduct.name} --help`
4339
- )} ${colors.dim("Explore product commands")}`
4340
- );
4341
- }
4342
- const marketplaceGroup2 = systemGroups.find((g) => g.name === "marketplace");
4343
- if (marketplaceGroup2) {
4344
- nextStepsItems.push(
4345
- `${colors.cyan("kb marketplace")} ${colors.dim("Open marketplace commands")}`
4346
- );
4347
- }
4348
- nextStepsItems.push("");
4349
- nextStepsItems.push(
4350
- colors.dim("Use 'kb <product> --help' or 'kb <group> --help' to see commands for a specific product or group.")
4351
- );
4352
4986
  sections.push({
4353
- header: "Next Steps",
4354
- items: nextStepsItems
4355
- });
4356
- return sideBorderBox({
4357
- title: "KB Labs CLI",
4358
- sections,
4359
- status: "info"
4987
+ items: [colors.dim("kb marketplace <command> --help")]
4360
4988
  });
4989
+ return sideBorderBox({ title: "\u{1F9E9} Plugin Management", sections, status: "info" });
4361
4990
  }
4362
- function renderPluginsHelp(registry2) {
4363
- const tracker = new TimingTracker();
4364
- registry2.list().filter(
4365
- (cmd) => cmd.name?.startsWith("marketplace:") || cmd.category === "system"
4366
- );
4367
- const sections = [];
4368
- const commandMap = {
4369
- "marketplace:install": "Install package(s) from marketplace",
4370
- "marketplace:update": "Update package(s) from marketplace",
4371
- "marketplace:list": "List all discovered plugins",
4372
- "marketplace:enable": "Enable a plugin",
4373
- "marketplace:disable": "Disable a plugin",
4374
- "marketplace:link": "Link a local plugin for development",
4375
- "marketplace:unlink": "Unlink a local plugin",
4376
- "marketplace:doctor": "Diagnose plugin issues",
4377
- "marketplace:scaffold": "Generate a new plugin template",
4378
- "marketplace:clear-cache": "Clear plugin discovery cache"
4379
- };
4380
- const maxLength = Math.max(
4381
- ...Object.keys(commandMap).map((c) => c.length),
4382
- 20
4383
- );
4384
- const commandItems = Object.entries(commandMap).map(
4385
- ([cmdName, desc]) => `${colors.cyan(cmdName.padEnd(maxLength))} ${colors.dim(desc)}`
4386
- );
4387
- sections.push({
4388
- header: "Plugin Management Commands",
4389
- items: commandItems
4390
- });
4391
- const examples = [
4392
- `kb marketplace list ${colors.dim("List all plugins")}`,
4393
- `kb marketplace enable @kb-labs/devlink-cli ${colors.dim("Enable a plugin")}`,
4394
- `kb marketplace doctor ${colors.dim("Diagnose plugin issues")}`,
4395
- `kb marketplace scaffold my-plugin ${colors.dim("Generate plugin template")}`
4396
- ];
4397
- sections.push({
4398
- header: "Examples",
4399
- items: examples.map((ex) => colors.dim(ex))
4400
- });
4401
- sections.push({
4402
- header: "Next Steps",
4403
- items: [colors.dim("Use 'kb marketplace <command> --help' for detailed help")]
4404
- });
4405
- return sideBorderBox({
4406
- title: "\u{1F9E9} Plugin Management",
4407
- sections,
4408
- status: "info",
4409
- timing: tracker.total()
4410
- });
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";
4411
5001
  }
4412
5002
  function renderProductHelp(groupName, commands) {
4413
- const tracker = new TimingTracker();
4414
5003
  const sections = [];
4415
- const manifestVersions = collectManifestVersions(commands);
4416
- if (manifestVersions.length > 0) {
4417
- sections.push({
4418
- header: "Manifest",
4419
- items: [colors.cyan(manifestVersions.join(" + "))]
4420
- });
4421
- }
4422
5004
  const availableMap = /* @__PURE__ */ new Map();
4423
5005
  const unavailableMap = /* @__PURE__ */ new Map();
4424
5006
  for (const cmd of commands) {
5007
+ const key = cmd.manifest.segments.join(" ");
4425
5008
  if (cmd.available && !cmd.shadowed) {
4426
- if (!availableMap.has(cmd.manifest.id)) {
4427
- availableMap.set(cmd.manifest.id, cmd);
5009
+ if (!availableMap.has(key)) {
5010
+ availableMap.set(key, cmd);
4428
5011
  }
4429
- } else if (!unavailableMap.has(cmd.manifest.id)) {
4430
- unavailableMap.set(cmd.manifest.id, cmd);
5012
+ } else if (!unavailableMap.has(key)) {
5013
+ unavailableMap.set(key, cmd);
4431
5014
  }
4432
5015
  }
4433
- const available = Array.from(availableMap.values()).sort(
4434
- (a, b) => a.manifest.id.localeCompare(b.manifest.id)
4435
- );
5016
+ const available = Array.from(availableMap.values());
4436
5017
  const unavailable = Array.from(unavailableMap.values()).sort(
4437
- (a, b) => a.manifest.id.localeCompare(b.manifest.id)
5018
+ (a, b) => a.manifest.segments.join(" ").localeCompare(b.manifest.segments.join(" "))
4438
5019
  );
5020
+ const hasUnavailable = unavailable.length > 0;
5021
+ const displayPath = (cmd) => cmd.manifest.segments.join(" ");
4439
5022
  const maxLength = Math.max(
4440
- ...[...available, ...unavailable].map((c) => c.manifest.id.replace(/:/g, " ").length),
5023
+ ...[...available, ...unavailable].map((c) => displayPath(c).length),
4441
5024
  20
4442
5025
  );
4443
- const availableItems = [];
4444
- for (const cmd of available) {
4445
- const status = colors.green("\u2713");
4446
- const displayId = cmd.manifest.id.replace(/:/g, " ");
4447
- const paddedId = displayId.padEnd(maxLength);
4448
- availableItems.push(
4449
- `${status} ${colors.cyan(paddedId)} ${colors.dim(
4450
- cmd.manifest.describe
4451
- )}`
4452
- );
4453
- if (cmd.manifest.examples && cmd.manifest.examples.length > 0) {
4454
- for (const example of cmd.manifest.examples.slice(0, 2)) {
4455
- 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, []);
4456
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 });
4457
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 });
4458
5053
  }
4459
- sections.push({
4460
- header: "Available Commands",
4461
- items: availableItems
4462
- });
4463
- if (unavailable.length > 0) {
5054
+ if (hasUnavailable) {
4464
5055
  const unavailableItems = [];
4465
5056
  for (const cmd of unavailable) {
4466
- const status = colors.red("\u2717");
4467
- const displayId = cmd.manifest.id.replace(/:/g, " ");
4468
- const paddedId = displayId.padEnd(maxLength);
4469
- unavailableItems.push(
4470
- `${status} ${colors.dim(paddedId)} ${colors.dim(
4471
- cmd.manifest.describe
4472
- )}`
4473
- );
5057
+ const paddedPath = displayPath(cmd).padEnd(maxLength);
5058
+ unavailableItems.push(`${colors.red("\u2717")} ${colors.dim(paddedPath)} ${colors.dim(cmd.manifest.describe)}`);
4474
5059
  if (cmd.unavailableReason) {
4475
- unavailableItems.push(
4476
- ` ${colors.red(`Reason: ${cmd.unavailableReason}`)}`
4477
- );
5060
+ unavailableItems.push(` ${colors.red(`Reason: ${cmd.unavailableReason}`)}`);
4478
5061
  }
4479
5062
  if (cmd.hint) {
4480
5063
  unavailableItems.push(` ${colors.yellow(`Hint: ${cmd.hint}`)}`);
4481
5064
  }
4482
5065
  }
4483
- sections.push({
4484
- header: "Unavailable",
4485
- items: unavailableItems
4486
- });
4487
- }
4488
- const uniqueCommands = /* @__PURE__ */ new Map();
4489
- for (const cmd of available) {
4490
- if (!uniqueCommands.has(cmd.manifest.id)) {
4491
- uniqueCommands.set(cmd.manifest.id, cmd);
4492
- }
4493
- }
4494
- const nextStepsItems = Array.from(uniqueCommands.values()).slice(0, 3).map((cmd) => {
4495
- const displayId = cmd.manifest.id.replace(/:/g, " ");
4496
- return `${colors.cyan(`kb ${displayId}`)} ${colors.dim(
4497
- cmd.manifest.describe
4498
- )}`;
4499
- });
4500
- if (nextStepsItems.length === 0) {
4501
- nextStepsItems.push(colors.dim("No available commands in this product"));
4502
- } else {
4503
- nextStepsItems.push("");
4504
- nextStepsItems.push(
4505
- colors.dim(
4506
- `Use 'kb ${groupName} <command> --help' for detailed help`
4507
- )
4508
- );
5066
+ sections.push({ header: "Unavailable", items: unavailableItems });
4509
5067
  }
4510
- nextStepsItems.push("");
4511
- nextStepsItems.push(
4512
- colors.dim("Use 'kb --help' to see all products and system commands.")
4513
- );
4514
5068
  sections.push({
4515
- header: "Next Steps",
4516
- items: nextStepsItems
5069
+ items: [colors.dim(`kb ${groupName} <command> --help`)]
4517
5070
  });
4518
5071
  return sideBorderBox({
4519
5072
  title: groupName,
4520
5073
  sections,
4521
- status: "info",
4522
- timing: tracker.total()
5074
+ status: "info"
4523
5075
  });
4524
5076
  }
4525
5077
  function renderManifestCommandHelp(registered) {
@@ -4609,9 +5161,47 @@ function groupCommands(commands, onlyAvailable) {
4609
5161
  );
4610
5162
  }
4611
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
+
4612
5202
  // src/index.ts
4613
5203
  init_discover();
4614
5204
 
4615
- 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 };
4616
5206
  //# sourceMappingURL=index.js.map
4617
5207
  //# sourceMappingURL=index.js.map