@kb-labs/cli-commands 2.13.0 → 2.15.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.
@@ -146,7 +146,8 @@ interface CommandLookupResult {
146
146
  }
147
147
  declare class InMemoryRegistry implements CommandRegistry {
148
148
  private systemCommands;
149
- private pluginCommands;
149
+ private pluginByCanonical;
150
+ private pluginAliases;
150
151
  private byName;
151
152
  private groups;
152
153
  private manifests;
@@ -154,18 +155,45 @@ declare class InMemoryRegistry implements CommandRegistry {
154
155
  register(cmd: Command): void;
155
156
  registerGroup(group: CommandGroup): void;
156
157
  registerManifest(cmd: RegisteredCommand): void;
158
+ /**
159
+ * Check whether any system command already owns the canonical id or its parts.
160
+ * Returns the colliding key if found, null otherwise.
161
+ */
162
+ private _findSystemCollision;
163
+ /**
164
+ * Register all lookup aliases for a plugin command.
165
+ *
166
+ * Priority (what beats what) is handled in resolveToCanonical at query time.
167
+ * Here we just build the mapping.
168
+ *
169
+ * Aliases registered:
170
+ * - canonicalId itself ("marketplace:plugins:list")
171
+ * - bare id ("list") ← low priority, may clash
172
+ * - 2-part shorthand ("marketplace:list", "marketplace list")
173
+ * - manifest.aliases[] (user-defined aliases, except collisions)
174
+ */
175
+ private _registerPluginAliases;
176
+ /**
177
+ * Register synthetic subgroups for help display.
178
+ */
179
+ private _registerSyntheticGroups;
180
+ /**
181
+ * Resolve any user input to its canonical plugin ID.
182
+ *
183
+ * Returns canonicalId if found in plugin aliases, undefined otherwise.
184
+ */
185
+ private resolveToCanonical;
157
186
  markPartial(partial: boolean): void;
158
187
  isPartial(): boolean;
159
188
  getManifest(id: string): RegisteredCommand | undefined;
160
189
  listManifests(): RegisteredCommand[];
161
190
  has(name: string): boolean;
162
191
  /**
163
- * Get command with type information for secure routing
164
- *
165
- * Returns type='system' for commands from registerGroup() - execute in-process
166
- * Returns type='plugin' for commands from registerManifest() - execute in subprocess
192
+ * Get command with type information for secure routing.
167
193
  *
168
- * This separation prevents malicious plugins from escaping the sandbox.
194
+ * System commands ALWAYS win checked first before any plugin lookup.
195
+ * Returns type='system' for system commands (in-process execution).
196
+ * Returns type='plugin' for plugin commands (subprocess execution).
169
197
  */
170
198
  getWithType(nameOrPath: string | string[]): CommandLookupResult | undefined;
171
199
  get(nameOrPath: string | string[]): Command | CommandGroup | undefined;
@@ -180,11 +208,13 @@ declare class InMemoryRegistry implements CommandRegistry {
180
208
  declare const registry: InMemoryRegistry;
181
209
  declare function findCommand(nameOrPath: string | string[]): Command | CommandGroup | undefined;
182
210
  /**
183
- * Find command with type information for secure routing
211
+ * Find command with type information for secure routing.
184
212
  *
185
213
  * Use this in bootstrap.ts to determine execution path:
186
214
  * - type='system' → execute via cmd.run() in-process
187
215
  * - type='plugin' → execute via executePlugin() in plugin-executor
216
+ *
217
+ * System commands ALWAYS take priority over plugin commands.
188
218
  */
189
219
  declare function findCommandWithType(nameOrPath: string | string[]): CommandLookupResult | undefined;
190
220
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CommandGroup, P as ProductGroup, a as Command, R as RegisteredCommand } from './discover-BY7VE2Hf.js';
2
- export { A as AvailabilityCheck, b as CacheFile, c as CommandLookupResult, d as CommandManifest, e as CommandModule, f as CommandType, D as DiscoveryResult, F as FlagDefinition, G as GlobalFlags, g as PackageCacheEntry, h as discoverManifests, i as discoverManifestsByNamespace, j as findCommand, k as findCommandWithType, r as registry } from './discover-BY7VE2Hf.js';
1
+ import { C as CommandGroup, P as ProductGroup, a as Command, R as RegisteredCommand } from './discover-Be0ez3AR.js';
2
+ export { A as AvailabilityCheck, b as CacheFile, c as CommandLookupResult, d as CommandManifest, e as CommandModule, f as CommandType, D as DiscoveryResult, F as FlagDefinition, G as GlobalFlags, g as PackageCacheEntry, h as discoverManifests, i as discoverManifestsByNamespace, j as findCommand, k as findCommandWithType, r as registry } from './discover-Be0ez3AR.js';
3
3
  import { ILogger } from '@kb-labs/core-platform';
4
4
  export { TimingTracker } from '@kb-labs/shared-cli-ui';
5
5
  import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
package/dist/index.js CHANGED
@@ -1017,17 +1017,30 @@ function manifestToCommand(registered) {
1017
1017
  }
1018
1018
  };
1019
1019
  }
