@kb-labs/cli-commands 1.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -3
- package/dist/index.js +504 -1736
- package/dist/index.js.map +1 -1
- package/package.json +26 -21
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { safeColors, safeSymbols, getContextCwd,
|
|
1
|
+
import { safeColors, safeSymbols, getContextCwd, toPosixPath, TimingTracker, sideBorderBox, formatCommandHelp } from '@kb-labs/shared-cli-ui';
|
|
2
2
|
export { TimingTracker } from '@kb-labs/shared-cli-ui';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
@@ -8,12 +8,13 @@ import path from 'path';
|
|
|
8
8
|
import { parse } from 'yaml';
|
|
9
9
|
import { glob } from 'glob';
|
|
10
10
|
import { defineSystemCommand, defineSystemCommandGroup } from '@kb-labs/shared-command-kit';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { createRequire } from 'module';
|
|
14
|
-
import { validateManifest } from '@kb-labs/plugin-contracts';
|
|
11
|
+
import { createRegistry, formatDiagnosticReport } from '@kb-labs/core-registry';
|
|
12
|
+
import { readMarketplaceLock, DiagnosticCollector } from '@kb-labs/core-discovery';
|
|
15
13
|
import { platform } from '@kb-labs/core-runtime';
|
|
14
|
+
import { CredentialsManager } from '@kb-labs/cli-runtime/gateway';
|
|
16
15
|
import Ajv from 'ajv';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
import { colors, getLogLevel } from '@kb-labs/cli-runtime';
|
|
17
18
|
|
|
18
19
|
var __defProp = Object.defineProperty;
|
|
19
20
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -327,6 +328,7 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
|
|
|
327
328
|
manifestVersion: "1.0",
|
|
328
329
|
id: commandId,
|
|
329
330
|
group: cmd.group || namespace,
|
|
331
|
+
subgroup: cmd.subgroup,
|
|
330
332
|
describe: cmd.describe || "",
|
|
331
333
|
longDescription: cmd.longDescription,
|
|
332
334
|
aliases: cmd.aliases,
|
|
@@ -578,7 +580,7 @@ async function discoverNodeModules(cwd) {
|
|
|
578
580
|
}
|
|
579
581
|
const isAllowlisted = config.allow?.includes(pkg.name) || config.linked?.includes(pkg.name);
|
|
580
582
|
if (!isAllowlisted) {
|
|
581
|
-
log("debug", `Plugin ${pkg.name} skipped (not allowlisted). Add to kb-labs.config.json plugins.allow or enable via 'kb
|
|
583
|
+
log("debug", `Plugin ${pkg.name} skipped (not allowlisted). Add to kb-labs.config.json plugins.allow or enable via 'kb marketplace enable'`);
|
|
582
584
|
return;
|
|
583
585
|
}
|
|
584
586
|
const manifestInfo = await findManifestPath(pkgRoot, pkg);
|
|
@@ -608,7 +610,7 @@ async function discoverNodeModules(cwd) {
|
|
|
608
610
|
}
|
|
609
611
|
const isAllowlisted = config.allow?.includes(pkg.name) || config.linked?.includes(pkg.name);
|
|
610
612
|
if (!isAllowlisted) {
|
|
611
|
-
log("debug", `Plugin ${pkg.name} skipped (not allowlisted). Add to kb-labs.config.json plugins.allow or enable via 'kb
|
|
613
|
+
log("debug", `Plugin ${pkg.name} skipped (not allowlisted). Add to kb-labs.config.json plugins.allow or enable via 'kb marketplace enable'`);
|
|
612
614
|
return;
|
|
613
615
|
}
|
|
614
616
|
const manifestInfo = await findManifestPath(pkgRoot, pkg);
|
|
@@ -877,17 +879,20 @@ async function discoverManifests(cwd, noCache = false) {
|
|
|
877
879
|
const ttlMs = cached.ttlMs ?? DISK_CACHE_TTL_MS;
|
|
878
880
|
const enforceHashValidation = cacheAge >= ttlMs;
|
|
879
881
|
log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms validateHash=${enforceHashValidation}`);
|
|
882
|
+
let staleCount = 0;
|
|
880
883
|
for (const entry of Object.values(cached.packages)) {
|
|
881
884
|
const stale = await isPackageCacheStale(entry, { validateHash: enforceHashValidation });
|
|
882
885
|
if (!stale) {
|
|
883
886
|
freshResults.push(entry.result);
|
|
887
|
+
} else {
|
|
888
|
+
staleCount += 1;
|
|
884
889
|
}
|
|
885
890
|
}
|
|
886
891
|
const hasNewWorkspacePackages = await detectNewWorkspacePackages(
|
|
887
892
|
cwd,
|
|
888
893
|
cached.packages
|
|
889
894
|
);
|
|
890
|
-
if (freshResults.length > 0 && !hasNewWorkspacePackages) {
|
|
895
|
+
if (staleCount === 0 && freshResults.length > 0 && !hasNewWorkspacePackages) {
|
|
891
896
|
const totalTime2 = Date.now() - startTime;
|
|
892
897
|
const sourceCounts2 = freshResults.reduce((acc, r) => {
|
|
893
898
|
acc[r.source] = (acc[r.source] || 0) + 1;
|
|
@@ -900,6 +905,9 @@ async function discoverManifests(cwd, noCache = false) {
|
|
|
900
905
|
if (hasNewWorkspacePackages) {
|
|
901
906
|
log("debug", "[plugins][cache] invalidated: new workspace packages detected");
|
|
902
907
|
}
|
|
908
|
+
if (staleCount > 0) {
|
|
909
|
+
log("debug", `[plugins][cache] invalidated: ${staleCount} stale package(s) detected`);
|
|
910
|
+
}
|
|
903
911
|
}
|
|
904
912
|
}
|
|
905
913
|
let workspace = [];
|
|
@@ -982,168 +990,6 @@ var init_discover = __esm({
|
|
|
982
990
|
}
|
|
983
991
|
});
|
|
984
992
|
|
|
985
|
-
// src/registry/plugins-state.ts
|
|
986
|
-
var plugins_state_exports = {};
|
|
987
|
-
__export(plugins_state_exports, {
|
|
988
|
-
clearCache: () => clearCache,
|
|
989
|
-
computePackageIntegrity: () => computePackageIntegrity,
|
|
990
|
-
disablePlugin: () => disablePlugin,
|
|
991
|
-
enablePlugin: () => enablePlugin,
|
|
992
|
-
getPluginsStatePath: () => getPluginsStatePath,
|
|
993
|
-
grantPermissions: () => grantPermissions,
|
|
994
|
-
isPluginEnabled: () => isPluginEnabled,
|
|
995
|
-
linkPlugin: () => linkPlugin,
|
|
996
|
-
loadPluginsState: () => loadPluginsState,
|
|
997
|
-
recordCrash: () => recordCrash,
|
|
998
|
-
savePluginsState: () => savePluginsState,
|
|
999
|
-
unlinkPlugin: () => unlinkPlugin
|
|
1000
|
-
});
|
|
1001
|
-
function getPluginsStatePath(cwd) {
|
|
1002
|
-
return path.join(cwd, ".kb", "plugins.json");
|
|
1003
|
-
}
|
|
1004
|
-
async function loadPluginsState(cwd) {
|
|
1005
|
-
const statePath = getPluginsStatePath(cwd);
|
|
1006
|
-
try {
|
|
1007
|
-
const content = await promises.readFile(statePath, "utf8");
|
|
1008
|
-
const state = JSON.parse(content);
|
|
1009
|
-
return {
|
|
1010
|
-
...DEFAULT_STATE,
|
|
1011
|
-
...state,
|
|
1012
|
-
enabled: state.enabled || [],
|
|
1013
|
-
disabled: state.disabled || [],
|
|
1014
|
-
linked: state.linked || [],
|
|
1015
|
-
permissions: state.permissions || {},
|
|
1016
|
-
integrity: state.integrity || {},
|
|
1017
|
-
crashes: state.crashes || {}
|
|
1018
|
-
};
|
|
1019
|
-
} catch {
|
|
1020
|
-
return { ...DEFAULT_STATE };
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
async function savePluginsState(cwd, state) {
|
|
1024
|
-
const statePath = getPluginsStatePath(cwd);
|
|
1025
|
-
const dir = path.dirname(statePath);
|
|
1026
|
-
await promises.mkdir(dir, { recursive: true });
|
|
1027
|
-
state.lastUpdated = Date.now();
|
|
1028
|
-
await promises.writeFile(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
1029
|
-
}
|
|
1030
|
-
function isPluginEnabled(state, packageName, defaultEnabled = false) {
|
|
1031
|
-
if (state.disabled.includes(packageName)) {
|
|
1032
|
-
return false;
|
|
1033
|
-
}
|
|
1034
|
-
if (state.enabled.includes(packageName)) {
|
|
1035
|
-
return true;
|
|
1036
|
-
}
|
|
1037
|
-
return defaultEnabled;
|
|
1038
|
-
}
|
|
1039
|
-
async function enablePlugin(cwd, packageName) {
|
|
1040
|
-
const state = await loadPluginsState(cwd);
|
|
1041
|
-
if (!state.enabled.includes(packageName)) {
|
|
1042
|
-
state.enabled.push(packageName);
|
|
1043
|
-
}
|
|
1044
|
-
state.disabled = state.disabled.filter((p) => p !== packageName);
|
|
1045
|
-
await savePluginsState(cwd, state);
|
|
1046
|
-
}
|
|
1047
|
-
async function disablePlugin(cwd, packageName) {
|
|
1048
|
-
const state = await loadPluginsState(cwd);
|
|
1049
|
-
if (!state.disabled.includes(packageName)) {
|
|
1050
|
-
state.disabled.push(packageName);
|
|
1051
|
-
}
|
|
1052
|
-
state.enabled = state.enabled.filter((p) => p !== packageName);
|
|
1053
|
-
await savePluginsState(cwd, state);
|
|
1054
|
-
}
|
|
1055
|
-
async function linkPlugin(cwd, pluginPath) {
|
|
1056
|
-
const state = await loadPluginsState(cwd);
|
|
1057
|
-
const absPath = path.resolve(cwd, pluginPath);
|
|
1058
|
-
if (!state.linked.includes(absPath)) {
|
|
1059
|
-
state.linked.push(absPath);
|
|
1060
|
-
}
|
|
1061
|
-
await savePluginsState(cwd, state);
|
|
1062
|
-
}
|
|
1063
|
-
async function unlinkPlugin(cwd, pluginPath) {
|
|
1064
|
-
const state = await loadPluginsState(cwd);
|
|
1065
|
-
const absPath = path.resolve(cwd, pluginPath);
|
|
1066
|
-
state.linked = state.linked.filter((p) => p !== absPath);
|
|
1067
|
-
await savePluginsState(cwd, state);
|
|
1068
|
-
}
|
|
1069
|
-
async function grantPermissions(cwd, packageName, permissions) {
|
|
1070
|
-
const state = await loadPluginsState(cwd);
|
|
1071
|
-
if (!state.permissions[packageName]) {
|
|
1072
|
-
state.permissions[packageName] = [];
|
|
1073
|
-
}
|
|
1074
|
-
for (const perm of permissions) {
|
|
1075
|
-
if (!state.permissions[packageName].includes(perm)) {
|
|
1076
|
-
state.permissions[packageName].push(perm);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
await savePluginsState(cwd, state);
|
|
1080
|
-
}
|
|
1081
|
-
async function recordCrash(cwd, packageName) {
|
|
1082
|
-
const state = await loadPluginsState(cwd);
|
|
1083
|
-
state.crashes[packageName] = (state.crashes[packageName] || 0) + 1;
|
|
1084
|
-
const CRASH_THRESHOLD = 3;
|
|
1085
|
-
if (state.crashes[packageName] >= CRASH_THRESHOLD && !state.disabled.includes(packageName)) {
|
|
1086
|
-
state.disabled.push(packageName);
|
|
1087
|
-
}
|
|
1088
|
-
await savePluginsState(cwd, state);
|
|
1089
|
-
}
|
|
1090
|
-
async function computePackageIntegrity(pkgRoot) {
|
|
1091
|
-
try {
|
|
1092
|
-
const pkgJsonPath = path.join(pkgRoot, "package.json");
|
|
1093
|
-
const content = await promises.readFile(pkgJsonPath, "utf8");
|
|
1094
|
-
const hash = createHash("sha256").update(content).digest("base64");
|
|
1095
|
-
return `sha256-${hash}`;
|
|
1096
|
-
} catch {
|
|
1097
|
-
return "";
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
async function clearCache(cwd, options) {
|
|
1101
|
-
const cleared = [];
|
|
1102
|
-
const cacheDir = path.join(cwd, ".kb", "cache");
|
|
1103
|
-
try {
|
|
1104
|
-
const entries = await promises.readdir(cacheDir);
|
|
1105
|
-
for (const entry of entries) {
|
|
1106
|
-
if (entry.includes("manifest") || entry.includes("plugin")) {
|
|
1107
|
-
const entryPath = path.join(cacheDir, entry);
|
|
1108
|
-
await promises.unlink(entryPath);
|
|
1109
|
-
cleared.push(entry);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
} catch {
|
|
1113
|
-
}
|
|
1114
|
-
const modulesCleared = [];
|
|
1115
|
-
if (options?.deep) {
|
|
1116
|
-
try {
|
|
1117
|
-
const cache = __require.cache;
|
|
1118
|
-
for (const key in cache) {
|
|
1119
|
-
if (key.includes("plugin") || key.includes("manifest") || key.includes("@kb-labs")) {
|
|
1120
|
-
delete cache[key];
|
|
1121
|
-
modulesCleared.push(key);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
} catch {
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
return {
|
|
1128
|
-
files: cleared,
|
|
1129
|
-
...options?.deep ? { modules: modulesCleared } : {}
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
var DEFAULT_STATE;
|
|
1133
|
-
var init_plugins_state = __esm({
|
|
1134
|
-
"src/registry/plugins-state.ts"() {
|
|
1135
|
-
DEFAULT_STATE = {
|
|
1136
|
-
enabled: [],
|
|
1137
|
-
disabled: [],
|
|
1138
|
-
linked: [],
|
|
1139
|
-
permissions: {},
|
|
1140
|
-
integrity: {},
|
|
1141
|
-
crashes: {},
|
|
1142
|
-
lastUpdated: Date.now()
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
993
|
// src/registry/service.ts
|
|
1148
994
|
function manifestToCommand(registered) {
|
|
1149
995
|
return {
|
|
@@ -1155,7 +1001,7 @@ function manifestToCommand(registered) {
|
|
|
1155
1001
|
flags: registered.manifest.flags,
|
|
1156
1002
|
examples: registered.manifest.examples,
|
|
1157
1003
|
async run() {
|
|
1158
|
-
throw new Error(`Command ${registered.manifest.id} should be executed via
|
|
1004
|
+
throw new Error(`Command ${registered.manifest.id} should be executed via plugin-executor, not via legacy run() path.`);
|
|
1159
1005
|
}
|
|
1160
1006
|
};
|
|
1161
1007
|
}
|
|
@@ -1191,6 +1037,22 @@ var InMemoryRegistry = class {
|
|
|
1191
1037
|
this.byName.set(alias, cmd);
|
|
1192
1038
|
}
|
|
1193
1039
|
}
|
|
1040
|
+
if (group.subgroups) {
|
|
1041
|
+
for (const sub of group.subgroups) {
|
|
1042
|
+
const subName = `${group.name} ${sub.name}`;
|
|
1043
|
+
this.groups.set(subName, sub);
|
|
1044
|
+
this.byName.set(subName, sub);
|
|
1045
|
+
for (const cmd of sub.commands) {
|
|
1046
|
+
const fullName = `${group.name} ${sub.name} ${cmd.name}`;
|
|
1047
|
+
this.systemCommands.set(fullName, cmd);
|
|
1048
|
+
this.byName.set(fullName, cmd);
|
|
1049
|
+
for (const alias of cmd.aliases || []) {
|
|
1050
|
+
this.systemCommands.set(alias, cmd);
|
|
1051
|
+
this.byName.set(alias, cmd);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1194
1056
|
}
|
|
1195
1057
|
registerManifest(cmd) {
|
|
1196
1058
|
const cmdId = cmd.manifest.id;
|
|
@@ -1216,9 +1078,38 @@ var InMemoryRegistry = class {
|
|
|
1216
1078
|
const spaceSeparated = cmdId.replace(/:/g, " ");
|
|
1217
1079
|
this.byName.set(spaceSeparated, commandAdapter);
|
|
1218
1080
|
}
|
|
1219
|
-
if (cmd.manifest.group) {
|
|
1081
|
+
if (cmd.manifest.group && cmd.manifest.subgroup) {
|
|
1082
|
+
const fullPath = `${cmd.manifest.group} ${cmd.manifest.subgroup} ${cmd.manifest.id}`;
|
|
1083
|
+
const colonPath = `${cmd.manifest.group}:${cmd.manifest.subgroup}:${cmd.manifest.id}`;
|
|
1084
|
+
this.byName.set(fullPath, commandAdapter);
|
|
1085
|
+
this.byName.set(colonPath, commandAdapter);
|
|
1086
|
+
this.manifests.set(fullPath, cmd);
|
|
1087
|
+
this.manifests.set(colonPath, cmd);
|
|
1088
|
+
this.pluginCommands.set(fullPath, cmd);
|
|
1089
|
+
this.pluginCommands.set(colonPath, cmd);
|
|
1090
|
+
const twoPartName = `${cmd.manifest.group} ${cmd.manifest.id}`;
|
|
1091
|
+
if (!this.byName.has(twoPartName)) {
|
|
1092
|
+
this.byName.set(twoPartName, commandAdapter);
|
|
1093
|
+
}
|
|
1094
|
+
const subgroupKey = `${cmd.manifest.group} ${cmd.manifest.subgroup}`;
|
|
1095
|
+
if (!this.groups.has(subgroupKey)) {
|
|
1096
|
+
this.groups.set(subgroupKey, {
|
|
1097
|
+
name: subgroupKey,
|
|
1098
|
+
describe: cmd.manifest.subgroup,
|
|
1099
|
+
commands: []
|
|
1100
|
+
});
|
|
1101
|
+
this.byName.set(subgroupKey, this.groups.get(subgroupKey));
|
|
1102
|
+
}
|
|
1103
|
+
this.groups.get(subgroupKey).commands.push(commandAdapter);
|
|
1104
|
+
} else if (cmd.manifest.group) {
|
|
1220
1105
|
const fullName = `${cmd.manifest.group} ${cmd.manifest.id}`;
|
|
1106
|
+
const colonName = `${cmd.manifest.group}:${cmd.manifest.id}`;
|
|
1221
1107
|
this.byName.set(fullName, commandAdapter);
|
|
1108
|
+
this.byName.set(colonName, commandAdapter);
|
|
1109
|
+
this.manifests.set(fullName, cmd);
|
|
1110
|
+
this.manifests.set(colonName, cmd);
|
|
1111
|
+
this.pluginCommands.set(fullName, cmd);
|
|
1112
|
+
this.pluginCommands.set(colonName, cmd);
|
|
1222
1113
|
}
|
|
1223
1114
|
if (cmd.manifest.aliases) {
|
|
1224
1115
|
for (const alias of cmd.manifest.aliases) {
|
|
@@ -1421,8 +1312,8 @@ function generateExamples(commandName, productName, cases) {
|
|
|
1421
1312
|
// src/commands/system/hello.ts
|
|
1422
1313
|
var hello = defineSystemCommand({
|
|
1423
1314
|
name: "hello",
|
|
1424
|
-
description: "Print
|
|
1425
|
-
longDescription:
|
|
1315
|
+
description: "Print Hello World",
|
|
1316
|
+
longDescription: 'Prints "Hello World" \u2014 a simple smoke-test for CLI functionality',
|
|
1426
1317
|
category: "info",
|
|
1427
1318
|
examples: generateExamples("hello", "kb", [
|
|
1428
1319
|
{ flags: {} }
|
|
@@ -1435,25 +1326,16 @@ var hello = defineSystemCommand({
|
|
|
1435
1326
|
startEvent: "HELLO_STARTED",
|
|
1436
1327
|
finishEvent: "HELLO_FINISHED"
|
|
1437
1328
|
},
|
|
1438
|
-
async handler(ctx,
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
ctx.platform?.logger?.info("Hello command executed", { who });
|
|
1329
|
+
async handler(ctx, _argv, flags) {
|
|
1330
|
+
const message = "Hello World";
|
|
1331
|
+
ctx.platform?.logger?.info("Hello command executed");
|
|
1442
1332
|
if (!flags.json) {
|
|
1443
1333
|
ctx.ui?.write(`${message}
|
|
1444
1334
|
`);
|
|
1445
1335
|
} else {
|
|
1446
|
-
ctx.ui?.json({
|
|
1447
|
-
message,
|
|
1448
|
-
who,
|
|
1449
|
-
status: "ready"
|
|
1450
|
-
});
|
|
1336
|
+
ctx.ui?.json({ message, status: "ready" });
|
|
1451
1337
|
}
|
|
1452
|
-
return {
|
|
1453
|
-
ok: true,
|
|
1454
|
-
message,
|
|
1455
|
-
who
|
|
1456
|
-
};
|
|
1338
|
+
return { ok: true, message };
|
|
1457
1339
|
}
|
|
1458
1340
|
});
|
|
1459
1341
|
var version = defineSystemCommand({
|
|
@@ -1476,31 +1358,31 @@ var version = defineSystemCommand({
|
|
|
1476
1358
|
const v = ctx?.cliVersion ?? ctx?.env?.CLI_VERSION ?? "0.0.0";
|
|
1477
1359
|
const cliVersion = String(v);
|
|
1478
1360
|
const nodeVersion = process.version;
|
|
1479
|
-
const
|
|
1361
|
+
const platform11 = `${process.platform} ${process.arch}`;
|
|
1480
1362
|
ctx.platform?.logger?.info("Version command executed", {
|
|
1481
1363
|
version: cliVersion,
|
|
1482
1364
|
nodeVersion,
|
|
1483
|
-
platform:
|
|
1365
|
+
platform: platform11
|
|
1484
1366
|
});
|
|
1485
1367
|
if (!flags.json) {
|
|
1486
1368
|
ctx.ui?.write(`KB Labs CLI v${cliVersion}
|
|
1487
1369
|
`);
|
|
1488
1370
|
ctx.ui?.write(`Node: ${nodeVersion}
|
|
1489
1371
|
`);
|
|
1490
|
-
ctx.ui?.write(`Platform: ${
|
|
1372
|
+
ctx.ui?.write(`Platform: ${platform11}
|
|
1491
1373
|
`);
|
|
1492
1374
|
} else {
|
|
1493
1375
|
ctx.ui?.json({
|
|
1494
1376
|
version: cliVersion,
|
|
1495
1377
|
nodeVersion,
|
|
1496
|
-
platform:
|
|
1378
|
+
platform: platform11
|
|
1497
1379
|
});
|
|
1498
1380
|
}
|
|
1499
1381
|
return {
|
|
1500
1382
|
ok: true,
|
|
1501
1383
|
version: cliVersion,
|
|
1502
1384
|
nodeVersion,
|
|
1503
|
-
platform:
|
|
1385
|
+
platform: platform11
|
|
1504
1386
|
};
|
|
1505
1387
|
}
|
|
1506
1388
|
});
|
|
@@ -1522,8 +1404,8 @@ var health = defineSystemCommand({
|
|
|
1522
1404
|
finishEvent: "HEALTH_FINISHED"
|
|
1523
1405
|
},
|
|
1524
1406
|
async handler(ctx, _argv, _flags) {
|
|
1525
|
-
const cliApi = await
|
|
1526
|
-
cache: {
|
|
1407
|
+
const cliApi = await createRegistry({
|
|
1408
|
+
cache: { ttlMs: 5e3 }
|
|
1527
1409
|
});
|
|
1528
1410
|
try {
|
|
1529
1411
|
ctx.platform?.logger?.info("Health check started");
|
|
@@ -1611,7 +1493,6 @@ var health = defineSystemCommand({
|
|
|
1611
1493
|
}
|
|
1612
1494
|
});
|
|
1613
1495
|
init_discover();
|
|
1614
|
-
init_plugins_state();
|
|
1615
1496
|
var diag = defineSystemCommand({
|
|
1616
1497
|
name: "diag",
|
|
1617
1498
|
description: "Comprehensive system diagnostics (plugins, cache, environment, versions)",
|
|
@@ -1634,13 +1515,13 @@ var diag = defineSystemCommand({
|
|
|
1634
1515
|
const diagnostics = [];
|
|
1635
1516
|
const nodeVersion = process.version;
|
|
1636
1517
|
const cliVersion = process.env.CLI_VERSION || process.env.CLI_VERSION || "0.1.0";
|
|
1637
|
-
const
|
|
1518
|
+
const platform11 = process.platform;
|
|
1638
1519
|
const arch = process.arch;
|
|
1639
1520
|
diagnostics.push({
|
|
1640
1521
|
category: "environment",
|
|
1641
1522
|
status: "ok",
|
|
1642
|
-
message: `Node ${nodeVersion}, CLI ${cliVersion}, ${
|
|
1643
|
-
details: { nodeVersion, cliVersion, platform:
|
|
1523
|
+
message: `Node ${nodeVersion}, CLI ${cliVersion}, ${platform11}/${arch}`,
|
|
1524
|
+
details: { nodeVersion, cliVersion, platform: platform11, arch }
|
|
1644
1525
|
});
|
|
1645
1526
|
try {
|
|
1646
1527
|
const discovered = await discoverManifests(cwd, false);
|
|
@@ -1649,7 +1530,7 @@ var diag = defineSystemCommand({
|
|
|
1649
1530
|
const disabled = manifests.filter((m) => !m.available).length;
|
|
1650
1531
|
const shadowed = manifests.filter((m) => m.shadowed).length;
|
|
1651
1532
|
diagnostics.push({
|
|
1652
|
-
category: "
|
|
1533
|
+
category: "marketplace",
|
|
1653
1534
|
status: disabled > 0 ? "warning" : "ok",
|
|
1654
1535
|
message: `Found ${discovered.length} packages, ${enabled} enabled, ${disabled} unavailable, ${shadowed} shadowed`,
|
|
1655
1536
|
details: {
|
|
@@ -1662,7 +1543,7 @@ var diag = defineSystemCommand({
|
|
|
1662
1543
|
});
|
|
1663
1544
|
} catch (err) {
|
|
1664
1545
|
diagnostics.push({
|
|
1665
|
-
category: "
|
|
1546
|
+
category: "marketplace",
|
|
1666
1547
|
status: "error",
|
|
1667
1548
|
message: `Discovery failed: ${err.message}`,
|
|
1668
1549
|
details: { error: err.message }
|
|
@@ -1702,26 +1583,29 @@ var diag = defineSystemCommand({
|
|
|
1702
1583
|
});
|
|
1703
1584
|
}
|
|
1704
1585
|
try {
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
enabled: enabledCount,
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1586
|
+
const lock = await readMarketplaceLock(cwd, new DiagnosticCollector());
|
|
1587
|
+
if (lock) {
|
|
1588
|
+
const entries = Object.entries(lock.installed);
|
|
1589
|
+
const enabledCount = entries.filter(([, e]) => e.enabled !== false).length;
|
|
1590
|
+
const disabledCount = entries.length - enabledCount;
|
|
1591
|
+
diagnostics.push({
|
|
1592
|
+
category: "marketplace-lock",
|
|
1593
|
+
status: "ok",
|
|
1594
|
+
message: `${entries.length} installed (${enabledCount} enabled, ${disabledCount} disabled)`,
|
|
1595
|
+
details: { total: entries.length, enabled: enabledCount, disabled: disabledCount }
|
|
1596
|
+
});
|
|
1597
|
+
} else {
|
|
1598
|
+
diagnostics.push({
|
|
1599
|
+
category: "marketplace-lock",
|
|
1600
|
+
status: "warning",
|
|
1601
|
+
message: "No marketplace.lock found \u2014 no plugins installed via marketplace"
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1720
1604
|
} catch (err) {
|
|
1721
1605
|
diagnostics.push({
|
|
1722
|
-
category: "
|
|
1606
|
+
category: "marketplace-lock",
|
|
1723
1607
|
status: "error",
|
|
1724
|
-
message: `
|
|
1608
|
+
message: `Marketplace lock check failed: ${err.message}`,
|
|
1725
1609
|
details: { error: err.message }
|
|
1726
1610
|
});
|
|
1727
1611
|
}
|
|
@@ -1810,10 +1694,10 @@ var diag = defineSystemCommand({
|
|
|
1810
1694
|
}
|
|
1811
1695
|
const nextSteps = [];
|
|
1812
1696
|
if (hasErrors) {
|
|
1813
|
-
nextSteps.push(`kb
|
|
1697
|
+
nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
|
|
1814
1698
|
}
|
|
1815
1699
|
if (hasWarnings) {
|
|
1816
|
-
nextSteps.push(`kb
|
|
1700
|
+
nextSteps.push(`kb marketplace list ${safeColors.muted("List all plugins")}`);
|
|
1817
1701
|
}
|
|
1818
1702
|
nextSteps.push(`kb diagnose ${safeColors.muted("Quick environment check")}`);
|
|
1819
1703
|
const sections = [
|
|
@@ -1840,1500 +1724,144 @@ var diag = defineSystemCommand({
|
|
|
1840
1724
|
}
|
|
1841
1725
|
}
|
|
1842
1726
|
});
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
// Type-safe examples using generateExamples()
|
|
1849
|
-
examples: generateExamples("list", "plugins", [
|
|
1850
|
-
{ flags: {} },
|
|
1851
|
-
// kb plugins list
|
|
1852
|
-
{ flags: { json: true } }
|
|
1853
|
-
// kb plugins list --json
|
|
1854
|
-
]),
|
|
1727
|
+
var registryDiagnostics = defineSystemCommand({
|
|
1728
|
+
name: "diagnostics",
|
|
1729
|
+
description: "Show registry diagnostic report",
|
|
1730
|
+
category: "registry",
|
|
1731
|
+
examples: [],
|
|
1855
1732
|
flags: {
|
|
1856
|
-
json: { type: "boolean", description: "Output in JSON format" }
|
|
1733
|
+
json: { type: "boolean", description: "Output in JSON format" },
|
|
1734
|
+
refresh: { type: "boolean", description: "Force fresh discovery (ignore cache)" }
|
|
1857
1735
|
},
|
|
1858
1736
|
analytics: {
|
|
1859
|
-
command: "
|
|
1860
|
-
startEvent: "
|
|
1861
|
-
finishEvent: "
|
|
1737
|
+
command: "registry:diagnostics",
|
|
1738
|
+
startEvent: "REGISTRY_DIAGNOSTICS_STARTED",
|
|
1739
|
+
finishEvent: "REGISTRY_DIAGNOSTICS_FINISHED"
|
|
1862
1740
|
},
|
|
1863
|
-
async handler(ctx, _argv,
|
|
1741
|
+
async handler(ctx, _argv, flags) {
|
|
1864
1742
|
const cwd = getContextCwd(ctx);
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
});
|
|
1869
|
-
await pluginRegistry.refresh();
|
|
1870
|
-
const manifests = registry.listManifests();
|
|
1871
|
-
const productGroups = registry.listProductGroups();
|
|
1872
|
-
const state = await loadPluginsState(cwd);
|
|
1873
|
-
pluginRegistry.list();
|
|
1874
|
-
const packages = /* @__PURE__ */ new Map();
|
|
1875
|
-
for (const cmd of manifests) {
|
|
1876
|
-
const pkgName = cmd.manifest.package || cmd.manifest.group;
|
|
1877
|
-
const namespace = cmd.manifest.namespace || cmd.manifest.group;
|
|
1878
|
-
if (!packages.has(pkgName)) {
|
|
1879
|
-
let version2 = "unknown";
|
|
1880
|
-
if (cmd.pkgRoot) {
|
|
1881
|
-
try {
|
|
1882
|
-
const pkgPath = path.join(cmd.pkgRoot, "package.json");
|
|
1883
|
-
const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
|
|
1884
|
-
version2 = pkgJson.version || "unknown";
|
|
1885
|
-
} catch {
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
if (version2 === "unknown") {
|
|
1889
|
-
try {
|
|
1890
|
-
const pkgPath = path.join(cwd, "node_modules", pkgName, "package.json");
|
|
1891
|
-
const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
|
|
1892
|
-
version2 = pkgJson.version || "unknown";
|
|
1893
|
-
} catch {
|
|
1894
|
-
try {
|
|
1895
|
-
const pkgPath = path.join(cwd, "packages", namespace.replace("@kb-labs/", "").replace("-cli", ""), "package.json");
|
|
1896
|
-
const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
|
|
1897
|
-
version2 = pkgJson.version || "unknown";
|
|
1898
|
-
} catch {
|
|
1899
|
-
try {
|
|
1900
|
-
const workspaceRoot = path.resolve(cwd, "..");
|
|
1901
|
-
const pkgDirs = await promises.readdir(path.join(workspaceRoot, "packages"), { withFileTypes: true });
|
|
1902
|
-
for (const dir of pkgDirs) {
|
|
1903
|
-
if (dir.isDirectory()) {
|
|
1904
|
-
try {
|
|
1905
|
-
const pkgPath = path.join(workspaceRoot, "packages", dir.name, "package.json");
|
|
1906
|
-
const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
|
|
1907
|
-
if (pkgJson.name === pkgName) {
|
|
1908
|
-
version2 = pkgJson.version || "unknown";
|
|
1909
|
-
break;
|
|
1910
|
-
}
|
|
1911
|
-
} catch {
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
} catch {
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
const enabled = isPluginEnabled(state, pkgName, cmd.source === "workspace" || cmd.source === "linked");
|
|
1921
|
-
let pluginState = enabled ? "enabled" : "disabled";
|
|
1922
|
-
if (cmd.source === "linked") {
|
|
1923
|
-
pluginState = "linked";
|
|
1924
|
-
} else if (!cmd.available) {
|
|
1925
|
-
pluginState = "error";
|
|
1926
|
-
}
|
|
1927
|
-
packages.set(pkgName, {
|
|
1928
|
-
name: pkgName,
|
|
1929
|
-
namespace,
|
|
1930
|
-
version: version2,
|
|
1931
|
-
source: cmd.source,
|
|
1932
|
-
enabled,
|
|
1933
|
-
commands: [],
|
|
1934
|
-
state: pluginState
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
|
-
packages.get(pkgName).commands.push(cmd);
|
|
1938
|
-
}
|
|
1939
|
-
const packageList = Array.from(packages.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
1940
|
-
const output = packageList.map((pkg) => ({
|
|
1941
|
-
name: pkg.name,
|
|
1942
|
-
namespace: pkg.namespace,
|
|
1943
|
-
version: pkg.version,
|
|
1944
|
-
source: pkg.source,
|
|
1945
|
-
enabled: pkg.enabled,
|
|
1946
|
-
state: pkg.state,
|
|
1947
|
-
commands: pkg.commands.map((cmd) => ({
|
|
1948
|
-
id: cmd.manifest.id,
|
|
1949
|
-
aliases: cmd.manifest.aliases || [],
|
|
1950
|
-
describe: cmd.manifest.describe,
|
|
1951
|
-
available: cmd.available,
|
|
1952
|
-
shadowed: cmd.shadowed,
|
|
1953
|
-
...cmd.unavailableReason && { reason: cmd.unavailableReason }
|
|
1954
|
-
}))
|
|
1955
|
-
}));
|
|
1956
|
-
const totalPlugins = output.length;
|
|
1957
|
-
const enabledCount = output.filter((p) => p.enabled).length;
|
|
1958
|
-
const disabledCount = output.filter((p) => !p.enabled).length;
|
|
1959
|
-
const linkedCount = packageList.filter((p) => p.state === "linked").length;
|
|
1960
|
-
ctx.platform?.logger?.info("Plugins list completed", {
|
|
1961
|
-
total: totalPlugins,
|
|
1962
|
-
enabled: enabledCount,
|
|
1963
|
-
disabled: disabledCount,
|
|
1964
|
-
products: productGroups.length
|
|
1965
|
-
});
|
|
1966
|
-
const sections = [];
|
|
1967
|
-
sections.push({
|
|
1968
|
-
header: "Summary",
|
|
1969
|
-
items: [
|
|
1970
|
-
`${safeColors.bold("Total")}: ${totalPlugins} plugins`,
|
|
1971
|
-
`${safeColors.bold("Enabled")}: ${enabledCount}`,
|
|
1972
|
-
`${safeColors.bold("Disabled")}: ${disabledCount}`,
|
|
1973
|
-
`${safeColors.bold("Linked")}: ${linkedCount}`,
|
|
1974
|
-
`${safeColors.bold("Commands")}: ${manifests.length} total`
|
|
1975
|
-
]
|
|
1976
|
-
});
|
|
1977
|
-
const stateIcons = {
|
|
1978
|
-
enabled: "\u2705",
|
|
1979
|
-
disabled: "\u23F8",
|
|
1980
|
-
error: "\u274C",
|
|
1981
|
-
outdated: "\u26A0",
|
|
1982
|
-
linked: "\u{1F9E9}"
|
|
1983
|
-
};
|
|
1984
|
-
const columns = [
|
|
1985
|
-
{ header: "Plugin", align: "left" },
|
|
1986
|
-
{ header: "NS", align: "left" },
|
|
1987
|
-
{ header: "Version", align: "left" },
|
|
1988
|
-
{ header: "Source", align: "left" },
|
|
1989
|
-
{ header: "State", align: "left" },
|
|
1990
|
-
{ header: "Cmds", align: "right" }
|
|
1991
|
-
];
|
|
1992
|
-
const tableRows = packageList.map((pkg) => {
|
|
1993
|
-
const stateIcon = stateIcons[pkg.state] || "\u2753";
|
|
1994
|
-
const name = pkg.name.length > 26 ? pkg.name.substring(0, 23) + "..." : pkg.name;
|
|
1995
|
-
const sourceLabel = pkg.source === "workspace" ? "workspace" : pkg.source === "linked" ? "linked" : "node_modules";
|
|
1996
|
-
const stateDisplay = `${stateIcon} ${pkg.state}`;
|
|
1997
|
-
return [name, pkg.namespace, pkg.version, sourceLabel, stateDisplay, String(pkg.commands.length)];
|
|
1998
|
-
});
|
|
1999
|
-
const tableLines = formatTable(columns, tableRows, { separator: "\u2500", padding: 2 });
|
|
2000
|
-
const errorLines = [];
|
|
2001
|
-
for (const pkg of packageList) {
|
|
2002
|
-
const hasErrors = pkg.commands.some((c) => !c.available || c.shadowed);
|
|
2003
|
-
if (hasErrors) {
|
|
2004
|
-
for (const cmd of pkg.commands) {
|
|
2005
|
-
if (!cmd.available) {
|
|
2006
|
-
errorLines.push(
|
|
2007
|
-
` ${safeColors.muted("\u274C")} ${cmd.manifest.id}: ${cmd.unavailableReason || "unavailable"}`
|
|
2008
|
-
);
|
|
2009
|
-
if (cmd.hint) {
|
|
2010
|
-
errorLines.push(` ${safeColors.warning(`Hint: ${cmd.hint}`)}`);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
if (cmd.shadowed) {
|
|
2014
|
-
errorLines.push(` ${safeColors.muted("\u26A0")} ${cmd.manifest.id}: shadowed`);
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
1743
|
+
const registry2 = await createRegistry({ root: cwd });
|
|
1744
|
+
if (flags.refresh) {
|
|
1745
|
+
await registry2.refresh();
|
|
2018
1746
|
}
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
}
|
|
2023
|
-
sections.push({
|
|
2024
|
-
header: "Plugins",
|
|
2025
|
-
items: pluginsItems
|
|
2026
|
-
});
|
|
2027
|
-
sections.push({
|
|
2028
|
-
header: "Next Steps",
|
|
2029
|
-
items: [
|
|
2030
|
-
`kb plugins enable <name> ${safeColors.muted("Enable a plugin")}`,
|
|
2031
|
-
`kb plugins disable <name> ${safeColors.muted("Disable a plugin")}`,
|
|
2032
|
-
`kb plugins doctor ${safeColors.muted("Diagnose plugin issues")}`,
|
|
2033
|
-
`kb plugins --json ${safeColors.muted("Get machine-readable output")}`
|
|
2034
|
-
]
|
|
2035
|
-
});
|
|
2036
|
-
return {
|
|
2037
|
-
ok: true,
|
|
2038
|
-
status: "success",
|
|
2039
|
-
message: `Found ${totalPlugins} plugins (${enabledCount} enabled, ${disabledCount} disabled)`,
|
|
2040
|
-
sections,
|
|
2041
|
-
json: {
|
|
2042
|
-
plugins: output,
|
|
2043
|
-
total: totalPlugins,
|
|
2044
|
-
enabled: enabledCount,
|
|
2045
|
-
disabled: disabledCount,
|
|
2046
|
-
products: productGroups.length
|
|
2047
|
-
}
|
|
2048
|
-
};
|
|
1747
|
+
const report = registry2.getDiagnostics();
|
|
1748
|
+
await registry2.dispose();
|
|
1749
|
+
return { ok: report.summary.errors === 0, report };
|
|
2049
1750
|
},
|
|
2050
1751
|
formatter(result, ctx, flags) {
|
|
2051
1752
|
if (flags.json) {
|
|
2052
|
-
|
|
1753
|
+
ctx.ui.info(JSON.stringify(result.report, null, 2));
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const text = formatDiagnosticReport(result.report);
|
|
1757
|
+
ctx.ui.info(text);
|
|
1758
|
+
if (result.report.summary.errors > 0) {
|
|
1759
|
+
ctx.ui.error(`${result.report.summary.errors} error(s) found \u2014 see details above`);
|
|
1760
|
+
} else if (result.report.summary.warnings > 0) {
|
|
1761
|
+
ctx.ui.warn(`${result.report.summary.warnings} warning(s) \u2014 no blockers`);
|
|
2053
1762
|
} else {
|
|
2054
|
-
ctx.ui.
|
|
1763
|
+
ctx.ui.success("Registry is healthy \u2014 no issues found");
|
|
2055
1764
|
}
|
|
2056
1765
|
}
|
|
2057
1766
|
});
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
flags: {
|
|
2069
|
-
json: { type: "boolean", description: "Output in JSON format" },
|
|
2070
|
-
plugin: { type: "string", description: "Filter by plugin ID" },
|
|
2071
|
-
sort: { type: "string", description: "Sort order: alpha (default), count, type" }
|
|
2072
|
-
},
|
|
2073
|
-
async handler(ctx, argv, flags) {
|
|
2074
|
-
const commands = registry.list();
|
|
2075
|
-
registry.listGroups();
|
|
2076
|
-
const grouped = {};
|
|
2077
|
-
for (const cmd of commands) {
|
|
2078
|
-
const groupName = cmd.name.split(/[\s:]/)[0] ?? cmd.name;
|
|
2079
|
-
if (!grouped[groupName]) {
|
|
2080
|
-
grouped[groupName] = [];
|
|
1767
|
+
async function clearCache(cwd, options) {
|
|
1768
|
+
const cleared = [];
|
|
1769
|
+
const cacheDir = path.join(cwd, ".kb", "cache");
|
|
1770
|
+
try {
|
|
1771
|
+
const entries = await promises.readdir(cacheDir);
|
|
1772
|
+
for (const entry of entries) {
|
|
1773
|
+
if (entry.includes("manifest") || entry.includes("plugin")) {
|
|
1774
|
+
const entryPath = path.join(cacheDir, entry);
|
|
1775
|
+
await promises.unlink(entryPath);
|
|
1776
|
+
cleared.push(entry);
|
|
2081
1777
|
}
|
|
2082
|
-
grouped[groupName].push(cmd);
|
|
2083
1778
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
const manifestsCachePath = path.join(cwd, ".kb", "marketplace.manifests.json");
|
|
1782
|
+
try {
|
|
1783
|
+
await promises.unlink(manifestsCachePath);
|
|
1784
|
+
cleared.push("marketplace.manifests.json");
|
|
1785
|
+
} catch {
|
|
1786
|
+
}
|
|
1787
|
+
const cliManifestsPath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
|
|
1788
|
+
try {
|
|
1789
|
+
await promises.unlink(cliManifestsPath);
|
|
1790
|
+
if (!cleared.includes("cli-manifests.json")) {
|
|
1791
|
+
cleared.push("cli-manifests.json");
|
|
1792
|
+
}
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
const modulesCleared = [];
|
|
1796
|
+
if (options?.deep) {
|
|
1797
|
+
try {
|
|
1798
|
+
const cache = __require.cache;
|
|
1799
|
+
for (const key in cache) {
|
|
1800
|
+
if (key.includes("plugin") || key.includes("manifest") || key.includes("@kb-labs")) {
|
|
1801
|
+
delete cache[key];
|
|
1802
|
+
modulesCleared.push(key);
|
|
2089
1803
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
) : grouped;
|
|
2093
|
-
const totalCommands = Object.values(filteredGroups).reduce((sum, cmds) => sum + cmds.length, 0);
|
|
2094
|
-
const totalGroups = Object.keys(filteredGroups).length;
|
|
2095
|
-
const sortOrder = flags.sort || "alpha";
|
|
2096
|
-
const validSorts = ["alpha", "count", "type"];
|
|
2097
|
-
if (!validSorts.includes(sortOrder)) {
|
|
2098
|
-
throw new Error(`Invalid sort order: ${sortOrder}. Valid options: ${validSorts.join(", ")}`);
|
|
1804
|
+
}
|
|
1805
|
+
} catch {
|
|
2099
1806
|
}
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
files: cleared,
|
|
1810
|
+
...options?.deep ? { modules: modulesCleared } : {}
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
var pluginsCacheClear = defineSystemCommand({
|
|
1814
|
+
name: "clear-cache",
|
|
1815
|
+
description: "Clear CLI plugin discovery cache",
|
|
1816
|
+
category: "marketplace",
|
|
1817
|
+
examples: ["kb marketplace clear-cache", "kb marketplace clear-cache --deep"],
|
|
1818
|
+
flags: {
|
|
1819
|
+
deep: { type: "boolean", description: "Also clear Node.js module cache" },
|
|
1820
|
+
json: { type: "boolean", description: "Output in JSON format" }
|
|
1821
|
+
},
|
|
1822
|
+
analytics: {
|
|
1823
|
+
command: "marketplace:clear-cache",
|
|
1824
|
+
startEvent: "MARKETPLACE_CACHE_CLEAR_STARTED",
|
|
1825
|
+
finishEvent: "MARKETPLACE_CACHE_CLEAR_FINISHED"
|
|
1826
|
+
},
|
|
1827
|
+
async handler(ctx, _argv, flags) {
|
|
1828
|
+
const deep = flags.deep;
|
|
1829
|
+
const cwd = getContextCwd(ctx);
|
|
1830
|
+
const result = await clearCache(cwd, { deep });
|
|
1831
|
+
ctx.platform?.logger?.info("Cache cleared", {
|
|
1832
|
+
filesCount: result.files.length,
|
|
1833
|
+
modulesCount: result.modules?.length || 0,
|
|
1834
|
+
deep
|
|
1835
|
+
});
|
|
2100
1836
|
if (flags.json) {
|
|
2101
|
-
const
|
|
2102
|
-
for (const [groupName, cmds] of Object.entries(filteredGroups)) {
|
|
2103
|
-
output[groupName] = cmds.map((cmd) => ({
|
|
2104
|
-
name: cmd.name,
|
|
2105
|
-
description: cmd.describe || "",
|
|
2106
|
-
category: cmd.category
|
|
2107
|
-
}));
|
|
2108
|
-
}
|
|
2109
|
-
return {
|
|
1837
|
+
const jsonResult = {
|
|
2110
1838
|
ok: true,
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
totalCommands
|
|
2117
|
-
}
|
|
1839
|
+
action: "cache:clear",
|
|
1840
|
+
files: result.files,
|
|
1841
|
+
modules: result.modules,
|
|
1842
|
+
count: result.files.length,
|
|
1843
|
+
modulesCount: result.modules?.length || 0
|
|
2118
1844
|
};
|
|
1845
|
+
console.log(JSON.stringify(jsonResult, null, 2));
|
|
1846
|
+
return jsonResult;
|
|
2119
1847
|
}
|
|
1848
|
+
const files = result.files ?? [];
|
|
1849
|
+
const modules = result.modules ?? [];
|
|
2120
1850
|
const sections = [];
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
...flags.plugin ? [`${safeColors.bold("Filter")}: ${flags.plugin}`] : []
|
|
2128
|
-
]
|
|
2129
|
-
});
|
|
2130
|
-
const sortGroups = (entries) => {
|
|
2131
|
-
switch (sortOrder) {
|
|
2132
|
-
case "count":
|
|
2133
|
-
return entries.sort(([, a], [, b]) => b.length - a.length);
|
|
2134
|
-
case "type":
|
|
2135
|
-
return entries.sort(([a], [b]) => a.localeCompare(b));
|
|
2136
|
-
case "alpha":
|
|
2137
|
-
default:
|
|
2138
|
-
return entries.sort(([a], [b]) => a.localeCompare(b));
|
|
2139
|
-
}
|
|
2140
|
-
};
|
|
2141
|
-
const systemGroups = [];
|
|
2142
|
-
const productGroups = [];
|
|
2143
|
-
const sortedGroups = Object.entries(filteredGroups);
|
|
2144
|
-
for (const [groupName, cmds] of sortedGroups) {
|
|
2145
|
-
const isSystemGroup = cmds.some(
|
|
2146
|
-
(cmd) => cmd.category && ["plugins", "jobs", "workflow", "worker", "debug", "info", "logging", "registry"].includes(cmd.category)
|
|
2147
|
-
);
|
|
2148
|
-
if (isSystemGroup) {
|
|
2149
|
-
systemGroups.push([groupName, cmds]);
|
|
2150
|
-
} else {
|
|
2151
|
-
productGroups.push([groupName, cmds]);
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
if (systemGroups.length > 0) {
|
|
2155
|
-
const systemItems = [];
|
|
2156
|
-
const sortedSystemGroups = sortGroups(systemGroups);
|
|
2157
|
-
for (const [groupName, cmds] of sortedSystemGroups) {
|
|
2158
|
-
systemItems.push("");
|
|
2159
|
-
systemItems.push(`\u2699\uFE0F ${safeColors.bold(groupName)} ${safeColors.muted(`(${cmds.length})`)}`);
|
|
2160
|
-
const sortedCmds = cmds.sort((a, b) => a.name.localeCompare(b.name));
|
|
2161
|
-
for (const cmd of sortedCmds) {
|
|
2162
|
-
const invocation = safeColors.bold(`kb ${cmd.name}`);
|
|
2163
|
-
const description = cmd.describe ? ` ${safeColors.muted(cmd.describe)}` : "";
|
|
2164
|
-
systemItems.push(` ${invocation}${description}`);
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
1851
|
+
if (files.length > 0) {
|
|
1852
|
+
sections.push({
|
|
1853
|
+
header: "Cache Files",
|
|
1854
|
+
items: files.map((f) => `\u2713 ${f}`)
|
|
1855
|
+
});
|
|
1856
|
+
} else {
|
|
2167
1857
|
sections.push({
|
|
2168
|
-
|
|
2169
|
-
items: systemItems
|
|
1858
|
+
items: ["No cache files found"]
|
|
2170
1859
|
});
|
|
2171
1860
|
}
|
|
2172
|
-
if (
|
|
2173
|
-
const productItems = [];
|
|
2174
|
-
const sortedProductGroups = sortGroups(productGroups);
|
|
2175
|
-
for (const [groupName, cmds] of sortedProductGroups) {
|
|
2176
|
-
productItems.push("");
|
|
2177
|
-
productItems.push(`\u{1F4E6} ${safeColors.bold(groupName)} ${safeColors.muted(`(${cmds.length})`)}`);
|
|
2178
|
-
const sortedCmds = cmds.sort((a, b) => a.name.localeCompare(b.name));
|
|
2179
|
-
for (const cmd of sortedCmds) {
|
|
2180
|
-
const invocation = safeColors.bold(`kb ${cmd.name}`);
|
|
2181
|
-
const description = cmd.describe ? ` ${safeColors.muted(cmd.describe)}` : "";
|
|
2182
|
-
productItems.push(` ${invocation}${description}`);
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
1861
|
+
if (deep && modules.length > 0) {
|
|
2185
1862
|
sections.push({
|
|
2186
|
-
header: "
|
|
2187
|
-
items:
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
sections.push({
|
|
2191
|
-
header: "Next Steps",
|
|
2192
|
-
items: [
|
|
2193
|
-
`kb plugins commands --plugin <name> ${safeColors.muted("Filter by plugin")}`,
|
|
2194
|
-
`kb plugins commands --sort <order> ${safeColors.muted("Sort: alpha, count, type")}`,
|
|
2195
|
-
`kb plugins commands --json ${safeColors.muted("Get machine-readable output")}`,
|
|
2196
|
-
`kb plugins list ${safeColors.muted("Show installed plugins")}`
|
|
2197
|
-
]
|
|
2198
|
-
});
|
|
2199
|
-
const jsonGroups = {};
|
|
2200
|
-
for (const [groupName, cmds] of Object.entries(filteredGroups)) {
|
|
2201
|
-
jsonGroups[groupName] = cmds.map((cmd) => ({
|
|
2202
|
-
name: cmd.name,
|
|
2203
|
-
description: cmd.describe || "",
|
|
2204
|
-
category: cmd.category
|
|
2205
|
-
}));
|
|
2206
|
-
}
|
|
2207
|
-
return {
|
|
2208
|
-
ok: true,
|
|
2209
|
-
status: "success",
|
|
2210
|
-
message: `Found ${totalCommands} commands in ${totalGroups} groups`,
|
|
2211
|
-
sections,
|
|
2212
|
-
json: {
|
|
2213
|
-
groups: jsonGroups,
|
|
2214
|
-
totalGroups,
|
|
2215
|
-
totalCommands
|
|
2216
|
-
}
|
|
2217
|
-
};
|
|
2218
|
-
},
|
|
2219
|
-
formatter(result, ctx, flags) {
|
|
2220
|
-
if (flags.json) {
|
|
2221
|
-
console.log(JSON.stringify(result.json, null, 2));
|
|
2222
|
-
} else {
|
|
2223
|
-
ctx.ui.success("Plugin Commands Registry", { sections: result.sections ?? [] });
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
});
|
|
2227
|
-
init_plugins_state();
|
|
2228
|
-
var pluginsEnable = defineSystemCommand({
|
|
2229
|
-
name: "enable",
|
|
2230
|
-
description: "Enable a plugin",
|
|
2231
|
-
category: "plugins",
|
|
2232
|
-
examples: generateExamples("enable", "plugins", [
|
|
2233
|
-
{ flags: {} },
|
|
2234
|
-
// kb plugins enable (will need <package> arg in actual usage)
|
|
2235
|
-
{ flags: { perm: ["fs.write"] } }
|
|
2236
|
-
// kb plugins enable --perm fs.write
|
|
2237
|
-
]),
|
|
2238
|
-
flags: {
|
|
2239
|
-
perm: {
|
|
2240
|
-
type: "array",
|
|
2241
|
-
description: "Grant specific permissions (e.g., --perm fs.write --perm net.fetch)"
|
|
2242
|
-
}
|
|
2243
|
-
},
|
|
2244
|
-
analytics: {
|
|
2245
|
-
command: "plugins:enable",
|
|
2246
|
-
startEvent: "PLUGINS_ENABLE_STARTED",
|
|
2247
|
-
finishEvent: "PLUGINS_ENABLE_FINISHED"
|
|
2248
|
-
},
|
|
2249
|
-
async handler(ctx, argv, flags) {
|
|
2250
|
-
if (argv.length === 0) {
|
|
2251
|
-
throw new Error("Please specify a plugin name to enable");
|
|
2252
|
-
}
|
|
2253
|
-
const packageName = argv[0];
|
|
2254
|
-
if (!packageName) {
|
|
2255
|
-
throw new Error("Please specify a plugin name to enable");
|
|
2256
|
-
}
|
|
2257
|
-
const permissions = Array.isArray(flags.perm) ? flags.perm.map(String) : [];
|
|
2258
|
-
const cwd = getContextCwd(ctx);
|
|
2259
|
-
ctx.platform?.logger?.info("Enabling plugin", { packageName, permissions });
|
|
2260
|
-
await enablePlugin(cwd, packageName);
|
|
2261
|
-
if (permissions.length > 0) {
|
|
2262
|
-
const { grantPermissions: grantPermissions2 } = await Promise.resolve().then(() => (init_plugins_state(), plugins_state_exports));
|
|
2263
|
-
await grantPermissions2(cwd, packageName, permissions);
|
|
2264
|
-
ctx.platform?.logger?.info("Plugin enabled with permissions", { packageName, permissions });
|
|
2265
|
-
return {
|
|
2266
|
-
ok: true,
|
|
2267
|
-
packageName,
|
|
2268
|
-
permissions,
|
|
2269
|
-
message: `Enabled ${packageName} with permissions: ${permissions.join(", ")}`
|
|
2270
|
-
};
|
|
2271
|
-
} else {
|
|
2272
|
-
ctx.platform?.logger?.info("Plugin enabled", { packageName });
|
|
2273
|
-
return {
|
|
2274
|
-
ok: true,
|
|
2275
|
-
packageName,
|
|
2276
|
-
message: `Enabled ${packageName}`
|
|
2277
|
-
};
|
|
2278
|
-
}
|
|
2279
|
-
},
|
|
2280
|
-
formatter(result, ctx, _flags) {
|
|
2281
|
-
ctx.ui.info(result.message ?? "Plugin enabled");
|
|
2282
|
-
ctx.ui.info(`Run 'kb plugins ls' to see updated status`);
|
|
2283
|
-
}
|
|
2284
|
-
});
|
|
2285
|
-
init_plugins_state();
|
|
2286
|
-
var pluginsDisable = defineSystemCommand({
|
|
2287
|
-
name: "disable",
|
|
2288
|
-
description: "Disable a plugin",
|
|
2289
|
-
category: "plugins",
|
|
2290
|
-
examples: generateExamples("disable", "plugins", [
|
|
2291
|
-
{ flags: {} }
|
|
2292
|
-
// kb plugins disable (requires <package> arg)
|
|
2293
|
-
]),
|
|
2294
|
-
flags: {},
|
|
2295
|
-
analytics: {
|
|
2296
|
-
command: "plugins:disable",
|
|
2297
|
-
startEvent: "PLUGINS_DISABLE_STARTED",
|
|
2298
|
-
finishEvent: "PLUGINS_DISABLE_FINISHED"
|
|
2299
|
-
},
|
|
2300
|
-
async handler(ctx, argv, _flags) {
|
|
2301
|
-
if (argv.length === 0) {
|
|
2302
|
-
throw new Error("Please specify a plugin name to disable");
|
|
2303
|
-
}
|
|
2304
|
-
const packageName = argv[0];
|
|
2305
|
-
if (!packageName) {
|
|
2306
|
-
throw new Error("Please specify a plugin name to disable");
|
|
2307
|
-
}
|
|
2308
|
-
const cwd = getContextCwd(ctx);
|
|
2309
|
-
ctx.platform?.logger?.info("Disabling plugin", { packageName });
|
|
2310
|
-
await disablePlugin(cwd, packageName);
|
|
2311
|
-
ctx.platform?.logger?.info("Plugin disabled", { packageName });
|
|
2312
|
-
return {
|
|
2313
|
-
ok: true,
|
|
2314
|
-
packageName,
|
|
2315
|
-
message: `Disabled ${packageName}`
|
|
2316
|
-
};
|
|
2317
|
-
},
|
|
2318
|
-
formatter(result, ctx, _flags) {
|
|
2319
|
-
ctx.ui.info(result.message ?? "Plugin disabled");
|
|
2320
|
-
ctx.ui.info(`Run 'kb plugins ls' to see updated status`);
|
|
2321
|
-
}
|
|
2322
|
-
});
|
|
2323
|
-
init_plugins_state();
|
|
2324
|
-
var pluginsLink = defineSystemCommand({
|
|
2325
|
-
name: "link",
|
|
2326
|
-
description: "Link a local plugin for development",
|
|
2327
|
-
category: "plugins",
|
|
2328
|
-
examples: generateExamples("link", "plugins", [
|
|
2329
|
-
{ flags: {} }
|
|
2330
|
-
// kb plugins link (requires <path> arg)
|
|
2331
|
-
]),
|
|
2332
|
-
flags: {},
|
|
2333
|
-
analytics: {
|
|
2334
|
-
command: "plugins:link",
|
|
2335
|
-
startEvent: "PLUGINS_LINK_STARTED",
|
|
2336
|
-
finishEvent: "PLUGINS_LINK_FINISHED"
|
|
2337
|
-
},
|
|
2338
|
-
async handler(ctx, argv, _flags) {
|
|
2339
|
-
if (argv.length === 0) {
|
|
2340
|
-
throw new Error("Please specify a plugin path to link");
|
|
2341
|
-
}
|
|
2342
|
-
const pluginPath = argv[0];
|
|
2343
|
-
if (!pluginPath) {
|
|
2344
|
-
throw new Error("Please specify a plugin path to link");
|
|
2345
|
-
}
|
|
2346
|
-
const cwd = getContextCwd(ctx);
|
|
2347
|
-
const absPath = path.resolve(cwd, pluginPath);
|
|
2348
|
-
ctx.platform?.logger?.info("Linking plugin", { pluginPath, absPath });
|
|
2349
|
-
const pkgJsonPath = path.join(absPath, "package.json");
|
|
2350
|
-
await promises.access(pkgJsonPath);
|
|
2351
|
-
const pkgJson = JSON.parse(await promises.readFile(pkgJsonPath, "utf8"));
|
|
2352
|
-
const hasManifest = pkgJson.kb?.commandsManifest || pkgJson.exports?.["./kb/commands"];
|
|
2353
|
-
if (!hasManifest) {
|
|
2354
|
-
ctx.platform?.logger?.warn("Plugin may not be valid", { packageName: pkgJson.name, pluginPath });
|
|
2355
|
-
}
|
|
2356
|
-
await linkPlugin(cwd, absPath);
|
|
2357
|
-
ctx.platform?.logger?.info("Plugin linked", { packageName: pkgJson.name, absPath });
|
|
2358
|
-
let configUpdated = false;
|
|
2359
|
-
try {
|
|
2360
|
-
const configPath = path.join(cwd, ".kb", "kb.config.json");
|
|
2361
|
-
const config = JSON.parse(await promises.readFile(configPath, "utf8"));
|
|
2362
|
-
if (!config.plugins) {
|
|
2363
|
-
config.plugins = {};
|
|
2364
|
-
}
|
|
2365
|
-
if (!config.plugins.linked) {
|
|
2366
|
-
config.plugins.linked = [];
|
|
2367
|
-
}
|
|
2368
|
-
if (!config.plugins.linked.includes(pkgJson.name)) {
|
|
2369
|
-
config.plugins.linked.push(pkgJson.name);
|
|
2370
|
-
await promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
2371
|
-
configUpdated = true;
|
|
2372
|
-
}
|
|
2373
|
-
} catch {
|
|
2374
|
-
}
|
|
2375
|
-
return {
|
|
2376
|
-
ok: true,
|
|
2377
|
-
packageName: pkgJson.name || pluginPath,
|
|
2378
|
-
absPath,
|
|
2379
|
-
hasManifest: !!hasManifest,
|
|
2380
|
-
configUpdated,
|
|
2381
|
-
message: `Linked ${pkgJson.name || pluginPath} from ${absPath}`
|
|
2382
|
-
};
|
|
2383
|
-
},
|
|
2384
|
-
formatter(result, ctx, _flags) {
|
|
2385
|
-
if (!result.hasManifest) {
|
|
2386
|
-
ctx.ui.warn(
|
|
2387
|
-
`Package ${result.packageName ?? "unknown"} doesn't appear to be a KB CLI plugin`
|
|
2388
|
-
);
|
|
2389
|
-
ctx.ui.info('Add "kb.commandsManifest" or "exports["./kb/commands"]" to package.json');
|
|
2390
|
-
}
|
|
2391
|
-
ctx.ui.info(result.message ?? "Plugin linked");
|
|
2392
|
-
if (result.configUpdated) {
|
|
2393
|
-
ctx.ui.info(`Added ${result.packageName ?? "unknown"} to kb.config.json plugins.linked`);
|
|
2394
|
-
}
|
|
2395
|
-
ctx.ui.info(`The plugin will be discovered on next CLI run`);
|
|
2396
|
-
}
|
|
2397
|
-
});
|
|
2398
|
-
init_plugins_state();
|
|
2399
|
-
var pluginsUnlink = defineSystemCommand({
|
|
2400
|
-
name: "unlink",
|
|
2401
|
-
description: "Unlink a local plugin",
|
|
2402
|
-
category: "plugins",
|
|
2403
|
-
examples: generateExamples("unlink", "plugins", [
|
|
2404
|
-
{ flags: {} }
|
|
2405
|
-
// kb plugins unlink (requires <path-or-name> arg)
|
|
2406
|
-
]),
|
|
2407
|
-
flags: {},
|
|
2408
|
-
analytics: {
|
|
2409
|
-
command: "plugins:unlink",
|
|
2410
|
-
startEvent: "PLUGINS_UNLINK_STARTED",
|
|
2411
|
-
finishEvent: "PLUGINS_UNLINK_FINISHED"
|
|
2412
|
-
},
|
|
2413
|
-
async handler(ctx, argv, _flags) {
|
|
2414
|
-
if (argv.length === 0) {
|
|
2415
|
-
throw new Error("Please specify a plugin path or name to unlink");
|
|
2416
|
-
}
|
|
2417
|
-
const identifier = argv[0];
|
|
2418
|
-
if (!identifier) {
|
|
2419
|
-
throw new Error("Please specify a plugin path or name to unlink");
|
|
2420
|
-
}
|
|
2421
|
-
const cwd = getContextCwd(ctx);
|
|
2422
|
-
const state = await loadPluginsState(cwd);
|
|
2423
|
-
ctx.platform?.logger?.info("Unlinking plugin", { identifier });
|
|
2424
|
-
let absPath;
|
|
2425
|
-
try {
|
|
2426
|
-
absPath = path.resolve(cwd, identifier);
|
|
2427
|
-
await promises.access(absPath);
|
|
2428
|
-
} catch {
|
|
2429
|
-
const matched = state.linked.find((p) => p.includes(identifier) || p.endsWith(identifier));
|
|
2430
|
-
if (matched) {
|
|
2431
|
-
absPath = matched;
|
|
2432
|
-
} else {
|
|
2433
|
-
ctx.platform?.logger?.warn("Plugin not found", { identifier, linkedPlugins: state.linked });
|
|
2434
|
-
throw new Error(`Plugin not found: ${identifier}`);
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
if (!absPath) {
|
|
2438
|
-
ctx.platform?.logger?.warn("Plugin not found", { identifier });
|
|
2439
|
-
throw new Error(`Plugin not found: ${identifier}`);
|
|
2440
|
-
}
|
|
2441
|
-
await unlinkPlugin(cwd, absPath);
|
|
2442
|
-
ctx.platform?.logger?.info("Plugin unlinked", { identifier, absPath });
|
|
2443
|
-
return {
|
|
2444
|
-
ok: true,
|
|
2445
|
-
identifier,
|
|
2446
|
-
absPath,
|
|
2447
|
-
message: `Unlinked ${identifier}`
|
|
2448
|
-
};
|
|
2449
|
-
},
|
|
2450
|
-
formatter(result, ctx, _flags) {
|
|
2451
|
-
ctx.ui.info(result.message ?? "Plugin unlinked");
|
|
2452
|
-
ctx.ui.info(`Run 'kb plugins ls' to see updated status`);
|
|
2453
|
-
}
|
|
2454
|
-
});
|
|
2455
|
-
function detectRepoRoot(start = process.cwd()) {
|
|
2456
|
-
let cur = path.resolve(start);
|
|
2457
|
-
while (true) {
|
|
2458
|
-
if (existsSync(path.join(cur, ".git"))) {
|
|
2459
|
-
return cur;
|
|
2460
|
-
}
|
|
2461
|
-
const parent = path.dirname(cur);
|
|
2462
|
-
if (parent === cur) {
|
|
2463
|
-
return start;
|
|
2464
|
-
}
|
|
2465
|
-
cur = parent;
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
async function findRestApiManifestPath(pkgRoot, pkg) {
|
|
2469
|
-
if (pkg.kbLabs?.manifest) {
|
|
2470
|
-
const manifestPath = path.isAbsolute(pkg.kbLabs.manifest) ? pkg.kbLabs.manifest : path.join(pkgRoot, pkg.kbLabs.manifest);
|
|
2471
|
-
try {
|
|
2472
|
-
await promises.access(manifestPath);
|
|
2473
|
-
return manifestPath;
|
|
2474
|
-
} catch {
|
|
2475
|
-
return null;
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
if (pkg.kb?.manifest) {
|
|
2479
|
-
const manifestPath = path.isAbsolute(pkg.kb.manifest) ? pkg.kb.manifest : path.join(pkgRoot, pkg.kb.manifest);
|
|
2480
|
-
try {
|
|
2481
|
-
await promises.access(manifestPath);
|
|
2482
|
-
return manifestPath;
|
|
2483
|
-
} catch {
|
|
2484
|
-
return null;
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
if (Array.isArray(pkg.kbLabs?.plugins)) {
|
|
2488
|
-
for (const pluginPath of pkg.kbLabs.plugins) {
|
|
2489
|
-
const manifestPath = path.isAbsolute(pluginPath) ? pluginPath : path.join(pkgRoot, pluginPath);
|
|
2490
|
-
try {
|
|
2491
|
-
await promises.access(manifestPath);
|
|
2492
|
-
return manifestPath;
|
|
2493
|
-
} catch {
|
|
2494
|
-
continue;
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
if (Array.isArray(pkg.kb?.plugins)) {
|
|
2499
|
-
for (const pluginPath of pkg.kb.plugins) {
|
|
2500
|
-
const manifestPath = path.isAbsolute(pluginPath) ? pluginPath : path.join(pkgRoot, pluginPath);
|
|
2501
|
-
try {
|
|
2502
|
-
await promises.access(manifestPath);
|
|
2503
|
-
return manifestPath;
|
|
2504
|
-
} catch {
|
|
2505
|
-
continue;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
const pluginsDir = path.join(pkgRoot, ".kblabs", "plugins");
|
|
2510
|
-
try {
|
|
2511
|
-
const entries = await promises.readdir(pluginsDir, { withFileTypes: true });
|
|
2512
|
-
for (const entry of entries) {
|
|
2513
|
-
if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
|
|
2514
|
-
return path.join(pluginsDir, entry.name);
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
} catch {
|
|
2518
|
-
}
|
|
2519
|
-
const conventionalPaths = [
|
|
2520
|
-
path.join(pkgRoot, "dist", "manifest.v2.js"),
|
|
2521
|
-
path.join(pkgRoot, "dist", "manifest.v2.ts"),
|
|
2522
|
-
path.join(pkgRoot, "src", "manifest.v2.ts"),
|
|
2523
|
-
path.join(pkgRoot, "manifest.v2.ts"),
|
|
2524
|
-
path.join(pkgRoot, "manifest.v2.js")
|
|
2525
|
-
];
|
|
2526
|
-
for (const manifestPath of conventionalPaths) {
|
|
2527
|
-
try {
|
|
2528
|
-
await promises.access(manifestPath);
|
|
2529
|
-
return manifestPath;
|
|
2530
|
-
} catch {
|
|
2531
|
-
continue;
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
return null;
|
|
2535
|
-
}
|
|
2536
|
-
async function discoverRestApiPlugins(repoRoot) {
|
|
2537
|
-
const results = [];
|
|
2538
|
-
const workspaceYaml = path.join(repoRoot, "pnpm-workspace.yaml");
|
|
2539
|
-
let workspacePatterns = [];
|
|
2540
|
-
try {
|
|
2541
|
-
const content = await promises.readFile(workspaceYaml, "utf8");
|
|
2542
|
-
const parsed = parse(content);
|
|
2543
|
-
workspacePatterns = parsed.packages || [];
|
|
2544
|
-
} catch {
|
|
2545
|
-
workspacePatterns = ["packages/*", "apps/*"];
|
|
2546
|
-
}
|
|
2547
|
-
if (workspacePatterns.length === 0) {
|
|
2548
|
-
let currentDir = repoRoot;
|
|
2549
|
-
for (let i = 0; i < 3; i++) {
|
|
2550
|
-
const parentYaml = path.join(path.dirname(currentDir), "pnpm-workspace.yaml");
|
|
2551
|
-
try {
|
|
2552
|
-
const content = await promises.readFile(parentYaml, "utf8");
|
|
2553
|
-
const parsed = parse(content);
|
|
2554
|
-
if (parsed.packages && parsed.packages.length > 0) {
|
|
2555
|
-
workspacePatterns = parsed.packages;
|
|
2556
|
-
repoRoot = path.dirname(currentDir);
|
|
2557
|
-
break;
|
|
2558
|
-
}
|
|
2559
|
-
} catch {
|
|
2560
|
-
}
|
|
2561
|
-
currentDir = path.dirname(currentDir);
|
|
2562
|
-
if (currentDir === path.dirname(currentDir)) {
|
|
2563
|
-
break;
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
const packageJsonPaths = [];
|
|
2568
|
-
for (const pattern of workspacePatterns) {
|
|
2569
|
-
const pkgPattern = pattern.endsWith("/package.json") ? pattern : pattern.endsWith("/*") ? `${pattern}/package.json` : `${pattern}/**/package.json`;
|
|
2570
|
-
try {
|
|
2571
|
-
const matches = await glob(pkgPattern, {
|
|
2572
|
-
cwd: repoRoot,
|
|
2573
|
-
absolute: true,
|
|
2574
|
-
ignore: ["**/node_modules/**", "**/.kb/**"]
|
|
2575
|
-
});
|
|
2576
|
-
packageJsonPaths.push(...matches);
|
|
2577
|
-
} catch {
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
const uniquePaths = Array.from(new Set(packageJsonPaths));
|
|
2581
|
-
for (const pkgJsonPath of uniquePaths) {
|
|
2582
|
-
const pkgRoot = path.dirname(pkgJsonPath);
|
|
2583
|
-
try {
|
|
2584
|
-
const pkgContent = await promises.readFile(pkgJsonPath, "utf8");
|
|
2585
|
-
const pkg = JSON.parse(pkgContent);
|
|
2586
|
-
const manifestPath = await findRestApiManifestPath(pkgRoot, pkg);
|
|
2587
|
-
if (!manifestPath) {
|
|
2588
|
-
continue;
|
|
2589
|
-
}
|
|
2590
|
-
try {
|
|
2591
|
-
const module = await import(manifestPath);
|
|
2592
|
-
const manifest = module.default || module.manifest || module;
|
|
2593
|
-
if (manifest && typeof manifest === "object" && "schema" in manifest && manifest.schema === "kb.plugin/2" && "rest" in manifest && manifest.rest && typeof manifest.rest === "object" && "routes" in manifest.rest && Array.isArray(manifest.rest.routes) && manifest.rest.routes.length > 0) {
|
|
2594
|
-
const manifestV2 = manifest;
|
|
2595
|
-
results.push({
|
|
2596
|
-
manifest: manifestV2,
|
|
2597
|
-
manifestPath: path.resolve(manifestPath),
|
|
2598
|
-
pluginRoot: pkgRoot
|
|
2599
|
-
});
|
|
2600
|
-
}
|
|
2601
|
-
} catch (_e) {
|
|
2602
|
-
continue;
|
|
2603
|
-
}
|
|
2604
|
-
} catch {
|
|
2605
|
-
continue;
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
return results;
|
|
2609
|
-
}
|
|
2610
|
-
var pluginsRegistry = defineSystemCommand({
|
|
2611
|
-
name: "registry",
|
|
2612
|
-
description: "List all REST API plugin manifests for REST API server",
|
|
2613
|
-
category: "plugins",
|
|
2614
|
-
examples: generateExamples("registry", "plugins", [
|
|
2615
|
-
{ flags: {} },
|
|
2616
|
-
{ flags: { json: true } }
|
|
2617
|
-
]),
|
|
2618
|
-
flags: {
|
|
2619
|
-
json: { type: "boolean", description: "Output in JSON format" }
|
|
2620
|
-
},
|
|
2621
|
-
analytics: {
|
|
2622
|
-
command: "plugins:registry",
|
|
2623
|
-
startEvent: "PLUGINS_REGISTRY_STARTED",
|
|
2624
|
-
finishEvent: "PLUGINS_REGISTRY_FINISHED"
|
|
2625
|
-
},
|
|
2626
|
-
async handler(ctx, argv, flags) {
|
|
2627
|
-
const cwd = getContextCwd(ctx);
|
|
2628
|
-
const repoRoot = detectRepoRoot(cwd);
|
|
2629
|
-
const restApiPlugins = await discoverRestApiPlugins(repoRoot);
|
|
2630
|
-
const manifests = restApiPlugins;
|
|
2631
|
-
const manifestsWithPaths = manifests.map((p) => ({
|
|
2632
|
-
manifest: p.manifest,
|
|
2633
|
-
manifestPath: p.manifestPath,
|
|
2634
|
-
pluginRoot: p.pluginRoot
|
|
2635
|
-
}));
|
|
2636
|
-
ctx.platform?.logger?.info("Plugins registry scan completed", { count: manifests.length });
|
|
2637
|
-
if (flags.json) {
|
|
2638
|
-
ctx.ui.json({
|
|
2639
|
-
ok: true,
|
|
2640
|
-
manifests: manifests.map((p) => p.manifest),
|
|
2641
|
-
manifestsWithPaths,
|
|
2642
|
-
total: manifests.length
|
|
2643
|
-
});
|
|
2644
|
-
return {
|
|
2645
|
-
ok: true,
|
|
2646
|
-
manifests: manifests.map((p) => p.manifest),
|
|
2647
|
-
manifestsWithPaths,
|
|
2648
|
-
total: manifests.length,
|
|
2649
|
-
plugins: manifests
|
|
2650
|
-
};
|
|
2651
|
-
}
|
|
2652
|
-
const sections = [
|
|
2653
|
-
{
|
|
2654
|
-
header: "Summary",
|
|
2655
|
-
items: [
|
|
2656
|
-
`Found: ${manifests.length} REST API plugin(s)`,
|
|
2657
|
-
`Repository: ${repoRoot}`
|
|
2658
|
-
]
|
|
2659
|
-
}
|
|
2660
|
-
];
|
|
2661
|
-
if (manifests.length > 0) {
|
|
2662
|
-
const pluginItems = [];
|
|
2663
|
-
for (const plugin of manifests) {
|
|
2664
|
-
const displayName = plugin.manifest.display?.name || plugin.manifest.id;
|
|
2665
|
-
const routesCount = plugin.manifest.rest?.routes?.length || 0;
|
|
2666
|
-
pluginItems.push(
|
|
2667
|
-
`${ctx.ui.symbols.success} ${plugin.manifest.id}@${plugin.manifest.version} - ${displayName}`
|
|
2668
|
-
);
|
|
2669
|
-
pluginItems.push(` Path: ${plugin.manifestPath}`);
|
|
2670
|
-
pluginItems.push(` Routes: ${routesCount}`);
|
|
2671
|
-
}
|
|
2672
|
-
sections.push({
|
|
2673
|
-
header: "Plugins",
|
|
2674
|
-
items: pluginItems
|
|
2675
|
-
});
|
|
2676
|
-
} else {
|
|
2677
|
-
sections.push({
|
|
2678
|
-
items: ["No REST API plugins found"]
|
|
2679
|
-
});
|
|
2680
|
-
}
|
|
2681
|
-
ctx.ui.info("Plugins Registry", { sections });
|
|
2682
|
-
return {
|
|
2683
|
-
ok: true,
|
|
2684
|
-
manifests: manifests.map((p) => p.manifest),
|
|
2685
|
-
manifestsWithPaths,
|
|
2686
|
-
total: manifests.length,
|
|
2687
|
-
plugins: manifests
|
|
2688
|
-
};
|
|
2689
|
-
}
|
|
2690
|
-
});
|
|
2691
|
-
var require2 = createRequire(import.meta.url);
|
|
2692
|
-
var pluginsDoctor = defineSystemCommand({
|
|
2693
|
-
name: "doctor",
|
|
2694
|
-
description: "Diagnose plugin issues and suggest fixes",
|
|
2695
|
-
category: "plugins",
|
|
2696
|
-
examples: generateExamples("doctor", "plugins", [
|
|
2697
|
-
{ flags: {} },
|
|
2698
|
-
// kb plugins doctor
|
|
2699
|
-
{ flags: { json: true } }
|
|
2700
|
-
// kb plugins doctor --json
|
|
2701
|
-
]),
|
|
2702
|
-
flags: {
|
|
2703
|
-
json: { type: "boolean", description: "Output in JSON format" }
|
|
2704
|
-
},
|
|
2705
|
-
analytics: {
|
|
2706
|
-
command: "plugins:doctor",
|
|
2707
|
-
startEvent: "PLUGINS_DOCTOR_STARTED",
|
|
2708
|
-
finishEvent: "PLUGINS_DOCTOR_FINISHED"
|
|
2709
|
-
},
|
|
2710
|
-
async handler(ctx, argv, _flags) {
|
|
2711
|
-
const targetPlugin = argv[0];
|
|
2712
|
-
const cwd = getContextCwd(ctx);
|
|
2713
|
-
const manifests = registry.listManifests();
|
|
2714
|
-
const issues = [];
|
|
2715
|
-
for (const cmd of manifests) {
|
|
2716
|
-
const pkgName = cmd.manifest.package || cmd.manifest.group || "";
|
|
2717
|
-
if (!pkgName) {
|
|
2718
|
-
continue;
|
|
2719
|
-
}
|
|
2720
|
-
if (targetPlugin && !pkgName.includes(targetPlugin)) {
|
|
2721
|
-
continue;
|
|
2722
|
-
}
|
|
2723
|
-
if (cmd.manifest.engine?.node) {
|
|
2724
|
-
const required = cmd.manifest.engine.node;
|
|
2725
|
-
const current = process.version;
|
|
2726
|
-
if (required.startsWith(">=")) {
|
|
2727
|
-
const requiredVersion = required.replace(">=", "").trim();
|
|
2728
|
-
const currentVersionParts = current.split(".");
|
|
2729
|
-
const requiredVersionParts = requiredVersion.split(".");
|
|
2730
|
-
if (currentVersionParts[0] && requiredVersionParts[0]) {
|
|
2731
|
-
const currentMajor = parseInt(currentVersionParts[0].replace("v", ""), 10);
|
|
2732
|
-
const requiredMajor = parseInt(requiredVersionParts[0], 10);
|
|
2733
|
-
if (!isNaN(currentMajor) && !isNaN(requiredMajor) && currentMajor < requiredMajor) {
|
|
2734
|
-
issues.push({
|
|
2735
|
-
package: pkgName,
|
|
2736
|
-
severity: "error",
|
|
2737
|
-
code: "NODE_VERSION_MISMATCH",
|
|
2738
|
-
message: `Requires Node ${required}, found ${current}`,
|
|
2739
|
-
fix: `Upgrade Node to ${required} or higher`
|
|
2740
|
-
});
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
if (cmd.manifest.engine?.kbCli) {
|
|
2746
|
-
const required = cmd.manifest.engine.kbCli;
|
|
2747
|
-
const current = process.env.CLI_VERSION || process.env.CLI_VERSION || "0.1.0";
|
|
2748
|
-
if (required.startsWith("^") && current !== "0.1.0") {
|
|
2749
|
-
const requiredParts = required.replace("^", "").split(".");
|
|
2750
|
-
const currentParts = current.split(".");
|
|
2751
|
-
if (requiredParts[0] && currentParts[0]) {
|
|
2752
|
-
const requiredMajor = parseInt(requiredParts[0], 10);
|
|
2753
|
-
const currentMajor = parseInt(currentParts[0], 10);
|
|
2754
|
-
if (!isNaN(requiredMajor) && !isNaN(currentMajor) && currentMajor < requiredMajor) {
|
|
2755
|
-
issues.push({
|
|
2756
|
-
package: pkgName,
|
|
2757
|
-
severity: "error",
|
|
2758
|
-
code: "CLI_VERSION_MISMATCH",
|
|
2759
|
-
message: `Requires kb-cli ${required}, found ${current}`,
|
|
2760
|
-
fix: `Upgrade kb-cli: pnpm -w update @kb-labs/cli`
|
|
2761
|
-
});
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
if (cmd.manifest.requires) {
|
|
2767
|
-
for (const req2 of cmd.manifest.requires) {
|
|
2768
|
-
const parts = req2.split("@");
|
|
2769
|
-
const pkgNameReq = parts[0];
|
|
2770
|
-
if (!pkgNameReq) {
|
|
2771
|
-
continue;
|
|
2772
|
-
}
|
|
2773
|
-
try {
|
|
2774
|
-
require2.resolve(pkgNameReq);
|
|
2775
|
-
} catch {
|
|
2776
|
-
issues.push({
|
|
2777
|
-
package: pkgName,
|
|
2778
|
-
severity: "error",
|
|
2779
|
-
code: "MISSING_PEER_DEP",
|
|
2780
|
-
message: `Missing peer dependency: ${req2}`,
|
|
2781
|
-
fix: `Install: pnpm add ${req2}`
|
|
2782
|
-
});
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
if (cmd.manifest.engine?.module) {
|
|
2787
|
-
const required = cmd.manifest.engine.module;
|
|
2788
|
-
const pkgJsonPath = path.join(cmd.pkgRoot || cwd, "package.json");
|
|
2789
|
-
try {
|
|
2790
|
-
const pkgJson = JSON.parse(await promises.readFile(pkgJsonPath, "utf8"));
|
|
2791
|
-
const isESM = pkgJson.type === "module";
|
|
2792
|
-
if (required === "esm" && !isESM) {
|
|
2793
|
-
issues.push({
|
|
2794
|
-
package: pkgName,
|
|
2795
|
-
severity: "warning",
|
|
2796
|
-
code: "MODULE_TYPE_MISMATCH",
|
|
2797
|
-
message: `Plugin declares ESM but package.json doesn't have "type": "module"`,
|
|
2798
|
-
fix: `Add "type": "module" to ${pkgName}/package.json`
|
|
2799
|
-
});
|
|
2800
|
-
} else if (required === "cjs" && isESM) {
|
|
2801
|
-
issues.push({
|
|
2802
|
-
package: pkgName,
|
|
2803
|
-
severity: "warning",
|
|
2804
|
-
code: "MODULE_TYPE_MISMATCH",
|
|
2805
|
-
message: `Plugin declares CJS but package.json has "type": "module"`,
|
|
2806
|
-
fix: `Remove "type": "module" from ${pkgName}/package.json or use .cjs extension`
|
|
2807
|
-
});
|
|
2808
|
-
}
|
|
2809
|
-
} catch {
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
if (!cmd.available && cmd.unavailableReason) {
|
|
2813
|
-
issues.push({
|
|
2814
|
-
package: pkgName,
|
|
2815
|
-
severity: "error",
|
|
2816
|
-
code: "UNAVAILABLE",
|
|
2817
|
-
message: cmd.unavailableReason,
|
|
2818
|
-
fix: cmd.hint || "Check dependencies and manifest"
|
|
2819
|
-
});
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
const summary = {
|
|
2823
|
-
total: issues.length,
|
|
2824
|
-
errors: issues.filter((i) => i.severity === "error").length,
|
|
2825
|
-
warnings: issues.filter((i) => i.severity === "warning").length,
|
|
2826
|
-
info: issues.filter((i) => i.severity === "info").length
|
|
2827
|
-
};
|
|
2828
|
-
ctx.platform?.logger?.info("Plugins doctor completed", summary);
|
|
2829
|
-
return {
|
|
2830
|
-
ok: summary.errors === 0,
|
|
2831
|
-
issues,
|
|
2832
|
-
summary
|
|
2833
|
-
};
|
|
2834
|
-
},
|
|
2835
|
-
formatter(result, ctx, flags) {
|
|
2836
|
-
const resultData = result;
|
|
2837
|
-
if (flags.json) {
|
|
2838
|
-
ctx.ui.json(resultData);
|
|
2839
|
-
return;
|
|
2840
|
-
}
|
|
2841
|
-
const { issues, summary } = resultData;
|
|
2842
|
-
if (issues.length === 0) {
|
|
2843
|
-
ctx.ui.info(`${ctx.ui.symbols.success} All plugins are healthy!`);
|
|
2844
|
-
return;
|
|
2845
|
-
}
|
|
2846
|
-
const errorCount = summary.errors;
|
|
2847
|
-
const warningCount = summary.warnings;
|
|
2848
|
-
const summaryItems = [
|
|
2849
|
-
`Total Issues: ${issues.length}`,
|
|
2850
|
-
`Errors: ${errorCount > 0 ? ctx.ui.colors.error(errorCount.toString()) : "none"}`,
|
|
2851
|
-
`Warnings: ${warningCount > 0 ? ctx.ui.colors.warning(warningCount.toString()) : "none"}`
|
|
2852
|
-
];
|
|
2853
|
-
const issueItems = [];
|
|
2854
|
-
const byPackage = /* @__PURE__ */ new Map();
|
|
2855
|
-
for (const issue of issues) {
|
|
2856
|
-
if (!byPackage.has(issue.package)) {
|
|
2857
|
-
byPackage.set(issue.package, []);
|
|
2858
|
-
}
|
|
2859
|
-
byPackage.get(issue.package).push(issue);
|
|
2860
|
-
}
|
|
2861
|
-
for (const [pkg, pkgIssues] of Array.from(byPackage.entries()).sort()) {
|
|
2862
|
-
issueItems.push(ctx.ui.colors.bold(`${pkg}:`));
|
|
2863
|
-
for (const issue of pkgIssues) {
|
|
2864
|
-
const icon = issue.severity === "error" ? ctx.ui.symbols.error : ctx.ui.symbols.warning;
|
|
2865
|
-
const color = issue.severity === "error" ? ctx.ui.colors.error : ctx.ui.colors.warning;
|
|
2866
|
-
issueItems.push(` ${icon} ${color(issue.code)}: ${issue.message}`);
|
|
2867
|
-
if (issue.fix) {
|
|
2868
|
-
issueItems.push(` ${ctx.ui.colors.info(`Fix: ${issue.fix}`)}`);
|
|
2869
|
-
}
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
const nextStepsItems = [
|
|
2873
|
-
`kb plugins enable <name> ${ctx.ui.colors.muted("Enable a disabled plugin")}`,
|
|
2874
|
-
`kb plugins clear-cache ${ctx.ui.colors.muted("Clear cache and rediscover")}`
|
|
2875
|
-
];
|
|
2876
|
-
const sections = [
|
|
2877
|
-
{
|
|
2878
|
-
header: "Summary",
|
|
2879
|
-
items: summaryItems
|
|
2880
|
-
},
|
|
2881
|
-
{
|
|
2882
|
-
header: "Issues",
|
|
2883
|
-
items: issueItems
|
|
2884
|
-
},
|
|
2885
|
-
{
|
|
2886
|
-
header: "Next Steps",
|
|
2887
|
-
items: nextStepsItems
|
|
2888
|
-
}
|
|
2889
|
-
];
|
|
2890
|
-
if (errorCount > 0) {
|
|
2891
|
-
ctx.ui.error("Plugin Diagnostics", { sections });
|
|
2892
|
-
} else if (warningCount > 0) {
|
|
2893
|
-
ctx.ui.warn("Plugin Diagnostics", { sections });
|
|
2894
|
-
} else {
|
|
2895
|
-
ctx.ui.success("Plugin Diagnostics", { sections });
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
});
|
|
2899
|
-
var pluginsScaffold = defineSystemCommand({
|
|
2900
|
-
name: "scaffold",
|
|
2901
|
-
description: "Generate a new KB CLI plugin template",
|
|
2902
|
-
category: "plugins",
|
|
2903
|
-
examples: generateExamples("scaffold", "plugins", [
|
|
2904
|
-
{ flags: {} },
|
|
2905
|
-
// kb plugins scaffold (requires <name> arg)
|
|
2906
|
-
{ flags: { format: "cjs" } }
|
|
2907
|
-
]),
|
|
2908
|
-
flags: {
|
|
2909
|
-
format: {
|
|
2910
|
-
type: "string",
|
|
2911
|
-
description: "Module format: esm or cjs",
|
|
2912
|
-
default: "esm",
|
|
2913
|
-
choices: ["esm", "cjs"]
|
|
2914
|
-
}
|
|
2915
|
-
},
|
|
2916
|
-
analytics: {
|
|
2917
|
-
command: "plugins:scaffold",
|
|
2918
|
-
startEvent: "PLUGINS_SCAFFOLD_STARTED",
|
|
2919
|
-
finishEvent: "PLUGINS_SCAFFOLD_FINISHED"
|
|
2920
|
-
},
|
|
2921
|
-
async handler(ctx, argv, flags) {
|
|
2922
|
-
if (argv.length === 0) {
|
|
2923
|
-
throw new Error("Please specify a plugin name");
|
|
2924
|
-
}
|
|
2925
|
-
const pluginName = argv[0];
|
|
2926
|
-
if (!pluginName) {
|
|
2927
|
-
throw new Error("Please specify a plugin name");
|
|
2928
|
-
}
|
|
2929
|
-
ctx.platform?.logger?.info("Scaffolding plugin", { pluginName });
|
|
2930
|
-
const format = String(flags.format ?? "esm");
|
|
2931
|
-
const isESM = format === "esm";
|
|
2932
|
-
const extension = isESM ? "ts" : "ts";
|
|
2933
|
-
const moduleType = isESM ? "module" : "commonjs";
|
|
2934
|
-
const tsupFormat = isESM ? "esm" : "cjs";
|
|
2935
|
-
const baseDir = getContextCwd(ctx);
|
|
2936
|
-
const dir = path.join(baseDir, pluginName);
|
|
2937
|
-
try {
|
|
2938
|
-
await promises.access(dir);
|
|
2939
|
-
ctx.platform?.logger?.warn("Directory already exists", { dir });
|
|
2940
|
-
throw new Error(`Directory ${pluginName} already exists`);
|
|
2941
|
-
} catch (err) {
|
|
2942
|
-
if (err.code !== "ENOENT") {
|
|
2943
|
-
throw err;
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
await promises.mkdir(dir, { recursive: true });
|
|
2947
|
-
await promises.mkdir(path.join(dir, "src"), { recursive: true });
|
|
2948
|
-
await promises.mkdir(path.join(dir, "src", "kb"), { recursive: true });
|
|
2949
|
-
await promises.mkdir(path.join(dir, "src", "commands"), { recursive: true });
|
|
2950
|
-
const packageJson = {
|
|
2951
|
-
name: `@your-scope/${pluginName}-cli`,
|
|
2952
|
-
version: "1.0.0",
|
|
2953
|
-
type: moduleType,
|
|
2954
|
-
description: `KB Labs CLI plugin: ${pluginName}`,
|
|
2955
|
-
keywords: ["kb-cli-plugin"],
|
|
2956
|
-
main: `./dist/kb/manifest.js`,
|
|
2957
|
-
exports: {
|
|
2958
|
-
"./kb/manifest": "./dist/kb/manifest.js"
|
|
2959
|
-
},
|
|
2960
|
-
files: ["dist"],
|
|
2961
|
-
scripts: {
|
|
2962
|
-
build: `tsup src/kb/manifest.ts src/commands/hello.ts --format ${tsupFormat} --dts`,
|
|
2963
|
-
dev: `tsup src/kb/manifest.ts src/commands/hello.ts --format ${tsupFormat} --watch`
|
|
2964
|
-
},
|
|
2965
|
-
kb: {
|
|
2966
|
-
plugin: true,
|
|
2967
|
-
manifest: "./dist/kb/manifest.js"
|
|
2968
|
-
},
|
|
2969
|
-
dependencies: {
|
|
2970
|
-
"@kb-labs/plugin-manifest": "workspace:*",
|
|
2971
|
-
"@kb-labs/shared-cli-ui": "workspace:*",
|
|
2972
|
-
"@kb-labs/core-types": "workspace:*"
|
|
2973
|
-
},
|
|
2974
|
-
devDependencies: {
|
|
2975
|
-
tsup: "^8.5.0",
|
|
2976
|
-
typescript: "^5.6.3"
|
|
2977
|
-
}
|
|
2978
|
-
};
|
|
2979
|
-
await promises.writeFile(path.join(dir, "package.json"), JSON.stringify(packageJson, null, 2), "utf8");
|
|
2980
|
-
const tsconfig = {
|
|
2981
|
-
extends: "@kb-labs/devkit/tsconfig/lib.json",
|
|
2982
|
-
compilerOptions: {
|
|
2983
|
-
outDir: "dist",
|
|
2984
|
-
rootDir: "src"
|
|
2985
|
-
},
|
|
2986
|
-
include: ["src/**/*"]
|
|
2987
|
-
};
|
|
2988
|
-
await promises.writeFile(path.join(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2), "utf8");
|
|
2989
|
-
const manifestContent = `import type { ManifestV2 } from '@kb-labs/plugin-contracts';
|
|
2990
|
-
|
|
2991
|
-
export const manifest: ManifestV2 = {
|
|
2992
|
-
schema: 'kb.plugin/2',
|
|
2993
|
-
id: '@your-scope/${pluginName}',
|
|
2994
|
-
version: '0.1.0',
|
|
2995
|
-
display: {
|
|
2996
|
-
name: '${pluginName} Plugin',
|
|
2997
|
-
description: 'Example KB Labs CLI plugin using manifest v2'
|
|
2998
|
-
},
|
|
2999
|
-
permissions: {
|
|
3000
|
-
fs: {
|
|
3001
|
-
mode: 'read',
|
|
3002
|
-
allow: ['.']
|
|
3003
|
-
},
|
|
3004
|
-
env: {
|
|
3005
|
-
allow: ['NODE_ENV']
|
|
3006
|
-
}
|
|
3007
|
-
},
|
|
3008
|
-
cli: {
|
|
3009
|
-
commands: [
|
|
3010
|
-
{
|
|
3011
|
-
id: 'hello',
|
|
3012
|
-
group: '${pluginName}',
|
|
3013
|
-
describe: 'Hello command from ${pluginName}',
|
|
3014
|
-
flags: [],
|
|
3015
|
-
handler: './commands/hello#run'
|
|
3016
|
-
}
|
|
3017
|
-
]
|
|
3018
|
-
}
|
|
3019
|
-
};
|
|
3020
|
-
`;
|
|
3021
|
-
await promises.writeFile(path.join(dir, "src", "kb", `manifest.${extension}`), manifestContent, "utf8");
|
|
3022
|
-
const commandContent = `import { defineCommand, type CommandResult } from '@kb-labs/shared-command-kit';
|
|
3023
|
-
|
|
3024
|
-
// Define flag types for type safety
|
|
3025
|
-
type ${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloFlags = {
|
|
3026
|
-
json: { type: 'boolean'; description?: string; default?: boolean };
|
|
3027
|
-
quiet: { type: 'boolean'; description?: string; default?: boolean };
|
|
3028
|
-
};
|
|
3029
|
-
|
|
3030
|
-
// Define result type for type safety
|
|
3031
|
-
type ${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloResult = CommandResult & {
|
|
3032
|
-
count?: number;
|
|
3033
|
-
message?: string;
|
|
3034
|
-
timing?: number;
|
|
3035
|
-
};
|
|
3036
|
-
|
|
3037
|
-
export const run = defineCommand<${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloFlags, ${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloResult>({
|
|
3038
|
-
name: '${pluginName}:hello',
|
|
3039
|
-
flags: {
|
|
3040
|
-
json: {
|
|
3041
|
-
type: 'boolean',
|
|
3042
|
-
description: 'Output as JSON',
|
|
3043
|
-
default: false,
|
|
3044
|
-
},
|
|
3045
|
-
quiet: {
|
|
3046
|
-
type: 'boolean',
|
|
3047
|
-
description: 'Suppress non-error output',
|
|
3048
|
-
default: false,
|
|
3049
|
-
},
|
|
3050
|
-
},
|
|
3051
|
-
async handler(ctx, argv, flags) {
|
|
3052
|
-
// Full type safety: flags.json and flags.quiet are properly typed!
|
|
3053
|
-
ctx.platform?.logger?.info('${pluginName} hello started');
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
// Your command logic here
|
|
3057
|
-
const result = {
|
|
3058
|
-
count: 1,
|
|
3059
|
-
message: 'Hello from ${pluginName} plugin!'
|
|
3060
|
-
};
|
|
3061
|
-
|
|
3062
|
-
const duration = ctx.tracker.total();
|
|
3063
|
-
|
|
3064
|
-
ctx.platform?.logger?.info('${pluginName} hello completed', {
|
|
3065
|
-
durationMs: duration,
|
|
3066
|
-
});
|
|
3067
|
-
|
|
3068
|
-
if (flags.json) {
|
|
3069
|
-
ctx.ui.json({ ok: true, ...result, timing: duration });
|
|
3070
|
-
} else if (!flags.quiet) {
|
|
3071
|
-
const summary = [
|
|
3072
|
-
ctx.ui.colors.success('\u2713 Success'),
|
|
3073
|
-
\`Message: \${result.message}\`,
|
|
3074
|
-
\`Time: \${ctx.ui.colors.muted(duration + 'ms')}\`,
|
|
3075
|
-
];
|
|
3076
|
-
ctx.ui.write(ctx.ui.box('${pluginName} Command', summary));
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
|
-
return { ok: true, ...result, timing: duration };
|
|
3080
|
-
},
|
|
3081
|
-
});
|
|
3082
|
-
|
|
3083
|
-
// Alternative: Level 2 - Partial typing (flags only)
|
|
3084
|
-
// Remove the result type parameter to use partial typing:
|
|
3085
|
-
// export const run = defineCommand<${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloFlags>({ ... });
|
|
3086
|
-
|
|
3087
|
-
// Alternative: Level 1 - No typing (minimal)
|
|
3088
|
-
// Remove both type parameters for quick prototyping:
|
|
3089
|
-
// export const run = defineCommand({ ... });
|
|
3090
|
-
`;
|
|
3091
|
-
await promises.writeFile(path.join(dir, "src", "commands", `hello.${extension}`), commandContent, "utf8");
|
|
3092
|
-
const tsupConfig = `import config from "@kb-labs/devkit/tsup/node.js";
|
|
3093
|
-
|
|
3094
|
-
export default {
|
|
3095
|
-
...config,
|
|
3096
|
-
entry: ['src/kb/manifest.ts', 'src/commands/hello.ts'],
|
|
3097
|
-
format: ['${tsupFormat}'],
|
|
3098
|
-
dts: true,
|
|
3099
|
-
};
|
|
3100
|
-
`;
|
|
3101
|
-
await promises.writeFile(path.join(dir, "tsup.config.ts"), tsupConfig, "utf8");
|
|
3102
|
-
const readme = `# ${pluginName} CLI Plugin
|
|
3103
|
-
|
|
3104
|
-
KB Labs CLI plugin for ${pluginName}.
|
|
3105
|
-
|
|
3106
|
-
## Installation
|
|
3107
|
-
|
|
3108
|
-
\`\`\`bash
|
|
3109
|
-
pnpm add @your-scope/${pluginName}-cli
|
|
3110
|
-
\`\`\`
|
|
3111
|
-
|
|
3112
|
-
## Usage
|
|
3113
|
-
|
|
3114
|
-
\`\`\`bash
|
|
3115
|
-
kb ${pluginName} hello
|
|
3116
|
-
\`\`\`
|
|
3117
|
-
|
|
3118
|
-
## Development
|
|
3119
|
-
|
|
3120
|
-
\`\`\`bash
|
|
3121
|
-
# Build
|
|
3122
|
-
pnpm build
|
|
3123
|
-
|
|
3124
|
-
# Watch mode
|
|
3125
|
-
pnpm dev
|
|
3126
|
-
|
|
3127
|
-
# Link for local development
|
|
3128
|
-
kb plugins link ./${pluginName}
|
|
3129
|
-
\`\`\`
|
|
3130
|
-
`;
|
|
3131
|
-
await promises.writeFile(path.join(dir, "README.md"), readme, "utf8");
|
|
3132
|
-
const gitignore = `node_modules
|
|
3133
|
-
dist
|
|
3134
|
-
*.log
|
|
3135
|
-
.DS_Store
|
|
3136
|
-
`;
|
|
3137
|
-
await promises.writeFile(path.join(dir, ".gitignore"), gitignore, "utf8");
|
|
3138
|
-
ctx.platform?.logger?.info("Plugin scaffold created", { pluginName, dir });
|
|
3139
|
-
return {
|
|
3140
|
-
ok: true,
|
|
3141
|
-
pluginName,
|
|
3142
|
-
dir
|
|
3143
|
-
};
|
|
3144
|
-
},
|
|
3145
|
-
formatter(result, ctx, _flags) {
|
|
3146
|
-
const resultData = result;
|
|
3147
|
-
const { pluginName, dir } = resultData;
|
|
3148
|
-
const sections = [
|
|
3149
|
-
{
|
|
3150
|
-
items: [
|
|
3151
|
-
ctx.ui.colors.bold("Plugin Template Created:"),
|
|
3152
|
-
`${ctx.ui.symbols.success} ${ctx.ui.colors.info(dir)}`,
|
|
3153
|
-
"",
|
|
3154
|
-
ctx.ui.colors.bold("Next Steps:"),
|
|
3155
|
-
` ${ctx.ui.colors.info(`cd ${pluginName}`)}`,
|
|
3156
|
-
` ${ctx.ui.colors.info("pnpm install")}`,
|
|
3157
|
-
` ${ctx.ui.colors.info("pnpm build")}`,
|
|
3158
|
-
` ${ctx.ui.colors.info(`kb plugins link ./${pluginName}`)}`,
|
|
3159
|
-
` ${ctx.ui.colors.info(`kb ${pluginName} hello`)}`,
|
|
3160
|
-
"",
|
|
3161
|
-
ctx.ui.colors.muted("See docs/plugin-development.md for more details")
|
|
3162
|
-
]
|
|
3163
|
-
}
|
|
3164
|
-
];
|
|
3165
|
-
ctx.ui.success("Plugin Scaffold", { sections });
|
|
3166
|
-
}
|
|
3167
|
-
});
|
|
3168
|
-
async function validateManifestAgainstContracts(manifest, contractsPath) {
|
|
3169
|
-
try {
|
|
3170
|
-
const contractsModule = await import(path.resolve(contractsPath));
|
|
3171
|
-
const contracts = contractsModule.pluginContractsManifest || contractsModule.default;
|
|
3172
|
-
if (!contracts) {
|
|
3173
|
-
return { ok: false, issues: ["Contracts file does not export pluginContractsManifest"] };
|
|
3174
|
-
}
|
|
3175
|
-
const issues = [];
|
|
3176
|
-
if (typeof manifest === "object" && manifest !== null && "artifacts" in manifest) {
|
|
3177
|
-
const manifestArtifacts = manifest.artifacts || [];
|
|
3178
|
-
const contractArtifacts = contracts.artifacts || {};
|
|
3179
|
-
for (const artifact of manifestArtifacts) {
|
|
3180
|
-
if (!(artifact.id in contractArtifacts)) {
|
|
3181
|
-
issues.push(`Artifact ID "${artifact.id}" not found in contracts`);
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
if (typeof manifest === "object" && manifest !== null && "cli" in manifest) {
|
|
3186
|
-
const cli = manifest.cli;
|
|
3187
|
-
const manifestCommands = cli?.commands || [];
|
|
3188
|
-
const contractCommands = contracts.commands || {};
|
|
3189
|
-
for (const cmd of manifestCommands) {
|
|
3190
|
-
if (!(cmd.id in contractCommands)) {
|
|
3191
|
-
issues.push(`Command ID "${cmd.id}" not found in contracts`);
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
return { ok: issues.length === 0, issues };
|
|
3196
|
-
} catch (error) {
|
|
3197
|
-
return {
|
|
3198
|
-
ok: false,
|
|
3199
|
-
issues: [`Failed to load contracts: ${error instanceof Error ? error.message : String(error)}`]
|
|
3200
|
-
};
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
var pluginValidate = defineSystemCommand({
|
|
3204
|
-
name: "plugin:validate",
|
|
3205
|
-
description: "Validate plugin manifest and contracts",
|
|
3206
|
-
category: "plugins",
|
|
3207
|
-
flags: {
|
|
3208
|
-
manifest: {
|
|
3209
|
-
type: "string",
|
|
3210
|
-
description: "Path to manifest file (default: manifest.v2.ts)",
|
|
3211
|
-
default: "manifest.v2.ts"
|
|
3212
|
-
},
|
|
3213
|
-
contracts: {
|
|
3214
|
-
type: "string",
|
|
3215
|
-
description: "Path to contracts file for cross-validation"
|
|
3216
|
-
},
|
|
3217
|
-
fix: {
|
|
3218
|
-
type: "boolean",
|
|
3219
|
-
description: "Automatically fix common issues"
|
|
3220
|
-
}
|
|
3221
|
-
},
|
|
3222
|
-
async handler(ctx, argv, flags) {
|
|
3223
|
-
const cwd = getContextCwd(ctx);
|
|
3224
|
-
const manifestPath = path.resolve(cwd, flags.manifest || "manifest.v2.ts");
|
|
3225
|
-
ctx.ui.write(`Validating manifest: ${manifestPath}
|
|
3226
|
-
`);
|
|
3227
|
-
let manifestContent;
|
|
3228
|
-
try {
|
|
3229
|
-
manifestContent = await promises.readFile(manifestPath, "utf-8");
|
|
3230
|
-
} catch (error) {
|
|
3231
|
-
ctx.ui.error(`Failed to read manifest: ${error instanceof Error ? error.message : String(error)}
|
|
3232
|
-
`);
|
|
3233
|
-
return { ok: false };
|
|
3234
|
-
}
|
|
3235
|
-
let manifest;
|
|
3236
|
-
try {
|
|
3237
|
-
const modulePath = manifestPath.replace(/\.ts$/, "");
|
|
3238
|
-
const manifestModule = await import(modulePath);
|
|
3239
|
-
manifest = manifestModule.manifest || manifestModule.default;
|
|
3240
|
-
} catch (error) {
|
|
3241
|
-
try {
|
|
3242
|
-
manifest = JSON.parse(manifestContent);
|
|
3243
|
-
} catch (_jsonError) {
|
|
3244
|
-
ctx.ui.error(`Failed to parse manifest: ${error instanceof Error ? error.message : String(error)}
|
|
3245
|
-
`);
|
|
3246
|
-
return { ok: false };
|
|
3247
|
-
}
|
|
3248
|
-
}
|
|
3249
|
-
const validationResult = validateManifest(manifest);
|
|
3250
|
-
if (!validationResult.valid) {
|
|
3251
|
-
ctx.ui.error("\u274C Manifest validation failed:\n");
|
|
3252
|
-
for (const msg of validationResult.errors) {
|
|
3253
|
-
ctx.ui.write(` - ${msg}
|
|
3254
|
-
`);
|
|
3255
|
-
}
|
|
3256
|
-
return { ok: false, valid: false, errors: validationResult.errors.map((msg) => ({ path: "", message: msg })) };
|
|
3257
|
-
}
|
|
3258
|
-
ctx.ui.success("\u2705 Manifest structure is valid!\n");
|
|
3259
|
-
if (flags.contracts) {
|
|
3260
|
-
const contractsPath = path.resolve(cwd, flags.contracts);
|
|
3261
|
-
ctx.ui.write(`
|
|
3262
|
-
Validating against contracts: ${contractsPath}
|
|
3263
|
-
`);
|
|
3264
|
-
const crossValidation = await validateManifestAgainstContracts(manifest, contractsPath);
|
|
3265
|
-
if (!crossValidation.ok) {
|
|
3266
|
-
ctx.ui.error("\u274C Manifest does not match contracts:\n");
|
|
3267
|
-
for (const issue of crossValidation.issues) {
|
|
3268
|
-
ctx.ui.write(` - ${issue}
|
|
3269
|
-
`);
|
|
3270
|
-
}
|
|
3271
|
-
return { ok: false, valid: false, errors: crossValidation.issues.map((issue) => ({
|
|
3272
|
-
path: "",
|
|
3273
|
-
message: issue
|
|
3274
|
-
})) };
|
|
3275
|
-
}
|
|
3276
|
-
ctx.ui.success("\u2705 Manifest matches contracts!\n");
|
|
3277
|
-
}
|
|
3278
|
-
ctx.ui.success("\n\u2705 All validations passed!\n");
|
|
3279
|
-
return { ok: true, valid: true };
|
|
3280
|
-
}
|
|
3281
|
-
});
|
|
3282
|
-
|
|
3283
|
-
// src/builtins/plugins-cache-clear.ts
|
|
3284
|
-
init_plugins_state();
|
|
3285
|
-
var pluginsCacheClear = defineSystemCommand({
|
|
3286
|
-
name: "clear-cache",
|
|
3287
|
-
description: "Clear CLI plugin discovery cache",
|
|
3288
|
-
category: "plugins",
|
|
3289
|
-
examples: ["kb plugins clear-cache", "kb plugins clear-cache --deep"],
|
|
3290
|
-
flags: {
|
|
3291
|
-
deep: { type: "boolean", description: "Also clear Node.js module cache" },
|
|
3292
|
-
json: { type: "boolean", description: "Output in JSON format" }
|
|
3293
|
-
},
|
|
3294
|
-
analytics: {
|
|
3295
|
-
command: "plugins:clear-cache",
|
|
3296
|
-
startEvent: "PLUGINS_CACHE_CLEAR_STARTED",
|
|
3297
|
-
finishEvent: "PLUGINS_CACHE_CLEAR_FINISHED"
|
|
3298
|
-
},
|
|
3299
|
-
async handler(ctx, _argv, flags) {
|
|
3300
|
-
const deep = flags.deep;
|
|
3301
|
-
const cwd = getContextCwd(ctx);
|
|
3302
|
-
const result = await clearCache(cwd, { deep });
|
|
3303
|
-
ctx.platform?.logger?.info("Cache cleared", {
|
|
3304
|
-
filesCount: result.files.length,
|
|
3305
|
-
modulesCount: result.modules?.length || 0,
|
|
3306
|
-
deep
|
|
3307
|
-
});
|
|
3308
|
-
if (flags.json) {
|
|
3309
|
-
const jsonResult = {
|
|
3310
|
-
ok: true,
|
|
3311
|
-
action: "cache:clear",
|
|
3312
|
-
files: result.files,
|
|
3313
|
-
modules: result.modules,
|
|
3314
|
-
count: result.files.length,
|
|
3315
|
-
modulesCount: result.modules?.length || 0
|
|
3316
|
-
};
|
|
3317
|
-
console.log(JSON.stringify(jsonResult, null, 2));
|
|
3318
|
-
return jsonResult;
|
|
3319
|
-
}
|
|
3320
|
-
const files = result.files ?? [];
|
|
3321
|
-
const modules = result.modules ?? [];
|
|
3322
|
-
const sections = [];
|
|
3323
|
-
if (files.length > 0) {
|
|
3324
|
-
sections.push({
|
|
3325
|
-
header: "Cache Files",
|
|
3326
|
-
items: files.map((f) => `\u2713 ${f}`)
|
|
3327
|
-
});
|
|
3328
|
-
} else {
|
|
3329
|
-
sections.push({
|
|
3330
|
-
items: ["No cache files found"]
|
|
3331
|
-
});
|
|
3332
|
-
}
|
|
3333
|
-
if (deep && modules.length > 0) {
|
|
3334
|
-
sections.push({
|
|
3335
|
-
header: "Node Modules",
|
|
3336
|
-
items: [`Cleared ${modules.length} module(s) from cache`]
|
|
1863
|
+
header: "Node Modules",
|
|
1864
|
+
items: [`Cleared ${modules.length} module(s) from cache`]
|
|
3337
1865
|
});
|
|
3338
1866
|
}
|
|
3339
1867
|
sections.push({
|
|
@@ -4366,6 +2894,252 @@ var logsStats = defineSystemCommand({
|
|
|
4366
2894
|
ctx.ui.success("Log Storage Statistics", { sections });
|
|
4367
2895
|
}
|
|
4368
2896
|
});
|
|
2897
|
+
var authLogin = defineSystemCommand({
|
|
2898
|
+
name: "login",
|
|
2899
|
+
description: "Authenticate CLI with Gateway",
|
|
2900
|
+
longDescription: 'Exchange client credentials for JWT tokens and save to ~/.kb/credentials.json. Tokens auto-refresh on expiry. Use "kb auth create-service-account" to create credentials first.',
|
|
2901
|
+
category: "auth",
|
|
2902
|
+
examples: [
|
|
2903
|
+
"kb auth login --gateway-url http://localhost:4000 --client-id clt_xxx --client-secret cs_xxx",
|
|
2904
|
+
"kb auth login --gateway-url https://gateway.example.com --client-id clt_xxx --client-secret cs_xxx --json"
|
|
2905
|
+
],
|
|
2906
|
+
flags: {
|
|
2907
|
+
"gateway-url": { type: "string", description: "Gateway server URL (e.g. http://localhost:4000)" },
|
|
2908
|
+
"client-id": { type: "string", description: "Client ID from service account registration" },
|
|
2909
|
+
"client-secret": { type: "string", description: "Client Secret from service account registration" },
|
|
2910
|
+
json: { type: "boolean", description: "Output in JSON format" }
|
|
2911
|
+
},
|
|
2912
|
+
async handler(ctx, _argv, flags) {
|
|
2913
|
+
const gatewayUrl = flags["gateway-url"];
|
|
2914
|
+
const clientId = flags["client-id"];
|
|
2915
|
+
const clientSecret = flags["client-secret"];
|
|
2916
|
+
if (!gatewayUrl || !clientId || !clientSecret) {
|
|
2917
|
+
const msg = "All flags required: --gateway-url, --client-id, --client-secret";
|
|
2918
|
+
if (flags.json) {
|
|
2919
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
2920
|
+
} else {
|
|
2921
|
+
ctx.ui?.error?.(msg);
|
|
2922
|
+
}
|
|
2923
|
+
return { ok: false, error: msg, gatewayUrl: "", expiresIn: 0 };
|
|
2924
|
+
}
|
|
2925
|
+
const tokenUrl = `${gatewayUrl}/auth/token`;
|
|
2926
|
+
let tokenResponse;
|
|
2927
|
+
try {
|
|
2928
|
+
tokenResponse = await fetch(tokenUrl, {
|
|
2929
|
+
method: "POST",
|
|
2930
|
+
headers: { "Content-Type": "application/json" },
|
|
2931
|
+
body: JSON.stringify({ clientId, clientSecret })
|
|
2932
|
+
});
|
|
2933
|
+
} catch (err) {
|
|
2934
|
+
const msg = `Cannot reach Gateway at ${gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`;
|
|
2935
|
+
if (flags.json) {
|
|
2936
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
2937
|
+
} else {
|
|
2938
|
+
ctx.ui?.error?.(msg);
|
|
2939
|
+
}
|
|
2940
|
+
return { ok: false, error: msg, gatewayUrl, expiresIn: 0 };
|
|
2941
|
+
}
|
|
2942
|
+
if (!tokenResponse.ok) {
|
|
2943
|
+
const body = await tokenResponse.text().catch(() => "");
|
|
2944
|
+
const msg = `Authentication failed (HTTP ${tokenResponse.status}): ${body}`;
|
|
2945
|
+
if (flags.json) {
|
|
2946
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
2947
|
+
} else {
|
|
2948
|
+
ctx.ui?.error?.(msg);
|
|
2949
|
+
}
|
|
2950
|
+
return { ok: false, error: msg, gatewayUrl, expiresIn: 0 };
|
|
2951
|
+
}
|
|
2952
|
+
const tokenData = await tokenResponse.json();
|
|
2953
|
+
const credentialsManager = new CredentialsManager();
|
|
2954
|
+
const expiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
2955
|
+
await credentialsManager.save({
|
|
2956
|
+
gatewayUrl,
|
|
2957
|
+
accessToken: tokenData.accessToken,
|
|
2958
|
+
refreshToken: tokenData.refreshToken,
|
|
2959
|
+
expiresAt
|
|
2960
|
+
});
|
|
2961
|
+
if (flags.json) {
|
|
2962
|
+
ctx.ui?.json({
|
|
2963
|
+
ok: true,
|
|
2964
|
+
gatewayUrl,
|
|
2965
|
+
expiresIn: tokenData.expiresIn
|
|
2966
|
+
});
|
|
2967
|
+
} else {
|
|
2968
|
+
ctx.ui?.write?.(`Authenticated with Gateway at ${gatewayUrl}
|
|
2969
|
+
`);
|
|
2970
|
+
ctx.ui?.write?.(`Token expires in ${Math.floor(tokenData.expiresIn / 60)} minutes (auto-refresh enabled).
|
|
2971
|
+
`);
|
|
2972
|
+
}
|
|
2973
|
+
return { ok: true, gatewayUrl, expiresIn: tokenData.expiresIn };
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
var authLogout = defineSystemCommand({
|
|
2977
|
+
name: "logout",
|
|
2978
|
+
description: "Remove stored Gateway credentials",
|
|
2979
|
+
longDescription: "Deletes ~/.kb/credentials.json. CLI will no longer be able to reach Gateway until re-login.",
|
|
2980
|
+
category: "auth",
|
|
2981
|
+
examples: [
|
|
2982
|
+
"kb auth logout",
|
|
2983
|
+
"kb auth logout --json"
|
|
2984
|
+
],
|
|
2985
|
+
flags: {
|
|
2986
|
+
json: { type: "boolean", description: "Output in JSON format" }
|
|
2987
|
+
},
|
|
2988
|
+
async handler(ctx, _argv, flags) {
|
|
2989
|
+
const credentialsManager = new CredentialsManager();
|
|
2990
|
+
const existing = await credentialsManager.load();
|
|
2991
|
+
if (!existing) {
|
|
2992
|
+
if (flags.json) {
|
|
2993
|
+
ctx.ui?.json({ ok: true, message: "No credentials found \u2014 already logged out." });
|
|
2994
|
+
} else {
|
|
2995
|
+
ctx.ui?.write?.("No credentials found \u2014 already logged out.\n");
|
|
2996
|
+
}
|
|
2997
|
+
return { ok: true };
|
|
2998
|
+
}
|
|
2999
|
+
await credentialsManager.clear();
|
|
3000
|
+
if (flags.json) {
|
|
3001
|
+
ctx.ui?.json({ ok: true, message: "Logged out. Credentials removed." });
|
|
3002
|
+
} else {
|
|
3003
|
+
ctx.ui?.write?.("Logged out. Credentials removed.\n");
|
|
3004
|
+
}
|
|
3005
|
+
return { ok: true };
|
|
3006
|
+
}
|
|
3007
|
+
});
|
|
3008
|
+
var authStatus = defineSystemCommand({
|
|
3009
|
+
name: "status",
|
|
3010
|
+
description: "Show Gateway authentication status",
|
|
3011
|
+
longDescription: "Displays current credentials, token expiry, and Gateway URL. Optionally pings the Gateway to verify connectivity.",
|
|
3012
|
+
category: "auth",
|
|
3013
|
+
examples: [
|
|
3014
|
+
"kb auth status",
|
|
3015
|
+
"kb auth status --json"
|
|
3016
|
+
],
|
|
3017
|
+
flags: {
|
|
3018
|
+
json: { type: "boolean", description: "Output in JSON format" }
|
|
3019
|
+
},
|
|
3020
|
+
async handler(ctx, _argv, flags) {
|
|
3021
|
+
const credentialsManager = new CredentialsManager();
|
|
3022
|
+
const credentials = await credentialsManager.load();
|
|
3023
|
+
if (!credentials) {
|
|
3024
|
+
if (flags.json) {
|
|
3025
|
+
ctx.ui?.json({ ok: true, authenticated: false });
|
|
3026
|
+
} else {
|
|
3027
|
+
ctx.ui?.write?.('Not authenticated. Run "kb auth login" to configure Gateway connection.\n');
|
|
3028
|
+
}
|
|
3029
|
+
return { ok: true, authenticated: false };
|
|
3030
|
+
}
|
|
3031
|
+
const expired = credentialsManager.isExpired(credentials);
|
|
3032
|
+
const expiresAt = new Date(credentials.expiresAt).toISOString();
|
|
3033
|
+
const timeLeft = credentials.expiresAt - Date.now();
|
|
3034
|
+
const minutesLeft = Math.max(0, Math.floor(timeLeft / 6e4));
|
|
3035
|
+
if (flags.json) {
|
|
3036
|
+
ctx.ui?.json({
|
|
3037
|
+
ok: true,
|
|
3038
|
+
authenticated: true,
|
|
3039
|
+
gatewayUrl: credentials.gatewayUrl,
|
|
3040
|
+
tokenExpired: expired,
|
|
3041
|
+
expiresAt,
|
|
3042
|
+
minutesLeft
|
|
3043
|
+
});
|
|
3044
|
+
} else {
|
|
3045
|
+
ctx.ui?.write?.(`Gateway: ${credentials.gatewayUrl}
|
|
3046
|
+
`);
|
|
3047
|
+
if (expired) {
|
|
3048
|
+
ctx.ui?.write?.(`Token: expired (will auto-refresh on next request)
|
|
3049
|
+
`);
|
|
3050
|
+
} else {
|
|
3051
|
+
ctx.ui?.write?.(`Token: valid (expires in ${minutesLeft} min)
|
|
3052
|
+
`);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
return {
|
|
3056
|
+
ok: true,
|
|
3057
|
+
authenticated: true,
|
|
3058
|
+
gatewayUrl: credentials.gatewayUrl,
|
|
3059
|
+
tokenExpired: expired,
|
|
3060
|
+
expiresAt
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
});
|
|
3064
|
+
var authCreateServiceAccount = defineSystemCommand({
|
|
3065
|
+
name: "create-service-account",
|
|
3066
|
+
description: "Register a new service account with Gateway",
|
|
3067
|
+
longDescription: 'Creates a new client registration in Gateway and returns clientId + clientSecret. Use these credentials with "kb auth login" to authenticate.',
|
|
3068
|
+
category: "auth",
|
|
3069
|
+
examples: [
|
|
3070
|
+
"kb auth create-service-account --gateway-url http://localhost:4000 --name my-cli --namespace-id default",
|
|
3071
|
+
"kb auth create-service-account --gateway-url http://localhost:4000 --name ci-agent --namespace-id default --json"
|
|
3072
|
+
],
|
|
3073
|
+
flags: {
|
|
3074
|
+
"gateway-url": { type: "string", description: "Gateway server URL" },
|
|
3075
|
+
name: { type: "string", description: "Service account name (1-64 chars)" },
|
|
3076
|
+
"namespace-id": { type: "string", description: "Namespace ID (1-64 chars)" },
|
|
3077
|
+
json: { type: "boolean", description: "Output in JSON format" }
|
|
3078
|
+
},
|
|
3079
|
+
async handler(ctx, _argv, flags) {
|
|
3080
|
+
const gatewayUrl = flags["gateway-url"];
|
|
3081
|
+
const name = flags.name;
|
|
3082
|
+
const namespaceId = flags["namespace-id"];
|
|
3083
|
+
if (!gatewayUrl || !name || !namespaceId) {
|
|
3084
|
+
const msg = "All flags required: --gateway-url, --name, --namespace-id";
|
|
3085
|
+
if (flags.json) {
|
|
3086
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
3087
|
+
} else {
|
|
3088
|
+
ctx.ui?.error?.(msg);
|
|
3089
|
+
}
|
|
3090
|
+
return { ok: false, error: msg };
|
|
3091
|
+
}
|
|
3092
|
+
let response;
|
|
3093
|
+
try {
|
|
3094
|
+
response = await fetch(`${gatewayUrl}/auth/register`, {
|
|
3095
|
+
method: "POST",
|
|
3096
|
+
headers: { "Content-Type": "application/json" },
|
|
3097
|
+
body: JSON.stringify({ name, namespaceId })
|
|
3098
|
+
});
|
|
3099
|
+
} catch (err) {
|
|
3100
|
+
const msg = `Cannot reach Gateway at ${gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`;
|
|
3101
|
+
if (flags.json) {
|
|
3102
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
3103
|
+
} else {
|
|
3104
|
+
ctx.ui?.error?.(msg);
|
|
3105
|
+
}
|
|
3106
|
+
return { ok: false, error: msg };
|
|
3107
|
+
}
|
|
3108
|
+
if (!response.ok) {
|
|
3109
|
+
const body = await response.text().catch(() => "");
|
|
3110
|
+
const msg = `Registration failed (HTTP ${response.status}): ${body}`;
|
|
3111
|
+
if (flags.json) {
|
|
3112
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
3113
|
+
} else {
|
|
3114
|
+
ctx.ui?.error?.(msg);
|
|
3115
|
+
}
|
|
3116
|
+
return { ok: false, error: msg };
|
|
3117
|
+
}
|
|
3118
|
+
const data = await response.json();
|
|
3119
|
+
if (flags.json) {
|
|
3120
|
+
ctx.ui?.json({
|
|
3121
|
+
ok: true,
|
|
3122
|
+
clientId: data.clientId,
|
|
3123
|
+
clientSecret: data.clientSecret,
|
|
3124
|
+
hostId: data.hostId
|
|
3125
|
+
});
|
|
3126
|
+
} else {
|
|
3127
|
+
ctx.ui?.write?.("Service account created successfully.\n\n");
|
|
3128
|
+
ctx.ui?.write?.(` Client ID: ${data.clientId}
|
|
3129
|
+
`);
|
|
3130
|
+
ctx.ui?.write?.(` Client Secret: ${data.clientSecret}
|
|
3131
|
+
`);
|
|
3132
|
+
ctx.ui?.write?.(` Host ID: ${data.hostId}
|
|
3133
|
+
|
|
3134
|
+
`);
|
|
3135
|
+
ctx.ui?.write?.("Save these credentials securely \u2014 the secret is shown only once.\n\n");
|
|
3136
|
+
ctx.ui?.write?.("To authenticate:\n");
|
|
3137
|
+
ctx.ui?.write?.(` kb auth login --gateway-url ${gatewayUrl} --client-id ${data.clientId} --client-secret ${data.clientSecret}
|
|
3138
|
+
`);
|
|
3139
|
+
}
|
|
3140
|
+
return { ok: true, clientId: data.clientId, clientSecret: data.clientSecret, hostId: data.hostId };
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
4369
3143
|
|
|
4370
3144
|
// src/commands/system/groups.ts
|
|
4371
3145
|
var infoGroup = defineSystemCommandGroup("info", "System information commands", [
|
|
@@ -4374,18 +3148,8 @@ var infoGroup = defineSystemCommandGroup("info", "System information commands",
|
|
|
4374
3148
|
health,
|
|
4375
3149
|
diag
|
|
4376
3150
|
]);
|
|
4377
|
-
var
|
|
4378
|
-
|
|
4379
|
-
pluginsCommands,
|
|
4380
|
-
pluginsEnable,
|
|
4381
|
-
pluginsDisable,
|
|
4382
|
-
pluginsLink,
|
|
4383
|
-
pluginsUnlink,
|
|
4384
|
-
pluginsCacheClear,
|
|
4385
|
-
pluginsRegistry,
|
|
4386
|
-
pluginsDoctor,
|
|
4387
|
-
pluginsScaffold,
|
|
4388
|
-
pluginValidate
|
|
3151
|
+
var marketplaceGroup = defineSystemCommandGroup("marketplace", "Marketplace management commands", [
|
|
3152
|
+
pluginsCacheClear
|
|
4389
3153
|
]);
|
|
4390
3154
|
var docsGroup = defineSystemCommandGroup("docs", "Documentation generation commands", [
|
|
4391
3155
|
docsGenerateCliReference
|
|
@@ -4399,6 +3163,15 @@ var logsGroup = defineSystemCommandGroup("logs", "Log viewing and analysis comma
|
|
|
4399
3163
|
logsGet,
|
|
4400
3164
|
logsStats
|
|
4401
3165
|
]);
|
|
3166
|
+
var registryGroup = defineSystemCommandGroup("registry", "Entity registry commands", [
|
|
3167
|
+
registryDiagnostics
|
|
3168
|
+
]);
|
|
3169
|
+
var authGroup = defineSystemCommandGroup("auth", "Gateway authentication commands", [
|
|
3170
|
+
authLogin,
|
|
3171
|
+
authLogout,
|
|
3172
|
+
authStatus,
|
|
3173
|
+
authCreateServiceAccount
|
|
3174
|
+
]);
|
|
4402
3175
|
var req = createRequire(import.meta.url);
|
|
4403
3176
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
4404
3177
|
var __dirname$1 = path.dirname(__filename$1);
|
|
@@ -4521,7 +3294,7 @@ var manifestSchema = {
|
|
|
4521
3294
|
examples: { type: "array", items: { type: "string" } }
|
|
4522
3295
|
}
|
|
4523
3296
|
};
|
|
4524
|
-
var
|
|
3297
|
+
var validateManifest = ajv.compile(manifestSchema);
|
|
4525
3298
|
var validateFlag = ajv.compile(flagSchema);
|
|
4526
3299
|
function validateFlagDef(flag, manifestId) {
|
|
4527
3300
|
if (!validateFlag(flag)) {
|
|
@@ -4542,8 +3315,8 @@ function validateFlagDef(flag, manifestId) {
|
|
|
4542
3315
|
}
|
|
4543
3316
|
}
|
|
4544
3317
|
function validateManifestStructure(manifest) {
|
|
4545
|
-
if (!
|
|
4546
|
-
throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(
|
|
3318
|
+
if (!validateManifest(manifest)) {
|
|
3319
|
+
throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
|
|
4547
3320
|
}
|
|
4548
3321
|
if (manifest.manifestVersion !== "1.0") {
|
|
4549
3322
|
throw new Error(
|
|
@@ -4574,7 +3347,7 @@ function generateWhitespaceAliases(id) {
|
|
|
4574
3347
|
return [id.replace(":", " ")];
|
|
4575
3348
|
}
|
|
4576
3349
|
function normalizeAliases(manifest, logger) {
|
|
4577
|
-
const log3 = logger ??
|
|
3350
|
+
const log3 = logger ?? platform.logger;
|
|
4578
3351
|
const aliases = /* @__PURE__ */ new Set();
|
|
4579
3352
|
manifest.namespace || manifest.group;
|
|
4580
3353
|
if (manifest.aliases) {
|
|
@@ -4621,7 +3394,7 @@ function checkCollision(manifest, existing, currentSource, namespace) {
|
|
|
4621
3394
|
return { shouldShadow: false };
|
|
4622
3395
|
}
|
|
4623
3396
|
function preflightManifests(discoveryResults, logger) {
|
|
4624
|
-
const log3 = logger ??
|
|
3397
|
+
const log3 = logger ?? platform.logger;
|
|
4625
3398
|
const valid = [];
|
|
4626
3399
|
const skipped = [];
|
|
4627
3400
|
for (const result of discoveryResults) {
|
|
@@ -4651,7 +3424,7 @@ function preflightManifests(discoveryResults, logger) {
|
|
|
4651
3424
|
return { valid, skipped };
|
|
4652
3425
|
}
|
|
4653
3426
|
async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
4654
|
-
const log3 = options.logger ??
|
|
3427
|
+
const log3 = options.logger ?? platform.logger;
|
|
4655
3428
|
const registered = [];
|
|
4656
3429
|
const skipped = [];
|
|
4657
3430
|
const globalIds = /* @__PURE__ */ new Map();
|
|
@@ -4813,7 +3586,7 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
|
4813
3586
|
};
|
|
4814
3587
|
}
|
|
4815
3588
|
async function disposeAllPlugins(registry2, logger) {
|
|
4816
|
-
const log3 = logger ??
|
|
3589
|
+
const log3 = logger ?? platform.logger;
|
|
4817
3590
|
const manifests = registry2.listManifests();
|
|
4818
3591
|
const disposePromises = [];
|
|
4819
3592
|
for (const cmd of manifests) {
|
|
@@ -4831,7 +3604,7 @@ async function disposeAllPlugins(registry2, logger) {
|
|
|
4831
3604
|
}
|
|
4832
3605
|
await Promise.allSettled(disposePromises);
|
|
4833
3606
|
}
|
|
4834
|
-
var log2 =
|
|
3607
|
+
var log2 = platform.logger.child({ module: "cli:shutdown" });
|
|
4835
3608
|
var hooks = /* @__PURE__ */ new Set();
|
|
4836
3609
|
var signalsBound = false;
|
|
4837
3610
|
var exiting = false;
|
|
@@ -4869,7 +3642,7 @@ function registerShutdownHook(hook) {
|
|
|
4869
3642
|
var _registered = false;
|
|
4870
3643
|
var registeredCommands = [];
|
|
4871
3644
|
async function registerBuiltinCommands(input = {}) {
|
|
4872
|
-
const log3 = input.logger ??
|
|
3645
|
+
const log3 = input.logger ?? platform.logger;
|
|
4873
3646
|
if (_registered) {
|
|
4874
3647
|
return;
|
|
4875
3648
|
}
|
|
@@ -4877,9 +3650,11 @@ async function registerBuiltinCommands(input = {}) {
|
|
|
4877
3650
|
registry.markPartial(true);
|
|
4878
3651
|
registeredCommands.length = 0;
|
|
4879
3652
|
registry.registerGroup(infoGroup);
|
|
4880
|
-
registry.registerGroup(
|
|
3653
|
+
registry.registerGroup(marketplaceGroup);
|
|
3654
|
+
registry.registerGroup(registryGroup);
|
|
4881
3655
|
registry.registerGroup(docsGroup);
|
|
4882
3656
|
registry.registerGroup(logsGroup);
|
|
3657
|
+
registry.registerGroup(authGroup);
|
|
4883
3658
|
try {
|
|
4884
3659
|
const cwd = getContextCwd({ cwd: input.cwd });
|
|
4885
3660
|
const env = input.env ?? process.env;
|
|
@@ -4922,14 +3697,6 @@ async function registerBuiltinCommands(input = {}) {
|
|
|
4922
3697
|
} else {
|
|
4923
3698
|
registry.markPartial(false);
|
|
4924
3699
|
}
|
|
4925
|
-
const pluginRegistry = new PluginRegistry({
|
|
4926
|
-
strategies: ["workspace", "pkg", "dir", "file"]
|
|
4927
|
-
});
|
|
4928
|
-
await pluginRegistry.refresh();
|
|
4929
|
-
const plugins = pluginRegistry.list();
|
|
4930
|
-
if (plugins.length > 0) {
|
|
4931
|
-
log3.info(`Discovered ${plugins.length} plugins via cli-core`);
|
|
4932
|
-
}
|
|
4933
3700
|
} catch (err) {
|
|
4934
3701
|
log3.warn(`Discovery failed: ${err.message}`);
|
|
4935
3702
|
registry.markPartial(true);
|
|
@@ -5151,10 +3918,10 @@ function renderGlobalHelpNew(registry2) {
|
|
|
5151
3918
|
)} ${colors.dim("Explore product commands")}`
|
|
5152
3919
|
);
|
|
5153
3920
|
}
|
|
5154
|
-
const
|
|
5155
|
-
if (
|
|
3921
|
+
const marketplaceGroup2 = systemGroups.find((g) => g.name === "marketplace");
|
|
3922
|
+
if (marketplaceGroup2) {
|
|
5156
3923
|
nextStepsItems.push(
|
|
5157
|
-
`${colors.cyan("kb
|
|
3924
|
+
`${colors.cyan("kb marketplace")} ${colors.dim("Open marketplace commands")}`
|
|
5158
3925
|
);
|
|
5159
3926
|
}
|
|
5160
3927
|
nextStepsItems.push("");
|
|
@@ -5174,19 +3941,20 @@ function renderGlobalHelpNew(registry2) {
|
|
|
5174
3941
|
function renderPluginsHelp(registry2) {
|
|
5175
3942
|
const tracker = new TimingTracker();
|
|
5176
3943
|
registry2.list().filter(
|
|
5177
|
-
(cmd) => cmd.name?.startsWith("
|
|
3944
|
+
(cmd) => cmd.name?.startsWith("marketplace:") || cmd.category === "system"
|
|
5178
3945
|
);
|
|
5179
3946
|
const sections = [];
|
|
5180
3947
|
const commandMap = {
|
|
5181
|
-
"
|
|
5182
|
-
"
|
|
5183
|
-
"
|
|
5184
|
-
"
|
|
5185
|
-
"
|
|
5186
|
-
"
|
|
5187
|
-
"
|
|
5188
|
-
"
|
|
5189
|
-
"
|
|
3948
|
+
"marketplace:install": "Install package(s) from marketplace",
|
|
3949
|
+
"marketplace:update": "Update package(s) from marketplace",
|
|
3950
|
+
"marketplace:list": "List all discovered plugins",
|
|
3951
|
+
"marketplace:enable": "Enable a plugin",
|
|
3952
|
+
"marketplace:disable": "Disable a plugin",
|
|
3953
|
+
"marketplace:link": "Link a local plugin for development",
|
|
3954
|
+
"marketplace:unlink": "Unlink a local plugin",
|
|
3955
|
+
"marketplace:doctor": "Diagnose plugin issues",
|
|
3956
|
+
"marketplace:scaffold": "Generate a new plugin template",
|
|
3957
|
+
"marketplace:clear-cache": "Clear plugin discovery cache"
|
|
5190
3958
|
};
|
|
5191
3959
|
const maxLength = Math.max(
|
|
5192
3960
|
...Object.keys(commandMap).map((c) => c.length),
|
|
@@ -5200,10 +3968,10 @@ function renderPluginsHelp(registry2) {
|
|
|
5200
3968
|
items: commandItems
|
|
5201
3969
|
});
|
|
5202
3970
|
const examples = [
|
|
5203
|
-
`kb
|
|
5204
|
-
`kb
|
|
5205
|
-
`kb
|
|
5206
|
-
`kb
|
|
3971
|
+
`kb marketplace list ${colors.dim("List all plugins")}`,
|
|
3972
|
+
`kb marketplace enable @kb-labs/devlink-cli ${colors.dim("Enable a plugin")}`,
|
|
3973
|
+
`kb marketplace doctor ${colors.dim("Diagnose plugin issues")}`,
|
|
3974
|
+
`kb marketplace scaffold my-plugin ${colors.dim("Generate plugin template")}`
|
|
5207
3975
|
];
|
|
5208
3976
|
sections.push({
|
|
5209
3977
|
header: "Examples",
|
|
@@ -5211,7 +3979,7 @@ function renderPluginsHelp(registry2) {
|
|
|
5211
3979
|
});
|
|
5212
3980
|
sections.push({
|
|
5213
3981
|
header: "Next Steps",
|
|
5214
|
-
items: [colors.dim("Use 'kb
|
|
3982
|
+
items: [colors.dim("Use 'kb marketplace <command> --help' for detailed help")]
|
|
5215
3983
|
});
|
|
5216
3984
|
return sideBorderBox({
|
|
5217
3985
|
title: "\u{1F9E9} Plugin Management",
|