skillops-cli 1.3.1 → 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/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.1";
11
+ declare const SKILLOPS_VERSION = "1.3.3";
12
12
  /**
13
13
  * Count whitespace-delimited words in text.
14
14
  */
@@ -2165,7 +2165,7 @@ declare const COMMAND_HELP_SPECS: readonly CommandHelpSpec[];
2165
2165
  */
2166
2166
  declare function findCommandHelpSpec(commandOrAlias: string): CommandHelpSpec | undefined;
2167
2167
  /**
2168
- * Render the compact, emoji-free 3-Pillar Framework & Library global help tree (`npx skillops-cli --help`).
2168
+ * Render the compact, emoji-free global help tree (`npx skillops-cli --help`) focused on the 5 core commands.
2169
2169
  */
2170
2170
  declare function renderGlobalHelp(version: string, colorEnabled?: boolean): string;
2171
2171
  /**
@@ -2173,6 +2173,60 @@ declare function renderGlobalHelp(version: string, colorEnabled?: boolean): stri
2173
2173
  */
2174
2174
  declare function renderCommandHelp(spec: CommandHelpSpec, version: string, colorEnabled?: boolean): string;
2175
2175
 
2176
+ /**
2177
+ * Zero-dependency interactive terminal multi-select prompt (`src/ui/prompt.ts`).
2178
+ *
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
2184
+ */
2185
+ type MultiSelectTab = "skill" | "agent";
2186
+ interface MultiSelectOption {
2187
+ readonly value: string;
2188
+ readonly label: string;
2189
+ readonly kind?: "skill" | "agent" | "ide";
2190
+ readonly hint?: string;
2191
+ readonly initialSelected?: boolean;
2192
+ }
2193
+ interface MultiSelectState {
2194
+ readonly options: readonly MultiSelectOption[];
2195
+ readonly activeTab: MultiSelectTab;
2196
+ readonly cursor: number;
2197
+ readonly selected: ReadonlySet<string>;
2198
+ readonly searchMode: boolean;
2199
+ readonly searchQuery: string;
2200
+ readonly status: "active" | "submitted" | "cancelled";
2201
+ }
2202
+ interface RenderMultiSelectOptions {
2203
+ readonly subtitle?: string;
2204
+ readonly targetHint?: string;
2205
+ readonly maxVisible?: number;
2206
+ readonly colorEnabled?: boolean;
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[];
2212
+ /**
2213
+ * Initialize a deterministic multi-select state from a list of options.
2214
+ */
2215
+ declare function createMultiSelectState(options: readonly MultiSelectOption[]): MultiSelectState;
2216
+ /**
2217
+ * Pure state reducer for keyboard input in the tabbed + searchable multi-select TUI.
2218
+ */
2219
+ declare function applyMultiSelectKey(state: MultiSelectState, key: string): MultiSelectState;
2220
+ /**
2221
+ * Render a single frame of the tabbed + searchable multi-select tree prompt (guaranteed <= 78 columns).
2222
+ */
2223
+ declare function renderMultiSelectFrame(title: string, state: MultiSelectState, options?: RenderMultiSelectOptions): string;
2224
+ /**
2225
+ * Run an interactive multi-select prompt in a TTY terminal.
2226
+ * Returns the array of selected values, or `null` if cancelled by user.
2227
+ */
2228
+ declare function runMultiSelectPrompt(title: string, options: readonly MultiSelectOption[], renderOpts?: RenderMultiSelectOptions): Promise<string[] | null>;
2229
+
2176
2230
  /**
2177
2231
  * SkillOps Zero-Dependency Terminal UI (TUI) Aesthetic Engine.
2178
2232
  *
@@ -2292,4 +2346,4 @@ declare const DEFAULT_VALID_COMMANDS: readonly string[];
2292
2346
  */
2293
2347
  declare function suggestCommand(unknownCommand: string, validCommands?: readonly string[]): string | null;
2294
2348
 
2295
- 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 NextStepHint, type ParseSkillOptions, type ParsedSkillDirectory, type ParsedSubagentFile, type QuickstartOptions, type QuickstartReport, RUNTIME_HARNESS_SPECS, type RemoveOptions, 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, auditCatalog, auditCatalogTokenBudgets, auditSkillTokenBudget, auditSubagentTokenBudget, classifyCollisionSeverity, cleanSyncedArtifacts, compileSkillBundle, computeCollisionMatrix, computeFeatureCosine, computeIdfWeights, computeSha256, countWords, createCollisionPair, createCollisionReport, 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, renderNextSteps, renderProgressBar, renderSectionHeader, renderStep, renderTable, renderTokenBar, resolveRuntimeTargets, routeRepository, 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.1";
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]*)$/;
@@ -5131,45 +5131,56 @@ function renderGlobalHelp(version, colorEnabled) {
5131
5131
  const c = colorEnabled;
5132
5132
  const bar = style.dim("\u2502", c);
5133
5133
  const banner = renderHeaderBanner(
5134
- "Framework & Library for AI Skills & Subagents",
5134
+ "Skills & Agents Library for AI Coding IDEs",
5135
5135
  {
5136
5136
  version,
5137
5137
  colorEnabled: c
5138
5138
  }
5139
5139
  );
5140
- const formatCmdSection = (pillar) => {
5141
- const specs = COMMAND_HELP_SPECS.filter((s) => s.pillar === pillar);
5142
- return specs.map((s) => {
5143
- const aliasPart = s.aliases.length > 0 ? style.dim(` (${s.aliases.join(", ")})`, c) : "";
5144
- const cmdCol = padVisible(
5145
- `${style.bold(style.cyan(s.name, c), c)}${aliasPart}`,
5146
- 23,
5147
- "left"
5148
- );
5149
- return `${bar} ${cmdCol}${s.summary}`;
5150
- }).join("\n");
5151
- };
5140
+ const coreCmds = [
5141
+ {
5142
+ cmd: "add [name...]",
5143
+ desc: "Import Skills or Agents (interactive if no name given)"
5144
+ },
5145
+ {
5146
+ cmd: "search <query>",
5147
+ desc: "Search Skills & Agents by keyword, stack, or domain"
5148
+ },
5149
+ {
5150
+ cmd: "list [query]",
5151
+ desc: "List or filter available Skills & Agents in catalog"
5152
+ },
5153
+ {
5154
+ cmd: "info <name>",
5155
+ desc: "View details and how to use a Skill or Agent"
5156
+ },
5157
+ {
5158
+ cmd: "remove <name>",
5159
+ desc: "Remove an imported Skill or Agent from your project"
5160
+ },
5161
+ {
5162
+ cmd: "init",
5163
+ desc: "Auto-detect stack & IDE and import recommended bundle"
5164
+ }
5165
+ ];
5166
+ const cmdRows = coreCmds.map(
5167
+ (item) => `${bar} ${padVisible(style.bold(style.cyan(item.cmd, c), c), 18, "left")}${item.desc}`
5168
+ ).join("\n");
5152
5169
  return [
5153
5170
  banner,
5154
- `${bar} Usage: npx skillops-cli <command> [options]`,
5155
- renderSectionHeader("Quickstart (Library + Framework)", c),
5156
- `${bar} ${style.dim("$", c)} ${style.bold(style.green("npx skillops-cli quickstart", c), c)} ${style.dim("\u2014 Detect stack, sync Skills & MCP", c)}`,
5157
- `${bar} ${style.dim("$", c)} ${style.bold(style.cyan("npx skillops-cli info <name>", c), c)} ${style.dim("\u2014 Inspect Skill/Subagent & usage", c)}`,
5158
- `${bar} ${style.dim("$", c)} ${style.bold(style.magenta("npx skillops-cli create skill <name>", c), c)} ${style.dim("\u2014 Scaffold a custom Skill", c)}`,
5159
- renderSectionHeader("1. Library of High-End Skills & Subagents", c),
5160
- formatCmdSection("library"),
5161
- renderSectionHeader(
5162
- "2. Framework for Building Custom Skills & Subagents",
5163
- c
5164
- ),
5165
- formatCmdSection("framework"),
5166
- renderSectionHeader(
5167
- "3. Universal Runtime Sync, Token Economy & Diagnostics",
5168
- c
5169
- ),
5170
- formatCmdSection("runtime"),
5171
+ `${bar} Usage: skillops <command> [options]`,
5172
+ renderSectionHeader("Commands", c),
5173
+ cmdRows,
5174
+ renderSectionHeader("Options", c),
5175
+ `${bar} ${padVisible(style.cyan("-t, --target <ide>", c), 22, "left")}AI IDE (cursor, claude, copilot, windsurf, auto)`,
5176
+ `${bar} ${padVisible(style.cyan("-p, --project <dir>", c), 22, "left")}Project folder (default: current directory)`,
5177
+ `${bar} ${padVisible(style.cyan("--all", c), 22, "left")}Import all 10 Skills & 9 Agents`,
5178
+ renderSectionHeader("Examples", c),
5179
+ `${bar} ${style.dim("$", c)} ${style.green("skillops add", c)} ${style.dim("\u2014 Interactive selector", c)}`,
5180
+ `${bar} ${style.dim("$", c)} ${style.green("skillops add bug-reproducer-tdd red-team-breaker", c)} ${style.dim("\u2014 Direct import", c)}`,
5181
+ `${bar} ${style.dim("$", c)} ${style.green("skillops list", c)} ${style.dim("\u2014 Browse catalog", c)}`,
5171
5182
  bar,
5172
- `${style.dim("\u2570\u2500", c)} ${style.bold("Global Flags:", c)} ${style.cyan("-p <dir>", c)} \xB7 ${style.cyan("-t <harnesses>", c)} \xB7 ${style.cyan("-f <table|json|md>", c)} \xB7 ${style.cyan("-h <cmd>", c)}`
5183
+ `${style.dim("\u2570\u2500", c)} ${style.dim("Run", c)} ${style.cyan("skillops <cmd> -h", c)} ${style.dim("for command options.", c)}`
5173
5184
  ].join("\n");
5174
5185
  }