1020
+ function buildCanonicalId(manifest) {
1021
+ const { id, group, subgroup } = manifest;
1022
+ if (group && subgroup) {
1023
+ return `${group}:${subgroup}:${id}`;
1024
+ }
1025
+ if (group) {
1026
+ return `${group}:${id}`;
1027
+ }
1028
+ return id;
1029
+ }
1020
1030
  var InMemoryRegistry = class {
1021
- // Separate collections for security isolation
1031
+ // System commands (in-process): key = any registered name/alias
1022
1032
  systemCommands = /* @__PURE__ */ new Map();
1023
- // System commands (in-process)
1024
- pluginCommands = /* @__PURE__ */ new Map();
1025
- // Plugin commands (subprocess)
1026
- // Legacy unified collection for backward compatibility
1033
+ // Plugin commands: key = canonicalId only
1034
+ pluginByCanonical = /* @__PURE__ */ new Map();
1035
+ // Alias canonicalId: covers all user-input variants that resolve to a plugin
1036
+ pluginAliases = /* @__PURE__ */ new Map();
1037
+ // Legacy unified collection for backward compatibility (display/get)
1027
1038
  byName = /* @__PURE__ */ new Map();
1028
1039
  groups = /* @__PURE__ */ new Map();
1040
+ // manifests: any key → RegisteredCommand (for listing/lookup; still stores multi-key for compat)
1029
1041
  manifests = /* @__PURE__ */ new Map();
1030
1042
  partial = false;
1043
+ // ─── System command registration ────────────────────────────────────────
1031
1044
  register(cmd) {
1032
1045
  this.systemCommands.set(cmd.name, cmd);
1033
1046
  this.byName.set(cmd.name, cmd);
@@ -1066,11 +1079,12 @@ var InMemoryRegistry = class {
1066
1079
  }
1067
1080
  }
1068
1081
  }
