@kb-labs/cli-commands 1.0.0 → 2.1.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,19 +1,20 @@
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
- import { promises, existsSync } from 'fs';
5
- import path2 from 'path';
6
4
  import { fileURLToPath, pathToFileURL } from 'url';
5
+ import { promises, existsSync } from 'fs';
7
6
  import { createHash } from 'crypto';
7
+ 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;
@@ -161,225 +162,6 @@ var init_schema = __esm({
161
162
  }
162
163
  });
163
164
 
164
- // src/commands/system/plugin-setup-rollback.ts
165
- var plugin_setup_rollback_exports = {};
166
- __export(plugin_setup_rollback_exports, {
167
- createPluginSetupRollbackCommand: () => createPluginSetupRollbackCommand
168
- });
169
- function createPluginSetupRollbackCommand(options) {
170
- const { namespace } = options;
171
- return {
172
- name: `${namespace}:setup:rollback`,
173
- describe: `Rollback setup changes applied by ${namespace}`,
174
- category: namespace,
175
- examples: [
176
- `kb ${namespace} setup:rollback --list`,
177
- `kb ${namespace} setup:rollback --log .kb/logs/setup/${namespace}-<id>.json --yes`
178
- ],
179
- flags: ROLLBACK_COMMAND_FLAGS,
180
- async run(ctx, _argv, rawFlags) {
181
- const presenter = ctx.presenter ?? {};
182
- const output = ctx.output;
183
- const logger = ctx.platform?.logger;
184
- const cwd = getContextCwd(ctx) ?? process.cwd();
185
- logger?.info("Plugin setup rollback started", { namespace });
186
- const logsDir = path2.join(cwd, ".kb", "logs", "setup");
187
- const listOnly = rawFlags.list === true;
188
- const autoConfirm = rawFlags.yes === true;
189
- const logFlag = typeof rawFlags.log === "string" ? rawFlags.log : void 0;
190
- try {
191
- await promises.mkdir(logsDir, { recursive: true });
192
- if (listOnly) {
193
- await listLogs(logsDir, namespace, output || presenter, logger);
194
- return 0;
195
- }
196
- const resolvedLogPath = logFlag ? resolveLogPath(logsDir, logFlag, cwd) : await findLatestLog(logsDir, namespace);
197
- if (!resolvedLogPath) {
198
- logger?.warn("No setup log found", { namespace, logFlag });
199
- (output || presenter)?.error?.(
200
- `\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043D\u0438 \u043E\u0434\u0438\u043D \u043B\u043E\u0433 setup \u0434\u043B\u044F ${namespace}. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 --log \u0434\u043B\u044F \u0443\u043A\u0430\u0437\u0430\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u0430.`
201
- );
202
- return 1;
203
- }
204
- logger?.info("Using setup log", { logPath: resolvedLogPath });
205
- (output || presenter)?.info?.(`~ \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C \u043B\u043E\u0433: ${path2.relative(cwd, resolvedLogPath)}`);
206
- if (!autoConfirm) {
207
- (output || presenter)?.warn?.("\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0444\u043B\u0430\u0433 --yes \u0434\u043B\u044F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043E\u0442\u043A\u0430\u0442\u0430.");
208
- return 1;
209
- }
210
- const entries = await readJournalEntries(resolvedLogPath);
211
- if (entries.length === 0) {
212
- logger?.warn("Setup log is empty", { logPath: resolvedLogPath });
213
- (output || presenter)?.warn?.("\u041B\u043E\u0433 \u043F\u0443\u0441\u0442\u043E\u0439, \u043E\u0442\u043A\u0430\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.");
214
- return 0;
215
- }
216
- logger?.info("Applying rollback", { entriesCount: entries.length });
217
- await applyRollback(entries, cwd, output || presenter, logger);
218
- logger?.info("Rollback completed");
219
- (output || presenter)?.info?.("\u2713 \u041E\u0442\u043A\u0430\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D.");
220
- return 0;
221
- } catch (error) {
222
- logger?.error("Rollback failed", {
223
- error: error instanceof Error ? error.message : String(error)
224
- });
225
- (output || presenter)?.error?.(error);
226
- return 1;
227
- }
228
- }
229
- };
230
- }
231
- function resolveLogPath(logsDir, inputPath, cwd) {
232
- const candidate = path2.isAbsolute(inputPath) ? inputPath : path2.resolve(cwd, inputPath);
233
- if (candidate.startsWith(logsDir)) {
234
- return candidate;
235
- }
236
- return candidate;
237
- }
238
- async function listLogs(logsDir, namespace, output, logger) {
239
- const files = await safeReadDir(logsDir);
240
- const filtered = files.filter((file) => file.startsWith(namespace) && file.endsWith(".json")).sort().reverse();
241
- logger?.info("Listing setup logs", { namespace, count: filtered.length });
242
- if (filtered.length === 0) {
243
- output?.info?.("\u041B\u043E\u0433\u0438 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B.");
244
- return;
245
- }
246
- output?.info?.("\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u043B\u043E\u0433\u0438 setup:");
247
- for (const file of filtered) {
248
- output?.info?.(` \u2022 ${file}`);
249
- }
250
- }
251
- async function safeReadDir(dir) {
252
- try {
253
- return await promises.readdir(dir);
254
- } catch (error) {
255
- if (error?.code === "ENOENT") {
256
- return [];
257
- }
258
- throw error;
259
- }
260
- }
261
- async function findLatestLog(logsDir, namespace) {
262
- const entries = await safeReadDir(logsDir);
263
- const candidates = await Promise.all(
264
- entries.filter((file) => file.startsWith(namespace) && file.endsWith(".json")).map(async (file) => {
265
- const fullPath = path2.join(logsDir, file);
266
- const stat = await promises.stat(fullPath);
267
- return { path: fullPath, mtime: stat.mtimeMs };
268
- })
269
- );
270
- if (candidates.length === 0) {
271
- return null;
272
- }
273
- candidates.sort((a, b) => b.mtime - a.mtime);
274
- return candidates[0]?.path ?? null;
275
- }
276
- async function readJournalEntries(logPath) {
277
- const content = await promises.readFile(logPath, "utf8");
278
- const parsed = JSON.parse(content);
279
- if (!Array.isArray(parsed)) {
280
- throw new Error("\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u043B\u043E\u0433\u0430 setup.");
281
- }
282
- return parsed;
283
- }
284
- async function applyRollback(entries, cwd, output, logger) {
285
- logger?.info("Applying rollback operations", { entriesCount: entries.length });
286
- for (const entry of [...entries].reverse()) {
287
- const operation = entry.operation?.operation;
288
- if (!operation) {
289
- continue;
290
- }
291
- switch (operation.kind) {
292
- case "file":
293
- case "config":
294
- case "script":
295
- await restoreFileLikeOperation(entry, cwd, output, logger);
296
- break;
297
- default:
298
- logger?.warn("Skipping operation (rollback not implemented)", { kind: operation.kind });
299
- output?.warn?.(
300
- `\u041F\u0440\u043E\u043F\u0443\u0441\u043A \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 ${operation.kind} (\u043E\u0442\u043A\u0430\u0442 \u043D\u0435 \u0440\u0435\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u043D).`
301
- );
302
- }
303
- }
304
- }
305
- async function restoreFileLikeOperation(entry, cwd, output, logger) {
306
- const operation = entry.operation.operation;
307
- const relativePath = operation.kind === "script" ? operation.file : operation.path;
308
- const targetPath = resolveWithinWorkspace(cwd, relativePath);
309
- if (entry.backupPath) {
310
- await copyBackup(entry.backupPath, targetPath, output, logger);
311
- return;
312
- }
313
- if (entry.before?.exists === false) {
314
- await promises.rm(targetPath, { force: true, recursive: false });
315
- logger?.info("File removed during rollback", { path: targetPath });
316
- output?.info?.(`\u2212 \u0423\u0434\u0430\u043B\u0451\u043D ${path2.relative(cwd, targetPath)}`);
317
- return;
318
- }
319
- if (typeof entry.before?.content === "string") {
320
- if (entry.before.content.startsWith("<truncated")) {
321
- logger?.warn("Skipping truncated file", { path: targetPath });
322
- output?.warn?.(
323
- `\u041F\u0440\u043E\u043F\u0443\u0441\u043A ${path2.relative(cwd, targetPath)} \u2014 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u043B\u043E\u0433\u0430 \u0443\u0441\u0435\u0447\u0435\u043D\u043E.`
324
- );
325
- return;
326
- }
327
- await promises.mkdir(path2.dirname(targetPath), { recursive: true });
328
- await promises.writeFile(targetPath, entry.before.content, "utf8");
329
- logger?.info("File restored from log", { path: targetPath });
330
- output?.info?.(`\u21BA \u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D ${path2.relative(cwd, targetPath)}`);
331
- return;
332
- }
333
- logger?.warn("Skipping file (no restore data)", { path: targetPath });
334
- output?.warn?.(
335
- `\u041F\u0440\u043E\u043F\u0443\u0441\u043A ${path2.relative(cwd, targetPath)} \u2014 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0442 \u0434\u0430\u043D\u043D\u044B\u0435 \u0434\u043B\u044F \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F.`
336
- );
337
- }
338
- function resolveWithinWorkspace(workspace, target) {
339
- const resolvedWorkspace = path2.resolve(workspace);
340
- const resolvedTarget = path2.resolve(resolvedWorkspace, target);
341
- if (!resolvedTarget.startsWith(resolvedWorkspace)) {
342
- throw new Error(`\u041F\u0443\u0442\u044C ${target} \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u0440\u0430\u0431\u043E\u0447\u0435\u0439 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u0438.`);
343
- }
344
- return resolvedTarget;
345
- }
346
- async function copyBackup(backupPath, destination, output, logger) {
347
- try {
348
- await promises.access(backupPath);
349
- } catch {
350
- logger?.warn("Backup not found", { backupPath });
351
- output?.warn?.(`\u041F\u0440\u043E\u043F\u0443\u0441\u043A \u2014 backup ${backupPath} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.`);
352
- return;
353
- }
354
- await promises.mkdir(path2.dirname(destination), { recursive: true });
355
- await promises.copyFile(backupPath, destination);
356
- logger?.info("File restored from backup", { backupPath, destination });
357
- output?.info?.(`\u21BA \u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0438\u0437 backup ${path2.basename(destination)}`);
358
- }
359
- var ROLLBACK_COMMAND_FLAGS;
360
- var init_plugin_setup_rollback = __esm({
361
- "src/commands/system/plugin-setup-rollback.ts"() {
362
- ROLLBACK_COMMAND_FLAGS = [
363
- {
364
- name: "log",
365
- type: "string",
366
- description: "Path to a setup change log JSON file. Defaults to latest log for the namespace."
367
- },
368
- {
369
- name: "list",
370
- type: "boolean",
371
- description: "List available setup change logs and exit."
372
- },
373
- {
374
- name: "yes",
375
- type: "boolean",
376
- alias: "y",
377
- description: "Apply rollback without confirmation prompt."
378
- }
379
- ];
380
- }
381
- });
382
-
383
165
  // src/registry/discover.ts
384
166
  var discover_exports = {};
385
167
  __export(discover_exports, {
@@ -394,41 +176,9 @@ function createManifestV3Loader(commandId) {
394
176
  );
395
177
  };
396
178
  }
397
- async function loadSetupRollbackCommandModule({
398
- manifestV2,
399
- namespace,
400
- pkgName,
401
- pkgRoot
402
- }) {
403
- const module = await Promise.resolve().then(() => (init_plugin_setup_rollback(), plugin_setup_rollback_exports));
404
- if (typeof module.createPluginSetupRollbackCommand !== "function") {
405
- throw new Error("Failed to load plugin setup rollback command factory");
406
- }
407
- const command = module.createPluginSetupRollbackCommand({
408
- namespace,
409
- packageName: pkgName,
410
- pkgRoot
411
- });
412
- return {
413
- run: async (ctx, argv, flags) => {
414
- const result = await command.run(ctx, argv, flags);
415
- return typeof result === "number" ? result : void 0;
416
- }
417
- };
418
- }
419
179
  function ensureManifestLoader(manifest) {
420
180
  if (typeof manifest.loader !== "function") {
421
181
  const commandId = manifest.id || manifest.group || "unknown";
422
- if (manifest.isSetupRollback) {
423
- log("debug", `[plugins][cache] Rehydrated setup rollback loader for ${commandId}`);
424
- manifest.loader = () => loadSetupRollbackCommandModule({
425
- manifestV2: manifest.manifestV2,
426
- namespace: manifest.namespace || manifest.group || deriveNamespace(manifest.package || commandId),
427
- pkgName: manifest.package || commandId,
428
- pkgRoot: manifest.pkgRoot
429
- });
430
- return;
431
- }
432
182
  log("debug", `[plugins][cache] Rehydrated loader for ${commandId}`);
433
183
  manifest.loader = createManifestV3Loader(commandId);
434
184
  }
@@ -468,7 +218,7 @@ async function computeManifestHash(manifestPath) {
468
218
  }
469
219
  }