5175
5186
  function renderCommandHelp(spec, version, colorEnabled) {
@@ -5194,6 +5205,341 @@ function renderCommandHelp(spec, version, colorEnabled) {
5194
5205
  return [headerLines, optionsSection, examplesSection].filter(Boolean).join("\n");
5195
5206
  }
5196
5207
 
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
+ }
5227
+ function createMultiSelectState(options) {
5228
+ const initial = /* @__PURE__ */ new Set();
5229
+ for (const opt of options) {
5230
+ if (opt.initialSelected) {
5231
+ initial.add(opt.value);
5232
+ }
5233
+ }
5234
+ const hasSkills = options.some((opt) => optionMatchesTab(opt, "skill"));
5235
+ return {
5236
+ options,
5237
+ activeTab: hasSkills ? "skill" : "agent",
5238
+ cursor: 0,
5239
+ selected: initial,
5240
+ searchMode: false,
5241
+ searchQuery: "",
5242
+ status: "active"
5243
+ };
5244
+ }
5245
+ function applyMultiSelectKey(state, key) {
5246
+ if (state.status !== "active" || state.options.length === 0) {
5247
+ return state;
5248
+ }
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
+ }
5264
+ return {
5265
+ ...state,
5266
+ status: "cancelled"
5267
+ };
5268
+ }
5269
+ if (key === "\r" || key === "\n") {
5270
+ return {
5271
+ ...state,
5272
+ searchMode: false,
5273
+ status: "submitted"
5274
+ };
5275
+ }
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
+ }
5290
+ const nextCursor = (state.cursor - 1 + count) % count;
5291
+ return {
5292
+ ...state,
5293
+ cursor: nextCursor
5294
+ };
5295
+ }
5296
+ if (key === "\x1B[B" || key === "\x1BOB" || !state.searchMode && (key === "j" || key === "J")) {
5297
+ if (count === 0) {
5298
+ return state;
5299
+ }
5300
+ const nextCursor = (state.cursor + 1) % count;
5301
+ return {
5302
+ ...state,
5303
+ cursor: nextCursor
5304
+ };
5305
+ }
5306
+ if (key === " ") {
5307
+ const currentOpt = visible[state.cursor];
5308
+ if (!currentOpt) {
5309
+ return state;
5310
+ }
5311
+ const nextSelected = new Set(state.selected);
5312
+ if (nextSelected.has(currentOpt.value)) {
5313
+ nextSelected.delete(currentOpt.value);
5314
+ } else {
5315
+ nextSelected.add(currentOpt.value);
5316
+ }
5317
+ return {
5318
+ ...state,
5319
+ selected: nextSelected
5320
+ };
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
+ }
5373
+ if (key === "a" || key === "A") {
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) {
5387
+ nextSelected.add(opt.value);
5388
+ }
5389
+ }
5390
+ return {
5391
+ ...state,
5392
+ selected: nextSelected
5393
+ };
5394
+ }
5395
+ return state;
5396
+ }
5397
+ function renderMultiSelectFrame(title, state, options = {}) {
5398
+ const c = options.colorEnabled;
5399
+ const bar = style.dim("\u2502", c);
5400
+ const maxVisible = options.maxVisible ?? 10;
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;
5411
+ const header = `${style.bold(style.cyan(`\u25C6 ${title}`, c), 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)}`;
5413
+ const lines = [header];
5414
+ if (options.subtitle) {
5415
+ lines.push(`${bar} ${options.subtitle}`);
5416
+ }
5417
+ lines.push(instructions, bar);
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
5428
+ );
5429
+ if (total === 0) {
5430
+ const tabName = state.activeTab === "skill" ? "Skills" : "Agents";
5431
+ lines.push(
5432
+ `${bar} ${style.dim(`No ${tabName} match "${state.searchQuery}". Press Esc to clear filter.`, c)}`
5433
+ );
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
+ }
5470
+ }
5471
+ lines.push(bar);
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}` : "";
5482
+ lines.push(
5483
+ `${style.dim("\u2570\u2500", c)} ${style.bold(summaryText, c)}${style.dim(`${targetSuffix} \u2014 Enter to import`, c)}`
5484
+ );
5485
+ return lines.join("\n");
5486
+ }
5487
+ async function runMultiSelectPrompt(title, options, renderOpts = {}) {
5488
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
5489
+ return null;
5490
+ }
5491
+ let state = createMultiSelectState(options);
5492
+ let renderedLineCount = 0;
5493
+ const render = () => {
5494
+ const frame = renderMultiSelectFrame(title, state, renderOpts);
5495
+ if (renderedLineCount > 0) {
5496
+ process.stdout.write(`\x1B[${renderedLineCount}A\x1B[J`);
5497
+ }
5498
+ process.stdout.write(`${frame}
5499
+ `);
5500
+ renderedLineCount = frame.split("\n").length;
5501
+ };
5502
+ return new Promise((resolve) => {
5503
+ const stdin = process.stdin;
5504
+ const wasRaw = stdin.isRaw;
5505
+ stdin.setRawMode(true);
5506
+ stdin.resume();
5507
+ stdin.setEncoding("utf-8");
5508
+ process.stdout.write("\x1B[?25l");
5509
+ render();
5510
+ const cleanup = () => {
5511
+ stdin.removeListener("data", onData);
5512
+ if (stdin.isTTY) {
5513
+ stdin.setRawMode(Boolean(wasRaw));
5514
+ }
5515
+ stdin.pause();
5516
+ process.stdout.write("\x1B[?25h");
5517
+ };
5518
+ const onData = (chunk) => {
5519
+ const key = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
5520
+ state = applyMultiSelectKey(state, key);
5521
+ if (state.status === "cancelled") {
5522
+ if (renderedLineCount > 0) {
5523
+ process.stdout.write(`\x1B[${renderedLineCount}A\x1B[J`);
5524
+ }
5525
+ cleanup();
5526
+ resolve(null);
5527
+ return;
5528
+ }
5529
+ if (state.status === "submitted") {
5530
+ if (renderedLineCount > 0) {
5531
+ process.stdout.write(`\x1B[${renderedLineCount}A\x1B[J`);
5532
+ }
5533
+ cleanup();
5534
+ resolve([...state.selected]);
5535
+ return;
5536
+ }
5537
+ render();
5538
+ };
5539
+ stdin.on("data", onData);
5540
+ });
5541
+ }
5542
+
5197
5543
  // src/framework/index.ts
5198
5544
  function extractMarkdownHeadings(markdownBody) {
5199
5545
  const headings = [];
@@ -5950,6 +6296,7 @@ export {
5950
6296
  TokenWasteHotspotSchema,
5951
6297
  addSkillOrSubagent,
5952
6298
  analyzeRepoTokenEconomy,
6299
+ applyMultiSelectKey,
5953
6300
  auditCatalog,
5954
6301
  auditCatalogTokenBudgets,
5955
6302
  auditSkillTokenBudget,
@@ -5964,6 +6311,7 @@ export {
5964
6311
  countWords,
5965
6312
  createCollisionPair,
5966
6313
  createCollisionReport,
6314
+ createMultiSelectState,
5967
6315
  createSkillManifest,
5968
6316
  createSubagentManifest,
5969
6317
  detectInstalledHarnesses,
@@ -5988,6 +6336,7 @@ export {
5988
6336
  formatScaffoldReport,
5989
6337
  formatSyncReport,
5990
6338
  formatTokenEconomyReport,
6339
+ getFilteredTabOptions,
5991
6340
  handleMcpJsonRpcRequest,
5992
6341
  inspectCatalogItem,
5993
6342
  inspectHarnesses,
@@ -6007,6 +6356,7 @@ export {
6007
6356
  renderCommandHelp,
6008
6357
  renderGlobalHelp,
6009
6358
  renderHeaderBanner,
6359
+ renderMultiSelectFrame,
6010
6360
  renderNextSteps,
6011
6361
  renderProgressBar,
6012
6362
  renderSectionHeader,
@@ -6015,6 +6365,7 @@ export {
6015
6365
  renderTokenBar,
6016
6366
  resolveRuntimeTargets,
6017
6367
  routeRepository,
6368
+ runMultiSelectPrompt,
6018
6369
  runQuickstart,
6019
6370
  scaffoldFrameworkItem,
6020
6371
  scoreSkillAgainstProfile,