@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/{discover-CCE84_5E.d.ts → discover-DxmYCrcJ.d.ts} +135 -104
- package/dist/index.d.ts +24 -13
- package/dist/index.js +2124 -1534
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.ts +12 -12
- package/dist/registry/index.js +581 -659
- package/dist/registry/index.js.map +1 -1
- package/package.json +23 -23
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 {
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
...issue,
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
106
|
-
id: z.string().min(1)
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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"
|
|
335
|
-
throw new Error(`
|
|
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
|
|
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:
|
|
351
|
-
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(
|
|
319
|
+
loader: createManifestV3Loader(cmd.path),
|
|
358
320
|
package: pkgName,
|
|
359
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
840
|
+
let manifestMtimeChanged = false;
|
|
866
841
|
try {
|
|
867
|
-
manifestStat = await promises.stat(manifestFsPath);
|
|
868
|
-
|
|
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 (
|
|
848
|
+
if (manifestMtimeChanged) {
|
|
877
849
|
try {
|
|
878
|
-
const currentHash = await
|
|
850
|
+
const currentHash = await computeManifestIntegrity(manifestFsPath);
|
|
879
851
|
if (currentHash !== entry.manifestHash) {
|
|
880
|
-
log("debug", `Package cache invalidated: manifest
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1103
|
+
console.error(`${prefix} [discover] ${message}`, fields);
|
|
1121
1104
|
} else {
|
|
1122
|
-
|
|
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/
|
|
1139
|
-
function
|
|
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
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1167
|
+
dfs(node, [...prefix]);
|
|
1168
|
+
return results;
|
|
1162
1169
|
}
|
|
1163
|
-
var
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
this.
|
|
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
|
-
|
|
1186
|
-
|
|
1187
|
-
this.
|
|
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
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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
|
-
|
|
1231
|
-
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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
|
-
|
|
1252
|
-
|
|
1253
|
-
return space;
|
|
1316
|
+
if (node.command) {
|
|
1317
|
+
return { type: "command", command: node.command, rest: [] };
|
|
1254
1318
|
}
|
|
1255
|
-
|
|
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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
-
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
-
*
|
|
1359
|
+
* List all leaf plugin commands under a given node path.
|
|
1313
1360
|
*/
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
const
|
|
1318
|
-
if (!
|
|
1319
|
-
|
|
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
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
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
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
* Returns canonicalId if found in plugin aliases, undefined otherwise.
|
|
1403
|
+
* Collect all plugin commands reachable from node.
|
|
1349
1404
|
*/
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
|
1437
|
+
return { type: "ambiguous", input: tokens, candidates: deep };
|
|
1359
1438
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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
|
-
|
|
1365
|
-
return
|
|
1476
|
+
if (segs.length > MAX_PATH_DEPTH) {
|
|
1477
|
+
return `path too deep: ${segs.length} segments (max ${MAX_PATH_DEPTH})`;
|
|
1366
1478
|
}
|
|
1367
|
-
|
|
1368
|
-
return
|
|
1479
|
+
if (RESERVED_NAMESPACES.has(segs[0])) {
|
|
1480
|
+
return `"${segs[0]}" is a reserved namespace`;
|
|
1369
1481
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
1504
|
+
// ─── Registration ──────────────────────────────────────────────────────────
|
|
1505
|
+
register(cmd) {
|
|
1506
|
+
this.systemRouter.insertSystemCommand(cmd);
|
|
1379
1507
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
-
|
|
1417
|
-
|
|
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
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
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
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
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
|
-
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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
|
|
1589
|
+
return this.pluginRouter.resolve(tokens);
|
|
1486
1590
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
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
|
-
|
|
1500
|
-
|
|
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
|
-
|
|
1509
|
-
|
|
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
|
-
|
|
1524
|
-
return this.
|
|
1607
|
+
listCommandsUnder(segments) {
|
|
1608
|
+
return this.pluginRouter.listUnder(segments);
|
|
1525
1609
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
-
|
|
1546
|
-
|
|
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
|
-
|
|
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
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
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
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
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
|
|
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
|
|
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
|
|
1879
|
-
|
|
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
|
|
1883
|
-
const
|
|
1884
|
-
if (
|
|
1885
|
-
|
|
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
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
|
2666
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
1935
2667
|
formatter(result, ctx, flags) {
|
|
1936
2668
|
if (flags.json) {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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
|
|
2176
|
-
const productGroups = registry.listProductGroups();
|
|
2796
|
+
const registeredCommands2 = registry.listCommands();
|
|
2177
2797
|
ctx.platform?.logger?.info("Generating CLI reference", {
|
|
2178
|
-
commands:
|
|
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
|
|
2184
|
-
const group = cmd.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
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
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
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
|
-
|
|
3539
|
-
|
|
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 (
|
|
3543
|
-
|
|
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
|
-
|
|
3546
|
-
|
|
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
|
|
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
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
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
|
|
3620
|
-
const
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
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
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
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
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
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
|
|
3756
|
-
|
|
3757
|
-
|
|
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
|
|
4625
|
+
return path.join(home, ".zshrc");
|
|
3760
4626
|
}
|
|
3761
|
-
function
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
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
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
4632
|
+
if (shell === "fish") {
|
|
4633
|
+
return `${MARKER}
|
|
4634
|
+
test -f "${STATIC_FILE}" && source "${STATIC_FILE}"`;
|
|
3777
4635
|
}
|
|
3778
|
-
return
|
|
4636
|
+
return `${MARKER}
|
|
4637
|
+
command -v kb &>/dev/null && eval "$(kb completion bash 2>/dev/null)" || true`;
|
|
3779
4638
|
}
|
|
3780
|
-
function
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
`
|
|
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
|
|
3790
|
-
|
|
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
|
|
3793
|
-
const
|
|
3794
|
-
|
|
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
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
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
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
const
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
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
|
|
3877
|
-
|
|
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
|
-
|
|
3893
|
-
|
|
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
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
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
|
-
|
|
3978
|
-
|
|
4718
|
+
const aliasAlreadyInstalled = existing.includes(ALIAS_MARKER);
|
|
4719
|
+
if (!aliasAlreadyInstalled) {
|
|
4720
|
+
await promises.appendFile(rcPath, `
|
|
4721
|
+
${aliasLine(shell)}
|
|
4722
|
+
`);
|
|
3979
4723
|
}
|
|
3980
|
-
|
|
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(
|
|
4870
|
+
function renderGroupHelp(opts) {
|
|
4132
4871
|
const tracker = new TimingTracker();
|
|
4133
|
-
const
|
|
4134
|
-
(a, b) => a.name.localeCompare(b.name)
|
|
4135
|
-
);
|
|
4872
|
+
const groupName = opts.segments.join(" ");
|
|
4136
4873
|
const sections = [];
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
if (
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
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
|
-
|
|
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:
|
|
4911
|
+
title: groupName,
|
|
4175
4912
|
sections,
|
|
4176
4913
|
status: "success",
|
|
4177
4914
|
timing: tracker.total()
|
|
4178
4915
|
});
|
|
4179
4916
|
}
|
|
4180
|
-
function
|
|
4181
|
-
|
|
4182
|
-
|
|
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
|
|
4921
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
4193
4922
|
}
|
|
4194
|
-
function renderGlobalHelp(
|
|
4195
|
-
const
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
)
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
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
|
-
|
|
4970
|
+
sections.push({ header: "Plugins", items });
|
|
4223
4971
|
}
|
|
4224
|
-
|
|
4225
|
-
|
|
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
|
|
4247
|
-
const
|
|
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 (
|
|
4266
|
-
const
|
|
4267
|
-
|
|
4268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
const
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
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(
|
|
4427
|
-
availableMap.set(
|
|
5009
|
+
if (!availableMap.has(key)) {
|
|
5010
|
+
availableMap.set(key, cmd);
|
|
4428
5011
|
}
|
|
4429
|
-
} else if (!unavailableMap.has(
|
|
4430
|
-
unavailableMap.set(
|
|
5012
|
+
} else if (!unavailableMap.has(key)) {
|
|
5013
|
+
unavailableMap.set(key, cmd);
|
|
4431
5014
|
}
|
|
4432
5015
|
}
|
|
4433
|
-
const available = Array.from(availableMap.values())
|
|
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.
|
|
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
|
|
5023
|
+
...[...available, ...unavailable].map((c) => displayPath(c).length),
|
|
4441
5024
|
20
|
|
4442
5025
|
);
|
|
4443
|
-
const
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
const
|
|
4447
|
-
const
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
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
|
-
|
|
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
|
|
4467
|
-
|
|
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
|
-
|
|
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,
|
|
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
|