@kb-labs/cli-commands 2.94.0 → 2.98.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{discover-CCE84_5E.d.ts → discover-DxmYCrcJ.d.ts} +135 -104
- package/dist/index.d.ts +24 -13
- package/dist/index.js +2782 -2015
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.ts +12 -12
- package/dist/registry/index.js +581 -659
- package/dist/registry/index.js.map +1 -1
- package/package.json +23 -23
package/dist/registry/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import path2 from 'path';
|
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
4
|
import { existsSync, promises } from 'fs';
|
|
5
5
|
import { createHash } from 'crypto';
|
|
6
|
+
import { computeManifestIntegrity } from '@kb-labs/core-discovery';
|
|
6
7
|
import { parse } from 'yaml';
|
|
7
8
|
import { glob } from 'glob';
|
|
8
9
|
import { toPosixPath } from '@kb-labs/shared-cli-ui';
|
|
9
10
|
import { z } from 'zod';
|
|
10
|
-
import Ajv from 'ajv';
|
|
11
11
|
import { platform } from '@kb-labs/core-runtime';
|
|
12
12
|
import { getLogLevel } from '@kb-labs/cli-runtime';
|
|
13
13
|
|
|
@@ -107,62 +107,37 @@ var FlagDefinitionSchema = z.object({
|
|
|
107
107
|
choices: z.array(z.string()).optional(),
|
|
108
108
|
required: z.boolean().optional()
|
|
109
109
|
}).refine(
|
|
110
|
-
(data) =>
|
|
111
|
-
if (data.choices && data.type !== "string") {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
},
|
|
110
|
+
(data) => !(data.choices && data.type !== "string"),
|
|
116
111
|
{ message: "Choices are only allowed for string type flags" }
|
|
117
112
|
);
|
|
118
113
|
var EngineSchema = z.object({
|
|
119
114
|
node: z.string().optional(),
|
|
120
|
-
// e.g., ">=18", "^18.0.0"
|
|
121
115
|
kbCli: z.string().optional(),
|
|
122
|
-
// e.g., "^1.5.0"
|
|
123
116
|
module: z.enum(["esm", "cjs"]).optional()
|
|
124
117
|
}).optional();
|
|
125
118
|
var CommandManifestSchema = z.object({
|
|
126
119
|
manifestVersion: z.literal("1.0"),
|
|
127
|
-
|
|
128
|
-
id: z.string().min(1)
|
|
129
|
-
aliases: z.array(z.string()).optional(),
|
|
120
|
+
segments: z.array(z.string().min(1)).min(1),
|
|
121
|
+
id: z.string().min(1),
|
|
130
122
|
group: z.string().min(1),
|
|
123
|
+
subgroup: z.string().optional(),
|
|
124
|
+
aliases: z.array(z.string()).optional(),
|
|
125
|
+
category: z.string().optional(),
|
|
131
126
|
describe: z.string().min(1),
|
|
132
127
|
longDescription: z.string().optional(),
|
|
133
128
|
requires: z.array(z.string()).optional(),
|
|
134
|
-
// Package names with optional semver
|
|
135
129
|
flags: z.array(FlagDefinitionSchema).optional(),
|
|
136
130
|
examples: z.array(z.string()).optional(),
|
|
137
131
|
loader: z.any(),
|
|
138
|
-
// Function validation happens at runtime
|
|
139
|
-
// New fields (optional for backward compatibility)
|
|
140
132
|
package: z.string().optional(),
|
|
141
|
-
// Full package name
|
|
142
|
-
namespace: z.string().optional(),
|
|
143
|
-
// Derived from id if not provided
|
|
144
133
|
engine: EngineSchema,
|
|
145
134
|
permissions: z.array(z.string()).optional(),
|
|
146
|
-
// e.g., ["fs.read", "git.read", "net.fetch"]
|
|
147
135
|
telemetry: z.enum(["opt-in", "off"]).optional(),
|
|
148
136
|
manifestV2: z.any().optional(),
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Auto-generated setup command
|
|
153
|
-
isSetupRollback: z.boolean().optional(),
|
|
154
|
-
// Auto-generated setup rollback command
|
|
155
|
-
pkgRoot: z.string().optional()
|
|
156
|
-
// Package root path for handler resolution
|
|
137
|
+
pkgRoot: z.string().optional(),
|
|
138
|
+
_synthetic: z.boolean().optional(),
|
|
139
|
+
operationType: z.enum(["read", "mutate", "execute", "analyze"]).optional()
|
|
157
140
|
}).refine(
|
|
158
|
-
(data) => {
|
|
159
|
-
if (data.namespace && data.group && data.namespace !== data.group) {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
163
|
-
},
|
|
164
|
-
{ message: "namespace must match group" }
|
|
165
|
-
).refine(
|
|
166
141
|
(data) => {
|
|
167
142
|
if (data.requires) {
|
|
168
143
|
for (const req2 of data.requires) {
|
|
@@ -189,46 +164,36 @@ function validateManifests(manifests) {
|
|
|
189
164
|
if (result.success) {
|
|
190
165
|
validated.push(result.data);
|
|
191
166
|
} else {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
...issue,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
]);
|
|
198
|
-
errors.push(errorWithIndex);
|
|
167
|
+
errors.push(
|
|
168
|
+
new z.ZodError(
|
|
169
|
+
result.error.issues.map((issue) => ({ ...issue, path: [`[${i}]`, ...issue.path] }))
|
|
170
|
+
)
|
|
171
|
+
);
|
|
199
172
|
}
|
|
200
173
|
}
|
|
201
|
-
|
|
202
|
-
return { success: true, data: validated };
|
|
203
|
-
}
|
|
204
|
-
return { success: false, errors };
|
|
174
|
+
return errors.length === 0 ? { success: true, data: validated } : { success: false, errors };
|
|
205
175
|
}
|
|
206
|
-
function normalizeManifest(manifest, packageName
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
if (!normalized.package) {
|
|
217
|
-
normalized.package = packageName;
|
|
218
|
-
}
|
|
219
|
-
return normalized;
|
|
176
|
+
function normalizeManifest(manifest, packageName) {
|
|
177
|
+
const segs = manifest.segments;
|
|
178
|
+
return {
|
|
179
|
+
...manifest,
|
|
180
|
+
id: segs[segs.length - 1] ?? "",
|
|
181
|
+
group: segs[0] ?? "",
|
|
182
|
+
subgroup: segs.length >= 3 ? segs[1] : void 0,
|
|
183
|
+
package: manifest.package ?? packageName
|
|
184
|
+
};
|
|
220
185
|
}
|
|
221
186
|
|
|
222
187
|
// src/registry/discover.ts
|
|
188
|
+
var DISCOVERY_VERSION = 2;
|
|
223
189
|
var DEBUG_MODE = process.env.DEBUG_SANDBOX === "1" || process.env.NODE_ENV === "development";
|
|
224
190
|
var log = (level, message, fields) => {
|
|
225
191
|
if (!DEBUG_MODE) {
|
|
226
192
|
return;
|
|
227
193
|
}
|
|
228
194
|
const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : level === "info" ? "\u2139" : "\u{1F50D}";
|
|
229
|
-
const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
230
195
|
{
|
|
231
|
-
|
|
196
|
+
console.error(`${prefix} [discover] ${message}`);
|
|
232
197
|
}
|
|
233
198
|
};
|
|
234
199
|
function createManifestV3Loader(commandId) {
|
|
@@ -250,6 +215,14 @@ var __test = {
|
|
|
250
215
|
createManifestV3Loader
|
|
251
216
|
// resetInProcCache is exported directly (not via __test) since it's part of the public API
|
|
252
217
|
};
|
|
218
|
+
var NonPluginManifestError = class extends Error {
|
|
219
|
+
schema;
|
|
220
|
+
constructor(pkgName, schema) {
|
|
221
|
+
super(`${pkgName} uses manifest schema "${schema}", not kb.plugin/3 \u2014 skipping`);
|
|
222
|
+
this.name = "NonPluginManifestError";
|
|
223
|
+
this.schema = schema;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
253
226
|
function createUnavailableManifest(pkgName, error) {
|
|
254
227
|
const rawMsg = (error instanceof Error ? error.message : String(error) || "").toString();
|
|
255
228
|
let missing = null;
|
|
@@ -264,9 +237,11 @@ function createUnavailableManifest(pkgName, error) {
|
|
|
264
237
|
const group = (seg || pkgName).replace(/-cli$/, "");
|
|
265
238
|
const short = seg || pkgName;
|
|
266
239
|
const requires = missing ? [missing] : [];
|
|
240
|
+
const unavailableId = `manifest:${short}`;
|
|
267
241
|
const manifest = {
|
|
268
242
|
manifestVersion: "1.0",
|
|
269
|
-
|
|
243
|
+
segments: [group, unavailableId],
|
|
244
|
+
id: unavailableId,
|
|
270
245
|
group,
|
|
271
246
|
describe: `Commands from ${pkgName} are unavailable`,
|
|
272
247
|
requires,
|
|
@@ -289,14 +264,6 @@ var inProcDiscoveryCache = null;
|
|
|
289
264
|
function resetInProcCache() {
|
|
290
265
|
inProcDiscoveryCache = null;
|
|
291
266
|
}
|
|
292
|
-
async function computeManifestHash(manifestPath) {
|
|
293
|
-
try {
|
|
294
|
-
const content = await promises.readFile(manifestPath, "utf8");
|
|
295
|
-
return createHash("sha256").update(content).digest("hex");
|
|
296
|
-
} catch {
|
|
297
|
-
return "unknown";
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
267
|
async function computeLockfileHash(cwd) {
|
|
301
268
|
const lockfilePath = path2.join(cwd, "pnpm-lock.yaml");
|
|
302
269
|
try {
|
|
@@ -402,8 +369,12 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
|
|
|
402
369
|
const mod = await import(fileUrl);
|
|
403
370
|
const modTyped = mod;
|
|
404
371
|
const rawManifest = modTyped.manifest || modTyped.default;
|
|
405
|
-
if (!rawManifest || typeof rawManifest !== "object"
|
|
406
|
-
throw new Error(`
|
|
372
|
+
if (!rawManifest || typeof rawManifest !== "object") {
|
|
373
|
+
throw new Error(`No manifest export found in ${pkgName}`);
|
|
374
|
+
}
|
|
375
|
+
const schema = rawManifest.schema;
|
|
376
|
+
if (schema !== "kb.plugin/3") {
|
|
377
|
+
throw new NonPluginManifestError(pkgName, typeof schema === "string" ? schema : String(schema ?? "unknown"));
|
|
407
378
|
}
|
|
408
379
|
const manifest = rawManifest;
|
|
409
380
|
const namespace = getNamespaceFromManifest(manifest, pkgName);
|
|
@@ -414,20 +385,23 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
|
|
|
414
385
|
log("warn", `ManifestV3 ${manifest.id || pkgName} has no CLI commands or setup entry`);
|
|
415
386
|
}
|
|
416
387
|
const commandManifests = cliCommands.map((cmd) => {
|
|
417
|
-
const
|
|
388
|
+
const segments = cmd.path.trim().split(/\s+/).filter(Boolean);
|
|
389
|
+
const commandId = segments[segments.length - 1] ?? namespace;
|
|
418
390
|
const commandManifest = {
|
|
419
391
|
manifestVersion: "1.0",
|
|
392
|
+
segments,
|
|
420
393
|
id: commandId,
|
|
421
|
-
group:
|
|
422
|
-
subgroup:
|
|
394
|
+
group: segments[0] ?? namespace,
|
|
395
|
+
subgroup: segments.length >= 3 ? segments[1] : void 0,
|
|
396
|
+
category: cmd.category,
|
|
423
397
|
describe: cmd.describe || "",
|
|
424
398
|
longDescription: cmd.longDescription,
|
|
425
399
|
aliases: cmd.aliases,
|
|
426
400
|
flags: cmd.flags,
|
|
427
401
|
examples: cmd.examples,
|
|
428
|
-
loader: createManifestV3Loader(
|
|
402
|
+
loader: createManifestV3Loader(cmd.path),
|
|
429
403
|
package: pkgName,
|
|
430
|
-
|
|
404
|
+
operationType: cmd.operationType
|
|
431
405
|
};
|
|
432
406
|
commandManifest.manifestV2 = manifest;
|
|
433
407
|
commandManifest.pkgRoot = baseRoot;
|
|
@@ -441,7 +415,7 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
|
|
|
441
415
|
log("warn", `ManifestV3 validation warnings for ${pkgName}: ${errorMessages}`);
|
|
442
416
|
}
|
|
443
417
|
return (validation.success ? validation.data : commandManifests).map(
|
|
444
|
-
(m) => normalizeManifest(m, pkgName
|
|
418
|
+
(m) => normalizeManifest(m, pkgName)
|
|
445
419
|
);
|
|
446
420
|
}
|
|
447
421
|
async function readPackageJson(pkgPath) {
|
|
@@ -512,10 +486,11 @@ function validateUniqueIds(manifests, pkgName) {
|
|
|
512
486
|
const ids = /* @__PURE__ */ new Set();
|
|
513
487
|
const aliases = /* @__PURE__ */ new Set();
|
|
514
488
|
for (const m of manifests) {
|
|
515
|
-
|
|
489
|
+
const key = m.segments.join("/");
|
|
490
|
+
if (ids.has(key)) {
|
|
516
491
|
throw new Error(`Duplicate command ID "${m.id}" in package ${pkgName}`);
|
|
517
492
|
}
|
|
518
|
-
ids.add(
|
|
493
|
+
ids.add(key);
|
|
519
494
|
if (m.aliases) {
|
|
520
495
|
for (const alias of m.aliases) {
|
|
521
496
|
if (aliases.has(alias) || ids.has(alias)) {
|
|
@@ -608,6 +583,10 @@ async function loadManifestsForPackages(packageInfos, source, scope) {
|
|
|
608
583
|
log("debug", `[plugins][perf] ${pkgName} failed after ${pkgTime}ms`);
|
|
609
584
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
610
585
|
const errCode = err.code ?? "UNKNOWN";
|
|
586
|
+
if (err instanceof NonPluginManifestError) {
|
|
587
|
+
log("debug", `[plugins] ${pkgName} skipped: ${err.message}`);
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
611
590
|
log("warn", JSON.stringify({
|
|
612
591
|
code: "DISCOVERY_MANIFEST_LOAD_FAIL",
|
|
613
592
|
packageName: pkgName,
|
|
@@ -778,6 +757,10 @@ async function discoverNodeModules(cwd) {
|
|
|
778
757
|
} catch (err) {
|
|
779
758
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
780
759
|
const errCode = err.code ?? "UNKNOWN";
|
|
760
|
+
if (err instanceof NonPluginManifestError) {
|
|
761
|
+
log("debug", `[plugins] ${pkgName} skipped: ${err.message}`);
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
781
764
|
log("warn", JSON.stringify({
|
|
782
765
|
code: "DISCOVERY_MANIFEST_LOAD_FAIL",
|
|
783
766
|
packageName: pkgName,
|
|
@@ -867,6 +850,10 @@ async function loadCache(cwd, roots) {
|
|
|
867
850
|
log("debug", "Cache invalidated: Node version changed");
|
|
868
851
|
return null;
|
|
869
852
|
}
|
|
853
|
+
if ((cache.discoveryVersion ?? 0) !== DISCOVERY_VERSION) {
|
|
854
|
+
log("debug", `Cache invalidated: discovery logic changed (${cache.discoveryVersion ?? 0} \u2192 ${DISCOVERY_VERSION})`);
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
870
857
|
const currentCliVersion = process.env.CLI_VERSION || "0.1.0";
|
|
871
858
|
if (cache.cliVersion !== currentCliVersion) {
|
|
872
859
|
log("debug", "Cache invalidated: CLI version changed");
|
|
@@ -920,7 +907,7 @@ async function loadCache(cwd, roots) {
|
|
|
920
907
|
return null;
|
|
921
908
|
}
|
|
922
909
|
}
|
|
923
|
-
async function isPackageCacheStale(entry
|
|
910
|
+
async function isPackageCacheStale(entry) {
|
|
924
911
|
const manifestFsPath = entry.manifestPath.split("/").join(path2.sep);
|
|
925
912
|
const pkgJsonPath = path2.join(entry.result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
|
|
926
913
|
try {
|
|
@@ -933,26 +920,23 @@ async function isPackageCacheStale(entry, options) {
|
|
|
933
920
|
log("debug", `Package cache invalidated: missing package.json for ${entry.result.packageName} (${error instanceof Error ? error.message : "unknown"})`);
|
|
934
921
|
return true;
|
|
935
922
|
}
|
|
936
|
-
let
|
|
923
|
+
let manifestMtimeChanged = false;
|
|
937
924
|
try {
|
|
938
|
-
manifestStat = await promises.stat(manifestFsPath);
|
|
939
|
-
|
|
940
|
-
log("debug", `Package cache invalidated: manifest mtime changed for ${entry.result.packageName}`);
|
|
941
|
-
return true;
|
|
942
|
-
}
|
|
925
|
+
const manifestStat = await promises.stat(manifestFsPath);
|
|
926
|
+
manifestMtimeChanged = manifestStat.mtimeMs !== entry.manifestMtime;
|
|
943
927
|
} catch (error) {
|
|
944
928
|
log("debug", `Package cache invalidated: manifest deleted for ${entry.result.packageName} (${error instanceof Error ? error.message : "unknown"})`);
|
|
945
929
|
return true;
|
|
946
930
|
}
|
|
947
|
-
if (
|
|
931
|
+
if (manifestMtimeChanged) {
|
|
948
932
|
try {
|
|
949
|
-
const currentHash = await
|
|
933
|
+
const currentHash = await computeManifestIntegrity(manifestFsPath);
|
|
950
934
|
if (currentHash !== entry.manifestHash) {
|
|
951
|
-
log("debug", `Package cache invalidated: manifest
|
|
935
|
+
log("debug", `Package cache invalidated: manifest content changed for ${entry.result.packageName}`);
|
|
952
936
|
return true;
|
|
953
937
|
}
|
|
954
938
|
} catch (error) {
|
|
955
|
-
log("debug", `Package cache hash
|
|
939
|
+
log("debug", `Package cache hash check failed for ${entry.result.packageName}: ${error instanceof Error ? error.message : "unknown"}`);
|
|
956
940
|
return true;
|
|
957
941
|
}
|
|
958
942
|
}
|
|
@@ -971,7 +955,7 @@ async function saveCache(cwd, results, roots) {
|
|
|
971
955
|
continue;
|
|
972
956
|
}
|
|
973
957
|
try {
|
|
974
|
-
const manifestHash = await
|
|
958
|
+
const manifestHash = await computeManifestIntegrity(result.manifestPath);
|
|
975
959
|
const pkgJsonPath = path2.join(result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
|
|
976
960
|
const pkgStat = await promises.stat(pkgJsonPath);
|
|
977
961
|
const pkg = await readPackageJson(pkgJsonPath);
|
|
@@ -1026,6 +1010,7 @@ async function saveCache(cwd, results, roots) {
|
|
|
1026
1010
|
const cache = {
|
|
1027
1011
|
version: process.version,
|
|
1028
1012
|
cliVersion: process.env.CLI_VERSION || "0.1.0",
|
|
1013
|
+
discoveryVersion: DISCOVERY_VERSION,
|
|
1029
1014
|
timestamp: now,
|
|
1030
1015
|
ttlMs: DISK_CACHE_TTL_MS,
|
|
1031
1016
|
stateHash: stateHasher.digest("hex"),
|
|
@@ -1073,11 +1058,10 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
|
|
|
1073
1058
|
const freshResults = [];
|
|
1074
1059
|
const cacheAge = Date.now() - cached.timestamp;
|
|
1075
1060
|
const ttlMs = cached.ttlMs ?? DISK_CACHE_TTL_MS;
|
|
1076
|
-
|
|
1077
|
-
log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms validateHash=${enforceHashValidation}`);
|
|
1061
|
+
log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms`);
|
|
1078
1062
|
let staleCount = 0;
|
|
1079
1063
|
for (const entry of Object.values(cached.packages)) {
|
|
1080
|
-
const stale = await isPackageCacheStale(entry
|
|
1064
|
+
const stale = await isPackageCacheStale(entry);
|
|
1081
1065
|
if (!stale) {
|
|
1082
1066
|
freshResults.push(entry.result);
|
|
1083
1067
|
} else {
|
|
@@ -1144,7 +1128,19 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
|
|
|
1144
1128
|
if (projectWorkspace.length > 0) {
|
|
1145
1129
|
log("info", `Discovered ${projectWorkspace.length} project workspace packages with CLI manifests`);
|
|
1146
1130
|
}
|
|
1147
|
-
} catch {
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1133
|
+
const isMissingYaml = msg.includes("ENOENT") || msg.includes("no such file");
|
|
1134
|
+
if (isMissingYaml) {
|
|
1135
|
+
log("debug", `[plugins][discover] no pnpm-workspace.yaml at projectRoot=${roots.projectRoot} \u2014 skipping project workspace scan`);
|
|
1136
|
+
} else {
|
|
1137
|
+
log("warn", JSON.stringify({
|
|
1138
|
+
code: "PROJECT_WORKSPACE_DISCOVERY_FAILED",
|
|
1139
|
+
projectRoot: roots.projectRoot,
|
|
1140
|
+
error: msg,
|
|
1141
|
+
hint: "Project workspace plugins may be missing from CLI discovery"
|
|
1142
|
+
}));
|
|
1143
|
+
}
|
|
1148
1144
|
}
|
|
1149
1145
|
}
|
|
1150
1146
|
const dedupStart = Date.now();
|
|
@@ -1170,81 +1166,37 @@ async function discoverManifestsByNamespace(cwd, namespace, noCache = false) {
|
|
|
1170
1166
|
const allResults = await discoverManifests(cwd, noCache);
|
|
1171
1167
|
return allResults.filter((result) => {
|
|
1172
1168
|
return result.manifests.some((m) => {
|
|
1173
|
-
|
|
1174
|
-
return manifestNamespace === namespace;
|
|
1169
|
+
return m.group === namespace;
|
|
1175
1170
|
});
|
|
1176
1171
|
});
|
|
1177
1172
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
type: "object",
|
|
1181
|
-
required: ["name", "type"],
|
|
1182
|
-
additionalProperties: false,
|
|
1183
|
-
properties: {
|
|
1184
|
-
name: { type: "string" },
|
|
1185
|
-
type: { enum: ["string", "boolean", "number", "array"] },
|
|
1186
|
-
alias: { type: "string", pattern: "^[a-z]$" },
|
|
1187
|
-
// single letter
|
|
1188
|
-
description: { type: "string" },
|
|
1189
|
-
choices: { type: "array", items: { type: "string" } },
|
|
1190
|
-
required: { type: "boolean" },
|
|
1191
|
-
default: {}
|
|
1192
|
-
// must match type
|
|
1193
|
-
}
|
|
1194
|
-
};
|
|
1195
|
-
var manifestSchema = {
|
|
1196
|
-
type: "object",
|
|
1197
|
-
required: ["manifestVersion", "id", "group", "describe", "loader"],
|
|
1198
|
-
additionalProperties: true,
|
|
1199
|
-
properties: {
|
|
1200
|
-
manifestVersion: { type: "string", enum: ["1.0"] },
|
|
1201
|
-
id: { type: "string", pattern: "^[a-z0-9-]+(?::[a-z0-9-]+)*$" },
|
|
1202
|
-
// Simple or namespaced (colon-separated)
|
|
1203
|
-
aliases: { type: "array", items: { type: "string" } },
|
|
1204
|
-
group: { type: "string" },
|
|
1205
|
-
describe: { type: "string" },
|
|
1206
|
-
longDescription: { type: "string" },
|
|
1207
|
-
requires: { type: "array", items: { type: "string" } },
|
|
1208
|
-
flags: {
|
|
1209
|
-
type: "array",
|
|
1210
|
-
items: flagSchema
|
|
1211
|
-
},
|
|
1212
|
-
examples: { type: "array", items: { type: "string" } }
|
|
1213
|
-
}
|
|
1214
|
-
};
|
|
1215
|
-
var validateManifest = ajv.compile(manifestSchema);
|
|
1216
|
-
var validateFlag = ajv.compile(flagSchema);
|
|
1217
|
-
function validateFlagDef(flag, manifestId) {
|
|
1218
|
-
if (!validateFlag(flag)) {
|
|
1173
|
+
function validateManifestStructure(manifest) {
|
|
1174
|
+
if (manifest.manifestVersion !== "1.0") {
|
|
1219
1175
|
throw new Error(
|
|
1220
|
-
`
|
|
1176
|
+
`Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.segments?.join(" ") ?? manifest.id} (expected "1.0")`
|
|
1221
1177
|
);
|
|
1222
1178
|
}
|
|
1223
|
-
if (
|
|
1224
|
-
throw new Error(`
|
|
1225
|
-
}
|
|
1226
|
-
if (flag.default !== void 0) {
|
|
1227
|
-
const typeMatches = flag.type === "string" && typeof flag.default === "string" || flag.type === "boolean" && typeof flag.default === "boolean" || flag.type === "number" && typeof flag.default === "number" || flag.type === "array" && Array.isArray(flag.default);
|
|
1228
|
-
if (!typeMatches) {
|
|
1229
|
-
throw new Error(
|
|
1230
|
-
`Flag "${flag.name}" in ${manifestId}: default value type mismatch (expected ${flag.type})`
|
|
1231
|
-
);
|
|
1232
|
-
}
|
|
1179
|
+
if (!Array.isArray(manifest.segments) || manifest.segments.length === 0) {
|
|
1180
|
+
throw new Error(`Missing or empty segments in manifest ${manifest.id}`);
|
|
1233
1181
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
if (!validateManifest(manifest)) {
|
|
1237
|
-
throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
|
|
1182
|
+
if (!manifest.describe) {
|
|
1183
|
+
throw new Error(`Missing describe in manifest ${manifest.segments.join(" ")}`);
|
|
1238
1184
|
}
|
|
1239
|
-
if (manifest.
|
|
1240
|
-
throw new Error(
|
|
1241
|
-
`Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.id} (expected "1.0")`
|
|
1242
|
-
);
|
|
1185
|
+
if (!manifest.loader && !manifest.manifestV2) {
|
|
1186
|
+
throw new Error(`Command ${manifest.segments.join(" ")} must have either loader or manifestV2`);
|
|
1243
1187
|
}
|
|
1244
|
-
if (manifest.flags
|
|
1188
|
+
if (manifest.flags) {
|
|
1245
1189
|
for (const flag of manifest.flags) {
|
|
1246
|
-
if (flag
|
|
1247
|
-
|
|
1190
|
+
if (!flag.name || !flag.type) {
|
|
1191
|
+
throw new Error(`Invalid flag in ${manifest.segments.join(" ")}: missing name or type`);
|
|
1192
|
+
}
|
|
1193
|
+
if (flag.alias && flag.alias.length !== 1) {
|
|
1194
|
+
throw new Error(`Flag "${flag.name}" in ${manifest.segments.join(" ")}: alias must be a single character`);
|
|
1195
|
+
}
|
|
1196
|
+
if (flag.choices && flag.type !== "string") {
|
|
1197
|
+
throw new Error(
|
|
1198
|
+
`Flag "${flag.name}" in ${manifest.segments.join(" ")}: choices only allowed for string type`
|
|
1199
|
+
);
|
|
1248
1200
|
}
|
|
1249
1201
|
}
|
|
1250
1202
|
}
|
|
@@ -1255,61 +1207,8 @@ var SOURCE_PRIORITY = {
|
|
|
1255
1207
|
linked: 2,
|
|
1256
1208
|
node_modules: 1
|
|
1257
1209
|
};
|
|
1258
|
-
function normalizeCommandId(id, _namespace) {
|
|
1259
|
-
return id;
|
|
1260
|
-
}
|
|
1261
|
-
function generateWhitespaceAliases(id) {
|
|
1262
|
-
if (!id.includes(":")) {
|
|
1263
|
-
return [];
|
|
1264
|
-
}
|
|
1265
|
-
return [id.replace(":", " ")];
|
|
1266
|
-
}
|
|
1267
|
-
function normalizeAliases(manifest, logger) {
|
|
1268
|
-
const log2 = logger ?? platform.logger;
|
|
1269
|
-
const aliases = /* @__PURE__ */ new Set();
|
|
1270
|
-
manifest.namespace || manifest.group;
|
|
1271
|
-
if (manifest.aliases) {
|
|
1272
|
-
for (const alias of manifest.aliases) {
|
|
1273
|
-
if (!/^[a-z0-9-:]+$/i.test(alias)) {
|
|
1274
|
-
log2.warn(`Invalid alias "${alias}" in ${manifest.id}: aliases must be alphanumeric with hyphens or colons`);
|
|
1275
|
-
continue;
|
|
1276
|
-
}
|
|
1277
|
-
aliases.add(alias);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
const whitespaceAliases = generateWhitespaceAliases(manifest.id);
|
|
1281
|
-
for (const alias of whitespaceAliases) {
|
|
1282
|
-
aliases.add(alias);
|
|
1283
|
-
}
|
|
1284
|
-
return Array.from(aliases);
|
|
1285
|
-
}
|
|
1286
|
-
function checkNamespaceCollision(manifest, existing, namespace) {
|
|
1287
|
-
const existingGroup = existing.manifest.group || "";
|
|
1288
|
-
const currentGroup = manifest.group || "";
|
|
1289
|
-
if (existingGroup === currentGroup && existingGroup === namespace && existing.manifest.id === manifest.id) {
|
|
1290
|
-
throw new Error(
|
|
1291
|
-
`Command collision in group "${namespace}": "${manifest.id}" conflicts with existing "${existing.manifest.id}". Rename one of the commands to use a different ID (e.g., "${manifest.id}2" or use --alias to create a different alias).`
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
1210
|
function getSourcePriority(source) {
|
|
1296
|
-
return SOURCE_PRIORITY[source]
|
|
1297
|
-
}
|
|
1298
|
-
function checkCollision(manifest, existing, currentSource, namespace) {
|
|
1299
|
-
const existingPriority = getSourcePriority(existing.source);
|
|
1300
|
-
const currentPriority = getSourcePriority(currentSource);
|
|
1301
|
-
checkNamespaceCollision(manifest, existing, namespace);
|
|
1302
|
-
if (currentSource === "workspace" && existing.source === "workspace") {
|
|
1303
|
-
throw new Error(
|
|
1304
|
-
`Command ID collision: "${manifest.id}" is exported by multiple workspace packages. This is not allowed. Please rename one of the commands to use a different ID.`
|
|
1305
|
-
);
|
|
1306
|
-
}
|
|
1307
|
-
if (currentPriority > existingPriority) {
|
|
1308
|
-
return { shouldShadow: true };
|
|
1309
|
-
} else if (currentPriority < existingPriority) {
|
|
1310
|
-
return { shouldShadow: false };
|
|
1311
|
-
}
|
|
1312
|
-
return { shouldShadow: false };
|
|
1211
|
+
return SOURCE_PRIORITY[source] ?? 0;
|
|
1313
1212
|
}
|
|
1314
1213
|
function preflightManifests(discoveryResults, logger) {
|
|
1315
1214
|
const log2 = logger ?? platform.logger;
|
|
@@ -1324,24 +1223,17 @@ function preflightManifests(discoveryResults, logger) {
|
|
|
1324
1223
|
allowed.push(manifest);
|
|
1325
1224
|
} catch (error) {
|
|
1326
1225
|
const reason = error instanceof Error ? error.message : "Validation failed";
|
|
1327
|
-
const
|
|
1328
|
-
skipped.push({
|
|
1329
|
-
|
|
1330
|
-
source: result.source,
|
|
1331
|
-
reason
|
|
1332
|
-
});
|
|
1333
|
-
log2.warn(`Preflight skipped manifest ${manifestId}: ${reason}`);
|
|
1226
|
+
const id = manifest?.segments?.join(" ") ?? manifest?.id ?? result.packageName ?? "unknown";
|
|
1227
|
+
skipped.push({ id, source: result.source, reason });
|
|
1228
|
+
log2.warn(`Preflight skipped manifest ${id}: ${reason}`);
|
|
1334
1229
|
if (logLevel === "debug") {
|
|
1335
|
-
process.stderr.write(`[debug][preflight] skipped ${
|
|
1230
|
+
process.stderr.write(`[debug][preflight] skipped ${id} (${result.source}): ${reason}
|
|
1336
1231
|
`);
|
|
1337
1232
|
}
|
|
1338
1233
|
}
|
|
1339
1234
|
}
|
|
1340
1235
|
if (allowed.length > 0) {
|
|
1341
|
-
valid.push({
|
|
1342
|
-
...result,
|
|
1343
|
-
manifests: allowed
|
|
1344
|
-
});
|
|
1236
|
+
valid.push({ ...result, manifests: allowed });
|
|
1345
1237
|
}
|
|
1346
1238
|
}
|
|
1347
1239
|
return { valid, skipped };
|
|
@@ -1351,41 +1243,27 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
|
1351
1243
|
const registered = [];
|
|
1352
1244
|
const skipped = [];
|
|
1353
1245
|
const globalIds = /* @__PURE__ */ new Map();
|
|
1354
|
-
const globalAliases = /* @__PURE__ */ new Map();
|
|
1355
1246
|
const logLevel = getLogLevel();
|
|
1356
1247
|
let collisions = 0;
|
|
1357
1248
|
let errors = 0;
|
|
1358
1249
|
const sorted = [...discoveryResults].sort((a, b) => {
|
|
1359
|
-
|
|
1360
|
-
const priorityB = getSourcePriority(b.source);
|
|
1361
|
-
return priorityB - priorityA;
|
|
1250
|
+
return getSourcePriority(b.source) - getSourcePriority(a.source);
|
|
1362
1251
|
});
|
|
1363
1252
|
for (const result of sorted) {
|
|
1364
1253
|
for (const manifest of result.manifests) {
|
|
1365
|
-
const manifestId = manifest.
|
|
1366
|
-
const namespace = manifest.namespace || manifest.group;
|
|
1254
|
+
const manifestId = manifest.segments?.join(" ") ?? manifest.id ?? "unknown";
|
|
1367
1255
|
try {
|
|
1368
1256
|
try {
|
|
1369
1257
|
validateManifestStructure(manifest);
|
|
1370
1258
|
} catch (err) {
|
|
1371
1259
|
throw new Error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1372
1260
|
}
|
|
1373
|
-
const normalizedId = normalizeCommandId(manifest.id, namespace);
|
|
1374
|
-
if (normalizedId !== manifest.id) {
|
|
1375
|
-
log2.warn(`Command ID "${manifest.id}" normalized to "${normalizedId}"`);
|
|
1376
|
-
manifest.id = normalizedId;
|
|
1377
|
-
}
|
|
1378
|
-
const normalizedAliases = normalizeAliases(manifest, log2);
|
|
1379
|
-
if (normalizedAliases.length > 0) {
|
|
1380
|
-
manifest.aliases = normalizedAliases;
|
|
1381
|
-
}
|
|
1382
1261
|
const availability = checkRequires(manifest, {
|
|
1383
1262
|
cwd: options.cwd ?? result.pkgRoot
|
|
1384
1263
|
});
|
|
1385
1264
|
const cmd = {
|
|
1386
1265
|
manifest,
|
|
1387
1266
|
v3Manifest: manifest.manifestV2,
|
|
1388
|
-
// Extract V3 manifest from legacy field
|
|
1389
1267
|
available: availability.available,
|
|
1390
1268
|
unavailableReason: availability.available ? void 0 : availability.reason,
|
|
1391
1269
|
hint: availability.available ? void 0 : availability.hint,
|
|
@@ -1396,14 +1274,14 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
|
1396
1274
|
};
|
|
1397
1275
|
try {
|
|
1398
1276
|
const manifestModule = await import(result.manifestPath);
|
|
1399
|
-
if (
|
|
1277
|
+
if (typeof manifestModule.init === "function") {
|
|
1400
1278
|
await manifestModule.init({
|
|
1401
1279
|
cwd: result.pkgRoot,
|
|
1402
1280
|
package: result.packageName,
|
|
1403
1281
|
manifest: cmd.manifest
|
|
1404
1282
|
});
|
|
1405
1283
|
}
|
|
1406
|
-
if (
|
|
1284
|
+
if (typeof manifestModule.register === "function") {
|
|
1407
1285
|
await manifestModule.register({
|
|
1408
1286
|
registry: registry2,
|
|
1409
1287
|
command: cmd,
|
|
@@ -1411,18 +1289,25 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
|
1411
1289
|
package: result.packageName
|
|
1412
1290
|
});
|
|
1413
1291
|
}
|
|
1414
|
-
if (
|
|
1292
|
+
if (typeof manifestModule.dispose === "function") {
|
|
1415
1293
|
cmd._disposeHook = manifestModule.dispose;
|
|
1416
1294
|
}
|
|
1417
1295
|
} catch (hookError) {
|
|
1418
1296
|
const hookMsg = hookError instanceof Error ? hookError.message : String(hookError);
|
|
1419
|
-
log2.debug(`Lifecycle hooks unavailable for ${
|
|
1297
|
+
log2.debug(`Lifecycle hooks unavailable for ${manifestId}: ${hookMsg}`);
|
|
1420
1298
|
}
|
|
1421
|
-
const canonicalKey = manifest.
|
|
1299
|
+
const canonicalKey = manifest.segments.join(":");
|
|
1422
1300
|
const existing = globalIds.get(canonicalKey);
|
|
1423
1301
|
if (existing) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1302
|
+
if (existing.source === result.source && existing.source === "workspace") {
|
|
1303
|
+
collisions++;
|
|
1304
|
+
throw new Error(
|
|
1305
|
+
`Command path collision: "${manifestId}" exported by multiple workspace packages.`
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
const existPri = getSourcePriority(existing.source);
|
|
1309
|
+
const curPri = getSourcePriority(result.source);
|
|
1310
|
+
if (curPri > existPri) {
|
|
1426
1311
|
existing.shadowed = true;
|
|
1427
1312
|
globalIds.set(canonicalKey, cmd);
|
|
1428
1313
|
if (logLevel === "info" || logLevel === "debug") {
|
|
@@ -1437,63 +1322,15 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
|
1437
1322
|
} else {
|
|
1438
1323
|
globalIds.set(canonicalKey, cmd);
|
|
1439
1324
|
}
|
|
1440
|
-
const aliasesToCheck = manifest.aliases || [];
|
|
1441
|
-
for (const alias of aliasesToCheck) {
|
|
1442
|
-
const existingAlias = globalAliases.get(alias);
|
|
1443
|
-
if (existingAlias) {
|
|
1444
|
-
if (existingAlias.manifest.id === manifest.id) {
|
|
1445
|
-
continue;
|
|
1446
|
-
}
|
|
1447
|
-
const existingPriority = getSourcePriority(existingAlias.source);
|
|
1448
|
-
const currentPriority = getSourcePriority(result.source);
|
|
1449
|
-
if (currentPriority > existingPriority) {
|
|
1450
|
-
existingAlias.shadowed = true;
|
|
1451
|
-
globalAliases.set(alias, cmd);
|
|
1452
|
-
if (logLevel === "info" || logLevel === "debug") {
|
|
1453
|
-
log2.info(`Alias "${alias}" from ${result.source} shadows ${existingAlias.source} version`);
|
|
1454
|
-
}
|
|
1455
|
-
} else if (currentPriority < existingPriority) {
|
|
1456
|
-
if (logLevel === "info" || logLevel === "debug") {
|
|
1457
|
-
log2.info(`Alias "${alias}" from ${result.source} shadowed by ${existingAlias.source} version`);
|
|
1458
|
-
}
|
|
1459
|
-
continue;
|
|
1460
|
-
} else {
|
|
1461
|
-
collisions++;
|
|
1462
|
-
throw new Error(
|
|
1463
|
-
`Alias collision: "${alias}" used by both ${manifest.id} and ${existingAlias.manifest.id}. Rename one alias or use a different namespace.`
|
|
1464
|
-
);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
if (globalIds.has(alias)) {
|
|
1468
|
-
const conflictingCmd = globalIds.get(alias);
|
|
1469
|
-
const existingPriority = getSourcePriority(conflictingCmd.source);
|
|
1470
|
-
const currentPriority = getSourcePriority(result.source);
|
|
1471
|
-
if (currentPriority > existingPriority) {
|
|
1472
|
-
log2.warn(`Alias "${alias}" conflicts with command ID "${alias}". Alias will shadow command.`);
|
|
1473
|
-
globalAliases.set(alias, cmd);
|
|
1474
|
-
} else {
|
|
1475
|
-
throw new Error(
|
|
1476
|
-
`Alias "${alias}" conflicts with existing command ID "${alias}". Rename the alias or use a different name.`
|
|
1477
|
-
);
|
|
1478
|
-
}
|
|
1479
|
-
} else {
|
|
1480
|
-
globalAliases.set(alias, cmd);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
1325
|
if (!cmd.shadowed) {
|
|
1484
1326
|
registry2.registerManifest(cmd);
|
|
1327
|
+
registered.push(cmd);
|
|
1485
1328
|
}
|
|
1486
|
-
registered.push(cmd);
|
|
1487
1329
|
} catch (error) {
|
|
1488
1330
|
errors++;
|
|
1489
1331
|
const reason = error instanceof Error ? error.message : String(error);
|
|
1490
|
-
skipped.push({
|
|
1491
|
-
id: manifestId,
|
|
1492
|
-
source: result.source,
|
|
1493
|
-
reason
|
|
1494
|
-
});
|
|
1332
|
+
skipped.push({ id: manifestId, source: result.source, reason });
|
|
1495
1333
|
log2.error(`Skipped manifest ${manifestId} (${result.source}): ${reason}`);
|
|
1496
|
-
continue;
|
|
1497
1334
|
}
|
|
1498
1335
|
}
|
|
1499
1336
|
}
|
|
@@ -1503,24 +1340,18 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
|
|
|
1503
1340
|
log2.warn(` \u2022 ${skip.id} [${skip.source}] \u2192 ${skip.reason}`);
|
|
1504
1341
|
}
|
|
1505
1342
|
}
|
|
1506
|
-
return {
|
|
1507
|
-
registered,
|
|
1508
|
-
skipped,
|
|
1509
|
-
collisions,
|
|
1510
|
-
errors
|
|
1511
|
-
};
|
|
1343
|
+
return { registered, skipped, collisions, errors };
|
|
1512
1344
|
}
|
|
1513
1345
|
async function disposeAllPlugins(registry2, logger) {
|
|
1514
1346
|
const log2 = logger ?? platform.logger;
|
|
1515
|
-
const manifests = registry2.
|
|
1347
|
+
const manifests = registry2.listCommands();
|
|
1516
1348
|
const disposePromises = [];
|
|
1517
1349
|
for (const cmd of manifests) {
|
|
1518
1350
|
const disposeHook = cmd._disposeHook;
|
|
1519
|
-
if (
|
|
1351
|
+
if (typeof disposeHook === "function") {
|
|
1520
1352
|
disposePromises.push(
|
|
1521
1353
|
Promise.resolve(disposeHook()).catch((err) => {
|
|
1522
|
-
|
|
1523
|
-
log2.warn(`Dispose hook failed for ${cmd.manifest.id}: ${errMsg}`);
|
|
1354
|
+
log2.warn(`Dispose hook failed for ${cmd.manifest.segments.join(" ")}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1524
1355
|
})
|
|
1525
1356
|
);
|
|
1526
1357
|
}
|
|
@@ -1551,421 +1382,512 @@ async function runCommand(cmd, ctx, argv, flags) {
|
|
|
1551
1382
|
return typeof result === "number" ? result : 0;
|
|
1552
1383
|
}
|
|
1553
1384
|
|
|
1554
|
-
// src/registry/
|
|
1555
|
-
function
|
|
1556
|
-
return {
|
|
1557
|
-
name: registered.manifest.id,
|
|
1558
|
-
category: registered.manifest.group,
|
|
1559
|
-
describe: registered.manifest.describe,
|
|
1560
|
-
longDescription: registered.manifest.longDescription,
|
|
1561
|
-
aliases: registered.manifest.aliases || [],
|
|
1562
|
-
flags: registered.manifest.flags,
|
|
1563
|
-
examples: registered.manifest.examples,
|
|
1564
|
-
async run() {
|
|
1565
|
-
throw new Error(`Command ${registered.manifest.id} should be executed via plugin-executor, not via legacy run() path.`);
|
|
1566
|
-
}
|
|
1567
|
-
};
|
|
1385
|
+
// src/registry/trie-router.ts
|
|
1386
|
+
function makeNode() {
|
|
1387
|
+
return { children: /* @__PURE__ */ new Map() };
|
|
1568
1388
|
}
|
|
1569
|
-
function
|
|
1570
|
-
const
|
|
1571
|
-
|
|
1572
|
-
|
|
1389
|
+
function levenshtein(a, b) {
|
|
1390
|
+
const m = a.length;
|
|
1391
|
+
const n = b.length;
|
|
1392
|
+
const dp = Array.from(
|
|
1393
|
+
{ length: m + 1 },
|
|
1394
|
+
(_, i) => Array.from({ length: n + 1 }, (__, j) => i === 0 ? j : j === 0 ? i : 0)
|
|
1395
|
+
);
|
|
1396
|
+
for (let i = 1; i <= m; i++) {
|
|
1397
|
+
for (let j = 1; j <= n; j++) {
|
|
1398
|
+
if (a[i - 1] === b[j - 1]) {
|
|
1399
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
1400
|
+
} else {
|
|
1401
|
+
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1573
1404
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1405
|
+
return dp[m][n];
|
|
1406
|
+
}
|
|
1407
|
+
function findDeep(node, targetSegments, prefix) {
|
|
1408
|
+
const results = [];
|
|
1409
|
+
function dfs(current, path3) {
|
|
1410
|
+
if (current.command) {
|
|
1411
|
+
if (targetSegments.length <= path3.length) {
|
|
1412
|
+
const tail = path3.slice(path3.length - targetSegments.length);
|
|
1413
|
+
const matches = tail.every((seg, i) => seg === targetSegments[i]);
|
|
1414
|
+
if (matches) {
|
|
1415
|
+
results.push(path3.join(" "));
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
for (const [key, child] of current.children) {
|
|
1420
|
+
dfs(child, [...path3, key]);
|
|
1421
|
+
}
|
|
1576
1422
|
}
|
|
1577
|
-
|
|
1423
|
+
dfs(node, [...prefix]);
|
|
1424
|
+
return results;
|
|
1578
1425
|
}
|
|
1579
|
-
var
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
this.
|
|
1426
|
+
var TrieRouter = class {
|
|
1427
|
+
root = makeNode();
|
|
1428
|
+
/**
|
|
1429
|
+
* Insert a plugin command into the trie.
|
|
1430
|
+
*
|
|
1431
|
+
* Enforces 1-manifest-1-namespace: the first packageName to register any command
|
|
1432
|
+
* under a top-level group becomes the owner. Subsequent packages with a different
|
|
1433
|
+
* packageName are rejected (collides: true) — the caller is responsible for warning
|
|
1434
|
+
* and marking the command as shadowed.
|
|
1435
|
+
*
|
|
1436
|
+
* Returns { collides: false } on success, or { collides: true, ownerPackage } on conflict.
|
|
1437
|
+
*/
|
|
1438
|
+
insertCommand(segments, cmd) {
|
|
1439
|
+
if (segments.length === 0) {
|
|
1440
|
+
return { collides: false };
|
|
1441
|
+
}
|
|
1442
|
+
const incoming = cmd.packageName ?? "__unknown__";
|
|
1443
|
+
const topSeg = segments[0];
|
|
1444
|
+
if (!this.root.children.has(topSeg)) {
|
|
1445
|
+
this.root.children.set(topSeg, makeNode());
|
|
1446
|
+
}
|
|
1447
|
+
const groupNode = this.root.children.get(topSeg);
|
|
1448
|
+
if (groupNode.ownerPackage === void 0) {
|
|
1449
|
+
groupNode.ownerPackage = incoming;
|
|
1450
|
+
} else if (groupNode.ownerPackage !== incoming) {
|
|
1451
|
+
return { collides: true, ownerPackage: groupNode.ownerPackage };
|
|
1452
|
+
}
|
|
1453
|
+
let node = groupNode;
|
|
1454
|
+
for (const seg of segments.slice(1)) {
|
|
1455
|
+
if (!node.children.has(seg)) {
|
|
1456
|
+
node.children.set(seg, makeNode());
|
|
1457
|
+
}
|
|
1458
|
+
node = node.children.get(seg);
|
|
1599
1459
|
}
|
|
1460
|
+
node.command = cmd;
|
|
1461
|
+
return { collides: false };
|
|
1600
1462
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
this.
|
|
1463
|
+
insertSystemCommand(cmd) {
|
|
1464
|
+
const segments = [cmd.name];
|
|
1465
|
+
let node = this.root;
|
|
1466
|
+
for (const seg of segments) {
|
|
1467
|
+
if (!node.children.has(seg)) {
|
|
1468
|
+
node.children.set(seg, makeNode());
|
|
1469
|
+
}
|
|
1470
|
+
node = node.children.get(seg);
|
|
1471
|
+
}
|
|
1472
|
+
node.systemCommand = cmd;
|
|
1473
|
+
for (const alias of cmd.aliases ?? []) {
|
|
1474
|
+
let aliasNode = this.root;
|
|
1475
|
+
if (!aliasNode.children.has(alias)) {
|
|
1476
|
+
aliasNode.children.set(alias, makeNode());
|
|
1477
|
+
}
|
|
1478
|
+
aliasNode = aliasNode.children.get(alias);
|
|
1479
|
+
aliasNode.systemCommand = cmd;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
insertSystemGroup(group) {
|
|
1483
|
+
let groupNode = this.root;
|
|
1484
|
+
if (!groupNode.children.has(group.name)) {
|
|
1485
|
+
groupNode.children.set(group.name, makeNode());
|
|
1486
|
+
}
|
|
1487
|
+
groupNode = groupNode.children.get(group.name);
|
|
1488
|
+
groupNode.systemGroup = group;
|
|
1489
|
+
groupNode.groupDescribe = group.describe;
|
|
1604
1490
|
for (const cmd of group.commands) {
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1491
|
+
let cmdNode = groupNode;
|
|
1492
|
+
if (!cmdNode.children.has(cmd.name)) {
|
|
1493
|
+
cmdNode.children.set(cmd.name, makeNode());
|
|
1494
|
+
}
|
|
1495
|
+
cmdNode = cmdNode.children.get(cmd.name);
|
|
1496
|
+
cmdNode.systemCommand = cmd;
|
|
1497
|
+
for (const alias of cmd.aliases ?? []) {
|
|
1498
|
+
let aliasNode = groupNode;
|
|
1499
|
+
if (!aliasNode.children.has(alias)) {
|
|
1500
|
+
aliasNode.children.set(alias, makeNode());
|
|
1501
|
+
}
|
|
1502
|
+
aliasNode = aliasNode.children.get(alias);
|
|
1503
|
+
aliasNode.systemCommand = cmd;
|
|
1612
1504
|
}
|
|
1613
1505
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}
|
|
1506
|
+
for (const sub of group.subgroups ?? []) {
|
|
1507
|
+
let subNode = groupNode;
|
|
1508
|
+
if (!subNode.children.has(sub.name)) {
|
|
1509
|
+
subNode.children.set(sub.name, makeNode());
|
|
1510
|
+
}
|
|
1511
|
+
subNode = subNode.children.get(sub.name);
|
|
1512
|
+
subNode.systemGroup = sub;
|
|
1513
|
+
subNode.groupDescribe = sub.describe;
|
|
1514
|
+
for (const cmd of sub.commands) {
|
|
1515
|
+
let cmdNode = subNode;
|
|
1516
|
+
if (!cmdNode.children.has(cmd.name)) {
|
|
1517
|
+
cmdNode.children.set(cmd.name, makeNode());
|
|
1627
1518
|
}
|
|
1519
|
+
cmdNode = cmdNode.children.get(cmd.name);
|
|
1520
|
+
cmdNode.systemCommand = cmd;
|
|
1628
1521
|
}
|
|
1629
1522
|
}
|
|
1630
1523
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1524
|
+
getGroupDescribe(segments) {
|
|
1525
|
+
let node = this.root;
|
|
1526
|
+
for (const seg of segments) {
|
|
1527
|
+
const child = node.children.get(seg);
|
|
1528
|
+
if (!child) {
|
|
1529
|
+
return void 0;
|
|
1530
|
+
}
|
|
1531
|
+
node = child;
|
|
1638
1532
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1533
|
+
return node.groupDescribe;
|
|
1534
|
+
}
|
|
1535
|
+
setGroupDescribe(segments, describe) {
|
|
1536
|
+
let node = this.root;
|
|
1537
|
+
for (const seg of segments) {
|
|
1538
|
+
if (!node.children.has(seg)) {
|
|
1539
|
+
node.children.set(seg, makeNode());
|
|
1644
1540
|
}
|
|
1541
|
+
node = node.children.get(seg);
|
|
1645
1542
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
this.manifests.set(cmd.manifest.id, cmd);
|
|
1649
|
-
if (cmd.shadowed) {
|
|
1650
|
-
return;
|
|
1543
|
+
if (!node.groupDescribe) {
|
|
1544
|
+
node.groupDescribe = describe;
|
|
1651
1545
|
}
|
|
1652
|
-
this._registerPluginAliases(cmd, canonicalId, collisionAliases);
|
|
1653
|
-
const commandAdapter = manifestToCommand(cmd);
|
|
1654
|
-
this.byName.set(canonicalId, commandAdapter);
|
|
1655
|
-
const spaceCanonical = canonicalId.replace(/:/g, " ");
|
|
1656
|
-
this.byName.set(spaceCanonical, commandAdapter);
|
|
1657
|
-
this._registerSyntheticGroups(cmd, commandAdapter);
|
|
1658
1546
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1547
|
+
resolve(tokens) {
|
|
1548
|
+
if (tokens.length === 0) {
|
|
1549
|
+
return {
|
|
1550
|
+
type: "group",
|
|
1551
|
+
segments: [],
|
|
1552
|
+
childKeys: [...this.root.children.keys()]
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
let node = this.root;
|
|
1556
|
+
let i = 0;
|
|
1557
|
+
while (i < tokens.length) {
|
|
1558
|
+
const tok = tokens[i];
|
|
1559
|
+
const child = node.children.get(tok);
|
|
1560
|
+
if (!child) {
|
|
1561
|
+
return this._notFound(node, tokens, i);
|
|
1562
|
+
}
|
|
1563
|
+
node = child;
|
|
1564
|
+
i++;
|
|
1565
|
+
if (node.command && i < tokens.length) {
|
|
1566
|
+
return { type: "command", command: node.command, rest: tokens.slice(i) };
|
|
1567
|
+
}
|
|
1568
|
+
if (node.systemCommand && i < tokens.length) {
|
|
1569
|
+
return { type: "system-cmd", cmd: node.systemCommand, rest: tokens.slice(i) };
|
|
1570
|
+
}
|
|
1666
1571
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
return space;
|
|
1572
|
+
if (node.command) {
|
|
1573
|
+
return { type: "command", command: node.command, rest: [] };
|
|
1670
1574
|
}
|
|
1671
|
-
|
|
1575
|
+
if (node.systemCommand) {
|
|
1576
|
+
return { type: "system-cmd", cmd: node.systemCommand, rest: [] };
|
|
1577
|
+
}
|
|
1578
|
+
if (node.systemGroup) {
|
|
1579
|
+
return { type: "system-group", group: node.systemGroup, rest: [] };
|
|
1580
|
+
}
|
|
1581
|
+
if (node.children.size > 0 || node.groupDescribe !== void 0) {
|
|
1582
|
+
const segs = tokens.slice(0, i);
|
|
1583
|
+
return {
|
|
1584
|
+
type: "group",
|
|
1585
|
+
segments: segs,
|
|
1586
|
+
describe: node.groupDescribe,
|
|
1587
|
+
childKeys: [...node.children.keys()]
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
return { type: "not-found", input: tokens, suggestions: [] };
|
|
1672
1591
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
* - 2-part shorthand ("marketplace:list", "marketplace list")
|
|
1683
|
-
* - manifest.aliases[] (user-defined aliases, except collisions)
|
|
1684
|
-
*/
|
|
1685
|
-
_registerPluginAliases(cmd, canonicalId, collisionAliases) {
|
|
1686
|
-
const { id, group, subgroup } = cmd.manifest;
|
|
1687
|
-
const register = (key) => {
|
|
1688
|
-
if (!this.systemCommands.has(key) && !this.systemCommands.has(key.replace(/:/g, " "))) {
|
|
1689
|
-
if (!this.pluginAliases.has(key)) {
|
|
1690
|
-
this.pluginAliases.set(key, canonicalId);
|
|
1691
|
-
this.manifests.set(key, cmd);
|
|
1692
|
-
}
|
|
1592
|
+
complete(tokens) {
|
|
1593
|
+
if (tokens.length === 0) {
|
|
1594
|
+
return [...this.root.children.keys()];
|
|
1595
|
+
}
|
|
1596
|
+
let node = this.root;
|
|
1597
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
1598
|
+
const child = node.children.get(tokens[i]);
|
|
1599
|
+
if (!child) {
|
|
1600
|
+
return [];
|
|
1693
1601
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
register(twoPartColon);
|
|
1703
|
-
register(twoPartSpace);
|
|
1704
|
-
this.manifests.set(twoPartColon, cmd);
|
|
1705
|
-
this.manifests.set(twoPartSpace, cmd);
|
|
1706
|
-
const fullPath = `${group} ${subgroup} ${id}`;
|
|
1707
|
-
this.pluginAliases.set(fullPath, canonicalId);
|
|
1708
|
-
this.manifests.set(fullPath, cmd);
|
|
1709
|
-
const colonPath = `${group}:${subgroup}:${id}`;
|
|
1710
|
-
this.pluginAliases.set(colonPath, canonicalId);
|
|
1711
|
-
this.manifests.set(colonPath, cmd);
|
|
1712
|
-
} else if (group) {
|
|
1713
|
-
const colonName = `${group}:${id}`;
|
|
1714
|
-
const spaceName = `${group} ${id}`;
|
|
1715
|
-
this.pluginAliases.set(colonName, canonicalId);
|
|
1716
|
-
this.pluginAliases.set(spaceName, canonicalId);
|
|
1717
|
-
this.manifests.set(colonName, cmd);
|
|
1718
|
-
this.manifests.set(spaceName, cmd);
|
|
1719
|
-
}
|
|
1720
|
-
register(id);
|
|
1721
|
-
for (const alias of cmd.manifest.aliases || []) {
|
|
1722
|
-
if (!collisionAliases.has(alias)) {
|
|
1723
|
-
register(alias);
|
|
1602
|
+
node = child;
|
|
1603
|
+
}
|
|
1604
|
+
const partial = tokens[tokens.length - 1] ?? "";
|
|
1605
|
+
const results = [];
|
|
1606
|
+
const prefix = tokens.slice(0, tokens.length - 1);
|
|
1607
|
+
for (const [key] of node.children) {
|
|
1608
|
+
if (key.startsWith(partial)) {
|
|
1609
|
+
results.push([...prefix, key].join(" "));
|
|
1724
1610
|
}
|
|
1725
1611
|
}
|
|
1612
|
+
return results;
|
|
1726
1613
|
}
|
|
1727
1614
|
/**
|
|
1728
|
-
*
|
|
1615
|
+
* List all leaf plugin commands under a given node path.
|
|
1729
1616
|
*/
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
const
|
|
1734
|
-
if (!
|
|
1735
|
-
|
|
1736
|
-
name: subgroupKey,
|
|
1737
|
-
describe: subgroup,
|
|
1738
|
-
commands: []
|
|
1739
|
-
});
|
|
1740
|
-
this.byName.set(subgroupKey, this.groups.get(subgroupKey));
|
|
1617
|
+
listUnder(segments) {
|
|
1618
|
+
let node = this.root;
|
|
1619
|
+
for (const seg of segments) {
|
|
1620
|
+
const child = node.children.get(seg);
|
|
1621
|
+
if (!child) {
|
|
1622
|
+
return [];
|
|
1741
1623
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1624
|
+
node = child;
|
|
1625
|
+
}
|
|
1626
|
+
const results = [];
|
|
1627
|
+
this._collectCommands(node, results);
|
|
1628
|
+
return results;
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Get exact command at path.
|
|
1632
|
+
*/
|
|
1633
|
+
getAt(segments) {
|
|
1634
|
+
let node = this.root;
|
|
1635
|
+
for (const seg of segments) {
|
|
1636
|
+
const child = node.children.get(seg);
|
|
1637
|
+
if (!child) {
|
|
1638
|
+
return null;
|
|
1746
1639
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1640
|
+
node = child;
|
|
1641
|
+
}
|
|
1642
|
+
return node.command ?? null;
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* List top-level system groups and standalone system commands (depth=1 nodes).
|
|
1646
|
+
*/
|
|
1647
|
+
listSystemTopLevel() {
|
|
1648
|
+
const results = [];
|
|
1649
|
+
for (const [name, node] of this.root.children) {
|
|
1650
|
+
if (node.systemGroup) {
|
|
1651
|
+
results.push({ name, describe: node.systemGroup.describe, isGroup: true });
|
|
1652
|
+
} else if (node.systemCommand) {
|
|
1653
|
+
results.push({ name, describe: node.systemCommand.describe, isGroup: false });
|
|
1750
1654
|
}
|
|
1751
|
-
const fullPath = `${group} ${subgroup} ${id}`;
|
|
1752
|
-
this.byName.set(fullPath, commandAdapter);
|
|
1753
|
-
} else if (group) {
|
|
1754
|
-
const fullName = `${group} ${id}`;
|
|
1755
|
-
const colonName = `${group}:${id}`;
|
|
1756
|
-
this.byName.set(fullName, commandAdapter);
|
|
1757
|
-
this.byName.set(colonName, commandAdapter);
|
|
1758
1655
|
}
|
|
1656
|
+
return results;
|
|
1759
1657
|
}
|
|
1760
|
-
// ─── Resolution ──────────────────────────────────────────────────────────
|
|
1761
1658
|
/**
|
|
1762
|
-
*
|
|
1763
|
-
*
|
|
1764
|
-
* Returns canonicalId if found in plugin aliases, undefined otherwise.
|
|
1659
|
+
* Collect all plugin commands reachable from node.
|
|
1765
1660
|
*/
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1661
|
+
listAll() {
|
|
1662
|
+
const results = [];
|
|
1663
|
+
this._collectCommands(this.root, results);
|
|
1664
|
+
return results;
|
|
1665
|
+
}
|
|
1666
|
+
_collectCommands(node, out) {
|
|
1667
|
+
if (node.command) {
|
|
1668
|
+
out.push(node.command);
|
|
1769
1669
|
}
|
|
1770
|
-
const
|
|
1771
|
-
|
|
1772
|
-
return this.pluginAliases.get(converted);
|
|
1670
|
+
for (const child of node.children.values()) {
|
|
1671
|
+
this._collectCommands(child, out);
|
|
1773
1672
|
}
|
|
1774
|
-
return void 0;
|
|
1775
1673
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1674
|
+
_notFound(node, tokens, failIdx) {
|
|
1675
|
+
const failToken = tokens[failIdx];
|
|
1676
|
+
const prefix = tokens.slice(0, failIdx);
|
|
1677
|
+
const siblings = [...node.children.keys()];
|
|
1678
|
+
const fuzzy = siblings.map((s) => ({ s, d: levenshtein(failToken, s) })).filter(({ d }) => d <= 2).sort((a, b) => a.d - b.d).map(({ s }) => [...prefix, s, ...tokens.slice(failIdx + 1)].join(" "));
|
|
1679
|
+
if (fuzzy.length === 1) {
|
|
1680
|
+
return { type: "not-found", input: tokens, suggestions: fuzzy };
|
|
1681
|
+
}
|
|
1682
|
+
if (fuzzy.length > 1) {
|
|
1683
|
+
return { type: "ambiguous", input: tokens, candidates: fuzzy };
|
|
1684
|
+
}
|
|
1685
|
+
const remaining = tokens.slice(failIdx);
|
|
1686
|
+
const deep = findDeep(node, remaining, prefix);
|
|
1687
|
+
if (deep.length === 0) {
|
|
1688
|
+
return { type: "not-found", input: tokens, suggestions: [] };
|
|
1689
|
+
}
|
|
1690
|
+
if (deep.length === 1) {
|
|
1691
|
+
return { type: "not-found", input: tokens, suggestions: deep };
|
|
1692
|
+
}
|
|
1693
|
+
return { type: "ambiguous", input: tokens, candidates: deep };
|
|
1779
1694
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
// src/registry/archetype-flags.ts
|
|
1698
|
+
var outputFlag = { name: "output", type: "string", choices: ["json", "table", "csv"], description: "Output format" };
|
|
1699
|
+
var dryRunFlag = { name: "dry-run", type: "boolean", description: "Show what would happen without executing" };
|
|
1700
|
+
var yesFlag = { name: "yes", type: "boolean", alias: "y", description: "Skip confirmation prompts" };
|
|
1701
|
+
var waitFlag = { name: "wait", type: "boolean", description: "Block until execution completes" };
|
|
1702
|
+
var watchFlag = { name: "watch", type: "boolean", description: "Stream events as NDJSON" };
|
|
1703
|
+
var timeoutFlag = { name: "timeout", type: "string", description: "Max wait time (e.g. 30s, 5m)" };
|
|
1704
|
+
var limitFlag = { name: "limit", type: "number", description: "Max results to return" };
|
|
1705
|
+
var offsetFlag = { name: "offset", type: "number", description: "Offset for pagination" };
|
|
1706
|
+
var formatFlag = { name: "format", type: "string", choices: ["json", "text", "md"], description: "Output format" };
|
|
1707
|
+
var streamFlag = { name: "stream", type: "boolean", description: "Stream output progressively" };
|
|
1708
|
+
var schemaFlag = {
|
|
1709
|
+
name: "schema",
|
|
1710
|
+
type: "boolean",
|
|
1711
|
+
description: "Output JSON Schema for this command (flags, types, examples)"
|
|
1712
|
+
};
|
|
1713
|
+
var ARCHETYPE_FLAGS = {
|
|
1714
|
+
read: [outputFlag, limitFlag, offsetFlag],
|
|
1715
|
+
mutate: [outputFlag, dryRunFlag, yesFlag],
|
|
1716
|
+
execute: [outputFlag, waitFlag, watchFlag, timeoutFlag, yesFlag],
|
|
1717
|
+
analyze: [outputFlag, formatFlag, streamFlag]
|
|
1718
|
+
};
|
|
1719
|
+
function getArchetypeFlags(operationType, existing = []) {
|
|
1720
|
+
const toInject = ARCHETYPE_FLAGS[operationType] ?? [];
|
|
1721
|
+
const existingNames = new Set(existing.map((f) => f.name));
|
|
1722
|
+
return toInject.filter((f) => !existingNames.has(f.name));
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/registry/service.ts
|
|
1726
|
+
var RESERVED_NAMESPACES = /* @__PURE__ */ new Set(["__complete", "__internal"]);
|
|
1727
|
+
var MAX_PATH_DEPTH = 6;
|
|
1728
|
+
function validateSegments(segs) {
|
|
1729
|
+
if (segs.length === 0) {
|
|
1730
|
+
return "empty path \u2014 segments must not be empty";
|
|
1782
1731
|
}
|
|
1783
|
-
|
|
1784
|
-
return
|
|
1732
|
+
if (segs.length > MAX_PATH_DEPTH) {
|
|
1733
|
+
return `path too deep: ${segs.length} segments (max ${MAX_PATH_DEPTH})`;
|
|
1785
1734
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1735
|
+
if (RESERVED_NAMESPACES.has(segs[0])) {
|
|
1736
|
+
return `"${segs[0]}" is a reserved namespace`;
|
|
1737
|
+
}
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
var noopLogger = {
|
|
1741
|
+
debug: () => {
|
|
1742
|
+
},
|
|
1743
|
+
info: () => {
|
|
1744
|
+
},
|
|
1745
|
+
warn: () => {
|
|
1746
|
+
},
|
|
1747
|
+
error: () => {
|
|
1748
|
+
},
|
|
1749
|
+
child: () => noopLogger
|
|
1750
|
+
};
|
|
1751
|
+
var TrieBackedRegistry = class {
|
|
1752
|
+
systemRouter = new TrieRouter();
|
|
1753
|
+
pluginRouter = new TrieRouter();
|
|
1754
|
+
partial = false;
|
|
1755
|
+
logger = noopLogger;
|
|
1756
|
+
// ─── Logger ────────────────────────────────────────────────────────────────
|
|
1757
|
+
setLogger(logger) {
|
|
1758
|
+
this.logger = logger;
|
|
1792
1759
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1760
|
+
// ─── Registration ──────────────────────────────────────────────────────────
|
|
1761
|
+
register(cmd) {
|
|
1762
|
+
this.systemRouter.insertSystemCommand(cmd);
|
|
1795
1763
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
* Returns type='plugin' for plugin commands (subprocess execution).
|
|
1802
|
-
*/
|
|
1803
|
-
getWithType(nameOrPath) {
|
|
1804
|
-
const normalized = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
|
|
1805
|
-
if (this.systemCommands.has(normalized)) {
|
|
1806
|
-
return { cmd: this.systemCommands.get(normalized), type: "system" };
|
|
1807
|
-
}
|
|
1808
|
-
const colonVariant = normalized.replace(/ /g, ":");
|
|
1809
|
-
if (this.systemCommands.has(colonVariant)) {
|
|
1810
|
-
return { cmd: this.systemCommands.get(colonVariant), type: "system" };
|
|
1811
|
-
}
|
|
1812
|
-
if (this.groups.has(normalized)) {
|
|
1813
|
-
return { cmd: this.groups.get(normalized), type: "system" };
|
|
1814
|
-
}
|
|
1815
|
-
const canonicalId = this.resolveToCanonical(normalized);
|
|
1816
|
-
if (canonicalId) {
|
|
1817
|
-
const pluginCmd = this.pluginByCanonical.get(canonicalId);
|
|
1818
|
-
if (pluginCmd && !pluginCmd.shadowed) {
|
|
1819
|
-
const cmd2 = this.byName.get(canonicalId) ?? this.byName.get(normalized);
|
|
1820
|
-
if (cmd2) {
|
|
1821
|
-
return { cmd: cmd2, type: "plugin" };
|
|
1822
|
-
}
|
|
1764
|
+
registerGroup(group) {
|
|
1765
|
+
this.systemRouter.insertSystemGroup(group);
|
|
1766
|
+
if (group.subgroups) {
|
|
1767
|
+
for (const sub of group.subgroups) {
|
|
1768
|
+
this.systemRouter.setGroupDescribe([group.name, sub.name], sub.describe ?? "");
|
|
1823
1769
|
}
|
|
1824
1770
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
if ("commands" in cmd) {
|
|
1830
|
-
return { cmd, type: "system" };
|
|
1771
|
+
}
|
|
1772
|
+
registerManifest(cmd) {
|
|
1773
|
+
if (cmd.manifest._synthetic) {
|
|
1774
|
+
return;
|
|
1831
1775
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1776
|
+
const segs = cmd.manifest.segments;
|
|
1777
|
+
const pkg = cmd.packageName ?? "unknown";
|
|
1778
|
+
const validationError = validateSegments(segs);
|
|
1779
|
+
if (validationError) {
|
|
1780
|
+
this.logger.warn(`[registry] Plugin "${pkg}" skipped: ${validationError} (path: "${segs.join(" ")}")`);
|
|
1781
|
+
cmd.shadowed = true;
|
|
1782
|
+
return;
|
|
1834
1783
|
}
|
|
1835
|
-
const
|
|
1836
|
-
if (
|
|
1837
|
-
|
|
1784
|
+
const sysResult = this.systemRouter.resolve([...segs]);
|
|
1785
|
+
if (sysResult.type === "system-cmd" || sysResult.type === "system-group") {
|
|
1786
|
+
this.logger.warn(`[registry] Plugin "${pkg}" tried to register "${segs.join(" ")}" but it collides with a system command. Command will be shadowed.`);
|
|
1787
|
+
cmd.shadowed = true;
|
|
1788
|
+
return;
|
|
1838
1789
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
if (this.byName.has(nameOrPath)) {
|
|
1844
|
-
return this.byName.get(nameOrPath);
|
|
1845
|
-
}
|
|
1846
|
-
if (nameOrPath.includes(":")) {
|
|
1847
|
-
const spaceKey = nameOrPath.replace(/:/g, " ");
|
|
1848
|
-
if (this.byName.has(spaceKey)) {
|
|
1849
|
-
return this.byName.get(spaceKey);
|
|
1850
|
-
}
|
|
1851
|
-
const parts = nameOrPath.split(":");
|
|
1852
|
-
if (parts.length === 2) {
|
|
1853
|
-
const spaceKey2 = parts.join(" ");
|
|
1854
|
-
if (this.byName.has(spaceKey2)) {
|
|
1855
|
-
return this.byName.get(spaceKey2);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1790
|
+
if (cmd.manifest.operationType) {
|
|
1791
|
+
const injected = getArchetypeFlags(cmd.manifest.operationType, cmd.manifest.flags ?? []);
|
|
1792
|
+
if (injected.length > 0) {
|
|
1793
|
+
cmd.manifest.flags = [...cmd.manifest.flags ?? [], ...injected];
|
|
1858
1794
|
}
|
|
1859
1795
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
return this.byName.get(key);
|
|
1796
|
+
if (cmd.manifest.operationType && !cmd.manifest.flags?.some((f) => f.name === "schema")) {
|
|
1797
|
+
cmd.manifest.flags = [...cmd.manifest.flags ?? [], schemaFlag];
|
|
1863
1798
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1799
|
+
const result = this.pluginRouter.insertCommand(segs, cmd);
|
|
1800
|
+
if (result.collides) {
|
|
1801
|
+
this.logger.warn(
|
|
1802
|
+
`[registry] Plugin "${pkg}" tried to register "${segs.join(" ")}" but namespace "${segs[0]}" is owned by "${result.ownerPackage ?? "unknown"}". Command will be shadowed.`
|
|
1803
|
+
);
|
|
1804
|
+
cmd.shadowed = true;
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
for (const alias of cmd.manifest.aliases ?? []) {
|
|
1808
|
+
const aliasSegs = alias.trim().split(/\s+/).filter(Boolean);
|
|
1809
|
+
if (aliasSegs.length === 0) {
|
|
1810
|
+
continue;
|
|
1867
1811
|
}
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1812
|
+
const aliasValidationError = validateSegments(aliasSegs);
|
|
1813
|
+
if (aliasValidationError) {
|
|
1814
|
+
this.logger.warn(`[registry] Plugin "${pkg}" alias "${alias}" skipped: ${aliasValidationError}`);
|
|
1815
|
+
continue;
|
|
1872
1816
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1817
|
+
const aliasSysResult = this.systemRouter.resolve([...aliasSegs]);
|
|
1818
|
+
if (aliasSysResult.type === "system-cmd" || aliasSysResult.type === "system-group") {
|
|
1819
|
+
this.logger.warn(`[registry] Plugin "${pkg}" alias "${alias}" collides with a system command and will be skipped.`);
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
const aliasResult = this.pluginRouter.insertCommand(aliasSegs, cmd);
|
|
1823
|
+
if (aliasResult.collides) {
|
|
1824
|
+
this.logger.warn(
|
|
1825
|
+
`[registry] Plugin "${pkg}" alias "${alias}" conflicts with namespace "${aliasSegs[0]}" owned by "${aliasResult.ownerPackage ?? "unknown"}". Alias skipped.`
|
|
1826
|
+
);
|
|
1878
1827
|
}
|
|
1879
1828
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
const fullName = `${group.name} ${cmdName}`;
|
|
1886
|
-
if (this.byName.has(fullName)) {
|
|
1887
|
-
return this.byName.get(fullName);
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1829
|
+
const v3 = cmd.manifest.manifestV2;
|
|
1830
|
+
if (v3?.cli?.groupMeta) {
|
|
1831
|
+
for (const meta of v3.cli.groupMeta) {
|
|
1832
|
+
const metaSegs = meta.path.trim().split(/\s+/).filter(Boolean);
|
|
1833
|
+
this.pluginRouter.setGroupDescribe(metaSegs, meta.describe);
|
|
1890
1834
|
}
|
|
1891
1835
|
}
|
|
1892
|
-
return void 0;
|
|
1893
1836
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1837
|
+
// ─── Resolution ────────────────────────────────────────────────────────────
|
|
1838
|
+
resolve(tokens) {
|
|
1839
|
+
if (tokens.length > 0) {
|
|
1840
|
+
const sysResult = this.systemRouter.resolve(tokens);
|
|
1841
|
+
if (sysResult.type !== "not-found") {
|
|
1842
|
+
return sysResult;
|
|
1899
1843
|
}
|
|
1900
1844
|
}
|
|
1901
|
-
return
|
|
1845
|
+
return this.pluginRouter.resolve(tokens);
|
|
1902
1846
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1847
|
+
// ─── Tab completion ─────────────────────────────────────────────────────────
|
|
1848
|
+
complete(tokens) {
|
|
1849
|
+
const sysCompletions = this.systemRouter.complete(tokens);
|
|
1850
|
+
const pluginCompletions = this.pluginRouter.complete(tokens);
|
|
1851
|
+
return [.../* @__PURE__ */ new Set([...sysCompletions, ...pluginCompletions])];
|
|
1905
1852
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
if (group.name === prefix || group.name.startsWith(prefix + ":")) {
|
|
1910
|
-
result.push(group);
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
return result;
|
|
1853
|
+
// ─── Query ──────────────────────────────────────────────────────────────────
|
|
1854
|
+
listSystemTopLevel() {
|
|
1855
|
+
return this.systemRouter.listSystemTopLevel();
|
|
1914
1856
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
for (const group of this.groups.values()) {
|
|
1918
|
-
if (group.name === prefix || group.name.startsWith(prefix + ":")) {
|
|
1919
|
-
result.push(...group.commands);
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
return result;
|
|
1923
|
-
}
|
|
1924
|
-
listProductGroups() {
|
|
1925
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1926
|
-
for (const cmd of this.listManifests()) {
|
|
1927
|
-
const groupName = cmd.manifest.group;
|
|
1928
|
-
if (!groups.has(groupName)) {
|
|
1929
|
-
groups.set(groupName, {
|
|
1930
|
-
name: groupName,
|
|
1931
|
-
describe: cmd.manifest.group,
|
|
1932
|
-
commands: []
|
|
1933
|
-
});
|
|
1934
|
-
}
|
|
1935
|
-
groups.get(groupName).commands.push(cmd);
|
|
1936
|
-
}
|
|
1937
|
-
return Array.from(groups.values());
|
|
1857
|
+
getPluginGroupDescribe(segments) {
|
|
1858
|
+
return this.pluginRouter.getGroupDescribe(segments);
|
|
1938
1859
|
}
|
|
1939
|
-
|
|
1940
|
-
return this.
|
|
1860
|
+
listCommands() {
|
|
1861
|
+
return this.pluginRouter.listAll();
|
|
1941
1862
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1863
|
+
listCommandsUnder(segments) {
|
|
1864
|
+
return this.pluginRouter.listUnder(segments);
|
|
1865
|
+
}
|
|
1866
|
+
getCommandAt(segments) {
|
|
1867
|
+
return this.pluginRouter.getAt(segments);
|
|
1868
|
+
}
|
|
1869
|
+
// ─── Partial loading state ─────────────────────────────────────────────────
|
|
1870
|
+
markPartial(v) {
|
|
1871
|
+
this.partial = v;
|
|
1872
|
+
}
|
|
1873
|
+
isPartial() {
|
|
1874
|
+
return this.partial;
|
|
1875
|
+
}
|
|
1876
|
+
// ─── Diagnostics ──────────────────────────────────────────────────────────
|
|
1877
|
+
getDiagnostics() {
|
|
1878
|
+
return {
|
|
1879
|
+
systemCommandCount: this.systemRouter.listAll().length,
|
|
1880
|
+
systemGroupCount: 0,
|
|
1881
|
+
pluginCommandCount: this.pluginRouter.listAll().length,
|
|
1882
|
+
partialLoad: this.partial
|
|
1883
|
+
};
|
|
1959
1884
|
}
|
|
1960
1885
|
};
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
return registry.get(nameOrPath);
|
|
1964
|
-
}
|
|
1965
|
-
function findCommandWithType(nameOrPath) {
|
|
1966
|
-
return registry.getWithType(nameOrPath);
|
|
1886
|
+
function createRegistry() {
|
|
1887
|
+
return new TrieBackedRegistry();
|
|
1967
1888
|
}
|
|
1889
|
+
var registry = new TrieBackedRegistry();
|
|
1968
1890
|
|
|
1969
|
-
export { __test, checkRequires, discoverManifests, discoverManifestsByNamespace, disposeAllPlugins,
|
|
1891
|
+
export { TrieBackedRegistry, TrieRouter, __test, checkRequires, createRegistry, discoverManifests, discoverManifestsByNamespace, disposeAllPlugins, levenshtein, loadConfig, preflightManifests, registerManifests, registry, resetInProcCache, runCommand };
|
|
1970
1892
|
//# sourceMappingURL=index.js.map
|
|
1971
1893
|
//# sourceMappingURL=index.js.map
|