skillops-cli 1.3.2 → 1.3.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SkillOps (`npx skillops-cli@latest`)
2
2
 
3
- > **The Zero-API Framework & Benchmark-Verified Library for High-End AI Coding Skills & Context-Isolated Subagents (`v1.3.2`)**
3
+ > **The Zero-API Framework & Benchmark-Verified Library for High-End AI Coding Skills & Context-Isolated Subagents (`v1.3.3`)**
4
4
  > *Explore, search, inspect, and install benchmark-verified AI Coding Skills and Context-Isolated Subagents in 10 seconds (`npx skillops-cli@latest quickstart`), scaffold and certify your own custom Skills & Subagents (`npx skillops-cli@latest create skill <name>`), enforce Startup Token Tax budgets (`npx skillops-cli@latest budget`), and transpile natively across the Top 20 AI coding agents & IDEs — 100% offline with zero API keys.*
5
5
 
6
6
  [![CI Governance](https://img.shields.io/badge/governance-1%20Issue%20%3D%201%20PR-blue.svg)](./AGENTS.md)
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import YAML6 from "yaml";
10
10
  import fs from "fs";
11
11
  import YAML from "yaml";
12
12
  import { z } from "zod";
13
- var SKILLOPS_VERSION = "1.3.2";
13
+ var SKILLOPS_VERSION = "1.3.3";
14
14
  var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
15
15
  var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
16
16
  var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -5124,6 +5124,10 @@ function renderGlobalHelp(version, colorEnabled) {
5124
5124
  cmd: "add [name...]",
5125
5125
  desc: "Import Skills or Agents (interactive if no name given)"
5126
5126
  },
5127
+ {
5128
+ cmd: "search <query>",
5129
+ desc: "Search Skills & Agents by keyword, stack, or domain"
5130
+ },
5127
5131
  {
5128
5132
  cmd: "list [query]",
5129
5133
  desc: "List or filter available Skills & Agents in catalog"
@@ -5184,6 +5188,24 @@ function renderCommandHelp(spec, version, colorEnabled) {
5184
5188
  }
5185
5189
 
5186
5190
  // src/ui/prompt.ts
5191
+ function optionMatchesTab(opt, tab) {
5192
+ if (opt.kind === "agent") {
5193
+ return tab === "agent";
5194
+ }
5195
+ return tab === "skill";
5196
+ }
5197
+ function optionMatchesQuery(opt, rawQuery) {
5198
+ const q = rawQuery.trim().toLowerCase();
5199
+ if (!q) {
5200
+ return true;
5201
+ }
5202
+ return opt.label.toLowerCase().includes(q) || opt.value.toLowerCase().includes(q) || (opt.hint ?? "").toLowerCase().includes(q);
5203
+ }
5204
+ function getFilteredTabOptions(state, tab = state.activeTab) {
5205
+ return state.options.filter(
5206
+ (opt) => optionMatchesTab(opt, tab) && optionMatchesQuery(opt, state.searchQuery)
5207
+ );
5208
+ }
5187
5209
  function createMultiSelectState(options) {
5188
5210
  const initial = /* @__PURE__ */ new Set();
5189
5211
  for (const opt of options) {
@@ -5191,10 +5213,14 @@ function createMultiSelectState(options) {
5191
5213
  initial.add(opt.value);
5192
5214
  }
5193
5215
  }
5216
+ const hasSkills = options.some((opt) => optionMatchesTab(opt, "skill"));
5194
5217
  return {
5195
5218
  options,
5219
+ activeTab: hasSkills ? "skill" : "agent",
5196
5220
  cursor: 0,
5197
5221
  selected: initial,
5222
+ searchMode: false,
5223
+ searchQuery: "",
5198
5224
  status: "active"
5199
5225
  };
5200
5226
  }
@@ -5202,8 +5228,21 @@ function applyMultiSelectKey(state, key) {
5202
5228
  if (state.status !== "active" || state.options.length === 0) {
5203
5229
  return state;
5204
5230
  }
5205
- const count = state.options.length;
5206
- if (key === "" || key === "\x1B" || key === "q" || key === "Q") {
5231
+ if (key === "") {
5232
+ return {
5233
+ ...state,
5234
+ status: "cancelled"
5235
+ };
5236
+ }
5237
+ if (key === "\x1B") {
5238
+ if (state.searchMode || state.searchQuery.length > 0) {
5239
+ return {
5240
+ ...state,
5241
+ searchMode: false,
5242
+ searchQuery: "",
5243
+ cursor: 0
5244
+ };
5245
+ }
5207
5246
  return {
5208
5247
  ...state,
5209
5248
  status: "cancelled"
@@ -5212,17 +5251,34 @@ function applyMultiSelectKey(state, key) {
5212
5251
  if (key === "\r" || key === "\n") {
5213
5252
  return {
5214
5253
  ...state,
5254
+ searchMode: false,
5215
5255
  status: "submitted"
5216
5256
  };
5217
5257
  }
5218
- if (key === "\x1B[A" || key === "\x1BOA" || key === "k" || key === "K") {
5258
+ if (key === "\x1B[D" || key === "\x1BOD" || key === "\x1B[C" || key === "\x1BOC" || key === " " || !state.searchMode && (key === "h" || key === "H" || key === "l" || key === "L")) {
5259
+ const nextTab = state.activeTab === "skill" ? "agent" : "skill";
5260
+ return {
5261
+ ...state,
5262
+ activeTab: nextTab,
5263
+ cursor: 0
5264
+ };
5265
+ }
5266
+ const visible = getFilteredTabOptions(state, state.activeTab);
5267
+ const count = visible.length;
5268
+ if (key === "\x1B[A" || key === "\x1BOA" || !state.searchMode && (key === "k" || key === "K")) {
5269
+ if (count === 0) {
5270
+ return state;
5271
+ }
5219
5272
  const nextCursor = (state.cursor - 1 + count) % count;
5220
5273
  return {
5221
5274
  ...state,
5222
5275
  cursor: nextCursor
5223
5276
  };
5224
5277
  }
5225
- if (key === "\x1B[B" || key === "\x1BOB" || key === "j" || key === "J") {
5278
+ if (key === "\x1B[B" || key === "\x1BOB" || !state.searchMode && (key === "j" || key === "J")) {
5279
+ if (count === 0) {
5280
+ return state;
5281
+ }
5226
5282
  const nextCursor = (state.cursor + 1) % count;
5227
5283
  return {
5228
5284
  ...state,
@@ -5230,7 +5286,7 @@ function applyMultiSelectKey(state, key) {
5230
5286
  };
5231
5287
  }
5232
5288
  if (key === " ") {
5233
- const currentOpt = state.options[state.cursor];
5289
+ const currentOpt = visible[state.cursor];
5234
5290
  if (!currentOpt) {
5235
5291
  return state;
5236
5292
  }
@@ -5245,11 +5301,71 @@ function applyMultiSelectKey(state, key) {
5245
5301
  selected: nextSelected
5246
5302
  };
5247
5303
  }
5304
+ if (state.searchMode) {
5305
+ if (key === "\x7F" || key === "\b") {
5306
+ if (state.searchQuery.length === 0) {
5307
+ return {
5308
+ ...state,
5309
+ searchMode: false,
5310
+ cursor: 0
5311
+ };
5312
+ }
5313
+ const nextQuery = state.searchQuery.slice(0, -1);
5314
+ return {
5315
+ ...state,
5316
+ searchQuery: nextQuery,
5317
+ cursor: 0
5318
+ };
5319
+ }
5320
+ if (/^[a-zA-Z0-9_.-]$/.test(key)) {
5321
+ const nextQuery = `${state.searchQuery}${key}`;
5322
+ const nextState = {
5323
+ ...state,
5324
+ searchQuery: nextQuery,
5325
+ cursor: 0
5326
+ };
5327
+ const currentMatches = getFilteredTabOptions(nextState, state.activeTab);
5328
+ if (currentMatches.length === 0) {
5329
+ const otherTab = state.activeTab === "skill" ? "agent" : "skill";
5330
+ const otherMatches = getFilteredTabOptions(nextState, otherTab);
5331
+ if (otherMatches.length > 0) {
5332
+ return {
5333
+ ...nextState,
5334
+ activeTab: otherTab
5335
+ };
5336
+ }
5337
+ }
5338
+ return nextState;
5339
+ }
5340
+ return state;
5341
+ }
5342
+ if (key === "/") {
5343
+ return {
5344
+ ...state,
5345
+ searchMode: true,
5346
+ cursor: 0
5347
+ };
5348
+ }
5349
+ if (key === "q" || key === "Q") {
5350
+ return {
5351
+ ...state,
5352
+ status: "cancelled"
5353
+ };
5354
+ }
5248
5355
  if (key === "a" || key === "A") {
5249
- const allSelected = state.selected.size === count;
5250
- const nextSelected = /* @__PURE__ */ new Set();
5251
- if (!allSelected) {
5252
- for (const opt of state.options) {
5356
+ if (visible.length === 0) {
5357
+ return state;
5358
+ }
5359
+ const allTabSelected = visible.every(
5360
+ (opt) => state.selected.has(opt.value)
5361
+ );
5362
+ const nextSelected = new Set(state.selected);
5363
+ if (allTabSelected) {
5364
+ for (const opt of visible) {
5365
+ nextSelected.delete(opt.value);
5366
+ }
5367
+ } else {
5368
+ for (const opt of visible) {
5253
5369
  nextSelected.add(opt.value);
5254
5370
  }
5255
5371
  }
@@ -5264,54 +5380,89 @@ function renderMultiSelectFrame(title, state, options = {}) {
5264
5380
  const c = options.colorEnabled;
5265
5381
  const bar = style.dim("\u2502", c);
5266
5382
  const maxVisible = options.maxVisible ?? 10;
5267
- const total = state.options.length;
5383
+ const totalSkills = state.options.filter(
5384
+ (o) => optionMatchesTab(o, "skill")
5385
+ ).length;
5386
+ const totalAgents = state.options.filter(
5387
+ (o) => optionMatchesTab(o, "agent")
5388
+ ).length;
5389
+ const filteredSkills = getFilteredTabOptions(state, "skill");
5390
+ const filteredAgents = getFilteredTabOptions(state, "agent");
5391
+ const visible = state.activeTab === "skill" ? filteredSkills : filteredAgents;
5392
+ const total = visible.length;
5268
5393
  const header = `${style.bold(style.cyan(`\u25C6 ${title}`, c), c)}`;
5269
- const instructions = `${bar} ${style.dim("(\u2191/\u2193 move \xB7 Space select \xB7 'a' toggle all \xB7 Enter confirm)", c)}`;
5394
+ const instructions = state.searchMode ? `${bar} ${style.dim("\u2191/\u2193 move \xB7 \u2190/\u2192 switch tab \xB7 Space select \xB7 Esc clear search \xB7 Enter import", c)}` : `${bar} ${style.dim("\u2191/\u2193 move \xB7 \u2190/\u2192 switch tab \xB7 Space select \xB7 / search \xB7 a all \xB7 Enter import", c)}`;
5270
5395
  const lines = [header];
5271
5396
  if (options.subtitle) {
5272
5397
  lines.push(`${bar} ${options.subtitle}`);
5273
5398
  }
5274
5399
  lines.push(instructions, bar);
5275
- let startIdx = 0;
5276
- if (total > maxVisible) {
5277
- const half = Math.floor(maxVisible / 2);
5278
- startIdx = Math.max(0, Math.min(state.cursor - half, total - maxVisible));
5279
- }
5280
- const endIdx = Math.min(total, startIdx + maxVisible);
5281
- if (startIdx > 0) {
5282
- lines.push(`${bar} ${style.dim(`\u2191 ${startIdx} more above...`, c)}`);
5283
- }
5284
- const maxLabelCols = Math.min(
5285
- 26,
5286
- state.options.reduce((m, o) => Math.max(m, visibleLength(o.label)), 14)
5400
+ const hasQuery = state.searchQuery.length > 0;
5401
+ const skillCountStr = hasQuery ? `${filteredSkills.length}/${totalSkills}` : `${totalSkills}`;
5402
+ const agentCountStr = hasQuery ? `${filteredAgents.length}/${totalAgents}` : `${totalAgents}`;
5403
+ const skillTabLabel = state.activeTab === "skill" ? style.bold(style.cyan(`[ \u25C6 Skills (${skillCountStr}) ]`, c), c) : style.dim(`\u25CB Skills (${skillCountStr})`, c);
5404
+ const agentTabLabel = state.activeTab === "agent" ? style.bold(style.magenta(`[ \u25C6 Agents (${agentCountStr}) ]`, c), c) : style.dim(`\u25CB Agents (${agentCountStr})`, c);
5405
+ const rawQuerySlice = state.searchQuery.length > 14 ? `\u2026${state.searchQuery.slice(-13)}` : state.searchQuery;
5406
+ const searchSuffix = state.searchMode || hasQuery ? ` ${style.yellow(`/ Filter: ${rawQuerySlice}${state.searchMode ? "\u2588" : ""}`, c)}` : ` ${style.dim("(/ search)", c)}`;
5407
+ lines.push(
5408
+ `${style.dim("\u251C\u2500", c)} ${skillTabLabel} ${agentTabLabel}${searchSuffix}`,
5409
+ bar
5287
5410
  );
5288
- for (let i = startIdx; i < endIdx; i++) {
5289
- const opt = state.options[i];
5290
- if (!opt) continue;
5291
- const isCurrent = i === state.cursor;
5292
- const isChecked = state.selected.has(opt.value);
5293
- const pointer = isCurrent ? style.cyan("\u276F", c) : " ";
5294
- const checkbox = isChecked ? style.green("\u25FC", c) : style.dim("\u25FB", c);
5295
- const kindTag = opt.kind === "skill" ? style.cyan("skill ", c) : opt.kind === "agent" ? style.magenta("agent ", c) : "";
5296
- const labelRaw = opt.label.length > maxLabelCols ? `${opt.label.slice(0, maxLabelCols - 1)}\u2026` : opt.label;
5297
- const labelPadded = padVisible(labelRaw, maxLabelCols + 1, "left");
5298
- const styledLabel = isCurrent ? style.bold(labelPadded, c) : isChecked ? style.green(labelPadded, c) : labelPadded;
5299
- const prefixWidth = 6 + (opt.kind === "skill" || opt.kind === "agent" ? 6 : 0) + maxLabelCols + 1;
5300
- const maxHintCols = Math.max(10, 75 - prefixWidth);
5301
- const rawHint = opt.hint ?? "";
5302
- const shortHint = rawHint.length > maxHintCols ? `${rawHint.slice(0, maxHintCols - 1)}\u2026` : rawHint;
5303
- const hintPart = shortHint ? ` ${style.dim(shortHint, c)}` : "";
5411
+ if (total === 0) {
5412
+ const tabName = state.activeTab === "skill" ? "Skills" : "Agents";
5304
5413
  lines.push(
5305
- `${bar} ${pointer} ${checkbox} ${kindTag}${styledLabel}${hintPart}`
5414
+ `${bar} ${style.dim(`No ${tabName} match "${state.searchQuery}". Press Esc to clear filter.`, c)}`
5306
5415
  );
5307
- }
5308
- if (endIdx < total) {
5309
- lines.push(`${bar} ${style.dim(`\u2193 ${total - endIdx} more below...`, c)}`);
5416
+ } else {
5417
+ let startIdx = 0;
5418
+ if (total > maxVisible) {
5419
+ const half = Math.floor(maxVisible / 2);
5420
+ startIdx = Math.max(0, Math.min(state.cursor - half, total - maxVisible));
5421
+ }
5422
+ const endIdx = Math.min(total, startIdx + maxVisible);
5423
+ if (startIdx > 0) {
5424
+ lines.push(`${bar} ${style.dim(`\u2191 ${startIdx} more above...`, c)}`);
5425
+ }
5426
+ const maxLabelCols = Math.min(
5427
+ 26,
5428
+ visible.reduce((m, o) => Math.max(m, visibleLength(o.label)), 14)
5429
+ );
5430
+ for (let i = startIdx; i < endIdx; i++) {
5431
+ const opt = visible[i];
5432
+ if (!opt) continue;
5433
+ const isCurrent = i === state.cursor;
5434
+ const isChecked = state.selected.has(opt.value);
5435
+ const pointer = isCurrent ? style.cyan("\u276F", c) : " ";
5436
+ const checkbox = isChecked ? style.green("\u25C9", c) : style.dim("\u25EF", c);
5437
+ const labelRaw = opt.label.length > maxLabelCols ? `${opt.label.slice(0, maxLabelCols - 1)}\u2026` : opt.label;
5438
+ const labelPadded = padVisible(labelRaw, maxLabelCols + 1, "left");
5439
+ const styledLabel = isCurrent ? style.bold(labelPadded, c) : isChecked ? style.green(labelPadded, c) : labelPadded;
5440
+ const prefixWidth = 7 + maxLabelCols + 1;
5441
+ const maxHintCols = Math.max(10, 76 - prefixWidth);
5442
+ const rawHint = opt.hint ?? "";
5443
+ const shortHint = rawHint.length > maxHintCols ? `${rawHint.slice(0, maxHintCols - 1)}\u2026` : rawHint;
5444
+ const hintPart = shortHint ? ` ${style.dim(shortHint, c)}` : "";
5445
+ lines.push(`${bar} ${pointer} ${checkbox} ${styledLabel}${hintPart}`);
5446
+ }
5447
+ if (endIdx < total) {
5448
+ lines.push(
5449
+ `${bar} ${style.dim(`\u2193 ${total - endIdx} more below...`, c)}`
5450
+ );
5451
+ }
5310
5452
  }
5311
5453
  lines.push(bar);
5312
- const targetSuffix = options.targetHint ? ` \xB7 IDE: ${options.targetHint}` : "";
5454
+ const selectedSkillsCount = state.options.filter(
5455
+ (o) => optionMatchesTab(o, "skill") && state.selected.has(o.value)
5456
+ ).length;
5457
+ const selectedAgentsCount = state.options.filter(
5458
+ (o) => optionMatchesTab(o, "agent") && state.selected.has(o.value)
5459
+ ).length;
5460
+ const summaryText = `${state.selected.size} selected (${selectedSkillsCount} Skills, ${selectedAgentsCount} Agents)`;
5461
+ const rawTarget = options.targetHint ?? "";
5462
+ const shortTarget = rawTarget.length > 18 ? `${rawTarget.slice(0, 17)}\u2026` : rawTarget;
5463
+ const targetSuffix = shortTarget ? ` \xB7 IDE: ${shortTarget}` : "";
5313
5464
  lines.push(
5314
- `${style.dim("\u2570\u2500", c)} ${style.bold(`${state.selected.size} selected`, c)}${style.dim(`${targetSuffix} \u2014 Enter to import`, c)}`
5465
+ `${style.dim("\u2570\u2500", c)} ${style.bold(summaryText, c)}${style.dim(`${targetSuffix} \u2014 Enter to import`, c)}`
5315
5466
  );
5316
5467
  return lines.join("\n");
5317
5468
  }
@@ -6930,7 +7081,7 @@ async function runCliInteractive(argv) {
6930
7081
  "SkillOps \u2014 Select Skills & Agents to Import",
6931
7082
  options,
6932
7083
  {
6933
- targetHint: `${targets.join(", ")} -> ${repoPath.replace(/\\/g, "/")}`
7084
+ targetHint: targets.join(", ")
6934
7085
  }
6935
7086
  );
6936
7087
  if (!selected || selected.length === 0) {
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ import { z } from 'zod';
8
8
  * telemetry and Multi-Runtime Transpilation lockfiles.
9
9
  */
10
10
 
11
- declare const SKILLOPS_VERSION = "1.3.2";
11
+ declare const SKILLOPS_VERSION = "1.3.3";
12
12
  /**
13
13
  * Count whitespace-delimited words in text.
14
14
  */
@@ -2176,9 +2176,13 @@ declare function renderCommandHelp(spec: CommandHelpSpec, version: string, color
2176
2176
  /**
2177
2177
  * Zero-dependency interactive terminal multi-select prompt (`src/ui/prompt.ts`).
2178
2178
  *
2179
- * Supports keyboard navigation (`↑`/`↓` or `j`/`k`), `Space` to toggle selection,
2180
- * `a` to toggle all, `Enter` to confirm, and `Esc`/`Ctrl+C` to cancel.
2179
+ * Supports:
2180
+ * - Horizontal tabs (`←`/`→` or `Tab`) separating `Skills` and `Agents`
2181
+ * - Vertical navigation (`↑`/`↓` or `j`/`k`)
2182
+ * - Real-time keyword filtering (`/` to search, `Backspace` to edit, `Esc` to clear)
2183
+ * - `Space` to toggle selection across tabs, `a` to toggle all in current tab, `Enter` to confirm
2181
2184
  */
2185
+ type MultiSelectTab = "skill" | "agent";
2182
2186
  interface MultiSelectOption {
2183
2187
  readonly value: string;
2184
2188
  readonly label: string;
@@ -2188,8 +2192,11 @@ interface MultiSelectOption {
2188
2192
  }
2189
2193
  interface MultiSelectState {
2190
2194
  readonly options: readonly MultiSelectOption[];
2195
+ readonly activeTab: MultiSelectTab;
2191
2196
  readonly cursor: number;
2192
2197
  readonly selected: ReadonlySet<string>;
2198
+ readonly searchMode: boolean;
2199
+ readonly searchQuery: string;
2193
2200
  readonly status: "active" | "submitted" | "cancelled";
2194
2201
  }
2195
2202
  interface RenderMultiSelectOptions {
@@ -2198,16 +2205,20 @@ interface RenderMultiSelectOptions {
2198
2205
  readonly maxVisible?: number;
2199
2206
  readonly colorEnabled?: boolean;
2200
2207
  }
2208
+ /**
2209
+ * Return the filtered options for a given tab (`skill` or `agent`) and active `searchQuery`.
2210
+ */
2211
+ declare function getFilteredTabOptions(state: MultiSelectState, tab?: MultiSelectTab): MultiSelectOption[];
2201
2212
  /**
2202
2213
  * Initialize a deterministic multi-select state from a list of options.
2203
2214
  */
2204
2215
  declare function createMultiSelectState(options: readonly MultiSelectOption[]): MultiSelectState;
2205
2216
  /**
2206
- * Pure state reducer for keyboard input in the multi-select TUI.
2217
+ * Pure state reducer for keyboard input in the tabbed + searchable multi-select TUI.
2207
2218
  */
2208
2219
  declare function applyMultiSelectKey(state: MultiSelectState, key: string): MultiSelectState;
2209
2220
  /**
2210
- * Render a single frame of the multi-select tree prompt (guaranteed <= 78 columns).
2221
+ * Render a single frame of the tabbed + searchable multi-select tree prompt (guaranteed <= 78 columns).
2211
2222
  */
2212
2223
  declare function renderMultiSelectFrame(title: string, state: MultiSelectState, options?: RenderMultiSelectOptions): string;
2213
2224
  /**
@@ -2335,4 +2346,4 @@ declare const DEFAULT_VALID_COMMANDS: readonly string[];
2335
2346
  */
2336
2347
  declare function suggestCommand(unknownCommand: string, validCommands?: readonly string[]): string | null;
2337
2348
 
2338
- export { ALL_RUNTIME_TARGETS, type AddOptions, type AnalyzeTokenEconomyOptions, type AuditCatalogOptions, type BadgeType, type BenchmarkArm, BenchmarkArmSchema, type BenchmarkDelta, BenchmarkDeltaSchema, type BoxOptions, COMMAND_HELP_SPECS, CORE_RUNTIME_TARGETS, type CatalogEvalSummary, CatalogEvalSummarySchema, type CatalogItemInfo, type CatalogSearchMatch, type CatalogSearchReport, type CatalogTokenBudgetSummary, type CatalogTrack, CatalogTrackSchema, type CleanOptions, type CollisionPair, CollisionPairSchema, type CollisionReport, CollisionReportSchema, type CollisionSeverity, CollisionSeveritySchema, type ColorOverrideOptions, type CommandHelpSpec, type CommandOptionSpec, DEFAULT_VALID_COMMANDS, type DiagnoseRepositoryOptions, type DoctorCheckItem, type DoctorReport, type EvalAssertion, EvalAssertionSchema, type EvalCase, EvalCaseSchema, ExitCode, type ExitCodeValue, type HarnessCategory, type HarnessInspectionEntry, type HarnessInspectionReport, type HarnessSkillFormat, type JsonRpcRequest, type JsonRpcResponse, type LoadedSkillOpsConfigResult, type LockfileArtifactDiagnostic, type McpConfigAction, type McpServerContext, type McpSetupOptions, type McpSetupReport, type MultiSelectOption, type MultiSelectState, type NextStepHint, type ParseSkillOptions, type ParsedSkillDirectory, type ParsedSubagentFile, type QuickstartOptions, type QuickstartReport, RUNTIME_HARNESS_SPECS, type RemoveOptions, type RenderMultiSelectOptions, RepoFingerprinter, type RepoProfile, RepoProfileSchema, type RouteOptions, type RoutedBundle, RoutedBundleSchema, type RoutingSignals, RoutingSignalsSchema, type RuntimeHarnessSpec, type RuntimeTarget, RuntimeTargetSchema, SKILLOPS_VERSION, type ScaffoldOptions, type ScaffoldResult, type ScoredSkill, ScoredSkillSchema, type ScriptEntrypoint, ScriptEntrypointSchema, type SkillBenchmark, SkillBenchmarkSchema, type SkillEntrypoints, SkillEntrypointsSchema, type SkillEvalSuite, SkillEvalSuiteSchema, type SkillManifest, type SkillManifestInput, SkillManifestSchema, type SkillOpsConfig, SkillOpsConfigSchema, type SkillOpsDetectedStackConfig, SkillOpsDetectedStackConfigSchema, type SkillOpsTokenEconomyConfig, SkillOpsTokenEconomyConfigSchema, type SkillTokenBudgetAudit, type SkippedSkill, SkippedSkillSchema, type SubagentManifest, SubagentManifestSchema, type SubagentTokenBudgetAudit, type SyncLockEntry, SyncLockEntrySchema, type SyncLockfile, SyncLockfileSchema, type SyncOptions, type SyncReport, SyncReportSchema, type SyncedArtifact, SyncedArtifactSchema, type TableOptions, type TokenBarOptions, type TokenEconomyReport, TokenEconomyReportSchema, type TokenFileCategory, TokenFileCategorySchema, type TokenFileEntry, TokenFileEntrySchema, type TokenWasteHotspot, TokenWasteHotspotSchema, addSkillOrSubagent, analyzeRepoTokenEconomy, applyMultiSelectKey, auditCatalog, auditCatalogTokenBudgets, auditSkillTokenBudget, auditSubagentTokenBudget, classifyCollisionSeverity, cleanSyncedArtifacts, compileSkillBundle, computeCollisionMatrix, computeFeatureCosine, computeIdfWeights, computeSha256, countWords, createCollisionPair, createCollisionReport, createMultiSelectState, createSkillManifest, createSubagentManifest, detectInstalledHarnesses, diagnoseRepository, discoverSkillDirectories, discoverSubagentFiles, estimateTokenCount, evaluateCatalog, evaluateSkillDirectory, extractSkillFeatures, findCommandHelpSpec, fingerprintRepository, formatBadge, formatCatalogItemInfo, formatCatalogSearchReport, formatDoctorReport, formatEvalSummary, formatHarnessInspectionReport, formatMcpSetupReport, formatQuickstartReport, formatRoutedBundle, formatScaffoldReport, formatSyncReport, formatTokenEconomyReport, handleMcpJsonRpcRequest, inspectCatalogItem, inspectHarnesses, isColorEnabled, levenshteinDistance, loadSkillOpsConfig, normalizePackageName, padVisible, parseSkillDirectory, parseSkillMdFile, parseSubagentFile, parseSubagentMdFile, removeSkillOrSubagent, renderAiUsageGuide, renderBox, renderCodexAgentsIndex, renderCommandHelp, renderGlobalHelp, renderHeaderBanner, renderMultiSelectFrame, renderNextSteps, renderProgressBar, renderSectionHeader, renderStep, renderTable, renderTokenBar, resolveRuntimeTargets, routeRepository, runMultiSelectPrompt, runQuickstart, scaffoldFrameworkItem, scoreSkillAgainstProfile, searchCatalog, setupMcpForHarnesses, splitFrontmatter, startMcpStdioServer, stripAnsi, stripCodexAgentsBlock, stripNegativeTriggerClauses, style, suggestCommand, syncRepository, tokenizeRoutingText, visibleLength };
2349
+ export { ALL_RUNTIME_TARGETS, type AddOptions, type AnalyzeTokenEconomyOptions, type AuditCatalogOptions, type BadgeType, type BenchmarkArm, BenchmarkArmSchema, type BenchmarkDelta, BenchmarkDeltaSchema, type BoxOptions, COMMAND_HELP_SPECS, CORE_RUNTIME_TARGETS, type CatalogEvalSummary, CatalogEvalSummarySchema, type CatalogItemInfo, type CatalogSearchMatch, type CatalogSearchReport, type CatalogTokenBudgetSummary, type CatalogTrack, CatalogTrackSchema, type CleanOptions, type CollisionPair, CollisionPairSchema, type CollisionReport, CollisionReportSchema, type CollisionSeverity, CollisionSeveritySchema, type ColorOverrideOptions, type CommandHelpSpec, type CommandOptionSpec, DEFAULT_VALID_COMMANDS, type DiagnoseRepositoryOptions, type DoctorCheckItem, type DoctorReport, type EvalAssertion, EvalAssertionSchema, type EvalCase, EvalCaseSchema, ExitCode, type ExitCodeValue, type HarnessCategory, type HarnessInspectionEntry, type HarnessInspectionReport, type HarnessSkillFormat, type JsonRpcRequest, type JsonRpcResponse, type LoadedSkillOpsConfigResult, type LockfileArtifactDiagnostic, type McpConfigAction, type McpServerContext, type McpSetupOptions, type McpSetupReport, type MultiSelectOption, type MultiSelectState, type MultiSelectTab, type NextStepHint, type ParseSkillOptions, type ParsedSkillDirectory, type ParsedSubagentFile, type QuickstartOptions, type QuickstartReport, RUNTIME_HARNESS_SPECS, type RemoveOptions, type RenderMultiSelectOptions, RepoFingerprinter, type RepoProfile, RepoProfileSchema, type RouteOptions, type RoutedBundle, RoutedBundleSchema, type RoutingSignals, RoutingSignalsSchema, type RuntimeHarnessSpec, type RuntimeTarget, RuntimeTargetSchema, SKILLOPS_VERSION, type ScaffoldOptions, type ScaffoldResult, type ScoredSkill, ScoredSkillSchema, type ScriptEntrypoint, ScriptEntrypointSchema, type SkillBenchmark, SkillBenchmarkSchema, type SkillEntrypoints, SkillEntrypointsSchema, type SkillEvalSuite, SkillEvalSuiteSchema, type SkillManifest, type SkillManifestInput, SkillManifestSchema, type SkillOpsConfig, SkillOpsConfigSchema, type SkillOpsDetectedStackConfig, SkillOpsDetectedStackConfigSchema, type SkillOpsTokenEconomyConfig, SkillOpsTokenEconomyConfigSchema, type SkillTokenBudgetAudit, type SkippedSkill, SkippedSkillSchema, type SubagentManifest, SubagentManifestSchema, type SubagentTokenBudgetAudit, type SyncLockEntry, SyncLockEntrySchema, type SyncLockfile, SyncLockfileSchema, type SyncOptions, type SyncReport, SyncReportSchema, type SyncedArtifact, SyncedArtifactSchema, type TableOptions, type TokenBarOptions, type TokenEconomyReport, TokenEconomyReportSchema, type TokenFileCategory, TokenFileCategorySchema, type TokenFileEntry, TokenFileEntrySchema, type TokenWasteHotspot, TokenWasteHotspotSchema, addSkillOrSubagent, analyzeRepoTokenEconomy, applyMultiSelectKey, auditCatalog, auditCatalogTokenBudgets, auditSkillTokenBudget, auditSubagentTokenBudget, classifyCollisionSeverity, cleanSyncedArtifacts, compileSkillBundle, computeCollisionMatrix, computeFeatureCosine, computeIdfWeights, computeSha256, countWords, createCollisionPair, createCollisionReport, createMultiSelectState, createSkillManifest, createSubagentManifest, detectInstalledHarnesses, diagnoseRepository, discoverSkillDirectories, discoverSubagentFiles, estimateTokenCount, evaluateCatalog, evaluateSkillDirectory, extractSkillFeatures, findCommandHelpSpec, fingerprintRepository, formatBadge, formatCatalogItemInfo, formatCatalogSearchReport, formatDoctorReport, formatEvalSummary, formatHarnessInspectionReport, formatMcpSetupReport, formatQuickstartReport, formatRoutedBundle, formatScaffoldReport, formatSyncReport, formatTokenEconomyReport, getFilteredTabOptions, handleMcpJsonRpcRequest, inspectCatalogItem, inspectHarnesses, isColorEnabled, levenshteinDistance, loadSkillOpsConfig, normalizePackageName, padVisible, parseSkillDirectory, parseSkillMdFile, parseSubagentFile, parseSubagentMdFile, removeSkillOrSubagent, renderAiUsageGuide, renderBox, renderCodexAgentsIndex, renderCommandHelp, renderGlobalHelp, renderHeaderBanner, renderMultiSelectFrame, renderNextSteps, renderProgressBar, renderSectionHeader, renderStep, renderTable, renderTokenBar, resolveRuntimeTargets, routeRepository, runMultiSelectPrompt, runQuickstart, scaffoldFrameworkItem, scoreSkillAgainstProfile, searchCatalog, setupMcpForHarnesses, splitFrontmatter, startMcpStdioServer, stripAnsi, stripCodexAgentsBlock, stripNegativeTriggerClauses, style, suggestCommand, syncRepository, tokenizeRoutingText, visibleLength };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import fs from "fs";
3
3
  import YAML from "yaml";
4
4
  import { z } from "zod";
5
- var SKILLOPS_VERSION = "1.3.2";
5
+ var SKILLOPS_VERSION = "1.3.3";
6
6
  var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
7
  var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
8
8
  var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -5142,6 +5142,10 @@ function renderGlobalHelp(version, colorEnabled) {
5142
5142
  cmd: "add [name...]",
5143
5143
  desc: "Import Skills or Agents (interactive if no name given)"
5144
5144
  },
5145
+ {
5146
+ cmd: "search <query>",
5147
+ desc: "Search Skills & Agents by keyword, stack, or domain"
5148
+ },
5145
5149
  {
5146
5150
  cmd: "list [query]",
5147
5151
  desc: "List or filter available Skills & Agents in catalog"
@@ -5202,6 +5206,24 @@ function renderCommandHelp(spec, version, colorEnabled) {
5202
5206
  }
5203
5207
 
5204
5208
  // src/ui/prompt.ts
5209
+ function optionMatchesTab(opt, tab) {
5210
+ if (opt.kind === "agent") {
5211
+ return tab === "agent";
5212
+ }
5213
+ return tab === "skill";
5214
+ }
5215
+ function optionMatchesQuery(opt, rawQuery) {
5216
+ const q = rawQuery.trim().toLowerCase();
5217
+ if (!q) {
5218
+ return true;
5219
+ }
5220
+ return opt.label.toLowerCase().includes(q) || opt.value.toLowerCase().includes(q) || (opt.hint ?? "").toLowerCase().includes(q);
5221
+ }
5222
+ function getFilteredTabOptions(state, tab = state.activeTab) {
5223
+ return state.options.filter(
5224
+ (opt) => optionMatchesTab(opt, tab) && optionMatchesQuery(opt, state.searchQuery)
5225
+ );
5226
+ }
5205
5227
  function createMultiSelectState(options) {
5206
5228
  const initial = /* @__PURE__ */ new Set();
5207
5229
  for (const opt of options) {
@@ -5209,10 +5231,14 @@ function createMultiSelectState(options) {
5209
5231
  initial.add(opt.value);
5210
5232
  }
5211
5233
  }
5234
+ const hasSkills = options.some((opt) => optionMatchesTab(opt, "skill"));
5212
5235
  return {
5213
5236
  options,
5237
+ activeTab: hasSkills ? "skill" : "agent",
5214
5238
  cursor: 0,
5215
5239
  selected: initial,
5240
+ searchMode: false,
5241
+ searchQuery: "",
5216
5242
  status: "active"
5217
5243
  };
5218
5244
  }
@@ -5220,8 +5246,21 @@ function applyMultiSelectKey(state, key) {
5220
5246
  if (state.status !== "active" || state.options.length === 0) {
5221
5247
  return state;
5222
5248
  }
5223
- const count = state.options.length;
5224
- if (key === "" || key === "\x1B" || key === "q" || key === "Q") {
5249
+ if (key === "") {
5250
+ return {
5251
+ ...state,
5252
+ status: "cancelled"
5253
+ };
5254
+ }
5255
+ if (key === "\x1B") {
5256
+ if (state.searchMode || state.searchQuery.length > 0) {
5257
+ return {
5258
+ ...state,
5259
+ searchMode: false,
5260
+ searchQuery: "",
5261
+ cursor: 0
5262
+ };
5263
+ }
5225
5264
  return {
5226
5265
  ...state,
5227
5266
  status: "cancelled"
@@ -5230,17 +5269,34 @@ function applyMultiSelectKey(state, key) {
5230
5269
  if (key === "\r" || key === "\n") {
5231
5270
  return {
5232
5271
  ...state,
5272
+ searchMode: false,
5233
5273
  status: "submitted"
5234
5274
  };
5235
5275
  }
5236
- if (key === "\x1B[A" || key === "\x1BOA" || key === "k" || key === "K") {
5276
+ if (key === "\x1B[D" || key === "\x1BOD" || key === "\x1B[C" || key === "\x1BOC" || key === " " || !state.searchMode && (key === "h" || key === "H" || key === "l" || key === "L")) {
5277
+ const nextTab = state.activeTab === "skill" ? "agent" : "skill";
5278
+ return {
5279
+ ...state,
5280
+ activeTab: nextTab,
5281
+ cursor: 0
5282
+ };
5283
+ }
5284
+ const visible = getFilteredTabOptions(state, state.activeTab);
5285
+ const count = visible.length;
5286
+ if (key === "\x1B[A" || key === "\x1BOA" || !state.searchMode && (key === "k" || key === "K")) {
5287
+ if (count === 0) {
5288
+ return state;
5289
+ }
5237
5290
  const nextCursor = (state.cursor - 1 + count) % count;
5238
5291
  return {
5239
5292
  ...state,
5240
5293
  cursor: nextCursor
5241
5294
  };
5242
5295
  }
5243
- if (key === "\x1B[B" || key === "\x1BOB" || key === "j" || key === "J") {
5296
+ if (key === "\x1B[B" || key === "\x1BOB" || !state.searchMode && (key === "j" || key === "J")) {
5297
+ if (count === 0) {
5298
+ return state;
5299
+ }
5244
5300
  const nextCursor = (state.cursor + 1) % count;
5245
5301
  return {
5246
5302
  ...state,
@@ -5248,7 +5304,7 @@ function applyMultiSelectKey(state, key) {
5248
5304
  };
5249
5305
  }
5250
5306
  if (key === " ") {
5251
- const currentOpt = state.options[state.cursor];
5307
+ const currentOpt = visible[state.cursor];
5252
5308
  if (!currentOpt) {
5253
5309
  return state;
5254
5310
  }
@@ -5263,11 +5319,71 @@ function applyMultiSelectKey(state, key) {
5263
5319
  selected: nextSelected
5264
5320
  };
5265
5321
  }
5322
+ if (state.searchMode) {
5323
+ if (key === "\x7F" || key === "\b") {
5324
+ if (state.searchQuery.length === 0) {
5325
+ return {
5326
+ ...state,
5327
+ searchMode: false,
5328
+ cursor: 0
5329
+ };
5330
+ }
5331
+ const nextQuery = state.searchQuery.slice(0, -1);
5332
+ return {
5333
+ ...state,
5334
+ searchQuery: nextQuery,
5335
+ cursor: 0
5336
+ };
5337
+ }
5338
+ if (/^[a-zA-Z0-9_.-]$/.test(key)) {
5339
+ const nextQuery = `${state.searchQuery}${key}`;
5340
+ const nextState = {
5341
+ ...state,
5342
+ searchQuery: nextQuery,
5343
+ cursor: 0
5344
+ };
5345
+ const currentMatches = getFilteredTabOptions(nextState, state.activeTab);
5346
+ if (currentMatches.length === 0) {
5347
+ const otherTab = state.activeTab === "skill" ? "agent" : "skill";
5348
+ const otherMatches = getFilteredTabOptions(nextState, otherTab);
5349
+ if (otherMatches.length > 0) {
5350
+ return {
5351
+ ...nextState,
5352
+ activeTab: otherTab
5353
+ };
5354
+ }
5355
+ }
5356
+ return nextState;
5357
+ }
5358
+ return state;
5359
+ }
5360
+ if (key === "/") {
5361
+ return {
5362
+ ...state,
5363
+ searchMode: true,
5364
+ cursor: 0
5365
+ };
5366
+ }
5367
+ if (key === "q" || key === "Q") {
5368
+ return {
5369
+ ...state,
5370
+ status: "cancelled"
5371
+ };
5372
+ }
5266
5373
  if (key === "a" || key === "A") {
5267
- const allSelected = state.selected.size === count;
5268
- const nextSelected = /* @__PURE__ */ new Set();
5269
- if (!allSelected) {
5270
- for (const opt of state.options) {
5374
+ if (visible.length === 0) {
5375
+ return state;
5376
+ }
5377
+ const allTabSelected = visible.every(
5378
+ (opt) => state.selected.has(opt.value)
5379
+ );
5380
+ const nextSelected = new Set(state.selected);
5381
+ if (allTabSelected) {
5382
+ for (const opt of visible) {
5383
+ nextSelected.delete(opt.value);
5384
+ }
5385
+ } else {
5386
+ for (const opt of visible) {
5271
5387
  nextSelected.add(opt.value);
5272
5388
  }
5273
5389
  }
@@ -5282,54 +5398,89 @@ function renderMultiSelectFrame(title, state, options = {}) {
5282
5398
  const c = options.colorEnabled;
5283
5399
  const bar = style.dim("\u2502", c);
5284
5400
  const maxVisible = options.maxVisible ?? 10;
5285
- const total = state.options.length;
5401
+ const totalSkills = state.options.filter(
5402
+ (o) => optionMatchesTab(o, "skill")
5403
+ ).length;
5404
+ const totalAgents = state.options.filter(
5405
+ (o) => optionMatchesTab(o, "agent")
5406
+ ).length;
5407
+ const filteredSkills = getFilteredTabOptions(state, "skill");
5408
+ const filteredAgents = getFilteredTabOptions(state, "agent");
5409
+ const visible = state.activeTab === "skill" ? filteredSkills : filteredAgents;
5410
+ const total = visible.length;
5286
5411
  const header = `${style.bold(style.cyan(`\u25C6 ${title}`, c), c)}`;
5287
- const instructions = `${bar} ${style.dim("(\u2191/\u2193 move \xB7 Space select \xB7 'a' toggle all \xB7 Enter confirm)", c)}`;
5412
+ const instructions = state.searchMode ? `${bar} ${style.dim("\u2191/\u2193 move \xB7 \u2190/\u2192 switch tab \xB7 Space select \xB7 Esc clear search \xB7 Enter import", c)}` : `${bar} ${style.dim("\u2191/\u2193 move \xB7 \u2190/\u2192 switch tab \xB7 Space select \xB7 / search \xB7 a all \xB7 Enter import", c)}`;
5288
5413
  const lines = [header];
5289
5414
  if (options.subtitle) {
5290
5415
  lines.push(`${bar} ${options.subtitle}`);
5291
5416
  }
5292
5417
  lines.push(instructions, bar);
5293
- let startIdx = 0;
5294
- if (total > maxVisible) {
5295
- const half = Math.floor(maxVisible / 2);
5296
- startIdx = Math.max(0, Math.min(state.cursor - half, total - maxVisible));
5297
- }
5298
- const endIdx = Math.min(total, startIdx + maxVisible);
5299
- if (startIdx > 0) {
5300
- lines.push(`${bar} ${style.dim(`\u2191 ${startIdx} more above...`, c)}`);
5301
- }
5302
- const maxLabelCols = Math.min(
5303
- 26,
5304
- state.options.reduce((m, o) => Math.max(m, visibleLength(o.label)), 14)
5418
+ const hasQuery = state.searchQuery.length > 0;
5419
+ const skillCountStr = hasQuery ? `${filteredSkills.length}/${totalSkills}` : `${totalSkills}`;
5420
+ const agentCountStr = hasQuery ? `${filteredAgents.length}/${totalAgents}` : `${totalAgents}`;
5421
+ const skillTabLabel = state.activeTab === "skill" ? style.bold(style.cyan(`[ \u25C6 Skills (${skillCountStr}) ]`, c), c) : style.dim(`\u25CB Skills (${skillCountStr})`, c);
5422
+ const agentTabLabel = state.activeTab === "agent" ? style.bold(style.magenta(`[ \u25C6 Agents (${agentCountStr}) ]`, c), c) : style.dim(`\u25CB Agents (${agentCountStr})`, c);
5423
+ const rawQuerySlice = state.searchQuery.length > 14 ? `\u2026${state.searchQuery.slice(-13)}` : state.searchQuery;
5424
+ const searchSuffix = state.searchMode || hasQuery ? ` ${style.yellow(`/ Filter: ${rawQuerySlice}${state.searchMode ? "\u2588" : ""}`, c)}` : ` ${style.dim("(/ search)", c)}`;
5425
+ lines.push(
5426
+ `${style.dim("\u251C\u2500", c)} ${skillTabLabel} ${agentTabLabel}${searchSuffix}`,
5427
+ bar
5305
5428
  );
5306
- for (let i = startIdx; i < endIdx; i++) {
5307
- const opt = state.options[i];
5308
- if (!opt) continue;
5309
- const isCurrent = i === state.cursor;
5310
- const isChecked = state.selected.has(opt.value);
5311
- const pointer = isCurrent ? style.cyan("\u276F", c) : " ";
5312
- const checkbox = isChecked ? style.green("\u25FC", c) : style.dim("\u25FB", c);
5313
- const kindTag = opt.kind === "skill" ? style.cyan("skill ", c) : opt.kind === "agent" ? style.magenta("agent ", c) : "";
5314
- const labelRaw = opt.label.length > maxLabelCols ? `${opt.label.slice(0, maxLabelCols - 1)}\u2026` : opt.label;
5315
- const labelPadded = padVisible(labelRaw, maxLabelCols + 1, "left");
5316
- const styledLabel = isCurrent ? style.bold(labelPadded, c) : isChecked ? style.green(labelPadded, c) : labelPadded;
5317
- const prefixWidth = 6 + (opt.kind === "skill" || opt.kind === "agent" ? 6 : 0) + maxLabelCols + 1;
5318
- const maxHintCols = Math.max(10, 75 - prefixWidth);
5319
- const rawHint = opt.hint ?? "";
5320
- const shortHint = rawHint.length > maxHintCols ? `${rawHint.slice(0, maxHintCols - 1)}\u2026` : rawHint;
5321
- const hintPart = shortHint ? ` ${style.dim(shortHint, c)}` : "";
5429
+ if (total === 0) {
5430
+ const tabName = state.activeTab === "skill" ? "Skills" : "Agents";
5322
5431
  lines.push(
5323
- `${bar} ${pointer} ${checkbox} ${kindTag}${styledLabel}${hintPart}`
5432
+ `${bar} ${style.dim(`No ${tabName} match "${state.searchQuery}". Press Esc to clear filter.`, c)}`
5324
5433
  );
5325
- }
5326
- if (endIdx < total) {
5327
- lines.push(`${bar} ${style.dim(`\u2193 ${total - endIdx} more below...`, c)}`);
5434
+ } else {
5435
+ let startIdx = 0;
5436
+ if (total > maxVisible) {
5437
+ const half = Math.floor(maxVisible / 2);
5438
+ startIdx = Math.max(0, Math.min(state.cursor - half, total - maxVisible));
5439
+ }
5440
+ const endIdx = Math.min(total, startIdx + maxVisible);
5441
+ if (startIdx > 0) {
5442
+ lines.push(`${bar} ${style.dim(`\u2191 ${startIdx} more above...`, c)}`);
5443
+ }
5444
+ const maxLabelCols = Math.min(
5445
+ 26,
5446
+ visible.reduce((m, o) => Math.max(m, visibleLength(o.label)), 14)
5447
+ );
5448
+ for (let i = startIdx; i < endIdx; i++) {
5449
+ const opt = visible[i];
5450
+ if (!opt) continue;
5451
+ const isCurrent = i === state.cursor;
5452
+ const isChecked = state.selected.has(opt.value);
5453
+ const pointer = isCurrent ? style.cyan("\u276F", c) : " ";
5454
+ const checkbox = isChecked ? style.green("\u25C9", c) : style.dim("\u25EF", c);
5455
+ const labelRaw = opt.label.length > maxLabelCols ? `${opt.label.slice(0, maxLabelCols - 1)}\u2026` : opt.label;
5456
+ const labelPadded = padVisible(labelRaw, maxLabelCols + 1, "left");
5457
+ const styledLabel = isCurrent ? style.bold(labelPadded, c) : isChecked ? style.green(labelPadded, c) : labelPadded;
5458
+ const prefixWidth = 7 + maxLabelCols + 1;
5459
+ const maxHintCols = Math.max(10, 76 - prefixWidth);
5460
+ const rawHint = opt.hint ?? "";
5461
+ const shortHint = rawHint.length > maxHintCols ? `${rawHint.slice(0, maxHintCols - 1)}\u2026` : rawHint;
5462
+ const hintPart = shortHint ? ` ${style.dim(shortHint, c)}` : "";
5463
+ lines.push(`${bar} ${pointer} ${checkbox} ${styledLabel}${hintPart}`);
5464
+ }
5465
+ if (endIdx < total) {
5466
+ lines.push(
5467
+ `${bar} ${style.dim(`\u2193 ${total - endIdx} more below...`, c)}`
5468
+ );
5469
+ }
5328
5470
  }
5329
5471
  lines.push(bar);
5330
- const targetSuffix = options.targetHint ? ` \xB7 IDE: ${options.targetHint}` : "";
5472
+ const selectedSkillsCount = state.options.filter(
5473
+ (o) => optionMatchesTab(o, "skill") && state.selected.has(o.value)
5474
+ ).length;
5475
+ const selectedAgentsCount = state.options.filter(
5476
+ (o) => optionMatchesTab(o, "agent") && state.selected.has(o.value)
5477
+ ).length;
5478
+ const summaryText = `${state.selected.size} selected (${selectedSkillsCount} Skills, ${selectedAgentsCount} Agents)`;
5479
+ const rawTarget = options.targetHint ?? "";
5480
+ const shortTarget = rawTarget.length > 18 ? `${rawTarget.slice(0, 17)}\u2026` : rawTarget;
5481
+ const targetSuffix = shortTarget ? ` \xB7 IDE: ${shortTarget}` : "";
5331
5482
  lines.push(
5332
- `${style.dim("\u2570\u2500", c)} ${style.bold(`${state.selected.size} selected`, c)}${style.dim(`${targetSuffix} \u2014 Enter to import`, c)}`
5483
+ `${style.dim("\u2570\u2500", c)} ${style.bold(summaryText, c)}${style.dim(`${targetSuffix} \u2014 Enter to import`, c)}`
5333
5484
  );
5334
5485
  return lines.join("\n");
5335
5486
  }
@@ -6185,6 +6336,7 @@ export {
6185
6336
  formatScaffoldReport,
6186
6337
  formatSyncReport,
6187
6338
  formatTokenEconomyReport,
6339
+ getFilteredTabOptions,
6188
6340
  handleMcpJsonRpcRequest,
6189
6341
  inspectCatalogItem,
6190
6342
  inspectHarnesses,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillops-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "SkillOps — The Zero-API Framework & Benchmark-Verified Library for High-End AI Coding Skills & Context-Isolated Subagents with Universal Top-20 AI Agent Transpilation & Token Economy Auditing.",
5
5
  "type": "module",
6
6
  "license": "MIT",