@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.d.ts +5 -3
- package/dist/index.js +517 -2005
- package/dist/index.js.map +1 -1
- package/package.json +38 -35
package/dist/index.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { safeColors, safeSymbols, getContextCwd,
|
|
1
|
+
import { safeColors, safeSymbols, getContextCwd, toPosixPath, TimingTracker, sideBorderBox, formatCommandHelp } from '@kb-labs/shared-cli-ui';
|
|
2
2
|
export { TimingTracker } from '@kb-labs/shared-cli-ui';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
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 {
|
|
12
|
-
import {
|
|
13
|
-
import { createRequire } from 'module';
|
|
14
|
-
import { validateManifest } from '@kb-labs/plugin-contracts';
|
|
11
|
+
import { createRegistry, formatDiagnosticReport } from '@kb-labs/core-registry';
|
|
12
|
+
import { readMarketplaceLock, DiagnosticCollector } from '@kb-labs/core-discovery';
|
|
15
13
|
import { platform } from '@kb-labs/core-runtime';
|
|
14
|
+
import { CredentialsManager } from '@kb-labs/cli-runtime/gateway';
|
|
16
15
|
import Ajv from 'ajv';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
import { colors, getLogLevel } from '@kb-labs/cli-runtime';
|
|
17
18
|
|
|
18
19
|
var __defProp = Object.defineProperty;
|
|
19
20
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
518
|
-
const pkg = await readPackageJson(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
708
|
-
const pkg = await readPackageJson(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
820
|
-
pkg = await readPackageJson(
|
|
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
|
|
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 =
|
|
855
|
-
pkg = await readPackageJson(
|
|
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
|
|
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 =
|
|
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(
|
|
1005
|
-
const pkgJsonPath =
|
|
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 =
|
|
1043
|
-
await promises.mkdir(
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
1681
|
-
longDescription:
|
|
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,
|
|
1695
|
-
const
|
|
1696
|
-
|
|
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
|
|
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:
|
|
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: ${
|
|
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:
|
|
1378
|
+
platform: platform11
|
|
1753
1379
|
});
|
|
1754
1380
|
}
|
|
1755
1381
|
return {
|
|
1756
1382
|
ok: true,
|
|
1757
1383
|
version: cliVersion,
|
|
1758
1384
|
nodeVersion,
|
|
1759
|
-
platform:
|
|
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
|
|
1782
|
-
cache: {
|
|
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
|
|
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}, ${
|
|
1899
|
-
details: { nodeVersion, cliVersion, platform:
|
|
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: "
|
|
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: "
|
|
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 =
|
|
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
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
enabled: enabledCount,
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
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: "
|
|
1606
|
+
category: "marketplace-lock",
|
|
1979
1607
|
status: "error",
|
|
1980
|
-
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
|
|
1697
|
+
nextSteps.push(`kb marketplace doctor ${safeColors.muted("Diagnose plugin issues")}`);
|
|
2070
1698
|
}
|
|
2071
1699
|
if (hasWarnings) {
|
|
2072
|
-
nextSteps.push(`kb
|
|
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
|
|
3156
|
-
name: "
|
|
3157
|
-
description: "
|
|
3158
|
-
category: "
|
|
3159
|
-
examples:
|
|
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
|
-
|
|
3166
|
-
|
|
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: "
|
|
3174
|
-
startEvent: "
|
|
3175
|
-
finishEvent: "
|
|
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
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
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
|
-
|
|
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.
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
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
|
|
1767
|
+
async function clearCache(cwd, options) {
|
|
1768
|
+
const cleared = [];
|
|
1769
|
+
const cacheDir = path.join(cwd, ".kb", "cache");
|
|
3425
1770
|
try {
|
|
3426
|
-
const
|
|
3427
|
-
const
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
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
|
-
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
const modulesCleared = [];
|
|
1796
|
+
if (options?.deep) {
|
|
3492
1797
|
try {
|
|
3493
|
-
const
|
|
3494
|
-
const
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3540
|
-
|
|
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: "
|
|
3545
|
-
examples: ["kb
|
|
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: "
|
|
3552
|
-
startEvent: "
|
|
3553
|
-
finishEvent: "
|
|
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 ||
|
|
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
|
|
4634
|
-
|
|
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 =
|
|
3177
|
+
var __dirname$1 = path.dirname(__filename$1);
|
|
4661
3178
|
function resolveFromCwd(spec, cwd) {
|
|
4662
|
-
const cliRoot =
|
|
3179
|
+
const cliRoot = path.resolve(__dirname$1, "../../..");
|
|
4663
3180
|
let monorepoRoot = cwd;
|
|
4664
|
-
while (monorepoRoot !==
|
|
4665
|
-
if (existsSync(
|
|
3181
|
+
while (monorepoRoot !== path.dirname(monorepoRoot)) {
|
|
3182
|
+
if (existsSync(path.join(monorepoRoot, "pnpm-workspace.yaml"))) {
|
|
4666
3183
|
break;
|
|
4667
3184
|
}
|
|
4668
|
-
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
|
-
|
|
3194
|
+
path.join(monorepoRoot, "../kb-labs-cli"),
|
|
4678
3195
|
// Explicit kb-labs-cli path
|
|
4679
|
-
|
|
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 !==
|
|
4698
|
-
if (existsSync(
|
|
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 =
|
|
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 =
|
|
4727
|
-
const packagePath = scope ?
|
|
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
|
|
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 (!
|
|
4802
|
-
throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(
|
|
3318
|
+
if (!validateManifest(manifest)) {
|
|
3319
|
+
throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 =
|
|
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 ??
|
|
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(
|
|
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
|
|
5411
|
-
if (
|
|
3921
|
+
const marketplaceGroup2 = systemGroups.find((g) => g.name === "marketplace");
|
|
3922
|
+
if (marketplaceGroup2) {
|
|
5412
3923
|
nextStepsItems.push(
|
|
5413
|
-
`${colors.cyan("kb
|
|
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("
|
|
3944
|
+
(cmd) => cmd.name?.startsWith("marketplace:") || cmd.category === "system"
|
|
5434
3945
|
);
|
|
5435
3946
|
const sections = [];
|
|
5436
3947
|
const commandMap = {
|
|
5437
|
-
"
|
|
5438
|
-
"
|
|
5439
|
-
"
|
|
5440
|
-
"
|
|
5441
|
-
"
|
|
5442
|
-
"
|
|
5443
|
-
"
|
|
5444
|
-
"
|
|
5445
|
-
"
|
|
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
|
|
5460
|
-
`kb
|
|
5461
|
-
`kb
|
|
5462
|
-
`kb
|
|
3971
|
+
`kb marketplace list ${colors.dim("List all plugins")}`,
|
|
3972
|
+
`kb marketplace enable @kb-labs/devlink-cli ${colors.dim("Enable a plugin")}`,
|
|
3973
|
+
`kb marketplace doctor ${colors.dim("Diagnose plugin issues")}`,
|
|
3974
|
+
`kb marketplace scaffold my-plugin ${colors.dim("Generate plugin template")}`
|
|
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
|
|
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",
|