@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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { safeColors, safeSymbols, getContextCwd, formatTable, toPosixPath, TimingTracker, sideBorderBox, formatCommandHelp } from '@kb-labs/shared-cli-ui';
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 { createCliAPI } from '@kb-labs/cli-api';
12
- import { PluginRegistry, getLogger, createNoOpLogger, colors, getLogLevel } from '@kb-labs/cli-core';
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 plugins enable'`);
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 plugins enable'`);
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 V3 adapter, not via legacy run() path.`);
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 a friendly greeting",
1425
- longDescription: "Prints a simple greeting message for testing CLI functionality",
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, argv, flags) {
1439
- const who = ctx?.user ?? "KB Labs";
1440
- const message = `Hello, ${who}!`;
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 platform8 = `${process.platform} ${process.arch}`;
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: platform8
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: ${platform8}
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: platform8
1378
+ platform: platform11
1497
1379
  });
1498
1380
  }
1499
1381
  return {
1500
1382
  ok: true,
1501
1383
  version: cliVersion,
1502
1384
  nodeVersion,
1503
- platform: platform8
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 createCliAPI({
1526
- cache: { inMemory: true, ttlMs: 5e3 }
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 platform8 = process.platform;
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}, ${platform8}/${arch}`,
1643
- details: { nodeVersion, cliVersion, platform: platform8, arch }
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: "plugins",
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: "plugins",
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 state = await loadPluginsState(cwd);
1706
- const enabledCount = state.enabled.length;
1707
- const disabledCount = state.disabled.length;
1708
- const linkedCount = state.linked.length;
1709
- diagnostics.push({
1710
- category: "plugins-state",
1711
- status: "ok",
1712
- message: `${enabledCount} enabled, ${disabledCount} disabled, ${linkedCount} linked`,
1713
- details: {
1714
- enabled: enabledCount,
1715
- disabled: disabledCount,
1716
- linked: linkedCount,
1717
- permissions: Object.keys(state.permissions).length
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: "plugins-state",
1606
+ category: "marketplace-lock",
1723
1607
  status: "error",
1724
- message: `State check failed: ${err.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 plugins doctor ${safeColors.muted("Diagnose plugin issues")}`);
1697
+ nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
1814
1698
  }
1815
1699
  if (hasWarnings) {
1816
- nextSteps.push(`kb plugins ls ${safeColors.muted("List all plugins")}`);
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
- init_plugins_state();
1844
- var pluginsList = defineSystemCommand({
1845
- name: "list",
1846
- description: "List all discovered CLI plugins",
1847
- category: "plugins",
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: "plugins:list",
1860
- startEvent: "PLUGINS_LIST_STARTED",
1861
- finishEvent: "PLUGINS_LIST_FINISHED"
1737
+ command: "registry:diagnostics",
1738
+ startEvent: "REGISTRY_DIAGNOSTICS_STARTED",
1739
+ finishEvent: "REGISTRY_DIAGNOSTICS_FINISHED"
1862
1740
  },
1863
- async handler(ctx, _argv, _flags) {
1741
+ async handler(ctx, _argv, flags) {
1864
1742
  const cwd = getContextCwd(ctx);
1865
- const pluginRegistry = new PluginRegistry({
1866
- strategies: ["workspace", "pkg", "dir", "file"],
1867
- roots: [cwd]
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 pluginsItems = [...tableLines];
2020
- if (errorLines.length > 0) {
2021
- pluginsItems.push("", ...errorLines);
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
- console.log(JSON.stringify(result.json, null, 2));
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.info("KB Labs CLI Plugins", { sections: result.sections });
1763
+ ctx.ui.success("Registry is healthy \u2014 no issues found");
2055
1764
  }
2056
1765
  }
2057
1766
  });
2058
- var pluginsCommands = defineSystemCommand({
2059
- name: "commands",
2060
- description: "Show all plugin commands with their real invocation syntax",
2061
- category: "plugins",
2062
- examples: generateExamples("commands", "plugins", [
2063
- { flags: {} },
2064
- { flags: { plugin: "@kb-labs/mind" } },
2065
- { flags: { sort: "count" } },
2066
- { flags: { json: true } }
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
- const pluginFilter = flags.plugin ?? "";
2085
- const filteredGroups = pluginFilter ? Object.fromEntries(
2086
- Object.entries(grouped).filter(([name, cmds]) => {
2087
- if (name === pluginFilter || name.includes(pluginFilter)) {
2088
- return true;
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
- return cmds.some((cmd) => cmd.category === pluginFilter || cmd.category?.includes(pluginFilter));
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 output = {};
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
- status: "success",
2112
- message: `Found ${totalCommands} commands in ${totalGroups} groups`,
2113
- json: {
2114
- groups: output,
2115
- totalGroups,
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
- sections.push({
2122
- header: "Summary",
2123
- items: [
2124
- `${safeColors.bold("Groups")}: ${totalGroups}`,
2125
- `${safeColors.bold("Commands")}: ${totalCommands}`,
2126
- `${safeColors.bold("Sort")}: ${sortOrder}`,
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
- header: "System Commands",
2169
- items: systemItems
1858
+ items: ["No cache files found"]
2170
1859
  });
2171
1860
  }
2172
- if (productGroups.length > 0) {
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: "Product Commands",
2187
- items: productItems
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 pluginsGroup = defineSystemCommandGroup("plugins", "Plugin management commands", [
4378
- pluginsList,
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 validateManifest2 = ajv.compile(manifestSchema);
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 (!validateManifest2(manifest)) {
4546
- throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest2.errors)}`);
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 ?? createNoOpLogger();
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 ?? createNoOpLogger();
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 ?? createNoOpLogger();
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 ?? createNoOpLogger();
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 = getLogger("cli:shutdown");
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 ?? createNoOpLogger();
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(pluginsGroup);
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 pluginsGroup2 = systemGroups.find((g) => g.name === "system:plugins");
5155
- if (pluginsGroup2) {
3921
+ const marketplaceGroup2 = systemGroups.find((g) => g.name === "marketplace");
3922
+ if (marketplaceGroup2) {
5156
3923
  nextStepsItems.push(
5157
- `${colors.cyan("kb plugins")} ${colors.dim("List and manage plugins")}`
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("plugins:") || cmd.category === "system"
3944
+ (cmd) => cmd.name?.startsWith("marketplace:") || cmd.category === "system"
5178
3945
  );
5179
3946
  const sections = [];
5180
3947
  const commandMap = {
5181
- "plugins:ls": "List all discovered plugins",
5182
- "plugins:enable": "Enable a plugin",
5183
- "plugins:disable": "Disable a plugin",
5184
- "plugins:link": "Link a local plugin for development",
5185
- "plugins:unlink": "Unlink a local plugin",
5186
- "plugins:doctor": "Diagnose plugin issues",
5187
- "plugins:watch": "Watch for manifest changes and hot-reload",
5188
- "plugins:scaffold": "Generate a new plugin template",
5189
- "plugins:clear-cache": "Clear plugin discovery cache"
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 plugins ls ${colors.dim("List all plugins")}`,
5204
- `kb plugins enable @kb-labs/devlink-cli ${colors.dim("Enable a plugin")}`,
5205
- `kb plugins doctor ${colors.dim("Diagnose plugin issues")}`,
5206
- `kb plugins scaffold my-plugin ${colors.dim("Generate plugin template")}`
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 plugins <command> --help' for detailed help")]
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",