1082
+ // ─── Plugin command registration ────────────────────────────────────────
1069
1083
  registerManifest(cmd) {
1070
- const cmdId = cmd.manifest.id;
1071
- const hasCollision = this.systemCommands.has(cmdId);
1072
- if (hasCollision) {
1073
- console.warn(`[registry] Plugin command "${cmdId}" collides with system command. System command takes priority.`);
1084
+ const canonicalId = buildCanonicalId(cmd.manifest);
1085
+ const collisionKey = this._findSystemCollision(cmd.manifest, canonicalId);
1086
+ if (collisionKey) {
1087
+ console.warn(`[registry] Plugin command "${canonicalId}" collides with system command "${collisionKey}". System command takes priority.`);
1074
1088
  cmd.shadowed = true;
1075
1089
  }
1076
1090
  const collisionAliases = /* @__PURE__ */ new Set();
@@ -1080,58 +1094,134 @@ var InMemoryRegistry = class {
1080
1094
  collisionAliases.add(alias);
1081
1095
  }
1082
1096
  }
1083
- this.pluginCommands.set(cmdId, cmd);
1084
- this.manifests.set(cmdId, cmd);
1085
- if (!hasCollision) {
1086
- const commandAdapter = manifestToCommand(cmd);
1087
- this.byName.set(cmdId, commandAdapter);
1088
- this.byName.set(commandAdapter.name, commandAdapter);
1089
- if (cmdId.includes(":")) {
1090
- const spaceSeparated = cmdId.replace(/:/g, " ");
1091
- this.byName.set(spaceSeparated, commandAdapter);
1092
- }
1093
- if (cmd.manifest.group && cmd.manifest.subgroup) {
1094
- const fullPath = `${cmd.manifest.group} ${cmd.manifest.subgroup} ${cmd.manifest.id}`;
1095
- const colonPath = `${cmd.manifest.group}:${cmd.manifest.subgroup}:${cmd.manifest.id}`;
1096
- this.byName.set(fullPath, commandAdapter);
1097
- this.byName.set(colonPath, commandAdapter);
1098
- this.manifests.set(fullPath, cmd);
1099
- this.manifests.set(colonPath, cmd);
1100
- this.pluginCommands.set(fullPath, cmd);
1101
- this.pluginCommands.set(colonPath, cmd);
1102
- const twoPartName = `${cmd.manifest.group} ${cmd.manifest.id}`;
1103
- if (!this.byName.has(twoPartName)) {
1104
- this.byName.set(twoPartName, commandAdapter);
1105
- }
1106
- const subgroupKey = `${cmd.manifest.group} ${cmd.manifest.subgroup}`;
1107
- if (!this.groups.has(subgroupKey)) {
1108
- this.groups.set(subgroupKey, {
1109
- name: subgroupKey,
1110
- describe: cmd.manifest.subgroup,
1111
- commands: []
1112
- });
1113
- this.byName.set(subgroupKey, this.groups.get(subgroupKey));
1097
+ this.pluginByCanonical.set(canonicalId, cmd);
1098
+ this.manifests.set(canonicalId, cmd);
1099
+ this.manifests.set(cmd.manifest.id, cmd);
1100
+ if (cmd.shadowed) {
1101
+ return;
1102
+ }
1103
+ this._registerPluginAliases(cmd, canonicalId, collisionAliases);
1104
+ const commandAdapter = manifestToCommand(cmd);
1105
+ this.byName.set(canonicalId, commandAdapter);
1106
+ const spaceCanonical = canonicalId.replace(/:/g, " ");
1107
+ this.byName.set(spaceCanonical, commandAdapter);
1108
+ this._registerSyntheticGroups(cmd, commandAdapter);
1109
+ }
1110
+ /**
1111
+ * Check whether any system command already owns the canonical id or its parts.
1112
+ * Returns the colliding key if found, null otherwise.
1113
+ */
1114
+ _findSystemCollision(manifest, canonicalId) {
1115
+ if (this.systemCommands.has(canonicalId)) return canonicalId;
1116
+ if (this.systemCommands.has(manifest.id)) return manifest.id;
1117
+ const space = canonicalId.replace(/:/g, " ");
1118
+ if (this.systemCommands.has(space)) return space;
1119
+ return null;
1120
+ }
1121
+ /**
1122
+ * Register all lookup aliases for a plugin command.
1123
+ *
1124
+ * Priority (what beats what) is handled in resolveToCanonical at query time.
1125
+ * Here we just build the mapping.
1126
+ *
1127
+ * Aliases registered:
1128
+ * - canonicalId itself ("marketplace:plugins:list")
1129
+ * - bare id ("list") ← low priority, may clash
1130
+ * - 2-part shorthand ("marketplace:list", "marketplace list")
1131
+ * - manifest.aliases[] (user-defined aliases, except collisions)
1132
+ */
1133
+ _registerPluginAliases(cmd, canonicalId, collisionAliases) {
1134
+ const { id, group, subgroup } = cmd.manifest;
1135
+ const register = (key) => {
1136
+ if (!this.systemCommands.has(key) && !this.systemCommands.has(key.replace(/:/g, " "))) {
1137
+ if (!this.pluginAliases.has(key)) {
1138
+ this.pluginAliases.set(key, canonicalId);
1139
+ this.manifests.set(key, cmd);
1114
1140
  }
1115
- this.groups.get(subgroupKey).commands.push(commandAdapter);
1116
- } else if (cmd.manifest.group) {
1117
- const fullName = `${cmd.manifest.group} ${cmd.manifest.id}`;
1118
- const colonName = `${cmd.manifest.group}:${cmd.manifest.id}`;
1119
- this.byName.set(fullName, commandAdapter);
1120
- this.byName.set(colonName, commandAdapter);
1121
- this.manifests.set(fullName, cmd);
1122
- this.manifests.set(colonName, cmd);
1123
- this.pluginCommands.set(fullName, cmd);
1124
- this.pluginCommands.set(colonName, cmd);
1125
1141
  }
1126
- if (cmd.manifest.aliases) {
1127
- for (const alias of cmd.manifest.aliases) {
1128
- if (!collisionAliases.has(alias)) {
1129
- this.byName.set(alias, commandAdapter);
1130
- }
1131
- }
1142
+ };
1143
+ this.pluginAliases.set(canonicalId, canonicalId);
1144
+ const spaceCanonical = canonicalId.replace(/:/g, " ");
1145
+ this.pluginAliases.set(spaceCanonical, canonicalId);
1146
+ this.manifests.set(spaceCanonical, cmd);
1147
+ if (group && subgroup) {
1148
+ const twoPartColon = `${group}:${id}`;
1149
+ const twoPartSpace = `${group} ${id}`;
1150
+ register(twoPartColon);
1151
+ register(twoPartSpace);
1152
+ this.manifests.set(twoPartColon, cmd);
1153
+ this.manifests.set(twoPartSpace, cmd);
1154
+ const fullPath = `${group} ${subgroup} ${id}`;
1155
+ this.pluginAliases.set(fullPath, canonicalId);
1156
+ this.manifests.set(fullPath, cmd);
1157
+ const colonPath = `${group}:${subgroup}:${id}`;
1158
+ this.pluginAliases.set(colonPath, canonicalId);
1159
+ this.manifests.set(colonPath, cmd);
1160
+ } else if (group) {
1161
+ const colonName = `${group}:${id}`;
1162
+ const spaceName = `${group} ${id}`;
1163
+ this.pluginAliases.set(colonName, canonicalId);
1164
+ this.pluginAliases.set(spaceName, canonicalId);
1165
+ this.manifests.set(colonName, cmd);
1166
+ this.manifests.set(spaceName, cmd);
1167
+ }
1168
+ register(id);
1169
+ for (const alias of cmd.manifest.aliases || []) {
1170
+ if (!collisionAliases.has(alias)) {
1171
+ register(alias);
1172
+ }
1173
+ }
1174
+ }
1175
+ /**
1176
+ * Register synthetic subgroups for help display.
1177
+ */
1178
+ _registerSyntheticGroups(cmd, commandAdapter) {
1179
+ const { id, group, subgroup } = cmd.manifest;
1180
+ if (group && subgroup) {
1181
+ const subgroupKey = `${group} ${subgroup}`;
1182
+ if (!this.groups.has(subgroupKey)) {
1183
+ this.groups.set(subgroupKey, {
1184
+ name: subgroupKey,
1185
+ describe: subgroup,
1186
+ commands: []
1187
+ });
1188
+ this.byName.set(subgroupKey, this.groups.get(subgroupKey));
1189
+ }
1190
+ this.groups.get(subgroupKey).commands.push(commandAdapter);
1191
+ const twoPartSpace = `${group} ${id}`;
1192
+ if (!this.byName.has(twoPartSpace)) {
1193
+ this.byName.set(twoPartSpace, commandAdapter);
1132
1194
  }
1195
+ const twoPartColon = `${group}:${id}`;
1196
+ if (!this.byName.has(twoPartColon)) {
1197
+ this.byName.set(twoPartColon, commandAdapter);
1198
+ }
1199
+ const fullPath = `${group} ${subgroup} ${id}`;
1200
+ this.byName.set(fullPath, commandAdapter);
1201
+ } else if (group) {
1202
+ const fullName = `${group} ${id}`;
1203
+ const colonName = `${group}:${id}`;
1204
+ this.byName.set(fullName, commandAdapter);
1205
+ this.byName.set(colonName, commandAdapter);
1133
1206
  }
1134
1207
  }
1208
+ // ─── Resolution ──────────────────────────────────────────────────────────
1209
+ /**
1210
+ * Resolve any user input to its canonical plugin ID.
1211
+ *
1212
+ * Returns canonicalId if found in plugin aliases, undefined otherwise.
1213
+ */
1214
+ resolveToCanonical(input) {
1215
+ if (this.pluginAliases.has(input)) {
1216
+ return this.pluginAliases.get(input);
1217
+ }
1218
+ const converted = input.includes(":") ? input.replace(/:/g, " ") : input.replace(/ /g, ":");
1219
+ if (this.pluginAliases.has(converted)) {
1220
+ return this.pluginAliases.get(converted);
1221
+ }
1222
+ return void 0;
1223
+ }
1224
+ // ─── Public API ──────────────────────────────────────────────────────────
1135
1225
  markPartial(partial) {
1136
1226
  this.partial = partial;
1137
1227
  }
@@ -1143,23 +1233,43 @@ var InMemoryRegistry = class {
1143
1233
  }
1144
1234
  listManifests() {
1145
1235
  const unique = /* @__PURE__ */ new Set();
1146
- for (const cmd of this.manifests.values()) {
1236
+ for (const cmd of this.pluginByCanonical.values()) {
1147
1237
  unique.add(cmd);
1148
1238
  }
1149
1239
  return Array.from(unique);
1150
1240
  }
1151
1241
  has(name) {
1152
- return this.byName.has(name);
1242
+ return this.byName.has(name) || this.pluginAliases.has(name);
1153
1243
  }
1154
1244
  /**
1155
- * Get command with type information for secure routing
1156
- *
1157
- * Returns type='system' for commands from registerGroup() - execute in-process
1158
- * Returns type='plugin' for commands from registerManifest() - execute in subprocess
1245
+ * Get command with type information for secure routing.
1159
1246
  *
1160
- * This separation prevents malicious plugins from escaping the sandbox.
1247
+ * System commands ALWAYS win checked first before any plugin lookup.
1248
+ * Returns type='system' for system commands (in-process execution).
1249
+ * Returns type='plugin' for plugin commands (subprocess execution).
1161
1250
  */
1162
1251
  getWithType(nameOrPath) {
1252
+ const normalized = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
1253
+ if (this.systemCommands.has(normalized)) {
1254
+ return { cmd: this.systemCommands.get(normalized), type: "system" };
1255
+ }
1256
+ const colonVariant = normalized.replace(/ /g, ":");
1257
+ if (this.systemCommands.has(colonVariant)) {
1258
+ return { cmd: this.systemCommands.get(colonVariant), type: "system" };
1259
+ }
1260
+ if (this.groups.has(normalized)) {
1261
+ return { cmd: this.groups.get(normalized), type: "system" };
1262
+ }
1263
+ const canonicalId = this.resolveToCanonical(normalized);
1264
+ if (canonicalId) {
1265
+ const pluginCmd = this.pluginByCanonical.get(canonicalId);
1266
+ if (pluginCmd && !pluginCmd.shadowed) {
1267
+ const cmd2 = this.byName.get(canonicalId) ?? this.byName.get(normalized);
1268
+ if (cmd2) {
1269
+ return { cmd: cmd2, type: "plugin" };
1270
+ }
1271
+ }
1272
+ }
1163
1273
  const cmd = this.get(nameOrPath);
1164
1274
  if (!cmd) {
1165
1275
  return void 0;
@@ -1167,18 +1277,11 @@ var InMemoryRegistry = class {
1167
1277
  if ("commands" in cmd) {
1168
1278
  return { cmd, type: "system" };
1169
1279
  }
1170
- const normalizedName = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
1171
- if (this.systemCommands.has(normalizedName)) {
1280
+ if (this.systemCommands.has(normalized) || this.systemCommands.has(colonVariant)) {
1172
1281
  return { cmd, type: "system" };
1173
1282
  }
1174
- if (normalizedName.includes(":")) {
1175
- const spaceVersion = normalizedName.replace(":", " ");
1176
- if (this.systemCommands.has(spaceVersion)) {
1177
- return { cmd, type: "system" };
1178
- }
1179
- }
1180
- const manifestCmd = this.getManifestCommand(normalizedName);
1181
- if (manifestCmd) {
1283
+ const manifestCmd = this.getManifestCommand(normalized);
1284
+ if (manifestCmd && !manifestCmd.shadowed) {
1182
1285
  return { cmd, type: "plugin" };
1183
1286
  }
1184
1287
  return { cmd, type: "system" };
@@ -1189,15 +1292,15 @@ var InMemoryRegistry = class {
1189
1292
  return this.byName.get(nameOrPath);
1190
1293
  }
1191
1294
  if (nameOrPath.includes(":")) {
1295
+ const spaceKey = nameOrPath.replace(/:/g, " ");
1296
+ if (this.byName.has(spaceKey)) {
1297
+ return this.byName.get(spaceKey);
1298
+ }
1192
1299
  const parts = nameOrPath.split(":");
1193
1300
  if (parts.length === 2) {
1194
- const exactMatch = this.byName.get(nameOrPath);
1195
- if (exactMatch) {
1196
- return exactMatch;
1197
- }
1198
- const spaceKey = parts.join(" ");
1199
- if (this.byName.has(spaceKey)) {
1200
- return this.byName.get(spaceKey);
1301
+ const spaceKey2 = parts.join(" ");
1302
+ if (this.byName.has(spaceKey2)) {
1303
+ return this.byName.get(spaceKey2);
1201
1304
  }
1202
1305
  }
1203
1306
  }
@@ -1288,7 +1391,11 @@ var InMemoryRegistry = class {
1288
1391
  if (this.manifests.has(idOrAlias)) {
1289
1392
  return this.manifests.get(idOrAlias);
1290
1393
  }
1291
- for (const cmd of this.manifests.values()) {
1394
+ const canonicalId = this.resolveToCanonical(idOrAlias);
1395
+ if (canonicalId) {
1396
+ return this.pluginByCanonical.get(canonicalId);
1397
+ }
1398
+ for (const cmd of this.pluginByCanonical.values()) {
1292
1399
  if (cmd.manifest.aliases?.includes(idOrAlias)) {
1293
1400
  return cmd;
1294
1401
  }