470
220
  async function computeLockfileHash(cwd) {
471
- const lockfilePath = path2.join(cwd, "pnpm-lock.yaml");
221
+ const lockfilePath = path.join(cwd, "pnpm-lock.yaml");
472
222
  try {
473
223
  const content = await promises.readFile(lockfilePath, "utf8");
474
224
  return createHash("sha256").update(content).digest("hex");
@@ -477,7 +227,7 @@ async function computeLockfileHash(cwd) {
477
227
  }
478
228
  }
479
229
  async function computeConfigHash(cwd) {
480
- const configPath = path2.join(cwd, ".kb", "kb.config.json");
230
+ const configPath = path.join(cwd, ".kb", "kb.config.json");
481
231
  try {
482
232
  const content = await promises.readFile(configPath, "utf8");
483
233
  return createHash("sha256").update(content).digest("hex");
@@ -486,7 +236,7 @@ async function computeConfigHash(cwd) {
486
236
  }
487
237
  }
488
238
  async function computePluginsStateHash(cwd) {
489
- const pluginsPath = path2.join(cwd, ".kb", "plugins.json");
239
+ const pluginsPath = path.join(cwd, ".kb", "plugins.json");
490
240
  try {
491
241
  const content = await promises.readFile(pluginsPath, "utf8");
492
242
  return createHash("sha256").update(content).digest("hex");
@@ -499,7 +249,7 @@ async function detectNewWorkspacePackages(cwd, cachedPackages) {
499
249
  return true;
500
250
  }
501
251
  try {
502
- const workspaceYaml = path2.join(cwd, "pnpm-workspace.yaml");
252
+ const workspaceYaml = path.join(cwd, "pnpm-workspace.yaml");
503
253
  const content = await promises.readFile(workspaceYaml, "utf8");
504
254
  const parsed = parse(content);
505
255
  if (!Array.isArray(parsed.packages)) {
@@ -507,15 +257,15 @@ async function detectNewWorkspacePackages(cwd, cachedPackages) {
507
257
  }
508
258
  const knownPackages = new Set(Object.keys(cachedPackages));
509
259
  for (const pattern of parsed.packages) {
510
- const pkgPattern = path2.join(pattern, PACKAGE_JSON);
260
+ const pkgPattern = path.join(pattern, PACKAGE_JSON);
511
261
  const pkgFiles = await glob(pkgPattern, {
512
262
  cwd,
513
263
  absolute: false,
514
264
  ignore: [".kb/**", "node_modules/**", "**/node_modules/**"]
515
265
  });
516
266
  for (const pkgFile of pkgFiles) {
517
- const pkgRoot = path2.dirname(path2.join(cwd, pkgFile));
518
- const pkg = await readPackageJson(path2.join(cwd, pkgFile));
267
+ const pkgRoot = path.dirname(path.join(cwd, pkgFile));
268
+ const pkg = await readPackageJson(path.join(cwd, pkgFile));
519
269
  if (!pkg || !pkg.name) {
520
270
  continue;
521
271
  }
@@ -558,11 +308,6 @@ function getNamespaceFromManifest(manifestV2, packageName) {
558
308
  const last = parts[parts.length - 1] || packageName;
559
309
  return last.replace(/^@/, "");
560
310
  }
561
- function deriveNamespace(packageName) {
562
- const parts = packageName.split("/");
563
- const lastPart = parts[parts.length - 1] || packageName;
564
- return lastPart.replace(/^@/, "");
565
- }
566
311
  async function loadManifest(manifestPath, pkgName, pkgRoot) {
567
312
  const fileUrl = pathToFileURL(manifestPath).href;
568
313
  const mod = await import(fileUrl);
@@ -571,7 +316,7 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
571
316
  throw new Error(`Unsupported manifest format in ${pkgName}. Only kb.plugin/3 schema is supported.`);
572
317
  }
573
318
  const namespace = getNamespaceFromManifest(manifest, pkgName);
574
- const manifestDir = path2.dirname(manifestPath);
319
+ const manifestDir = path.dirname(manifestPath);
575
320
  const baseRoot = pkgRoot || manifestDir;
576
321
  const cliCommands = Array.isArray(manifest.cli?.commands) ? manifest.cli.commands : [];
577
322
  if (cliCommands.length === 0 && !manifest.setup) {
@@ -583,6 +328,7 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
583
328
  manifestVersion: "1.0",
584
329
  id: commandId,
585
330
  group: cmd.group || namespace,
331
+ subgroup: cmd.subgroup,
586
332
  describe: cmd.describe || "",
587
333
  longDescription: cmd.longDescription,
588
334
  aliases: cmd.aliases,
@@ -616,7 +362,7 @@ async function readPackageJson(pkgPath) {
616
362
  }
617
363
  }
618
364
  async function loadConfig(cwd) {
619
- const configPath = path2.join(cwd, ".kb", "kb.config.json");
365
+ const configPath = path.join(cwd, ".kb", "kb.config.json");
620
366
  try {
621
367
  const content = await promises.readFile(configPath, "utf8");
622
368
  const config = JSON.parse(content);
@@ -631,7 +377,7 @@ async function loadConfig(cwd) {
631
377
  }
632
378
  async function findManifestPath(pkgRoot, pkg) {
633
379
  if (pkg.kb?.manifest) {
634
- const manifestPath = path2.join(pkgRoot, pkg.kb.manifest);
380
+ const manifestPath = path.join(pkgRoot, pkg.kb.manifest);
635
381
  try {
636
382
  await promises.access(manifestPath);
637
383
  return { path: manifestPath, deprecated: false };
@@ -643,7 +389,7 @@ async function findManifestPath(pkgRoot, pkg) {
643
389
  const exportPath = pkg.exports["./kb/commands"];
644
390
  const manifestPath = typeof exportPath === "string" ? exportPath : exportPath.default || exportPath.import;
645
391
  if (manifestPath) {
646
- const resolved = path2.resolve(pkgRoot, manifestPath);
392
+ const resolved = path.resolve(pkgRoot, manifestPath);
647
393
  try {
648
394
  await promises.access(resolved);
649
395
  return { path: resolved, deprecated: false };
@@ -688,7 +434,7 @@ function validateUniqueIds(manifests, pkgName) {
688
434
  }
689
435
  }
690
436
  async function discoverWorkspace(cwd) {
691
- const workspaceYaml = path2.join(cwd, "pnpm-workspace.yaml");
437
+ const workspaceYaml = path.join(cwd, "pnpm-workspace.yaml");
692
438
  const content = await promises.readFile(workspaceYaml, "utf8");
693
439
  const parsed = parse(content);
694
440
  if (!parsed.packages || !Array.isArray(parsed.packages)) {
@@ -696,7 +442,7 @@ async function discoverWorkspace(cwd) {
696
442
  }
697
443
  const packageInfos = [];
698
444
  for (const pattern of parsed.packages) {
699
- const pkgPattern = path2.join(pattern, PACKAGE_JSON);
445
+ const pkgPattern = path.join(pattern, PACKAGE_JSON);
700
446
  const pkgFiles = await glob(pkgPattern, {
701
447
  cwd,
702
448
  absolute: false,
@@ -704,8 +450,8 @@ async function discoverWorkspace(cwd) {
704
450
  // Ignore .kb, node_modules
705
451
  });
706
452
  for (const pkgFile of pkgFiles) {
707
- const pkgRoot = path2.dirname(path2.join(cwd, pkgFile));
708
- const pkg = await readPackageJson(path2.join(cwd, pkgFile));
453
+ const pkgRoot = path.dirname(path.join(cwd, pkgFile));
454
+ const pkg = await readPackageJson(path.join(cwd, pkgFile));
709
455
  if (!pkg) {
710
456
  continue;
711
457
  }
@@ -770,7 +516,7 @@ async function discoverWorkspace(cwd) {
770
516
  }
771
517
  async function discoverCurrentPackage(cwd) {
772
518
  try {
773
- const pkg = await readPackageJson(path2.join(cwd, PACKAGE_JSON));
519
+ const pkg = await readPackageJson(path.join(cwd, PACKAGE_JSON));
774
520
  if (!pkg) {
775
521
  return null;
776
522
  }
@@ -798,7 +544,7 @@ async function discoverCurrentPackage(cwd) {
798
544
  return null;
799
545
  }
800
546
  async function discoverNodeModules(cwd) {
801
- const nmDir = path2.join(cwd, "node_modules");
547
+ const nmDir = path.join(cwd, "node_modules");
802
548
  const config = await loadConfig(cwd);
803
549
  try {
804
550
  const entries = await promises.readdir(nmDir, { withFileTypes: true });
@@ -812,12 +558,12 @@ async function discoverNodeModules(cwd) {
812
558
  let pkgRoot;
813
559
  let pkg;
814
560
  if (entry.name.startsWith("@")) {
815
- const scopeDir = path2.join(nmDir, entry.name);
561
+ const scopeDir = path.join(nmDir, entry.name);
816
562
  try {
817
563
  const scopedDirs = await promises.readdir(scopeDir, { withFileTypes: true });
818
564
  for (const scopedEntry of scopedDirs.filter((d) => d.isDirectory())) {
819
- pkgRoot = path2.join(scopeDir, scopedEntry.name);
820
- pkg = await readPackageJson(path2.join(pkgRoot, PACKAGE_JSON));
565
+ pkgRoot = path.join(scopeDir, scopedEntry.name);
566
+ pkg = await readPackageJson(path.join(pkgRoot, PACKAGE_JSON));
821
567
  if (!pkg) {
822
568
  continue;
823
569
  }
@@ -834,7 +580,7 @@ async function discoverNodeModules(cwd) {
834
580
  }
835
581
  const isAllowlisted = config.allow?.includes(pkg.name) || config.linked?.includes(pkg.name);
836
582
  if (!isAllowlisted) {
837
- 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'`);
838
584
  return;
839
585
  }
840
586
  const manifestInfo = await findManifestPath(pkgRoot, pkg);
@@ -851,8 +597,8 @@ async function discoverNodeModules(cwd) {
851
597
  } catch {
852
598
  }
853
599
  } else {
854
- pkgRoot = path2.join(nmDir, entry.name);
855
- pkg = await readPackageJson(path2.join(pkgRoot, PACKAGE_JSON));
600
+ pkgRoot = path.join(nmDir, entry.name);
601
+ pkg = await readPackageJson(path.join(pkgRoot, PACKAGE_JSON));
856
602
  if (!pkg) {
857
603
  return;
858
604
  }
@@ -864,7 +610,7 @@ async function discoverNodeModules(cwd) {
864
610
  }
865
611
  const isAllowlisted = config.allow?.includes(pkg.name) || config.linked?.includes(pkg.name);
866
612
  if (!isAllowlisted) {
867
- 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'`);
868
614
  return;
869
615
  }
870
616
  const manifestInfo = await findManifestPath(pkgRoot, pkg);
@@ -951,7 +697,7 @@ function deduplicateManifests(all) {
951
697
  return Array.from(byPackageName.values());
952
698
  }
953
699
  async function loadCache(cwd) {
954
- const cachePath = path2.join(cwd, ".kb", "cache", "cli-manifests.json");
700
+ const cachePath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
955
701
  try {
956
702
  const content = await promises.readFile(cachePath, "utf8");
957
703
  const cache = JSON.parse(content);
@@ -1001,8 +747,8 @@ async function loadCache(cwd) {
1001
747
  }
1002
748
  }
1003
749
  async function isPackageCacheStale(entry, options) {
1004
- const manifestFsPath = entry.manifestPath.split("/").join(path2.sep);
1005
- const pkgJsonPath = path2.join(entry.result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
750
+ const manifestFsPath = entry.manifestPath.split("/").join(path.sep);
751
+ const pkgJsonPath = path.join(entry.result.pkgRoot.split("/").join(path.sep), PACKAGE_JSON);
1006
752
  try {
1007
753
  const pkgStat = await promises.stat(pkgJsonPath);
1008
754
  if (pkgStat.mtimeMs !== entry.pkgJsonMtime) {
@@ -1039,19 +785,19 @@ async function isPackageCacheStale(entry, options) {
1039
785
  return false;
1040
786
  }
1041
787
  async function saveCache(cwd, results) {
1042
- const cachePath = path2.join(cwd, ".kb", "cache", "cli-manifests.json");
1043
- await promises.mkdir(path2.dirname(cachePath), { recursive: true });
788
+ const cachePath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
789
+ await promises.mkdir(path.dirname(cachePath), { recursive: true });
1044
790
  const packages = {};
1045
791
  const now = Date.now();
1046
792
  const stateHasher = createHash("sha256");
1047
793
  for (const result of results) {
1048
794
  try {
1049
795
  const manifestHash = await computeManifestHash(result.manifestPath);
1050
- const pkgJsonPath = path2.join(result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
796
+ const pkgJsonPath = path.join(result.pkgRoot.split("/").join(path.sep), PACKAGE_JSON);
1051
797
  const pkgStat = await promises.stat(pkgJsonPath);
1052
798
  const pkg = await readPackageJson(pkgJsonPath);
1053
799
  const version2 = pkg?.version || "0.1.0";
1054
- const manifestStat = await promises.stat(result.manifestPath.split("/").join(path2.sep));
800
+ const manifestStat = await promises.stat(result.manifestPath.split("/").join(path.sep));
1055
801
  const manifestsForCache = result.manifests.map((manifest) => {
1056
802
  const manifestCopy = { ...manifest };
1057
803
  delete manifestCopy.loader;
@@ -1133,17 +879,20 @@ async function discoverManifests(cwd, noCache = false) {
1133
879
  const ttlMs = cached.ttlMs ?? DISK_CACHE_TTL_MS;
1134
880
  const enforceHashValidation = cacheAge >= ttlMs;
1135
881
  log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms validateHash=${enforceHashValidation}`);
882
+ let staleCount = 0;
1136
883
  for (const entry of Object.values(cached.packages)) {
1137
884
  const stale = await isPackageCacheStale(entry, { validateHash: enforceHashValidation });
1138
885
  if (!stale) {
1139
886
  freshResults.push(entry.result);
887
+ } else {
888
+ staleCount += 1;
1140
889
  }
1141
890
  }
1142
891
  const hasNewWorkspacePackages = await detectNewWorkspacePackages(
1143
892
  cwd,
1144
893
  cached.packages
1145
894
  );
1146
- if (freshResults.length > 0 && !hasNewWorkspacePackages) {
895
+ if (staleCount === 0 && freshResults.length > 0 && !hasNewWorkspacePackages) {
1147
896
  const totalTime2 = Date.now() - startTime;
1148
897
  const sourceCounts2 = freshResults.reduce((acc, r) => {
1149
898
  acc[r.source] = (acc[r.source] || 0) + 1;
@@ -1156,6 +905,9 @@ async function discoverManifests(cwd, noCache = false) {
1156
905
  if (hasNewWorkspacePackages) {
1157
906
  log("debug", "[plugins][cache] invalidated: new workspace packages detected");
1158
907
  }
908
+ if (staleCount > 0) {
909
+ log("debug", `[plugins][cache] invalidated: ${staleCount} stale package(s) detected`);
910
+ }
1159
911
  }
1160
912
  }
1161
913
  let workspace = [];
@@ -1238,168 +990,6 @@ var init_discover = __esm({
1238
990
  }
1239
991
  });
1240
992
 
1241
- // src/registry/plugins-state.ts
1242
- var plugins_state_exports = {};
1243
- __export(plugins_state_exports, {
1244
- clearCache: () => clearCache,
1245
- computePackageIntegrity: () => computePackageIntegrity,
1246
- disablePlugin: () => disablePlugin,
1247
- enablePlugin: () => enablePlugin,
1248
- getPluginsStatePath: () => getPluginsStatePath,
1249
- grantPermissions: () => grantPermissions,
1250
- isPluginEnabled: () => isPluginEnabled,
1251
- linkPlugin: () => linkPlugin,
1252
- loadPluginsState: () => loadPluginsState,
1253
- recordCrash: () => recordCrash,
1254
- savePluginsState: () => savePluginsState,
1255
- unlinkPlugin: () => unlinkPlugin
1256
- });
1257
- function getPluginsStatePath(cwd) {
1258
- return path2.join(cwd, ".kb", "plugins.json");
1259
- }
1260
- async function loadPluginsState(cwd) {
1261
- const statePath = getPluginsStatePath(cwd);
1262
- try {
1263
- const content = await promises.readFile(statePath, "utf8");
1264
- const state = JSON.parse(content);
1265
- return {
1266
- ...DEFAULT_STATE,
1267
- ...state,
1268
- enabled: state.enabled || [],
1269
- disabled: state.disabled || [],
1270
- linked: state.linked || [],
1271
- permissions: state.permissions || {},
1272
- integrity: state.integrity || {},
1273
- crashes: state.crashes || {}
1274
- };
1275
- } catch {
1276
- return { ...DEFAULT_STATE };
1277
- }
1278
- }
1279
- async function savePluginsState(cwd, state) {
1280
- const statePath = getPluginsStatePath(cwd);
1281
- const dir = path2.dirname(statePath);
1282
- await promises.mkdir(dir, { recursive: true });
1283
- state.lastUpdated = Date.now();
1284
- await promises.writeFile(statePath, JSON.stringify(state, null, 2), "utf8");
1285
- }
1286
- function isPluginEnabled(state, packageName, defaultEnabled = false) {
1287
- if (state.disabled.includes(packageName)) {
1288
- return false;
1289
- }
1290
- if (state.enabled.includes(packageName)) {
1291
- return true;
1292
- }
1293
- return defaultEnabled;
1294
- }
1295
- async function enablePlugin(cwd, packageName) {
1296
- const state = await loadPluginsState(cwd);
1297
- if (!state.enabled.includes(packageName)) {
1298
- state.enabled.push(packageName);
1299
- }
1300
- state.disabled = state.disabled.filter((p) => p !== packageName);
1301
- await savePluginsState(cwd, state);
1302
- }
1303
- async function disablePlugin(cwd, packageName) {
1304
- const state = await loadPluginsState(cwd);
1305
- if (!state.disabled.includes(packageName)) {
1306
- state.disabled.push(packageName);
1307
- }
1308
- state.enabled = state.enabled.filter((p) => p !== packageName);
1309
- await savePluginsState(cwd, state);
1310
- }
1311
- async function linkPlugin(cwd, pluginPath) {
1312
- const state = await loadPluginsState(cwd);
1313
- const absPath = path2.resolve(cwd, pluginPath);
1314
- if (!state.linked.includes(absPath)) {
1315
- state.linked.push(absPath);
1316
- }
1317
- await savePluginsState(cwd, state);
1318
- }
1319
- async function unlinkPlugin(cwd, pluginPath) {
1320
- const state = await loadPluginsState(cwd);
1321
- const absPath = path2.resolve(cwd, pluginPath);
1322
- state.linked = state.linked.filter((p) => p !== absPath);
1323
- await savePluginsState(cwd, state);
1324
- }
1325
- async function grantPermissions(cwd, packageName, permissions) {
1326
- const state = await loadPluginsState(cwd);
1327
- if (!state.permissions[packageName]) {
1328
- state.permissions[packageName] = [];
1329
- }
1330
- for (const perm of permissions) {
1331
- if (!state.permissions[packageName].includes(perm)) {
1332
- state.permissions[packageName].push(perm);
1333
- }
1334
- }
1335
- await savePluginsState(cwd, state);
1336
- }
1337
- async function recordCrash(cwd, packageName) {
1338
- const state = await loadPluginsState(cwd);
1339
- state.crashes[packageName] = (state.crashes[packageName] || 0) + 1;
1340
- const CRASH_THRESHOLD = 3;
1341
- if (state.crashes[packageName] >= CRASH_THRESHOLD && !state.disabled.includes(packageName)) {
1342
- state.disabled.push(packageName);
1343
- }
1344
- await savePluginsState(cwd, state);
1345
- }
1346
- async function computePackageIntegrity(pkgRoot) {
1347
- try {
1348
- const pkgJsonPath = path2.join(pkgRoot, "package.json");
1349
- const content = await promises.readFile(pkgJsonPath, "utf8");
1350
- const hash = createHash("sha256").update(content).digest("base64");
1351
- return `sha256-${hash}`;
1352
- } catch {
1353
- return "";
1354
- }
1355
- }
1356
- async function clearCache(cwd, options) {
1357
- const cleared = [];
1358
- const cacheDir = path2.join(cwd, ".kb", "cache");
1359
- try {
1360
- const entries = await promises.readdir(cacheDir);
1361
- for (const entry of entries) {
1362
- if (entry.includes("manifest") || entry.includes("plugin")) {
1363
- const entryPath = path2.join(cacheDir, entry);
1364
- await promises.unlink(entryPath);
1365
- cleared.push(entry);
1366
- }
1367
- }
1368
- } catch {
1369
- }
1370
- const modulesCleared = [];
1371
- if (options?.deep) {
1372
- try {
1373
- const cache = __require.cache;
1374
- for (const key in cache) {
1375
- if (key.includes("plugin") || key.includes("manifest") || key.includes("@kb-labs")) {
1376
- delete cache[key];
1377
- modulesCleared.push(key);
1378
- }
1379
- }
1380
- } catch {
1381
- }
1382
- }
1383
- return {
1384
- files: cleared,
1385
- ...options?.deep ? { modules: modulesCleared } : {}
1386
- };
1387
- }
1388
- var DEFAULT_STATE;
1389
- var init_plugins_state = __esm({
1390
- "src/registry/plugins-state.ts"() {
1391
- DEFAULT_STATE = {
1392
- enabled: [],
1393
- disabled: [],
1394
- linked: [],
1395
- permissions: {},
1396
- integrity: {},
1397
- crashes: {},
1398
- lastUpdated: Date.now()
1399
- };
1400
- }
1401
- });
1402
-
1403
993
  // src/registry/service.ts
1404
994
  function manifestToCommand(registered) {
1405
995
  return {
@@ -1411,7 +1001,7 @@ function manifestToCommand(registered) {
1411
1001
  flags: registered.manifest.flags,
1412
1002
  examples: registered.manifest.examples,
1413
1003
  async run() {
1414
- 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.`);
1415
1005
  }
1416
1006
  };
1417
1007
  }
@@ -1447,6 +1037,22 @@ var InMemoryRegistry = class {
1447
1037
  this.byName.set(alias, cmd);
1448
1038
  }
1449
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
+ }
1450
1056
  }
1451
1057
  registerManifest(cmd) {
1452
1058
  const cmdId = cmd.manifest.id;
@@ -1472,9 +1078,38 @@ var InMemoryRegistry = class {
1472
1078
  const spaceSeparated = cmdId.replace(/:/g, " ");
1473
1079
  this.byName.set(spaceSeparated, commandAdapter);
1474
1080
  }
1475
- 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) {
1476
1105
  const fullName = `${cmd.manifest.group} ${cmd.manifest.id}`;
1106
+ const colonName = `${cmd.manifest.group}:${cmd.manifest.id}`;
1477
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);
1478
1113
  }
1479
1114
  if (cmd.manifest.aliases) {
1480
1115
  for (const alias of cmd.manifest.aliases) {
@@ -1677,8 +1312,8 @@ function generateExamples(commandName, productName, cases) {
1677
1312
  // src/commands/system/hello.ts
1678
1313
  var hello = defineSystemCommand({
1679
1314
  name: "hello",
1680
- description: "Print a friendly greeting",
1681
- 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',
1682
1317
  category: "info",
1683
1318
  examples: generateExamples("hello", "kb", [
1684
1319
  { flags: {} }
@@ -1691,25 +1326,16 @@ var hello = defineSystemCommand({
1691
1326
  startEvent: "HELLO_STARTED",
1692
1327
  finishEvent: "HELLO_FINISHED"
1693
1328
  },
1694
- async handler(ctx, argv, flags) {
1695
- const who = ctx?.user ?? "KB Labs";
1696
- const message = `Hello, ${who}!`;
1697
- 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");
1698
1332
  if (!flags.json) {
1699
1333
  ctx.ui?.write(`${message}
1700
1334
  `);
1701
1335
  } else {
1702
- ctx.ui?.json({
1703
- message,
1704
- who,
1705
- status: "ready"
1706
- });
1336
+ ctx.ui?.json({ message, status: "ready" });
1707
1337
  }
1708
- return {
1709
- ok: true,
1710
- message,
1711
- who
1712
- };
1338
+ return { ok: true, message };
1713
1339
  }
1714
1340
  });
1715
1341
  var version = defineSystemCommand({
@@ -1732,31 +1358,31 @@ var version = defineSystemCommand({
1732
1358
  const v = ctx?.cliVersion ?? ctx?.env?.CLI_VERSION ?? "0.0.0";
1733
1359
  const cliVersion = String(v);
1734
1360
  const nodeVersion = process.version;
1735
- const platform8 = `${process.platform} ${process.arch}`;
1361
+ const platform11 = `${process.platform} ${process.arch}`;
1736
1362
  ctx.platform?.logger?.info("Version command executed", {
1737
1363
  version: cliVersion,
1738
1364
  nodeVersion,
1739
- platform: platform8
1365
+ platform: platform11
1740
1366
  });
1741
1367
  if (!flags.json) {
1742
1368
  ctx.ui?.write(`KB Labs CLI v${cliVersion}
1743
1369
  `);
1744
1370
  ctx.ui?.write(`Node: ${nodeVersion}
1745
1371
  `);
1746
- ctx.ui?.write(`Platform: ${platform8}
1372
+ ctx.ui?.write(`Platform: ${platform11}
1747
1373
  `);
1748
1374
  } else {
1749
1375
  ctx.ui?.json({
1750
1376
  version: cliVersion,
1751
1377
  nodeVersion,
1752
- platform: platform8
1378
+ platform: platform11
1753
1379
  });
1754
1380
  }
1755
1381
  return {
1756
1382
  ok: true,
1757
1383
  version: cliVersion,
1758
1384
  nodeVersion,
1759
- platform: platform8
1385
+ platform: platform11
1760
1386
  };
1761
1387
  }
1762
1388
  });
@@ -1778,8 +1404,8 @@ var health = defineSystemCommand({
1778
1404
  finishEvent: "HEALTH_FINISHED"
1779
1405
  },
1780
1406
  async handler(ctx, _argv, _flags) {
1781
- const cliApi = await createCliAPI({
1782
- cache: { inMemory: true, ttlMs: 5e3 }
1407
+ const cliApi = await createRegistry({
1408
+ cache: { ttlMs: 5e3 }
1783
1409
  });
1784
1410
  try {
1785
1411
  ctx.platform?.logger?.info("Health check started");
@@ -1867,7 +1493,6 @@ var health = defineSystemCommand({
1867
1493
  }
1868
1494
  });
1869
1495
  init_discover();
1870
- init_plugins_state();
1871
1496
  var diag = defineSystemCommand({
1872
1497
  name: "diag",
1873
1498
  description: "Comprehensive system diagnostics (plugins, cache, environment, versions)",
@@ -1890,13 +1515,13 @@ var diag = defineSystemCommand({
1890
1515
  const diagnostics = [];
1891
1516
  const nodeVersion = process.version;
1892
1517
  const cliVersion = process.env.CLI_VERSION || process.env.CLI_VERSION || "0.1.0";
1893
- const platform8 = process.platform;
1518
+ const platform11 = process.platform;
1894
1519
  const arch = process.arch;
1895
1520
  diagnostics.push({
1896
1521
  category: "environment",
1897
1522
  status: "ok",
1898
- message: `Node ${nodeVersion}, CLI ${cliVersion}, ${platform8}/${arch}`,
1899
- details: { nodeVersion, cliVersion, platform: platform8, arch }
1523
+ message: `Node ${nodeVersion}, CLI ${cliVersion}, ${platform11}/${arch}`,
1524
+ details: { nodeVersion, cliVersion, platform: platform11, arch }
1900
1525
  });
1901
1526
  try {
1902
1527
  const discovered = await discoverManifests(cwd, false);
@@ -1905,7 +1530,7 @@ var diag = defineSystemCommand({
1905
1530
  const disabled = manifests.filter((m) => !m.available).length;
1906
1531
  const shadowed = manifests.filter((m) => m.shadowed).length;
1907
1532
  diagnostics.push({
1908
- category: "plugins",
1533
+ category: "marketplace",
1909
1534
  status: disabled > 0 ? "warning" : "ok",
1910
1535
  message: `Found ${discovered.length} packages, ${enabled} enabled, ${disabled} unavailable, ${shadowed} shadowed`,
1911
1536
  details: {
@@ -1918,14 +1543,14 @@ var diag = defineSystemCommand({
1918
1543
  });
1919
1544
  } catch (err) {
1920
1545
  diagnostics.push({
1921
- category: "plugins",
1546
+ category: "marketplace",
1922
1547
  status: "error",
1923
1548
  message: `Discovery failed: ${err.message}`,
1924
1549
  details: { error: err.message }
1925
1550
  });
1926
1551
  }
1927
1552
  try {
1928
- const cachePath = path2.join(cwd, ".kb", "cache", "cli-manifests.json");
1553
+ const cachePath = path.join(cwd, ".kb", "cache", "cli-manifests.json");
1929
1554
  const cacheExists = await promises.access(cachePath).then(() => true).catch(() => false);
1930
1555
  if (cacheExists) {
1931
1556
  const cache = JSON.parse(await promises.readFile(cachePath, "utf8"));
@@ -1958,26 +1583,29 @@ var diag = defineSystemCommand({
1958
1583
  });
1959
1584
  }
1960
1585
  try {
1961
- const state = await loadPluginsState(cwd);
1962
- const enabledCount = state.enabled.length;
1963
- const disabledCount = state.disabled.length;
1964
- const linkedCount = state.linked.length;
1965
- diagnostics.push({
1966
- category: "plugins-state",
1967
- status: "ok",
1968
- message: `${enabledCount} enabled, ${disabledCount} disabled, ${linkedCount} linked`,
1969
- details: {
1970
- enabled: enabledCount,
1971
- disabled: disabledCount,
1972
- linked: linkedCount,
1973
- permissions: Object.keys(state.permissions).length
1974
- }
1975
- });
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
+ }
1976
1604
  } catch (err) {
1977
1605
  diagnostics.push({
1978
- category: "plugins-state",
1606
+ category: "marketplace-lock",
1979
1607
  status: "error",
1980
- message: `State check failed: ${err.message}`,
1608
+ message: `Marketplace lock check failed: ${err.message}`,
1981
1609
  details: { error: err.message }
1982
1610
  });
1983
1611
  }
@@ -2066,10 +1694,10 @@ var diag = defineSystemCommand({
2066
1694
  }
2067
1695
  const nextSteps = [];
2068
1696
  if (hasErrors) {
2069
- nextSteps.push(`kb plugins doctor ${safeColors.muted("Diagnose plugin issues")}`);
1697
+ nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
2070
1698
  }
2071
1699
  if (hasWarnings) {
2072
- nextSteps.push(`kb plugins ls ${safeColors.muted("List all plugins")}`);
1700
+ nextSteps.push(`kb marketplace list ${safeColors.muted("List all plugins")}`);
2073
1701
  }
2074
1702
  nextSteps.push(`kb diagnose ${safeColors.muted("Quick environment check")}`);
2075
1703
  const sections = [
@@ -2082,1475 +1710,119 @@ var diag = defineSystemCommand({
2082
1710
  items: diagItems
2083
1711
  },
2084
1712
  {
2085
- header: "Next Steps",
2086
- items: nextSteps
2087
- }
2088
- ];
2089
- if (hasErrors) {
2090
- ctx.ui.error("System Diagnostics", { sections });
2091
- } else if (hasWarnings) {
2092
- ctx.ui.warn("System Diagnostics", { sections });
2093
- } else {
2094
- ctx.ui.success("System Diagnostics", { sections });
2095
- }
2096
- }
2097
- }
2098
- });
2099
- init_plugins_state();
2100
- var pluginsList = defineSystemCommand({
2101
- name: "list",
2102
- description: "List all discovered CLI plugins",
2103
- category: "plugins",
2104
- // Type-safe examples using generateExamples()
2105
- examples: generateExamples("list", "plugins", [
2106
- { flags: {} },
2107
- // kb plugins list
2108
- { flags: { json: true } }
2109
- // kb plugins list --json
2110
- ]),
2111
- flags: {
2112
- json: { type: "boolean", description: "Output in JSON format" }
2113
- },
2114
- analytics: {
2115
- command: "plugins:list",
2116
- startEvent: "PLUGINS_LIST_STARTED",
2117
- finishEvent: "PLUGINS_LIST_FINISHED"
2118
- },
2119
- async handler(ctx, _argv, _flags) {
2120
- const cwd = getContextCwd(ctx);
2121
- const pluginRegistry = new PluginRegistry({
2122
- strategies: ["workspace", "pkg", "dir", "file"],
2123
- roots: [cwd]
2124
- });
2125
- await pluginRegistry.refresh();
2126
- const manifests = registry.listManifests();
2127
- const productGroups = registry.listProductGroups();
2128
- const state = await loadPluginsState(cwd);
2129
- pluginRegistry.list();
2130
- const packages = /* @__PURE__ */ new Map();
2131
- for (const cmd of manifests) {
2132
- const pkgName = cmd.manifest.package || cmd.manifest.group;
2133
- const namespace = cmd.manifest.namespace || cmd.manifest.group;
2134
- if (!packages.has(pkgName)) {
2135
- let version2 = "unknown";
2136
- if (cmd.pkgRoot) {
2137
- try {
2138
- const pkgPath = path2.join(cmd.pkgRoot, "package.json");
2139
- const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
2140
- version2 = pkgJson.version || "unknown";
2141
- } catch {
2142
- }
2143
- }
2144
- if (version2 === "unknown") {
2145
- try {
2146
- const pkgPath = path2.join(cwd, "node_modules", pkgName, "package.json");
2147
- const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
2148
- version2 = pkgJson.version || "unknown";
2149
- } catch {
2150
- try {
2151
- const pkgPath = path2.join(cwd, "packages", namespace.replace("@kb-labs/", "").replace("-cli", ""), "package.json");
2152
- const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
2153
- version2 = pkgJson.version || "unknown";
2154
- } catch {
2155
- try {
2156
- const workspaceRoot = path2.resolve(cwd, "..");
2157
- const pkgDirs = await promises.readdir(path2.join(workspaceRoot, "packages"), { withFileTypes: true });
2158
- for (const dir of pkgDirs) {
2159
- if (dir.isDirectory()) {
2160
- try {
2161
- const pkgPath = path2.join(workspaceRoot, "packages", dir.name, "package.json");
2162
- const pkgJson = JSON.parse(await promises.readFile(pkgPath, "utf8"));
2163
- if (pkgJson.name === pkgName) {
2164
- version2 = pkgJson.version || "unknown";
2165
- break;
2166
- }
2167
- } catch {
2168
- }
2169
- }
2170
- }
2171
- } catch {
2172
- }
2173
- }
2174
- }
2175
- }
2176
- const enabled = isPluginEnabled(state, pkgName, cmd.source === "workspace" || cmd.source === "linked");
2177
- let pluginState = enabled ? "enabled" : "disabled";
2178
- if (cmd.source === "linked") {
2179
- pluginState = "linked";
2180
- } else if (!cmd.available) {
2181
- pluginState = "error";
2182
- }
2183
- packages.set(pkgName, {
2184
- name: pkgName,
2185
- namespace,
2186
- version: version2,
2187
- source: cmd.source,
2188
- enabled,
2189
- commands: [],
2190
- state: pluginState
2191
- });
2192
- }
2193
- packages.get(pkgName).commands.push(cmd);
2194
- }
2195
- const packageList = Array.from(packages.values()).sort((a, b) => a.name.localeCompare(b.name));
2196
- const output = packageList.map((pkg) => ({
2197
- name: pkg.name,
2198
- namespace: pkg.namespace,
2199
- version: pkg.version,
2200
- source: pkg.source,
2201
- enabled: pkg.enabled,
2202
- state: pkg.state,
2203
- commands: pkg.commands.map((cmd) => ({
2204
- id: cmd.manifest.id,
2205
- aliases: cmd.manifest.aliases || [],
2206
- describe: cmd.manifest.describe,
2207
- available: cmd.available,
2208
- shadowed: cmd.shadowed,
2209
- ...cmd.unavailableReason && { reason: cmd.unavailableReason }
2210
- }))
2211
- }));
2212
- const totalPlugins = output.length;
2213
- const enabledCount = output.filter((p) => p.enabled).length;
2214
- const disabledCount = output.filter((p) => !p.enabled).length;
2215
- const linkedCount = packageList.filter((p) => p.state === "linked").length;
2216
- ctx.platform?.logger?.info("Plugins list completed", {
2217
- total: totalPlugins,
2218
- enabled: enabledCount,
2219
- disabled: disabledCount,
2220
- products: productGroups.length
2221
- });
2222
- const sections = [];
2223
- sections.push({
2224
- header: "Summary",
2225
- items: [
2226
- `${safeColors.bold("Total")}: ${totalPlugins} plugins`,
2227
- `${safeColors.bold("Enabled")}: ${enabledCount}`,
2228
- `${safeColors.bold("Disabled")}: ${disabledCount}`,
2229
- `${safeColors.bold("Linked")}: ${linkedCount}`,
2230
- `${safeColors.bold("Commands")}: ${manifests.length} total`
2231
- ]
2232
- });
2233
- const stateIcons = {
2234
- enabled: "\u2705",
2235
- disabled: "\u23F8",
2236
- error: "\u274C",
2237
- outdated: "\u26A0",
2238
- linked: "\u{1F9E9}"
2239
- };
2240
- const columns = [
2241
- { header: "Plugin", align: "left" },
2242
- { header: "NS", align: "left" },
2243
- { header: "Version", align: "left" },
2244
- { header: "Source", align: "left" },
2245
- { header: "State", align: "left" },
2246
- { header: "Cmds", align: "right" }
2247
- ];
2248
- const tableRows = packageList.map((pkg) => {
2249
- const stateIcon = stateIcons[pkg.state] || "\u2753";
2250
- const name = pkg.name.length > 26 ? pkg.name.substring(0, 23) + "..." : pkg.name;
2251
- const sourceLabel = pkg.source === "workspace" ? "workspace" : pkg.source === "linked" ? "linked" : "node_modules";
2252
- const stateDisplay = `${stateIcon} ${pkg.state}`;
2253
- return [name, pkg.namespace, pkg.version, sourceLabel, stateDisplay, String(pkg.commands.length)];
2254
- });
2255
- const tableLines = formatTable(columns, tableRows, { separator: "\u2500", padding: 2 });
2256
- const errorLines = [];
2257
- for (const pkg of packageList) {
2258
- const hasErrors = pkg.commands.some((c) => !c.available || c.shadowed);
2259
- if (hasErrors) {
2260
- for (const cmd of pkg.commands) {
2261
- if (!cmd.available) {
2262
- errorLines.push(
2263
- ` ${safeColors.muted("\u274C")} ${cmd.manifest.id}: ${cmd.unavailableReason || "unavailable"}`
2264
- );
2265
- if (cmd.hint) {
2266
- errorLines.push(` ${safeColors.warning(`Hint: ${cmd.hint}`)}`);
2267
- }
2268
- }
2269
- if (cmd.shadowed) {
2270
- errorLines.push(` ${safeColors.muted("\u26A0")} ${cmd.manifest.id}: shadowed`);
2271
- }
2272
- }
2273
- }
2274
- }
2275
- const pluginsItems = [...tableLines];
2276
- if (errorLines.length > 0) {
2277
- pluginsItems.push("", ...errorLines);
2278
- }
2279
- sections.push({
2280
- header: "Plugins",
2281
- items: pluginsItems
2282
- });
2283
- sections.push({
2284
- header: "Next Steps",
2285
- items: [
2286
- `kb plugins enable <name> ${safeColors.muted("Enable a plugin")}`,
2287
- `kb plugins disable <name> ${safeColors.muted("Disable a plugin")}`,
2288
- `kb plugins doctor ${safeColors.muted("Diagnose plugin issues")}`,
2289
- `kb plugins --json ${safeColors.muted("Get machine-readable output")}`
2290
- ]
2291
- });
2292
- return {
2293
- ok: true,
2294
- status: "success",
2295
- message: `Found ${totalPlugins} plugins (${enabledCount} enabled, ${disabledCount} disabled)`,
2296
- sections,
2297
- json: {
2298
- plugins: output,
2299
- total: totalPlugins,
2300
- enabled: enabledCount,
2301
- disabled: disabledCount,
2302
- products: productGroups.length
2303
- }
2304
- };
2305
- },
2306
- formatter(result, ctx, flags) {
2307
- if (flags.json) {
2308
- console.log(JSON.stringify(result.json, null, 2));
2309
- } else {
2310
- ctx.ui.info("KB Labs CLI Plugins", { sections: result.sections });
2311
- }
2312
- }
2313
- });
2314
- var pluginsCommands = defineSystemCommand({
2315
- name: "commands",
2316
- description: "Show all plugin commands with their real invocation syntax",
2317
- category: "plugins",
2318
- examples: generateExamples("commands", "plugins", [
2319
- { flags: {} },
2320
- { flags: { plugin: "@kb-labs/mind" } },
2321
- { flags: { sort: "count" } },
2322
- { flags: { json: true } }
2323
- ]),
2324
- flags: {
2325
- json: { type: "boolean", description: "Output in JSON format" },
2326
- plugin: { type: "string", description: "Filter by plugin ID" },
2327
- sort: { type: "string", description: "Sort order: alpha (default), count, type" }
2328
- },
2329
- async handler(ctx, argv, flags) {
2330
- const commands = registry.list();
2331
- registry.listGroups();
2332
- const grouped = {};
2333
- for (const cmd of commands) {
2334
- const groupName = cmd.name.split(/[\s:]/)[0] ?? cmd.name;
2335
- if (!grouped[groupName]) {
2336
- grouped[groupName] = [];
2337
- }
2338
- grouped[groupName].push(cmd);
2339
- }
2340
- const pluginFilter = flags.plugin ?? "";
2341
- const filteredGroups = pluginFilter ? Object.fromEntries(
2342
- Object.entries(grouped).filter(([name, cmds]) => {
2343
- if (name === pluginFilter || name.includes(pluginFilter)) {
2344
- return true;
2345
- }
2346
- return cmds.some((cmd) => cmd.category === pluginFilter || cmd.category?.includes(pluginFilter));
2347
- })
2348
- ) : grouped;
2349
- const totalCommands = Object.values(filteredGroups).reduce((sum, cmds) => sum + cmds.length, 0);
2350
- const totalGroups = Object.keys(filteredGroups).length;
2351
- const sortOrder = flags.sort || "alpha";
2352
- const validSorts = ["alpha", "count", "type"];
2353
- if (!validSorts.includes(sortOrder)) {
2354
- throw new Error(`Invalid sort order: ${sortOrder}. Valid options: ${validSorts.join(", ")}`);
2355
- }
2356
- if (flags.json) {
2357
- const output = {};
2358
- for (const [groupName, cmds] of Object.entries(filteredGroups)) {
2359
- output[groupName] = cmds.map((cmd) => ({
2360
- name: cmd.name,
2361
- description: cmd.describe || "",
2362
- category: cmd.category
2363
- }));
2364
- }
2365
- return {
2366
- ok: true,
2367
- status: "success",
2368
- message: `Found ${totalCommands} commands in ${totalGroups} groups`,
2369
- json: {
2370
- groups: output,
2371
- totalGroups,
2372
- totalCommands
2373
- }
2374
- };
2375
- }
2376
- const sections = [];
2377
- sections.push({
2378
- header: "Summary",
2379
- items: [
2380
- `${safeColors.bold("Groups")}: ${totalGroups}`,
2381
- `${safeColors.bold("Commands")}: ${totalCommands}`,
2382
- `${safeColors.bold("Sort")}: ${sortOrder}`,
2383
- ...flags.plugin ? [`${safeColors.bold("Filter")}: ${flags.plugin}`] : []
2384
- ]
2385
- });
2386
- const sortGroups = (entries) => {
2387
- switch (sortOrder) {
2388
- case "count":
2389
- return entries.sort(([, a], [, b]) => b.length - a.length);
2390
- case "type":
2391
- return entries.sort(([a], [b]) => a.localeCompare(b));
2392
- case "alpha":
2393
- default:
2394
- return entries.sort(([a], [b]) => a.localeCompare(b));
2395
- }
2396
- };
2397
- const systemGroups = [];
2398
- const productGroups = [];
2399
- const sortedGroups = Object.entries(filteredGroups);
2400
- for (const [groupName, cmds] of sortedGroups) {
2401
- const isSystemGroup = cmds.some(
2402
- (cmd) => cmd.category && ["plugins", "jobs", "workflow", "worker", "debug", "info", "logging", "registry"].includes(cmd.category)
2403
- );
2404
- if (isSystemGroup) {
2405
- systemGroups.push([groupName, cmds]);
2406
- } else {
2407
- productGroups.push([groupName, cmds]);
2408
- }
2409
- }
2410
- if (systemGroups.length > 0) {
2411
- const systemItems = [];
2412
- const sortedSystemGroups = sortGroups(systemGroups);
2413
- for (const [groupName, cmds] of sortedSystemGroups) {
2414
- systemItems.push("");
2415
- systemItems.push(`\u2699\uFE0F ${safeColors.bold(groupName)} ${safeColors.muted(`(${cmds.length})`)}`);
2416
- const sortedCmds = cmds.sort((a, b) => a.name.localeCompare(b.name));
2417
- for (const cmd of sortedCmds) {
2418
- const invocation = safeColors.bold(`kb ${cmd.name}`);
2419
- const description = cmd.describe ? ` ${safeColors.muted(cmd.describe)}` : "";
2420
- systemItems.push(` ${invocation}${description}`);
2421
- }
2422
- }
2423
- sections.push({
2424
- header: "System Commands",
2425
- items: systemItems
2426
- });
2427
- }
2428
- if (productGroups.length > 0) {
2429
- const productItems = [];
2430
- const sortedProductGroups = sortGroups(productGroups);
2431
- for (const [groupName, cmds] of sortedProductGroups) {
2432
- productItems.push("");
2433
- productItems.push(`\u{1F4E6} ${safeColors.bold(groupName)} ${safeColors.muted(`(${cmds.length})`)}`);
2434
- const sortedCmds = cmds.sort((a, b) => a.name.localeCompare(b.name));
2435
- for (const cmd of sortedCmds) {
2436
- const invocation = safeColors.bold(`kb ${cmd.name}`);
2437
- const description = cmd.describe ? ` ${safeColors.muted(cmd.describe)}` : "";
2438
- productItems.push(` ${invocation}${description}`);
2439
- }
2440
- }
2441
- sections.push({
2442
- header: "Product Commands",
2443
- items: productItems
2444
- });
2445
- }
2446
- sections.push({
2447
- header: "Next Steps",
2448
- items: [
2449
- `kb plugins commands --plugin <name> ${safeColors.muted("Filter by plugin")}`,
2450
- `kb plugins commands --sort <order> ${safeColors.muted("Sort: alpha, count, type")}`,
2451
- `kb plugins commands --json ${safeColors.muted("Get machine-readable output")}`,
2452
- `kb plugins list ${safeColors.muted("Show installed plugins")}`
2453
- ]
2454
- });
2455
- const jsonGroups = {};
2456
- for (const [groupName, cmds] of Object.entries(filteredGroups)) {
2457
- jsonGroups[groupName] = cmds.map((cmd) => ({
2458
- name: cmd.name,
2459
- description: cmd.describe || "",
2460
- category: cmd.category
2461
- }));
2462
- }
2463
- return {
2464
- ok: true,
2465
- status: "success",
2466
- message: `Found ${totalCommands} commands in ${totalGroups} groups`,
2467
- sections,
2468
- json: {
2469
- groups: jsonGroups,
2470
- totalGroups,
2471
- totalCommands
2472
- }
2473
- };
2474
- },
2475
- formatter(result, ctx, flags) {
2476
- if (flags.json) {
2477
- console.log(JSON.stringify(result.json, null, 2));
2478
- } else {
2479
- ctx.ui.success("Plugin Commands Registry", { sections: result.sections ?? [] });
2480
- }
2481
- }
2482
- });
2483
- init_plugins_state();
2484
- var pluginsEnable = defineSystemCommand({
2485
- name: "enable",
2486
- description: "Enable a plugin",
2487
- category: "plugins",
2488
- examples: generateExamples("enable", "plugins", [
2489
- { flags: {} },
2490
- // kb plugins enable (will need <package> arg in actual usage)
2491
- { flags: { perm: ["fs.write"] } }
2492
- // kb plugins enable --perm fs.write
2493
- ]),
2494
- flags: {
2495
- perm: {
2496
- type: "array",
2497
- description: "Grant specific permissions (e.g., --perm fs.write --perm net.fetch)"
2498
- }
2499
- },
2500
- analytics: {
2501
- command: "plugins:enable",
2502
- startEvent: "PLUGINS_ENABLE_STARTED",
2503
- finishEvent: "PLUGINS_ENABLE_FINISHED"
2504
- },
2505
- async handler(ctx, argv, flags) {
2506
- if (argv.length === 0) {
2507
- throw new Error("Please specify a plugin name to enable");
2508
- }
2509
- const packageName = argv[0];
2510
- if (!packageName) {
2511
- throw new Error("Please specify a plugin name to enable");
2512
- }
2513
- const permissions = Array.isArray(flags.perm) ? flags.perm.map(String) : [];
2514
- const cwd = getContextCwd(ctx);
2515
- ctx.platform?.logger?.info("Enabling plugin", { packageName, permissions });
2516
- await enablePlugin(cwd, packageName);
2517
- if (permissions.length > 0) {
2518
- const { grantPermissions: grantPermissions2 } = await Promise.resolve().then(() => (init_plugins_state(), plugins_state_exports));
2519
- await grantPermissions2(cwd, packageName, permissions);
2520
- ctx.platform?.logger?.info("Plugin enabled with permissions", { packageName, permissions });
2521
- return {
2522
- ok: true,
2523
- packageName,
2524
- permissions,
2525
- message: `Enabled ${packageName} with permissions: ${permissions.join(", ")}`
2526
- };
2527
- } else {
2528
- ctx.platform?.logger?.info("Plugin enabled", { packageName });
2529
- return {
2530
- ok: true,
2531
- packageName,
2532
- message: `Enabled ${packageName}`
2533
- };
2534
- }
2535
- },
2536
- formatter(result, ctx, _flags) {
2537
- ctx.ui.info(result.message ?? "Plugin enabled");
2538
- ctx.ui.info(`Run 'kb plugins ls' to see updated status`);
2539
- }
2540
- });
2541
- init_plugins_state();
2542
- var pluginsDisable = defineSystemCommand({
2543
- name: "disable",
2544
- description: "Disable a plugin",
2545
- category: "plugins",
2546
- examples: generateExamples("disable", "plugins", [
2547
- { flags: {} }
2548
- // kb plugins disable (requires <package> arg)
2549
- ]),
2550
- flags: {},
2551
- analytics: {
2552
- command: "plugins:disable",
2553
- startEvent: "PLUGINS_DISABLE_STARTED",
2554
- finishEvent: "PLUGINS_DISABLE_FINISHED"
2555
- },
2556
- async handler(ctx, argv, _flags) {
2557
- if (argv.length === 0) {
2558
- throw new Error("Please specify a plugin name to disable");
2559
- }
2560
- const packageName = argv[0];
2561
- if (!packageName) {
2562
- throw new Error("Please specify a plugin name to disable");
2563
- }
2564
- const cwd = getContextCwd(ctx);
2565
- ctx.platform?.logger?.info("Disabling plugin", { packageName });
2566
- await disablePlugin(cwd, packageName);
2567
- ctx.platform?.logger?.info("Plugin disabled", { packageName });
2568
- return {
2569
- ok: true,
2570
- packageName,
2571
- message: `Disabled ${packageName}`
2572
- };
2573
- },
2574
- formatter(result, ctx, _flags) {
2575
- ctx.ui.info(result.message ?? "Plugin disabled");
2576
- ctx.ui.info(`Run 'kb plugins ls' to see updated status`);
2577
- }
2578
- });
2579
- init_plugins_state();
2580
- var pluginsLink = defineSystemCommand({
2581
- name: "link",
2582
- description: "Link a local plugin for development",
2583
- category: "plugins",
2584
- examples: generateExamples("link", "plugins", [
2585
- { flags: {} }
2586
- // kb plugins link (requires <path> arg)
2587
- ]),
2588
- flags: {},
2589
- analytics: {
2590
- command: "plugins:link",
2591
- startEvent: "PLUGINS_LINK_STARTED",
2592
- finishEvent: "PLUGINS_LINK_FINISHED"
2593
- },
2594
- async handler(ctx, argv, _flags) {
2595
- if (argv.length === 0) {
2596
- throw new Error("Please specify a plugin path to link");
2597
- }
2598
- const pluginPath = argv[0];
2599
- if (!pluginPath) {
2600
- throw new Error("Please specify a plugin path to link");
2601
- }
2602
- const cwd = getContextCwd(ctx);
2603
- const absPath = path2.resolve(cwd, pluginPath);
2604
- ctx.platform?.logger?.info("Linking plugin", { pluginPath, absPath });
2605
- const pkgJsonPath = path2.join(absPath, "package.json");
2606
- await promises.access(pkgJsonPath);
2607
- const pkgJson = JSON.parse(await promises.readFile(pkgJsonPath, "utf8"));
2608
- const hasManifest = pkgJson.kb?.commandsManifest || pkgJson.exports?.["./kb/commands"];
2609
- if (!hasManifest) {
2610
- ctx.platform?.logger?.warn("Plugin may not be valid", { packageName: pkgJson.name, pluginPath });
2611
- }
2612
- await linkPlugin(cwd, absPath);
2613
- ctx.platform?.logger?.info("Plugin linked", { packageName: pkgJson.name, absPath });
2614
- let configUpdated = false;
2615
- try {
2616
- const configPath = path2.join(cwd, ".kb", "kb.config.json");
2617
- const config = JSON.parse(await promises.readFile(configPath, "utf8"));
2618
- if (!config.plugins) {
2619
- config.plugins = {};
2620
- }
2621
- if (!config.plugins.linked) {
2622
- config.plugins.linked = [];
2623
- }
2624
- if (!config.plugins.linked.includes(pkgJson.name)) {
2625
- config.plugins.linked.push(pkgJson.name);
2626
- await promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
2627
- configUpdated = true;
2628
- }
2629
- } catch {
2630
- }
2631
- return {
2632
- ok: true,
2633
- packageName: pkgJson.name || pluginPath,
2634
- absPath,
2635
- hasManifest: !!hasManifest,
2636
- configUpdated,
2637
- message: `Linked ${pkgJson.name || pluginPath} from ${absPath}`
2638
- };
2639
- },
2640
- formatter(result, ctx, _flags) {
2641
- if (!result.hasManifest) {
2642
- ctx.ui.warn(
2643
- `Package ${result.packageName ?? "unknown"} doesn't appear to be a KB CLI plugin`
2644
- );
2645
- ctx.ui.info('Add "kb.commandsManifest" or "exports["./kb/commands"]" to package.json');
2646
- }
2647
- ctx.ui.info(result.message ?? "Plugin linked");
2648
- if (result.configUpdated) {
2649
- ctx.ui.info(`Added ${result.packageName ?? "unknown"} to kb.config.json plugins.linked`);
2650
- }
2651
- ctx.ui.info(`The plugin will be discovered on next CLI run`);
2652
- }
2653
- });
2654
- init_plugins_state();
2655
- var pluginsUnlink = defineSystemCommand({
2656
- name: "unlink",
2657
- description: "Unlink a local plugin",
2658
- category: "plugins",
2659
- examples: generateExamples("unlink", "plugins", [
2660
- { flags: {} }
2661
- // kb plugins unlink (requires <path-or-name> arg)
2662
- ]),
2663
- flags: {},
2664
- analytics: {
2665
- command: "plugins:unlink",
2666
- startEvent: "PLUGINS_UNLINK_STARTED",
2667
- finishEvent: "PLUGINS_UNLINK_FINISHED"
2668
- },
2669
- async handler(ctx, argv, _flags) {
2670
- if (argv.length === 0) {
2671
- throw new Error("Please specify a plugin path or name to unlink");
2672
- }
2673
- const identifier = argv[0];
2674
- if (!identifier) {
2675
- throw new Error("Please specify a plugin path or name to unlink");
2676
- }
2677
- const cwd = getContextCwd(ctx);
2678
- const state = await loadPluginsState(cwd);
2679
- ctx.platform?.logger?.info("Unlinking plugin", { identifier });
2680
- let absPath;
2681
- try {
2682
- absPath = path2.resolve(cwd, identifier);
2683
- await promises.access(absPath);
2684
- } catch {
2685
- const matched = state.linked.find((p) => p.includes(identifier) || p.endsWith(identifier));
2686
- if (matched) {
2687
- absPath = matched;
2688
- } else {
2689
- ctx.platform?.logger?.warn("Plugin not found", { identifier, linkedPlugins: state.linked });
2690
- throw new Error(`Plugin not found: ${identifier}`);
2691
- }
2692
- }
2693
- if (!absPath) {
2694
- ctx.platform?.logger?.warn("Plugin not found", { identifier });
2695
- throw new Error(`Plugin not found: ${identifier}`);
2696
- }
2697
- await unlinkPlugin(cwd, absPath);
2698
- ctx.platform?.logger?.info("Plugin unlinked", { identifier, absPath });
2699
- return {
2700
- ok: true,
2701
- identifier,
2702
- absPath,
2703
- message: `Unlinked ${identifier}`
2704
- };
2705
- },
2706
- formatter(result, ctx, _flags) {
2707
- ctx.ui.info(result.message ?? "Plugin unlinked");
2708
- ctx.ui.info(`Run 'kb plugins ls' to see updated status`);
2709
- }
2710
- });
2711
- function detectRepoRoot(start = process.cwd()) {
2712
- let cur = path2.resolve(start);
2713
- while (true) {
2714
- if (existsSync(path2.join(cur, ".git"))) {
2715
- return cur;
2716
- }
2717
- const parent = path2.dirname(cur);
2718
- if (parent === cur) {
2719
- return start;
2720
- }
2721
- cur = parent;
2722
- }
2723
- }
2724
- async function findRestApiManifestPath(pkgRoot, pkg) {
2725
- if (pkg.kbLabs?.manifest) {
2726
- const manifestPath = path2.isAbsolute(pkg.kbLabs.manifest) ? pkg.kbLabs.manifest : path2.join(pkgRoot, pkg.kbLabs.manifest);
2727
- try {
2728
- await promises.access(manifestPath);
2729
- return manifestPath;
2730
- } catch {
2731
- return null;
2732
- }
2733
- }
2734
- if (pkg.kb?.manifest) {
2735
- const manifestPath = path2.isAbsolute(pkg.kb.manifest) ? pkg.kb.manifest : path2.join(pkgRoot, pkg.kb.manifest);
2736
- try {
2737
- await promises.access(manifestPath);
2738
- return manifestPath;
2739
- } catch {
2740
- return null;
2741
- }
2742
- }
2743
- if (Array.isArray(pkg.kbLabs?.plugins)) {
2744
- for (const pluginPath of pkg.kbLabs.plugins) {
2745
- const manifestPath = path2.isAbsolute(pluginPath) ? pluginPath : path2.join(pkgRoot, pluginPath);
2746
- try {
2747
- await promises.access(manifestPath);
2748
- return manifestPath;
2749
- } catch {
2750
- continue;
2751
- }
2752
- }
2753
- }
2754
- if (Array.isArray(pkg.kb?.plugins)) {
2755
- for (const pluginPath of pkg.kb.plugins) {
2756
- const manifestPath = path2.isAbsolute(pluginPath) ? pluginPath : path2.join(pkgRoot, pluginPath);
2757
- try {
2758
- await promises.access(manifestPath);
2759
- return manifestPath;
2760
- } catch {
2761
- continue;
2762
- }
2763
- }
2764
- }
2765
- const pluginsDir = path2.join(pkgRoot, ".kblabs", "plugins");
2766
- try {
2767
- const entries = await promises.readdir(pluginsDir, { withFileTypes: true });
2768
- for (const entry of entries) {
2769
- if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
2770
- return path2.join(pluginsDir, entry.name);
2771
- }
2772
- }
2773
- } catch {
2774
- }
2775
- const conventionalPaths = [
2776
- path2.join(pkgRoot, "dist", "manifest.v2.js"),
2777
- path2.join(pkgRoot, "dist", "manifest.v2.ts"),
2778
- path2.join(pkgRoot, "src", "manifest.v2.ts"),
2779
- path2.join(pkgRoot, "manifest.v2.ts"),
2780
- path2.join(pkgRoot, "manifest.v2.js")
2781
- ];
2782
- for (const manifestPath of conventionalPaths) {
2783
- try {
2784
- await promises.access(manifestPath);
2785
- return manifestPath;
2786
- } catch {
2787
- continue;
2788
- }
2789
- }
2790
- return null;
2791
- }
2792
- async function discoverRestApiPlugins(repoRoot) {
2793
- const results = [];
2794
- const workspaceYaml = path2.join(repoRoot, "pnpm-workspace.yaml");
2795
- let workspacePatterns = [];
2796
- try {
2797
- const content = await promises.readFile(workspaceYaml, "utf8");
2798
- const parsed = parse(content);
2799
- workspacePatterns = parsed.packages || [];
2800
- } catch {
2801
- workspacePatterns = ["packages/*", "apps/*"];
2802
- }
2803
- if (workspacePatterns.length === 0) {
2804
- let currentDir = repoRoot;
2805
- for (let i = 0; i < 3; i++) {
2806
- const parentYaml = path2.join(path2.dirname(currentDir), "pnpm-workspace.yaml");
2807
- try {
2808
- const content = await promises.readFile(parentYaml, "utf8");
2809
- const parsed = parse(content);
2810
- if (parsed.packages && parsed.packages.length > 0) {
2811
- workspacePatterns = parsed.packages;
2812
- repoRoot = path2.dirname(currentDir);
2813
- break;
2814
- }
2815
- } catch {
2816
- }
2817
- currentDir = path2.dirname(currentDir);
2818
- if (currentDir === path2.dirname(currentDir)) {
2819
- break;
2820
- }
2821
- }
2822
- }
2823
- const packageJsonPaths = [];
2824
- for (const pattern of workspacePatterns) {
2825
- const pkgPattern = pattern.endsWith("/package.json") ? pattern : pattern.endsWith("/*") ? `${pattern}/package.json` : `${pattern}/**/package.json`;
2826
- try {
2827
- const matches = await glob(pkgPattern, {
2828
- cwd: repoRoot,
2829
- absolute: true,
2830
- ignore: ["**/node_modules/**", "**/.kb/**"]
2831
- });
2832
- packageJsonPaths.push(...matches);
2833
- } catch {
2834
- }
2835
- }
2836
- const uniquePaths = Array.from(new Set(packageJsonPaths));
2837
- for (const pkgJsonPath of uniquePaths) {
2838
- const pkgRoot = path2.dirname(pkgJsonPath);
2839
- try {
2840
- const pkgContent = await promises.readFile(pkgJsonPath, "utf8");
2841
- const pkg = JSON.parse(pkgContent);
2842
- const manifestPath = await findRestApiManifestPath(pkgRoot, pkg);
2843
- if (!manifestPath) {
2844
- continue;
2845
- }
2846
- try {
2847
- const module = await import(manifestPath);
2848
- const manifest = module.default || module.manifest || module;
2849
- 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) {
2850
- const manifestV2 = manifest;
2851
- results.push({
2852
- manifest: manifestV2,
2853
- manifestPath: path2.resolve(manifestPath),
2854
- pluginRoot: pkgRoot
2855
- });
2856
- }
2857
- } catch (_e) {
2858
- continue;
2859
- }
2860
- } catch {
2861
- continue;
2862
- }
2863
- }
2864
- return results;
2865
- }
2866
- var pluginsRegistry = defineSystemCommand({
2867
- name: "registry",
2868
- description: "List all REST API plugin manifests for REST API server",
2869
- category: "plugins",
2870
- examples: generateExamples("registry", "plugins", [
2871
- { flags: {} },
2872
- { flags: { json: true } }
2873
- ]),
2874
- flags: {
2875
- json: { type: "boolean", description: "Output in JSON format" }
2876
- },
2877
- analytics: {
2878
- command: "plugins:registry",
2879
- startEvent: "PLUGINS_REGISTRY_STARTED",
2880
- finishEvent: "PLUGINS_REGISTRY_FINISHED"
2881
- },
2882
- async handler(ctx, argv, flags) {
2883
- const cwd = getContextCwd(ctx);
2884
- const repoRoot = detectRepoRoot(cwd);
2885
- const restApiPlugins = await discoverRestApiPlugins(repoRoot);
2886
- const manifests = restApiPlugins;
2887
- const manifestsWithPaths = manifests.map((p) => ({
2888
- manifest: p.manifest,
2889
- manifestPath: p.manifestPath,
2890
- pluginRoot: p.pluginRoot
2891
- }));
2892
- ctx.platform?.logger?.info("Plugins registry scan completed", { count: manifests.length });
2893
- if (flags.json) {
2894
- ctx.ui.json({
2895
- ok: true,
2896
- manifests: manifests.map((p) => p.manifest),
2897
- manifestsWithPaths,
2898
- total: manifests.length
2899
- });
2900
- return {
2901
- ok: true,
2902
- manifests: manifests.map((p) => p.manifest),
2903
- manifestsWithPaths,
2904
- total: manifests.length,
2905
- plugins: manifests
2906
- };
2907
- }
2908
- const sections = [
2909
- {
2910
- header: "Summary",
2911
- items: [
2912
- `Found: ${manifests.length} REST API plugin(s)`,
2913
- `Repository: ${repoRoot}`
2914
- ]
2915
- }
2916
- ];
2917
- if (manifests.length > 0) {
2918
- const pluginItems = [];
2919
- for (const plugin of manifests) {
2920
- const displayName = plugin.manifest.display?.name || plugin.manifest.id;
2921
- const routesCount = plugin.manifest.rest?.routes?.length || 0;
2922
- pluginItems.push(
2923
- `${ctx.ui.symbols.success} ${plugin.manifest.id}@${plugin.manifest.version} - ${displayName}`
2924
- );
2925
- pluginItems.push(` Path: ${plugin.manifestPath}`);
2926
- pluginItems.push(` Routes: ${routesCount}`);
2927
- }
2928
- sections.push({
2929
- header: "Plugins",
2930
- items: pluginItems
2931
- });
2932
- } else {
2933
- sections.push({
2934
- items: ["No REST API plugins found"]
2935
- });
2936
- }
2937
- ctx.ui.info("Plugins Registry", { sections });
2938
- return {
2939
- ok: true,
2940
- manifests: manifests.map((p) => p.manifest),
2941
- manifestsWithPaths,
2942
- total: manifests.length,
2943
- plugins: manifests
2944
- };
2945
- }
2946
- });
2947
- var require2 = createRequire(import.meta.url);
2948
- var pluginsDoctor = defineSystemCommand({
2949
- name: "doctor",
2950
- description: "Diagnose plugin issues and suggest fixes",
2951
- category: "plugins",
2952
- examples: generateExamples("doctor", "plugins", [
2953
- { flags: {} },
2954
- // kb plugins doctor
2955
- { flags: { json: true } }
2956
- // kb plugins doctor --json
2957
- ]),
2958
- flags: {
2959
- json: { type: "boolean", description: "Output in JSON format" }
2960
- },
2961
- analytics: {
2962
- command: "plugins:doctor",
2963
- startEvent: "PLUGINS_DOCTOR_STARTED",
2964
- finishEvent: "PLUGINS_DOCTOR_FINISHED"
2965
- },
2966
- async handler(ctx, argv, _flags) {
2967
- const targetPlugin = argv[0];
2968
- const cwd = getContextCwd(ctx);
2969
- const manifests = registry.listManifests();
2970
- const issues = [];
2971
- for (const cmd of manifests) {
2972
- const pkgName = cmd.manifest.package || cmd.manifest.group || "";
2973
- if (!pkgName) {
2974
- continue;
2975
- }
2976
- if (targetPlugin && !pkgName.includes(targetPlugin)) {
2977
- continue;
2978
- }
2979
- if (cmd.manifest.engine?.node) {
2980
- const required = cmd.manifest.engine.node;
2981
- const current = process.version;
2982
- if (required.startsWith(">=")) {
2983
- const requiredVersion = required.replace(">=", "").trim();
2984
- const currentVersionParts = current.split(".");
2985
- const requiredVersionParts = requiredVersion.split(".");
2986
- if (currentVersionParts[0] && requiredVersionParts[0]) {
2987
- const currentMajor = parseInt(currentVersionParts[0].replace("v", ""), 10);
2988
- const requiredMajor = parseInt(requiredVersionParts[0], 10);
2989
- if (!isNaN(currentMajor) && !isNaN(requiredMajor) && currentMajor < requiredMajor) {
2990
- issues.push({
2991
- package: pkgName,
2992
- severity: "error",
2993
- code: "NODE_VERSION_MISMATCH",
2994
- message: `Requires Node ${required}, found ${current}`,
2995
- fix: `Upgrade Node to ${required} or higher`
2996
- });
2997
- }
2998
- }
2999
- }
3000
- }
3001
- if (cmd.manifest.engine?.kbCli) {
3002
- const required = cmd.manifest.engine.kbCli;
3003
- const current = process.env.CLI_VERSION || process.env.CLI_VERSION || "0.1.0";
3004
- if (required.startsWith("^") && current !== "0.1.0") {
3005
- const requiredParts = required.replace("^", "").split(".");
3006
- const currentParts = current.split(".");
3007
- if (requiredParts[0] && currentParts[0]) {
3008
- const requiredMajor = parseInt(requiredParts[0], 10);
3009
- const currentMajor = parseInt(currentParts[0], 10);
3010
- if (!isNaN(requiredMajor) && !isNaN(currentMajor) && currentMajor < requiredMajor) {
3011
- issues.push({
3012
- package: pkgName,
3013
- severity: "error",
3014
- code: "CLI_VERSION_MISMATCH",
3015
- message: `Requires kb-cli ${required}, found ${current}`,
3016
- fix: `Upgrade kb-cli: pnpm -w update @kb-labs/cli`
3017
- });
3018
- }
3019
- }
3020
- }
3021
- }
3022
- if (cmd.manifest.requires) {
3023
- for (const req2 of cmd.manifest.requires) {
3024
- const parts = req2.split("@");
3025
- const pkgNameReq = parts[0];
3026
- if (!pkgNameReq) {
3027
- continue;
3028
- }
3029
- try {
3030
- require2.resolve(pkgNameReq);
3031
- } catch {
3032
- issues.push({
3033
- package: pkgName,
3034
- severity: "error",
3035
- code: "MISSING_PEER_DEP",
3036
- message: `Missing peer dependency: ${req2}`,
3037
- fix: `Install: pnpm add ${req2}`
3038
- });
3039
- }
3040
- }
3041
- }
3042
- if (cmd.manifest.engine?.module) {
3043
- const required = cmd.manifest.engine.module;
3044
- const pkgJsonPath = path2.join(cmd.pkgRoot || cwd, "package.json");
3045
- try {
3046
- const pkgJson = JSON.parse(await promises.readFile(pkgJsonPath, "utf8"));
3047
- const isESM = pkgJson.type === "module";
3048
- if (required === "esm" && !isESM) {
3049
- issues.push({
3050
- package: pkgName,
3051
- severity: "warning",
3052
- code: "MODULE_TYPE_MISMATCH",
3053
- message: `Plugin declares ESM but package.json doesn't have "type": "module"`,
3054
- fix: `Add "type": "module" to ${pkgName}/package.json`
3055
- });
3056
- } else if (required === "cjs" && isESM) {
3057
- issues.push({
3058
- package: pkgName,
3059
- severity: "warning",
3060
- code: "MODULE_TYPE_MISMATCH",
3061
- message: `Plugin declares CJS but package.json has "type": "module"`,
3062
- fix: `Remove "type": "module" from ${pkgName}/package.json or use .cjs extension`
3063
- });
3064
- }
3065
- } catch {
3066
- }
3067
- }
3068
- if (!cmd.available && cmd.unavailableReason) {
3069
- issues.push({
3070
- package: pkgName,
3071
- severity: "error",
3072
- code: "UNAVAILABLE",
3073
- message: cmd.unavailableReason,
3074
- fix: cmd.hint || "Check dependencies and manifest"
3075
- });
3076
- }
3077
- }
3078
- const summary = {
3079
- total: issues.length,
3080
- errors: issues.filter((i) => i.severity === "error").length,
3081
- warnings: issues.filter((i) => i.severity === "warning").length,
3082
- info: issues.filter((i) => i.severity === "info").length
3083
- };
3084
- ctx.platform?.logger?.info("Plugins doctor completed", summary);
3085
- return {
3086
- ok: summary.errors === 0,
3087
- issues,
3088
- summary
3089
- };
3090
- },
3091
- formatter(result, ctx, flags) {
3092
- const resultData = result;
3093
- if (flags.json) {
3094
- ctx.ui.json(resultData);
3095
- return;
3096
- }
3097
- const { issues, summary } = resultData;
3098
- if (issues.length === 0) {
3099
- ctx.ui.info(`${ctx.ui.symbols.success} All plugins are healthy!`);
3100
- return;
3101
- }
3102
- const errorCount = summary.errors;
3103
- const warningCount = summary.warnings;
3104
- const summaryItems = [
3105
- `Total Issues: ${issues.length}`,
3106
- `Errors: ${errorCount > 0 ? ctx.ui.colors.error(errorCount.toString()) : "none"}`,
3107
- `Warnings: ${warningCount > 0 ? ctx.ui.colors.warning(warningCount.toString()) : "none"}`
3108
- ];
3109
- const issueItems = [];
3110
- const byPackage = /* @__PURE__ */ new Map();
3111
- for (const issue of issues) {
3112
- if (!byPackage.has(issue.package)) {
3113
- byPackage.set(issue.package, []);
3114
- }
3115
- byPackage.get(issue.package).push(issue);
3116
- }
3117
- for (const [pkg, pkgIssues] of Array.from(byPackage.entries()).sort()) {
3118
- issueItems.push(ctx.ui.colors.bold(`${pkg}:`));
3119
- for (const issue of pkgIssues) {
3120
- const icon = issue.severity === "error" ? ctx.ui.symbols.error : ctx.ui.symbols.warning;
3121
- const color = issue.severity === "error" ? ctx.ui.colors.error : ctx.ui.colors.warning;
3122
- issueItems.push(` ${icon} ${color(issue.code)}: ${issue.message}`);
3123
- if (issue.fix) {
3124
- issueItems.push(` ${ctx.ui.colors.info(`Fix: ${issue.fix}`)}`);
1713
+ header: "Next Steps",
1714
+ items: nextSteps
3125
1715
  }
1716
+ ];
1717
+ if (hasErrors) {
1718
+ ctx.ui.error("System Diagnostics", { sections });
1719
+ } else if (hasWarnings) {
1720
+ ctx.ui.warn("System Diagnostics", { sections });
1721
+ } else {
1722
+ ctx.ui.success("System Diagnostics", { sections });
3126
1723
  }
3127
1724
  }
3128
- const nextStepsItems = [
3129
- `kb plugins enable <name> ${ctx.ui.colors.muted("Enable a disabled plugin")}`,
3130
- `kb plugins clear-cache ${ctx.ui.colors.muted("Clear cache and rediscover")}`
3131
- ];
3132
- const sections = [
3133
- {
3134
- header: "Summary",
3135
- items: summaryItems
3136
- },
3137
- {
3138
- header: "Issues",
3139
- items: issueItems
3140
- },
3141
- {
3142
- header: "Next Steps",
3143
- items: nextStepsItems
3144
- }
3145
- ];
3146
- if (errorCount > 0) {
3147
- ctx.ui.error("Plugin Diagnostics", { sections });
3148
- } else if (warningCount > 0) {
3149
- ctx.ui.warn("Plugin Diagnostics", { sections });
3150
- } else {
3151
- ctx.ui.success("Plugin Diagnostics", { sections });
3152
- }
3153
1725
  }
3154
1726
  });
3155
- var pluginsScaffold = defineSystemCommand({
3156
- name: "scaffold",
3157
- description: "Generate a new KB CLI plugin template",
3158
- category: "plugins",
3159
- examples: generateExamples("scaffold", "plugins", [
3160
- { flags: {} },
3161
- // kb plugins scaffold (requires <name> arg)
3162
- { flags: { format: "cjs" } }
3163
- ]),
1727
+ var registryDiagnostics = defineSystemCommand({
1728
+ name: "diagnostics",
1729
+ description: "Show registry diagnostic report",
1730
+ category: "registry",
1731
+ examples: [],
3164
1732
  flags: {
3165
- format: {
3166
- type: "string",
3167
- description: "Module format: esm or cjs",
3168
- default: "esm",
3169
- choices: ["esm", "cjs"]
3170
- }
1733
+ json: { type: "boolean", description: "Output in JSON format" },
1734
+ refresh: { type: "boolean", description: "Force fresh discovery (ignore cache)" }
3171
1735
  },
3172
1736
  analytics: {
3173
- command: "plugins:scaffold",
3174
- startEvent: "PLUGINS_SCAFFOLD_STARTED",
3175
- finishEvent: "PLUGINS_SCAFFOLD_FINISHED"
3176
- },
3177
- async handler(ctx, argv, flags) {
3178
- if (argv.length === 0) {
3179
- throw new Error("Please specify a plugin name");
3180
- }
3181
- const pluginName = argv[0];
3182
- if (!pluginName) {
3183
- throw new Error("Please specify a plugin name");
3184
- }
3185
- ctx.platform?.logger?.info("Scaffolding plugin", { pluginName });
3186
- const format = String(flags.format ?? "esm");
3187
- const isESM = format === "esm";
3188
- const extension = isESM ? "ts" : "ts";
3189
- const moduleType = isESM ? "module" : "commonjs";
3190
- const tsupFormat = isESM ? "esm" : "cjs";
3191
- const baseDir = getContextCwd(ctx);
3192
- const dir = path2.join(baseDir, pluginName);
3193
- try {
3194
- await promises.access(dir);
3195
- ctx.platform?.logger?.warn("Directory already exists", { dir });
3196
- throw new Error(`Directory ${pluginName} already exists`);
3197
- } catch (err) {
3198
- if (err.code !== "ENOENT") {
3199
- throw err;
3200
- }
3201
- }
3202
- await promises.mkdir(dir, { recursive: true });
3203
- await promises.mkdir(path2.join(dir, "src"), { recursive: true });
3204
- await promises.mkdir(path2.join(dir, "src", "kb"), { recursive: true });
3205
- await promises.mkdir(path2.join(dir, "src", "commands"), { recursive: true });
3206
- const packageJson = {
3207
- name: `@your-scope/${pluginName}-cli`,
3208
- version: "1.0.0",
3209
- type: moduleType,
3210
- description: `KB Labs CLI plugin: ${pluginName}`,
3211
- keywords: ["kb-cli-plugin"],
3212
- main: `./dist/kb/manifest.js`,
3213
- exports: {
3214
- "./kb/manifest": "./dist/kb/manifest.js"
3215
- },
3216
- files: ["dist"],
3217
- scripts: {
3218
- build: `tsup src/kb/manifest.ts src/commands/hello.ts --format ${tsupFormat} --dts`,
3219
- dev: `tsup src/kb/manifest.ts src/commands/hello.ts --format ${tsupFormat} --watch`
3220
- },
3221
- kb: {
3222
- plugin: true,
3223
- manifest: "./dist/kb/manifest.js"
3224
- },
3225
- dependencies: {
3226
- "@kb-labs/plugin-manifest": "workspace:*",
3227
- "@kb-labs/shared-cli-ui": "workspace:*",
3228
- "@kb-labs/core-types": "workspace:*"
3229
- },
3230
- devDependencies: {
3231
- tsup: "^8.5.0",
3232
- typescript: "^5.6.3"
3233
- }
3234
- };
3235
- await promises.writeFile(path2.join(dir, "package.json"), JSON.stringify(packageJson, null, 2), "utf8");
3236
- const tsconfig = {
3237
- extends: "@kb-labs/devkit/tsconfig/lib.json",
3238
- compilerOptions: {
3239
- outDir: "dist",
3240
- rootDir: "src"
3241
- },
3242
- include: ["src/**/*"]
3243
- };
3244
- await promises.writeFile(path2.join(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2), "utf8");
3245
- const manifestContent = `import type { ManifestV2 } from '@kb-labs/plugin-contracts';
3246
-
3247
- export const manifest: ManifestV2 = {
3248
- schema: 'kb.plugin/2',
3249
- id: '@your-scope/${pluginName}',
3250
- version: '0.1.0',
3251
- display: {
3252
- name: '${pluginName} Plugin',
3253
- description: 'Example KB Labs CLI plugin using manifest v2'
1737
+ command: "registry:diagnostics",
1738
+ startEvent: "REGISTRY_DIAGNOSTICS_STARTED",
1739
+ finishEvent: "REGISTRY_DIAGNOSTICS_FINISHED"
3254
1740
  },
3255
- permissions: {
3256
- fs: {
3257
- mode: 'read',
3258
- allow: ['.']
3259
- },
3260
- env: {
3261
- allow: ['NODE_ENV']
1741
+ async handler(ctx, _argv, flags) {
1742
+ const cwd = getContextCwd(ctx);
1743
+ const registry2 = await createRegistry({ root: cwd });
1744
+ if (flags.refresh) {
1745
+ await registry2.refresh();
3262
1746
  }
1747
+ const report = registry2.getDiagnostics();
1748
+ await registry2.dispose();
1749
+ return { ok: report.summary.errors === 0, report };
3263
1750
  },
3264
- cli: {
3265
- commands: [
3266
- {
3267
- id: 'hello',
3268
- group: '${pluginName}',
3269
- describe: 'Hello command from ${pluginName}',
3270
- flags: [],
3271
- handler: './commands/hello#run'
3272
- }
3273
- ]
3274
- }
3275
- };
3276
- `;
3277
- await promises.writeFile(path2.join(dir, "src", "kb", `manifest.${extension}`), manifestContent, "utf8");
3278
- const commandContent = `import { defineCommand, type CommandResult } from '@kb-labs/shared-command-kit';
3279
-
3280
- // Define flag types for type safety
3281
- type ${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloFlags = {
3282
- json: { type: 'boolean'; description?: string; default?: boolean };
3283
- quiet: { type: 'boolean'; description?: string; default?: boolean };
3284
- };
3285
-
3286
- // Define result type for type safety
3287
- type ${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloResult = CommandResult & {
3288
- count?: number;
3289
- message?: string;
3290
- timing?: number;
3291
- };
3292
-
3293
- export const run = defineCommand<${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloFlags, ${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloResult>({
3294
- name: '${pluginName}:hello',
3295
- flags: {
3296
- json: {
3297
- type: 'boolean',
3298
- description: 'Output as JSON',
3299
- default: false,
3300
- },
3301
- quiet: {
3302
- type: 'boolean',
3303
- description: 'Suppress non-error output',
3304
- default: false,
3305
- },
3306
- },
3307
- async handler(ctx, argv, flags) {
3308
- // Full type safety: flags.json and flags.quiet are properly typed!
3309
- ctx.platform?.logger?.info('${pluginName} hello started');
3310
-
3311
-
3312
- // Your command logic here
3313
- const result = {
3314
- count: 1,
3315
- message: 'Hello from ${pluginName} plugin!'
3316
- };
3317
-
3318
- const duration = ctx.tracker.total();
3319
-
3320
- ctx.platform?.logger?.info('${pluginName} hello completed', {
3321
- durationMs: duration,
3322
- });
3323
-
1751
+ formatter(result, ctx, flags) {
3324
1752
  if (flags.json) {
3325
- ctx.ui.json({ ok: true, ...result, timing: duration });
3326
- } else if (!flags.quiet) {
3327
- const summary = [
3328
- ctx.ui.colors.success('\u2713 Success'),
3329
- \`Message: \${result.message}\`,
3330
- \`Time: \${ctx.ui.colors.muted(duration + 'ms')}\`,
3331
- ];
3332
- ctx.ui.write(ctx.ui.box('${pluginName} Command', summary));
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`);
1762
+ } else {
1763
+ ctx.ui.success("Registry is healthy \u2014 no issues found");
3333
1764
  }
3334
-
3335
- return { ok: true, ...result, timing: duration };
3336
- },
3337
- });
3338
-
3339
- // Alternative: Level 2 - Partial typing (flags only)
3340
- // Remove the result type parameter to use partial typing:
3341
- // export const run = defineCommand<${pluginName.charAt(0).toUpperCase() + pluginName.slice(1)}HelloFlags>({ ... });
3342
-
3343
- // Alternative: Level 1 - No typing (minimal)
3344
- // Remove both type parameters for quick prototyping:
3345
- // export const run = defineCommand({ ... });
3346
- `;
3347
- await promises.writeFile(path2.join(dir, "src", "commands", `hello.${extension}`), commandContent, "utf8");
3348
- const tsupConfig = `import config from "@kb-labs/devkit/tsup/node.js";
3349
-
3350
- export default {
3351
- ...config,
3352
- entry: ['src/kb/manifest.ts', 'src/commands/hello.ts'],
3353
- format: ['${tsupFormat}'],
3354
- dts: true,
3355
- };
3356
- `;
3357
- await promises.writeFile(path2.join(dir, "tsup.config.ts"), tsupConfig, "utf8");
3358
- const readme = `# ${pluginName} CLI Plugin
3359
-
3360
- KB Labs CLI plugin for ${pluginName}.
3361
-
3362
- ## Installation
3363
-
3364
- \`\`\`bash
3365
- pnpm add @your-scope/${pluginName}-cli
3366
- \`\`\`
3367
-
3368
- ## Usage
3369
-
3370
- \`\`\`bash
3371
- kb ${pluginName} hello
3372
- \`\`\`
3373
-
3374
- ## Development
3375
-
3376
- \`\`\`bash
3377
- # Build
3378
- pnpm build
3379
-
3380
- # Watch mode
3381
- pnpm dev
3382
-
3383
- # Link for local development
3384
- kb plugins link ./${pluginName}
3385
- \`\`\`
3386
- `;
3387
- await promises.writeFile(path2.join(dir, "README.md"), readme, "utf8");
3388
- const gitignore = `node_modules
3389
- dist
3390
- *.log
3391
- .DS_Store
3392
- `;
3393
- await promises.writeFile(path2.join(dir, ".gitignore"), gitignore, "utf8");
3394
- ctx.platform?.logger?.info("Plugin scaffold created", { pluginName, dir });
3395
- return {
3396
- ok: true,
3397
- pluginName,
3398
- dir
3399
- };
3400
- },
3401
- formatter(result, ctx, _flags) {
3402
- const resultData = result;
3403
- const { pluginName, dir } = resultData;
3404
- const sections = [
3405
- {
3406
- items: [
3407
- ctx.ui.colors.bold("Plugin Template Created:"),
3408
- `${ctx.ui.symbols.success} ${ctx.ui.colors.info(dir)}`,
3409
- "",
3410
- ctx.ui.colors.bold("Next Steps:"),
3411
- ` ${ctx.ui.colors.info(`cd ${pluginName}`)}`,
3412
- ` ${ctx.ui.colors.info("pnpm install")}`,
3413
- ` ${ctx.ui.colors.info("pnpm build")}`,
3414
- ` ${ctx.ui.colors.info(`kb plugins link ./${pluginName}`)}`,
3415
- ` ${ctx.ui.colors.info(`kb ${pluginName} hello`)}`,
3416
- "",
3417
- ctx.ui.colors.muted("See docs/plugin-development.md for more details")
3418
- ]
3419
- }
3420
- ];
3421
- ctx.ui.success("Plugin Scaffold", { sections });
3422
1765
  }
3423
1766
  });
3424
- async function validateManifestAgainstContracts(manifest, contractsPath) {
1767
+ async function clearCache(cwd, options) {
1768
+ const cleared = [];
1769
+ const cacheDir = path.join(cwd, ".kb", "cache");
3425
1770
  try {
3426
- const contractsModule = await import(path2.resolve(contractsPath));
3427
- const contracts = contractsModule.pluginContractsManifest || contractsModule.default;
3428
- if (!contracts) {
3429
- return { ok: false, issues: ["Contracts file does not export pluginContractsManifest"] };
3430
- }
3431
- const issues = [];
3432
- if (typeof manifest === "object" && manifest !== null && "artifacts" in manifest) {
3433
- const manifestArtifacts = manifest.artifacts || [];
3434
- const contractArtifacts = contracts.artifacts || {};
3435
- for (const artifact of manifestArtifacts) {
3436
- if (!(artifact.id in contractArtifacts)) {
3437
- issues.push(`Artifact ID "${artifact.id}" not found in contracts`);
3438
- }
3439
- }
3440
- }
3441
- if (typeof manifest === "object" && manifest !== null && "cli" in manifest) {
3442
- const cli = manifest.cli;
3443
- const manifestCommands = cli?.commands || [];
3444
- const contractCommands = contracts.commands || {};
3445
- for (const cmd of manifestCommands) {
3446
- if (!(cmd.id in contractCommands)) {
3447
- issues.push(`Command ID "${cmd.id}" not found in contracts`);
3448
- }
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);
3449
1777
  }
3450
1778
  }
3451
- return { ok: issues.length === 0, issues };
3452
- } catch (error) {
3453
- return {
3454
- ok: false,
3455
- issues: [`Failed to load contracts: ${error instanceof Error ? error.message : String(error)}`]
3456
- };
1779
+ } catch {
3457
1780
  }
3458
- }
3459
- var pluginValidate = defineSystemCommand({
3460
- name: "plugin:validate",
3461
- description: "Validate plugin manifest and contracts",
3462
- category: "plugins",
3463
- flags: {
3464
- manifest: {
3465
- type: "string",
3466
- description: "Path to manifest file (default: manifest.v2.ts)",
3467
- default: "manifest.v2.ts"
3468
- },
3469
- contracts: {
3470
- type: "string",
3471
- description: "Path to contracts file for cross-validation"
3472
- },
3473
- fix: {
3474
- type: "boolean",
3475
- description: "Automatically fix common issues"
3476
- }
3477
- },
3478
- async handler(ctx, argv, flags) {
3479
- const cwd = getContextCwd(ctx);
3480
- const manifestPath = path2.resolve(cwd, flags.manifest || "manifest.v2.ts");
3481
- ctx.ui.write(`Validating manifest: ${manifestPath}
3482
- `);
3483
- let manifestContent;
3484
- try {
3485
- manifestContent = await promises.readFile(manifestPath, "utf-8");
3486
- } catch (error) {
3487
- ctx.ui.error(`Failed to read manifest: ${error instanceof Error ? error.message : String(error)}
3488
- `);
3489
- return { ok: false };
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");
3490
1792
  }
3491
- let manifest;
1793
+ } catch {
1794
+ }
1795
+ const modulesCleared = [];
1796
+ if (options?.deep) {
3492
1797
  try {
3493
- const modulePath = manifestPath.replace(/\.ts$/, "");
3494
- const manifestModule = await import(modulePath);
3495
- manifest = manifestModule.manifest || manifestModule.default;
3496
- } catch (error) {
3497
- try {
3498
- manifest = JSON.parse(manifestContent);
3499
- } catch (_jsonError) {
3500
- ctx.ui.error(`Failed to parse manifest: ${error instanceof Error ? error.message : String(error)}
3501
- `);
3502
- return { ok: false };
3503
- }
3504
- }
3505
- const validationResult = validateManifest(manifest);
3506
- if (!validationResult.valid) {
3507
- ctx.ui.error("\u274C Manifest validation failed:\n");
3508
- for (const msg of validationResult.errors) {
3509
- ctx.ui.write(` - ${msg}
3510
- `);
3511
- }
3512
- return { ok: false, valid: false, errors: validationResult.errors.map((msg) => ({ path: "", message: msg })) };
3513
- }
3514
- ctx.ui.success("\u2705 Manifest structure is valid!\n");
3515
- if (flags.contracts) {
3516
- const contractsPath = path2.resolve(cwd, flags.contracts);
3517
- ctx.ui.write(`
3518
- Validating against contracts: ${contractsPath}
3519
- `);
3520
- const crossValidation = await validateManifestAgainstContracts(manifest, contractsPath);
3521
- if (!crossValidation.ok) {
3522
- ctx.ui.error("\u274C Manifest does not match contracts:\n");
3523
- for (const issue of crossValidation.issues) {
3524
- ctx.ui.write(` - ${issue}
3525
- `);
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);
3526
1803
  }
3527
- return { ok: false, valid: false, errors: crossValidation.issues.map((issue) => ({
3528
- path: "",
3529
- message: issue
3530
- })) };
3531
1804
  }
3532
- ctx.ui.success("\u2705 Manifest matches contracts!\n");
1805
+ } catch {
3533
1806
  }
3534
- ctx.ui.success("\n\u2705 All validations passed!\n");
3535
- return { ok: true, valid: true };
3536
1807
  }
3537
- });
3538
-
3539
- // src/builtins/plugins-cache-clear.ts
3540
- init_plugins_state();
1808
+ return {
1809
+ files: cleared,
1810
+ ...options?.deep ? { modules: modulesCleared } : {}
1811
+ };
1812
+ }
3541
1813
  var pluginsCacheClear = defineSystemCommand({
3542
1814
  name: "clear-cache",
3543
1815
  description: "Clear CLI plugin discovery cache",
3544
- category: "plugins",
3545
- examples: ["kb plugins clear-cache", "kb plugins clear-cache --deep"],
1816
+ category: "marketplace",
1817
+ examples: ["kb marketplace clear-cache", "kb marketplace clear-cache --deep"],
3546
1818
  flags: {
3547
1819
  deep: { type: "boolean", description: "Also clear Node.js module cache" },
3548
1820
  json: { type: "boolean", description: "Output in JSON format" }
3549
1821
  },
3550
1822
  analytics: {
3551
- command: "plugins:clear-cache",
3552
- startEvent: "PLUGINS_CACHE_CLEAR_STARTED",
3553
- finishEvent: "PLUGINS_CACHE_CLEAR_FINISHED"
1823
+ command: "marketplace:clear-cache",
1824
+ startEvent: "MARKETPLACE_CACHE_CLEAR_STARTED",
1825
+ finishEvent: "MARKETPLACE_CACHE_CLEAR_FINISHED"
3554
1826
  },
3555
1827
  async handler(ctx, _argv, flags) {
3556
1828
  const deep = flags.deep;
@@ -3630,7 +1902,7 @@ var docsGenerateCliReference = defineSystemCommand({
3630
1902
  // eslint-disable-next-line sonarjs/cognitive-complexity -- Complex markdown generation with nested loops for product groups, commands, and formatting
3631
1903
  async handler(ctx, argv, flags) {
3632
1904
  const cwd = getContextCwd(ctx);
3633
- const outputPath = flags.output || path2.join(cwd, "CLI-REFERENCE.md");
1905
+ const outputPath = flags.output || path.join(cwd, "CLI-REFERENCE.md");
3634
1906
  const commands = registry.list();
3635
1907
  const productGroups = registry.listProductGroups();
3636
1908
  ctx.platform?.logger?.info("Generating CLI reference", {
@@ -4622,6 +2894,252 @@ var logsStats = defineSystemCommand({
4622
2894
  ctx.ui.success("Log Storage Statistics", { sections });
4623
2895
  }
4624
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
+ });
4625
3143
 
4626
3144
  // src/commands/system/groups.ts
4627
3145
  var infoGroup = defineSystemCommandGroup("info", "System information commands", [
@@ -4630,18 +3148,8 @@ var infoGroup = defineSystemCommandGroup("info", "System information commands",
4630
3148
  health,
4631
3149
  diag
4632
3150
  ]);
4633
- var pluginsGroup = defineSystemCommandGroup("plugins", "Plugin management commands", [
4634
- pluginsList,
4635
- pluginsCommands,
4636
- pluginsEnable,
4637
- pluginsDisable,
4638
- pluginsLink,
4639
- pluginsUnlink,
4640
- pluginsCacheClear,
4641
- pluginsRegistry,
4642
- pluginsDoctor,
4643
- pluginsScaffold,
4644
- pluginValidate
3151
+ var marketplaceGroup = defineSystemCommandGroup("marketplace", "Marketplace management commands", [
3152
+ pluginsCacheClear
4645
3153
  ]);
4646
3154
  var docsGroup = defineSystemCommandGroup("docs", "Documentation generation commands", [
4647
3155
  docsGenerateCliReference
@@ -4655,17 +3163,26 @@ var logsGroup = defineSystemCommandGroup("logs", "Log viewing and analysis comma
4655
3163
  logsGet,
4656
3164
  logsStats
4657
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
+ ]);
4658
3175
  var req = createRequire(import.meta.url);
4659
3176
  var __filename$1 = fileURLToPath(import.meta.url);
4660
- var __dirname$1 = path2.dirname(__filename$1);
3177
+ var __dirname$1 = path.dirname(__filename$1);
4661
3178
  function resolveFromCwd(spec, cwd) {
4662
- const cliRoot = path2.resolve(__dirname$1, "../../..");
3179
+ const cliRoot = path.resolve(__dirname$1, "../../..");
4663
3180
  let monorepoRoot = cwd;
4664
- while (monorepoRoot !== path2.dirname(monorepoRoot)) {
4665
- if (existsSync(path2.join(monorepoRoot, "pnpm-workspace.yaml"))) {
3181
+ while (monorepoRoot !== path.dirname(monorepoRoot)) {
3182
+ if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
4666
3183
  break;
4667
3184
  }
4668
- monorepoRoot = path2.dirname(monorepoRoot);
3185
+ monorepoRoot = path.dirname(monorepoRoot);
4669
3186
  }
4670
3187
  const pathsToTry = [
4671
3188
  cwd,
@@ -4674,9 +3191,9 @@ function resolveFromCwd(spec, cwd) {
4674
3191
  // CLI installation directory
4675
3192
  monorepoRoot,
4676
3193
  // Monorepo root (if found)
4677
- path2.join(monorepoRoot, "../kb-labs-cli"),
3194
+ path.join(monorepoRoot, "../kb-labs-cli"),
4678
3195
  // Explicit kb-labs-cli path
4679
- path2.join(monorepoRoot, "../kb-labs-mind")
3196
+ path.join(monorepoRoot, "../kb-labs-mind")
4680
3197
  // Explicit kb-labs-mind path
4681
3198
  ].filter(Boolean);
4682
3199
  for (const searchPath of pathsToTry) {
@@ -4694,12 +3211,12 @@ function checkRequires(manifest, options = {}) {
4694
3211
  }
4695
3212
  let isInMonorepo = false;
4696
3213
  let monorepoRoot = cwd;
4697
- while (monorepoRoot !== path2.dirname(monorepoRoot)) {
4698
- if (existsSync(path2.join(monorepoRoot, "pnpm-workspace.yaml"))) {
3214
+ while (monorepoRoot !== path.dirname(monorepoRoot)) {
3215
+ if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
4699
3216
  isInMonorepo = true;
4700
3217
  break;
4701
3218
  }
4702
- monorepoRoot = path2.dirname(monorepoRoot);
3219
+ monorepoRoot = path.dirname(monorepoRoot);
4703
3220
  }
4704
3221
  for (const dep of manifest.requires) {
4705
3222
  try {
@@ -4723,8 +3240,8 @@ function checkRequires(manifest, options = {}) {
4723
3240
  if (!packageName) {
4724
3241
  continue;
4725
3242
  }
4726
- const cliRoot = path2.resolve(__dirname$1, "../../..");
4727
- const packagePath = scope ? path2.join(cliRoot, "node_modules", "@" + scope, packageName, "package.json") : path2.join(cliRoot, "node_modules", packageName, "package.json");
3243
+ const cliRoot = path.resolve(__dirname$1, "../../..");
3244
+ const packagePath = scope ? path.join(cliRoot, "node_modules", "@" + scope, packageName, "package.json") : path.join(cliRoot, "node_modules", packageName, "package.json");
4728
3245
  if (existsSync(packagePath)) {
4729
3246
  continue;
4730
3247
  }
@@ -4777,7 +3294,7 @@ var manifestSchema = {
4777
3294
  examples: { type: "array", items: { type: "string" } }
4778
3295
  }
4779
3296
  };
4780
- var validateManifest2 = ajv.compile(manifestSchema);
3297
+ var validateManifest = ajv.compile(manifestSchema);
4781
3298
  var validateFlag = ajv.compile(flagSchema);
4782
3299
  function validateFlagDef(flag, manifestId) {
4783
3300
  if (!validateFlag(flag)) {
@@ -4798,8 +3315,8 @@ function validateFlagDef(flag, manifestId) {
4798
3315
  }
4799
3316
  }
4800
3317
  function validateManifestStructure(manifest) {
4801
- if (!validateManifest2(manifest)) {
4802
- 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)}`);
4803
3320
  }
4804
3321
  if (manifest.manifestVersion !== "1.0") {
4805
3322
  throw new Error(
@@ -4830,7 +3347,7 @@ function generateWhitespaceAliases(id) {
4830
3347
  return [id.replace(":", " ")];
4831
3348
  }
4832
3349
  function normalizeAliases(manifest, logger) {
4833
- const log3 = logger ?? createNoOpLogger();
3350
+ const log3 = logger ?? platform.logger;
4834
3351
  const aliases = /* @__PURE__ */ new Set();
4835
3352
  manifest.namespace || manifest.group;
4836
3353
  if (manifest.aliases) {
@@ -4877,7 +3394,7 @@ function checkCollision(manifest, existing, currentSource, namespace) {
4877
3394
  return { shouldShadow: false };
4878
3395
  }
4879
3396
  function preflightManifests(discoveryResults, logger) {
4880
- const log3 = logger ?? createNoOpLogger();
3397
+ const log3 = logger ?? platform.logger;
4881
3398
  const valid = [];
4882
3399
  const skipped = [];
4883
3400
  for (const result of discoveryResults) {
@@ -4907,7 +3424,7 @@ function preflightManifests(discoveryResults, logger) {
4907
3424
  return { valid, skipped };
4908
3425
  }
4909
3426
  async function registerManifests(discoveryResults, registry2, options = {}) {
4910
- const log3 = options.logger ?? createNoOpLogger();
3427
+ const log3 = options.logger ?? platform.logger;
4911
3428
  const registered = [];
4912
3429
  const skipped = [];
4913
3430
  const globalIds = /* @__PURE__ */ new Map();
@@ -5069,7 +3586,7 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
5069
3586
  };
5070
3587
  }
5071
3588
  async function disposeAllPlugins(registry2, logger) {
5072
- const log3 = logger ?? createNoOpLogger();
3589
+ const log3 = logger ?? platform.logger;
5073
3590
  const manifests = registry2.listManifests();
5074
3591
  const disposePromises = [];
5075
3592
  for (const cmd of manifests) {
@@ -5087,7 +3604,7 @@ async function disposeAllPlugins(registry2, logger) {
5087
3604
  }
5088
3605
  await Promise.allSettled(disposePromises);
5089
3606
  }
5090
- var log2 = getLogger("cli:shutdown");
3607
+ var log2 = platform.logger.child({ module: "cli:shutdown" });
5091
3608
  var hooks = /* @__PURE__ */ new Set();
5092
3609
  var signalsBound = false;
5093
3610
  var exiting = false;
@@ -5125,7 +3642,7 @@ function registerShutdownHook(hook) {
5125
3642
  var _registered = false;
5126
3643
  var registeredCommands = [];
5127
3644
  async function registerBuiltinCommands(input = {}) {
5128
- const log3 = input.logger ?? createNoOpLogger();
3645
+ const log3 = input.logger ?? platform.logger;
5129
3646
  if (_registered) {
5130
3647
  return;
5131
3648
  }
@@ -5133,9 +3650,11 @@ async function registerBuiltinCommands(input = {}) {
5133
3650
  registry.markPartial(true);
5134
3651
  registeredCommands.length = 0;
5135
3652
  registry.registerGroup(infoGroup);
5136
- registry.registerGroup(pluginsGroup);
3653
+ registry.registerGroup(marketplaceGroup);
3654
+ registry.registerGroup(registryGroup);
5137
3655
  registry.registerGroup(docsGroup);
5138
3656
  registry.registerGroup(logsGroup);
3657
+ registry.registerGroup(authGroup);
5139
3658
  try {
5140
3659
  const cwd = getContextCwd({ cwd: input.cwd });
5141
3660
  const env = input.env ?? process.env;
@@ -5178,14 +3697,6 @@ async function registerBuiltinCommands(input = {}) {
5178
3697
  } else {
5179
3698
  registry.markPartial(false);
5180
3699
  }
5181
- const pluginRegistry = new PluginRegistry({
5182
- strategies: ["workspace", "pkg", "dir", "file"]
5183
- });
5184
- await pluginRegistry.refresh();
5185
- const plugins = pluginRegistry.list();
5186
- if (plugins.length > 0) {
5187
- log3.info(`Discovered ${plugins.length} plugins via cli-core`);
5188
- }
5189
3700
  } catch (err) {
5190
3701
  log3.warn(`Discovery failed: ${err.message}`);
5191
3702
  registry.markPartial(true);
@@ -5407,10 +3918,10 @@ function renderGlobalHelpNew(registry2) {
5407
3918
  )} ${colors.dim("Explore product commands")}`
5408
3919
  );
5409
3920
  }
5410
- const pluginsGroup2 = systemGroups.find((g) => g.name === "system:plugins");
5411
- if (pluginsGroup2) {
3921
+ const marketplaceGroup2 = systemGroups.find((g) => g.name === "marketplace");
3922
+ if (marketplaceGroup2) {
5412
3923
  nextStepsItems.push(
5413
- `${colors.cyan("kb plugins")} ${colors.dim("List and manage plugins")}`
3924
+ `${colors.cyan("kb marketplace")} ${colors.dim("Open marketplace commands")}`
5414
3925
  );
5415
3926
  }
5416
3927
  nextStepsItems.push("");
@@ -5430,19 +3941,20 @@ function renderGlobalHelpNew(registry2) {
5430
3941
  function renderPluginsHelp(registry2) {
5431
3942
  const tracker = new TimingTracker();
5432
3943
  registry2.list().filter(
5433
- (cmd) => cmd.name?.startsWith("plugins:") || cmd.category === "system"
3944
+ (cmd) => cmd.name?.startsWith("marketplace:") || cmd.category === "system"
5434
3945
  );
5435
3946
  const sections = [];
5436
3947
  const commandMap = {
5437
- "plugins:ls": "List all discovered plugins",
5438
- "plugins:enable": "Enable a plugin",
5439
- "plugins:disable": "Disable a plugin",
5440
- "plugins:link": "Link a local plugin for development",
5441
- "plugins:unlink": "Unlink a local plugin",
5442
- "plugins:doctor": "Diagnose plugin issues",
5443
- "plugins:watch": "Watch for manifest changes and hot-reload",
5444
- "plugins:scaffold": "Generate a new plugin template",
5445
- "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"
5446
3958
  };
5447
3959
  const maxLength = Math.max(
5448
3960
  ...Object.keys(commandMap).map((c) => c.length),
@@ -5456,10 +3968,10 @@ function renderPluginsHelp(registry2) {
5456
3968
  items: commandItems
5457
3969
  });
5458
3970
  const examples = [
5459
- `kb plugins ls ${colors.dim("List all plugins")}`,
5460
- `kb plugins enable @kb-labs/devlink-cli ${colors.dim("Enable a plugin")}`,
5461
- `kb plugins doctor ${colors.dim("Diagnose plugin issues")}`,
5462
- `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")}`
5463
3975
  ];
5464
3976
  sections.push({
5465
3977
  header: "Examples",
@@ -5467,7 +3979,7 @@ function renderPluginsHelp(registry2) {
5467
3979
  });
5468
3980
  sections.push({
5469
3981
  header: "Next Steps",
5470
- items: [colors.dim("Use 'kb plugins <command> --help' for detailed help")]
3982
+ items: [colors.dim("Use 'kb marketplace <command> --help' for detailed help")]
5471
3983
  });
5472
3984
  return sideBorderBox({
5473
3985
  title: "\u{1F9E9} Plugin Management",