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