@skill-map/cli 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -462,6 +462,11 @@ var claudeProvider = {
462
462
  // a Claude Code project. Provider-owned (replaces the old central
463
463
  // detection table in `src/core/config/active-provider.ts`).
464
464
  detect: { markers: [".claude"] },
465
+ // Authoring target for `sm tutorial`: Claude Code discovers skills under
466
+ // `.claude/skills/<name>/SKILL.md`, so a materialised tutorial folder
467
+ // lands there. This is the WRITE side of the territory the `classify`
468
+ // below READS.
469
+ scaffold: { skillDir: ".claude/skills" },
465
470
  // Vendor provider: Claude Code only reads its own `.claude/` territory
466
471
  // and ignores `.codex/` / Antigravity layouts at runtime. Gating the
467
472
  // classifier behind the active lens prevents the walker from inventing
@@ -1147,6 +1152,13 @@ var agentSkillsProvider = {
1147
1152
  // (Antigravity adopted the open standard), so such projects auto-detect
1148
1153
  // as this universal lens. Provider-owned.
1149
1154
  detect: { markers: [".agents"] },
1155
+ // Authoring target for `sm tutorial`: the open standard discovers skills
1156
+ // under `.agents/skills/<name>/SKILL.md`. The same path is consumed by
1157
+ // Antigravity (adopted the standard rather than a `.gemini/` layout) and
1158
+ // OpenAI Codex (skills mirror the open standard), so `aka` surfaces both
1159
+ // names in the destination prompt to orient testers on those agents.
1160
+ // `aka` is display-only, `--for` still matches the `agent-skills` id.
1161
+ scaffold: { skillDir: ".agents/skills", aka: ["Antigravity", "OpenAI Codex"] },
1150
1162
  read: { extensions: [".md"], parser: "frontmatter-yaml" },
1151
1163
  kinds: {
1152
1164
  skill: {
@@ -3948,7 +3960,7 @@ var UPDATE_CHECK_TEXTS = {
3948
3960
  // package.json
3949
3961
  var package_default = {
3950
3962
  name: "@skill-map/cli",
3951
- version: "0.44.0",
3963
+ version: "0.45.0",
3952
3964
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
3953
3965
  license: "MIT",
3954
3966
  type: "module",
@@ -4418,40 +4430,40 @@ var updateCheckHook = {
4418
4430
  };
4419
4431
 
4420
4432
  // plugins/built-ins.ts
4421
- var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.44.0" };
4422
- var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.44.0" };
4423
- var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.44.0" };
4424
- var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.44.0" };
4425
- var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.44.0" };
4426
- var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.44.0" };
4427
- var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.44.0" };
4428
- var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.44.0" };
4429
- var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.44.0" };
4430
- var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.44.0" };
4431
- var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.44.0" };
4432
- var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.44.0" };
4433
- var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.44.0" };
4434
- var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.44.0" };
4435
- var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.44.0" };
4436
- var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.44.0" };
4437
- var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.44.0" };
4438
- var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.44.0" };
4439
- var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.44.0" };
4440
- var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.44.0" };
4441
- var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.44.0" };
4442
- var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.44.0" };
4443
- var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.44.0" };
4444
- var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.44.0" };
4445
- var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.44.0" };
4446
- var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.44.0" };
4447
- var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.44.0" };
4448
- var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.44.0" };
4449
- var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.44.0" };
4450
- var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.44.0" };
4451
- var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.44.0" };
4452
- var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.44.0" };
4453
- var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.44.0" };
4454
- var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.44.0" };
4433
+ var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.45.0" };
4434
+ var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.45.0" };
4435
+ var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.45.0" };
4436
+ var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.45.0" };
4437
+ var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.45.0" };
4438
+ var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.45.0" };
4439
+ var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.45.0" };
4440
+ var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.45.0" };
4441
+ var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.45.0" };
4442
+ var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.45.0" };
4443
+ var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.45.0" };
4444
+ var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.45.0" };
4445
+ var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.45.0" };
4446
+ var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.45.0" };
4447
+ var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.45.0" };
4448
+ var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.45.0" };
4449
+ var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.45.0" };
4450
+ var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.45.0" };
4451
+ var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.45.0" };
4452
+ var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.45.0" };
4453
+ var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.45.0" };
4454
+ var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.45.0" };
4455
+ var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.45.0" };
4456
+ var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.45.0" };
4457
+ var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.45.0" };
4458
+ var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.45.0" };
4459
+ var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.45.0" };
4460
+ var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.45.0" };
4461
+ var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.45.0" };
4462
+ var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.45.0" };
4463
+ var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.45.0" };
4464
+ var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.45.0" };
4465
+ var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.45.0" };
4466
+ var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.45.0" };
4455
4467
  var builtInBundles = [
4456
4468
  {
4457
4469
  id: "claude",
@@ -17000,13 +17012,13 @@ var SCAN_RUNNER_TEXTS = {
17000
17012
  * Active-provider bootstrap: filesystem auto-detect found exactly
17001
17013
  * one marker and persisted the detected id to project settings.
17002
17014
  */
17003
- activeProviderAutodetected: "Auto-detected activeProvider = {{id}} from filesystem markers; persisted to .skill-map/settings.json.",
17015
+ activeProviderAutodetected: "Auto-detected activeProvider = {{id}} from filesystem markers; persisted to .skill-map/settings.json.\n",
17004
17016
  /**
17005
17017
  * Active-provider bootstrap: persistence of the auto-detected id
17006
17018
  * failed (permission, disk full, etc). Non-fatal; the scan
17007
17019
  * continues with the value in memory for this run.
17008
17020
  */
17009
- activeProviderPersistFailed: "Auto-detected activeProvider = {{id}}, but persisting to .skill-map/settings.json failed: {{message}}. Run `sm config set activeProvider {{id}}` manually to make the choice sticky.",
17021
+ activeProviderPersistFailed: "Auto-detected activeProvider = {{id}}, but persisting to .skill-map/settings.json failed: {{message}}. Run `sm config set activeProvider {{id}}` manually to make the choice sticky.\n",
17010
17022
  /**
17011
17023
  * Active-provider bootstrap: ambiguous detection (2+ markers
17012
17024
  * present), interactive prompt header. Follows
@@ -17699,7 +17711,7 @@ var INIT_TEXTS = {
17699
17711
  * scan never inherits stale rows from a pre-current schema.
17700
17712
  */
17701
17713
  removedPriorDb: "{{glyph}} Removed prior DB at {{path}} (--force reset)\n",
17702
- runningFirstScan: "Running first scan...\n",
17714
+ runningFirstScan: "\nRunning first scan...\n",
17703
17715
  configLoadFailure: "{{glyph}} sm init: {{message}}\n",
17704
17716
  scanFailed: "{{glyph}} sm init: scan failed: {{message}}\n",
17705
17717
  firstScanSummary: "{{glyph}} First scan: {{nodes}} node{{nodesPlural}}, {{links}} link{{linksPlural}}, {{issues}} issue{{issuesPlural}}.\n",
@@ -28035,31 +28047,54 @@ var STUB_COMMANDS = [
28035
28047
  ];
28036
28048
 
28037
28049
  // cli/commands/tutorial.ts
28038
- import { cpSync as cpSync2, existsSync as existsSync31, mkdirSync as mkdirSync6, rmSync as rmSync2, statSync as statSync11 } from "fs";
28050
+ import { cpSync as cpSync2, existsSync as existsSync31, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync11 } from "fs";
28039
28051
  import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
28052
+ import { createInterface as createInterface5 } from "readline";
28040
28053
  import { fileURLToPath as fileURLToPath7 } from "url";
28041
28054
  import { Command as Command37, Option as Option35 } from "clipanion";
28042
28055
 
28043
28056
  // cli/i18n/tutorial.texts.ts
28044
28057
  var TUTORIAL_TEXTS = {
28045
28058
  // Success, written to stdout after `<cwd>/{{target}}` is created.
28046
- // The skill now lives at `.claude/skills/<slug>/`; Claude Code
28059
+ // The skill lives at `<skillDir>/<slug>/`; the tester's agent
28047
28060
  // auto-discovers it on the next boot, so the tester invokes by
28048
28061
  // speaking a trigger phrase rather than referencing the file path.
28049
- // English / Spanish triggers are surfaced side by side and the
28050
- // first phrase the tester types sets the tutorial language for the
28051
- // rest of the session.
28052
- written: " {{glyph}} Skill `{{slug}}` materialized at {{target}} (under {{cwd}})\n\n Open Claude Code in this directory. The skill is auto-\n discovered; invoke it with one of its trigger phrases. The\n first message you type sets the tutorial language for the\n rest of the session:\n\n {{enLabel}} {{enTrigger}}\n {{esLabel}} {{esTrigger}}\n",
28062
+ // Message stays host-agnostic ("your coding agent") because the
28063
+ // destination Provider varies. English / Spanish triggers are
28064
+ // surfaced side by side and the first phrase the tester types sets
28065
+ // the tutorial language for the rest of the session.
28066
+ written: " {{glyph}} Skill `{{slug}}` materialized at {{target}} (under {{cwd}}, for {{provider}})\n\n Open your coding agent in this directory. The skill is auto-\n discovered; invoke it with one of its trigger phrases. The\n first message you type sets the tutorial language for the\n rest of the session:\n\n {{enLabel}} {{enTrigger}}\n {{esLabel}} {{esTrigger}}\n",
28053
28067
  writtenLabelEn: "English",
28054
28068
  writtenLabelEs: "Espa\xF1ol",
28055
- // Refusal, `{{target}}` already exists and `--force` was not set.
28056
- // Goes to stderr, exit code 2 (operational error per spec § Exit codes).
28057
- // Mirrors the success body shape: glyph + headline, then a dim hint
28058
- // line spelling the fix.
28059
- alreadyExists: "{{glyph}} {{target}} already exists under {{cwd}}\n {{hint}}\n",
28060
- alreadyExistsHint: "Pass `--force` to overwrite (deletes the existing folder first).",
28069
+ // Destination-provider prompt (interactive stdin, no `--for`). Header
28070
+ // uses a yellow `?` glyph; options are a numbered list of provider
28071
+ // label (with any `aka` agents in parentheses) + skill directory, with
28072
+ // a `(default)` marker on the first option (Claude). The input line
28073
+ // accepts a number, a provider id, or an empty answer (which takes the
28074
+ // default).
28075
+ promptHeader: "{{glyph}} Which agent should host the tutorial skill?",
28076
+ promptOption: " {{index}}) {{label}}: {{skillDir}}{{marker}}",
28077
+ promptDefaultMarker: " (default)",
28078
+ promptInput: " Enter the number or provider id [default {{index}}]: ",
28079
+ // Prompt answer matched neither an index nor an id. Goes to stderr,
28080
+ // exit code 2. Mirrors the error shape: glyph + headline + dim hint.
28081
+ promptInvalid: "{{glyph}} sm tutorial: that is not one of the listed providers\n {{hint}}\n",
28082
+ // `--for` named a provider that does not exist or declares no
28083
+ // `scaffold.skillDir`. Goes to stderr, exit code 2.
28084
+ forUnknown: "{{glyph}} sm tutorial: unknown provider '{{provider}}' for --for\n {{hint}}\n",
28085
+ forUnknownHint: "Valid providers: {{ids}}.",
28086
+ // Defensive: no built-in provider declares a scaffold target. Should
28087
+ // never happen (claude always does). Goes to stderr, exit code 2.
28088
+ noTargets: "{{glyph}} sm tutorial: no provider declares a skill scaffold target.\n",
28089
+ // Refusal, the cwd is not empty and `--force` was not set. Goes to
28090
+ // stderr, exit code 2 (operational error per spec § Exit codes). The
28091
+ // tutorial seeds a self-contained scenario into the cwd, so it needs
28092
+ // an empty directory; the hint spells the two ways forward. Mirrors
28093
+ // the error shape: glyph + headline + dim hint.
28094
+ notEmpty: "{{glyph}} sm tutorial: the current directory is not empty (found {{entries}})\n {{hint}}\n",
28095
+ notEmptyHint: "sm tutorial seeds a self-contained scenario; run it in a fresh empty directory, or pass `--force` to use this one anyway.",
28061
28096
  // Invalid `variant` positional argument. Goes to stderr, exit code 2.
28062
- // Mirrors `alreadyExists`: glyph + headline + dim hint enumerating the
28097
+ // Mirrors the error shape: glyph + headline + dim hint enumerating the
28063
28098
  // valid values.
28064
28099
  invalidVariant: "{{glyph}} sm tutorial: unknown variant '{{variant}}'\n {{hint}}\n",
28065
28100
  invalidVariantHint: "Valid values: tutorial (default), master.",
@@ -28111,6 +28146,13 @@ var TutorialCommand = class extends SmCommand {
28111
28146
  ]
28112
28147
  });
28113
28148
  variant = Option35.String({ required: false });
28149
+ // Named `forProvider`, NOT `for` (reserved word). The CLI surface stays
28150
+ // `--for`; selects the destination Provider whose `scaffold.skillDir`
28151
+ // the skill is materialised under, skipping the interactive prompt.
28152
+ forProvider = Option35.String("--for", {
28153
+ required: false,
28154
+ description: "Destination provider id (e.g. claude, agent-skills). Skips the prompt."
28155
+ });
28114
28156
  force = Option35.Boolean("--force", false, {
28115
28157
  description: "Overwrite an existing target directory without prompting."
28116
28158
  });
@@ -28119,32 +28161,24 @@ var TutorialCommand = class extends SmCommand {
28119
28161
  const stderr = this.context.stderr;
28120
28162
  const stderrAnsi = this.ansiFor("stderr");
28121
28163
  const errGlyph = stderrAnsi.red("\u2715");
28122
- const rawVariant = this.variant;
28123
- if (rawVariant !== void 0 && !isTutorialVariant(rawVariant)) {
28124
- this.printer.error(
28125
- tx(TUTORIAL_TEXTS.invalidVariant, {
28126
- glyph: errGlyph,
28127
- variant: rawVariant,
28128
- hint: stderrAnsi.dim(TUTORIAL_TEXTS.invalidVariantHint)
28129
- })
28130
- );
28131
- return ExitCode.Error;
28132
- }
28133
- const variant = rawVariant ?? DEFAULT_VARIANT;
28164
+ const variant = this.resolveVariantArg(errGlyph, stderrAnsi);
28165
+ if (variant === null) return ExitCode.Error;
28134
28166
  const spec = VARIANT_SPECS[variant];
28135
- const targetDir = join21(ctx.cwd, ".claude", "skills", spec.slug);
28136
- const targetDisplay = `.claude/skills/${spec.slug}/`;
28137
- if (existsSync31(targetDir) && !this.force) {
28167
+ if (!this.force && !isDirEmpty(ctx.cwd)) {
28138
28168
  this.printer.error(
28139
- tx(TUTORIAL_TEXTS.alreadyExists, {
28169
+ tx(TUTORIAL_TEXTS.notEmpty, {
28140
28170
  glyph: errGlyph,
28141
- target: targetDisplay,
28142
- cwd: stderrAnsi.dim(displayCwd(ctx.cwd)),
28143
- hint: stderrAnsi.dim(TUTORIAL_TEXTS.alreadyExistsHint)
28171
+ entries: listCwdEntries(ctx.cwd),
28172
+ hint: stderrAnsi.dim(TUTORIAL_TEXTS.notEmptyHint)
28144
28173
  })
28145
28174
  );
28146
28175
  return ExitCode.Error;
28147
28176
  }
28177
+ const targets = listScaffoldTargets();
28178
+ const target = await this.resolveScaffoldTarget(targets, stderrAnsi, errGlyph);
28179
+ if (target === null) return ExitCode.Error;
28180
+ const targetDir = join21(ctx.cwd, target.skillDir, spec.slug);
28181
+ const targetDisplay = `${target.skillDir}/${spec.slug}/`;
28148
28182
  let sourceDir;
28149
28183
  try {
28150
28184
  sourceDir = resolveSkillSourceDir(variant);
@@ -28184,6 +28218,7 @@ var TutorialCommand = class extends SmCommand {
28184
28218
  glyph: ansi.green("\u2713"),
28185
28219
  slug: spec.slug,
28186
28220
  target: targetDisplay,
28221
+ provider: ansi.dim(target.label),
28187
28222
  cwd: ansi.dim(displayCwd(ctx.cwd)),
28188
28223
  enLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEn),
28189
28224
  esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs),
@@ -28193,15 +28228,154 @@ var TutorialCommand = class extends SmCommand {
28193
28228
  );
28194
28229
  return ExitCode.Ok;
28195
28230
  }
28231
+ /**
28232
+ * Validate the positional `variant` arg against the closed catalog.
28233
+ * Returns the resolved variant, or `null` after printing the
28234
+ * `invalidVariant` error (caller exits non-zero). Extracted from
28235
+ * `run()` to keep its cyclomatic complexity within the lint budget.
28236
+ */
28237
+ resolveVariantArg(errGlyph, stderrAnsi) {
28238
+ const rawVariant = this.variant;
28239
+ if (rawVariant !== void 0 && !isTutorialVariant(rawVariant)) {
28240
+ this.printer.error(
28241
+ tx(TUTORIAL_TEXTS.invalidVariant, {
28242
+ glyph: errGlyph,
28243
+ variant: rawVariant,
28244
+ hint: stderrAnsi.dim(TUTORIAL_TEXTS.invalidVariantHint)
28245
+ })
28246
+ );
28247
+ return null;
28248
+ }
28249
+ return rawVariant ?? DEFAULT_VARIANT;
28250
+ }
28251
+ /**
28252
+ * Resolve the destination Provider. Precedence:
28253
+ * 1. `--for <id>` (validated against the scaffold-capable catalog).
28254
+ * 2. Interactive stdin → numbered prompt defaulting to Claude (the
28255
+ * first entry); an empty answer accepts it.
28256
+ * 3. Non-interactive stdin → Claude (the first entry), so the verb
28257
+ * stays scriptable.
28258
+ * The verb requires an empty cwd, so there is no marker to detect: the
28259
+ * default is always the first scaffold-capable Provider (Claude).
28260
+ * Returns `null` after printing an error (caller exits non-zero).
28261
+ */
28262
+ async resolveScaffoldTarget(targets, stderrAnsi, errGlyph) {
28263
+ if (targets.length === 0) {
28264
+ this.printer.error(tx(TUTORIAL_TEXTS.noTargets, { glyph: errGlyph }));
28265
+ return null;
28266
+ }
28267
+ const requested = this.forProvider;
28268
+ if (requested !== void 0) {
28269
+ const found = targets.find((t) => t.id === requested);
28270
+ if (found === void 0) {
28271
+ this.printer.error(
28272
+ tx(TUTORIAL_TEXTS.forUnknown, {
28273
+ glyph: errGlyph,
28274
+ provider: requested,
28275
+ hint: stderrAnsi.dim(
28276
+ tx(TUTORIAL_TEXTS.forUnknownHint, { ids: targets.map((t) => t.id).join(", ") })
28277
+ )
28278
+ })
28279
+ );
28280
+ return null;
28281
+ }
28282
+ return found;
28283
+ }
28284
+ const defaultIndex = 0;
28285
+ const def = targets[defaultIndex];
28286
+ const stdin = this.context.stdin;
28287
+ if (targets.length === 1 || stdin.isTTY !== true) return def;
28288
+ const stderr = this.context.stderr;
28289
+ const picked = await promptForTarget(
28290
+ targets,
28291
+ defaultIndex,
28292
+ stdin,
28293
+ stderr,
28294
+ stderrAnsi.yellow("?")
28295
+ );
28296
+ if (picked === null) {
28297
+ this.printer.error(
28298
+ tx(TUTORIAL_TEXTS.promptInvalid, {
28299
+ glyph: errGlyph,
28300
+ hint: stderrAnsi.dim(
28301
+ tx(TUTORIAL_TEXTS.forUnknownHint, { ids: targets.map((t) => t.id).join(", ") })
28302
+ )
28303
+ })
28304
+ );
28305
+ return null;
28306
+ }
28307
+ return picked;
28308
+ }
28196
28309
  };
28197
28310
  function isTutorialVariant(value) {
28198
28311
  return VALID_VARIANTS.includes(value);
28199
28312
  }
28313
+ function toScaffoldTarget(provider) {
28314
+ const scaffold = provider.scaffold;
28315
+ if (!scaffold || !scaffold.skillDir) return null;
28316
+ return {
28317
+ id: provider.id,
28318
+ label: provider.presentation.label,
28319
+ skillDir: scaffold.skillDir,
28320
+ aka: scaffold.aka ?? []
28321
+ };
28322
+ }
28323
+ function listScaffoldTargets() {
28324
+ const out = [];
28325
+ for (const provider of builtIns().providers) {
28326
+ const target = toScaffoldTarget(provider);
28327
+ if (target !== null) out.push(target);
28328
+ }
28329
+ return out;
28330
+ }
28331
+ function labelWithAka(target) {
28332
+ return target.aka.length > 0 ? `${target.label} (${target.aka.join(", ")})` : target.label;
28333
+ }
28334
+ async function promptForTarget(targets, defaultIndex, stdin, stderr, glyph) {
28335
+ const lines = [tx(TUTORIAL_TEXTS.promptHeader, { glyph })];
28336
+ for (let i = 0; i < targets.length; i += 1) {
28337
+ const t = targets[i];
28338
+ lines.push(
28339
+ tx(TUTORIAL_TEXTS.promptOption, {
28340
+ index: i + 1,
28341
+ label: labelWithAka(t),
28342
+ skillDir: `${t.skillDir}/`,
28343
+ marker: i === defaultIndex ? TUTORIAL_TEXTS.promptDefaultMarker : ""
28344
+ })
28345
+ );
28346
+ }
28347
+ stderr.write(lines.join("\n") + "\n");
28348
+ const rl = createInterface5({ input: stdin, output: stderr });
28349
+ try {
28350
+ const answer = await new Promise(
28351
+ (resolveP) => rl.question(tx(TUTORIAL_TEXTS.promptInput, { index: defaultIndex + 1 }), resolveP)
28352
+ );
28353
+ const trimmed = answer.trim();
28354
+ if (trimmed === "") return targets[defaultIndex];
28355
+ const asNumber = Number.parseInt(trimmed, 10);
28356
+ if (!Number.isNaN(asNumber) && asNumber >= 1 && asNumber <= targets.length) {
28357
+ return targets[asNumber - 1];
28358
+ }
28359
+ const byId = targets.find((t) => t.id.toLowerCase() === trimmed.toLowerCase());
28360
+ return byId ?? null;
28361
+ } finally {
28362
+ rl.close();
28363
+ }
28364
+ }
28200
28365
  function displayCwd(cwd) {
28201
28366
  const segments = cwd.split("/").filter((s) => s.length > 0);
28202
28367
  if (segments.length === 0) return "./";
28203
28368
  return `./${segments[segments.length - 1]}/`;
28204
28369
  }
28370
+ function isDirEmpty(dir) {
28371
+ return readdirSync10(dir).length === 0;
28372
+ }
28373
+ function listCwdEntries(dir) {
28374
+ const entries = readdirSync10(dir).sort();
28375
+ const shown = entries.slice(0, 5);
28376
+ const more = entries.length > shown.length ? ", ..." : "";
28377
+ return shown.join(", ") + more;
28378
+ }
28205
28379
  var cachedSourceDirs = /* @__PURE__ */ new Map();
28206
28380
  function resolveSkillSourceDir(variant) {
28207
28381
  const cached = cachedSourceDirs.get(variant);