@skill-map/cli 0.30.0 → 0.31.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.
@@ -0,0 +1,209 @@
1
+ # Tour: plugins-tour
2
+
3
+ Guided tour of the **built-in plugins** that ship with `sm`. Three
4
+ steps: a quick mental model of what bundles are plus a peek at
5
+ the catalogue, then the six extension kinds rounded off by opening
6
+ one bundle to see them in the wild, and finally a deeper drill
7
+ into a single extension (detail view, diagnostic, disable/enable
8
+ toggle). By the end the tester has the mental model and knows
9
+ which verbs reach which surface.
10
+
11
+ ## Precondition check
12
+
13
+ Before announcing the first step, verify the fixture is initialised
14
+ (the cwd has `.claude/agents/master-agent.md`,
15
+ `.claude/skills/master-skill/SKILL.md`, AND `.skill-map/` with
16
+ `settings.json` and `skill-map.db`). Pre-flight already ran
17
+ `sm init --no-scan` and appended the master entries to
18
+ `.skillmapignore`. If any of that is missing, surface the
19
+ bootstrap mismatch ("master-state.yml says we are running, but
20
+ the bootstrap is missing. Run `sm-master` from an empty dir or
21
+ restore the files.") and stop.
22
+
23
+ ## Step `tour-1-intro` — how plugins work (~4 min)
24
+
25
+ **Context**: A short tour of what a plugin is, how they're
26
+ packaged into bundles, and a peek at the four bundles that ship
27
+ pre-installed.
28
+
29
+ > Plugins are how skill-map gets extended. A **plugin** groups one
30
+ > or more **extensions**, the actual code units that run inside
31
+ > the kernel. So when we say "skill-map has a plugin for Claude",
32
+ > what we really mean is "there is a plugin called `claude` that
33
+ > contains one extension (a provider) which knows how to walk
34
+ > `.claude/`".
35
+ >
36
+ > Plugins ship as **bundles**. A bundle is the deployable unit,
37
+ > one directory with a `plugin.json` manifest and the extension
38
+ > code. Two ways they reach your project:
39
+ >
40
+ > 📦 **Built-in bundles**
41
+ > Travel inside the CLI itself. Available the moment you
42
+ > `npm install -g @skill-map/cli`.
43
+ >
44
+ > 📥 **Drop-in bundles**
45
+ > You (your company, or someone else) drop them by hand under
46
+ > `<cwd>/.skill-map/plugins/`. The directory lives inside the
47
+ > project, so a bundle committed here travels with the repo
48
+ > and the rest of the team picks it up on the next pull.
49
+
50
+ > Now let's look at what's actually installed. `sm plugins list`
51
+ > shows every bundle the CLI shipped with. Run it in your second
52
+ > terminal:
53
+
54
+ ```bash
55
+ sm plugins list
56
+ ```
57
+
58
+ > There are the four bundles. The next step zooms into the six
59
+ > kinds of extension that bundles can carry, you'll see at least
60
+ > one of each living inside `core`.
61
+
62
+ Mark `tour-1-intro: done`.
63
+
64
+ ## Step `tour-2-kinds` — the six extension kinds (~5 min)
65
+
66
+ > An extension has a **kind**. The kind tells the kernel where it
67
+ > plugs into the pipeline. There are exactly six kinds:
68
+ >
69
+ > 🗂️ **provider**
70
+ > Decides what kind each `.md` file is. The `claude` provider,
71
+ > for instance, walks `.claude/` and types each file it finds
72
+ > (agent, command, or skill).
73
+ > Examples: `claude`, `gemini`, `agent-skills`.
74
+ >
75
+ > 🔍 **extractor**
76
+ > Reads a node's body and emits structured findings (links,
77
+ > counts, annotations).
78
+ > Example: `markdown-link`, `external-url-counter`, `tools-count`.
79
+ >
80
+ > 🩺 **analyzer**
81
+ > Cross-checks the scan and emits issues plus various
82
+ > detections (errors, warnings, informational signals: broken
83
+ > refs, stale annotations, schema drift, and more).
84
+ > Example: `broken-ref`, `stability`, `unknown-field`.
85
+ >
86
+ > ⚡ **action**
87
+ > Performs a write operation on a node, the graph, or the
88
+ > filesystem. May modify your `.md` files (frontmatter, body)
89
+ > ONLY with your explicit permission.
90
+ > Examples: `bump`, `mark-superseded`.
91
+ >
92
+ > 🎨 **formatter**
93
+ > Renders a result in a specific shape (`sm export --format md`
94
+ > and `--format json`).
95
+ > Example: `ascii`, `json`.
96
+ >
97
+ > 🎣 **hook**
98
+ > Fires on one of 10 lifecycle events (`boot`, `scan.started`,
99
+ > `shutdown`, etc.). `update-check`, for instance, listens on
100
+ > `boot` (throttled to once a day) and prints a banner if a
101
+ > newer skill-map is available on npm.
102
+ > Example: `update-check`.
103
+ >
104
+ > Putting it together: a **bundle** packages one or more
105
+ > **extensions**, each extension has a **kind**, the kind decides
106
+ > where it plugs into the kernel.
107
+ >
108
+ > Heads up: every `sm plugins` verb you'll run in this tour is
109
+ > also available from the UI. From any `sm serve` session, open
110
+ > the **gear icon → Plugins** tab to browse and toggle plugins
111
+ > from there. CLI and UI use the same store, so a change in one
112
+ > is reflected in the other.
113
+
114
+ > Now let's see those six kinds inside a real bundle. Open `core`
115
+ > in your second terminal:
116
+
117
+ ```bash
118
+ sm plugins show core
119
+ ```
120
+
121
+ Expected: a table with 24 rows, each carrying `kind/id@version`.
122
+ You can spot at least one of each of the six kinds you just read
123
+ about, all packed into a single bundle.
124
+
125
+ Mark `tour-2-kinds: done`.
126
+
127
+ ## Step `tour-3-explore` — explore one extension up close (~4 min)
128
+
129
+ **Context**: Drill into a single extension to see its detail,
130
+ run the diagnostic, then toggle one off and back on so you see
131
+ the change persists.
132
+
133
+ > Pick one extension and look at its details. We'll use
134
+ > `core/external-url-counter`, an extractor that counts how many
135
+ > external URLs each node body contains:
136
+
137
+ ```bash
138
+ sm plugins show core/external-url-counter
139
+ ```
140
+
141
+ Expected: a focused detail block for that one extension (header,
142
+ Kind, Version, Stability, Description, Preconditions, Entry).
143
+
144
+ > Now run the diagnostic. The `doctor` verb reports every plugin
145
+ > and extension status in one go: enabled, disabled, load errors,
146
+ > spec compatibility, manifest validity.
147
+
148
+ ```bash
149
+ sm plugins doctor
150
+ ```
151
+
152
+ Expected on a clean machine: `27 enabled · 0 issues · 0 warnings`.
153
+ If any plugin reports a load error, manifest validity issue, or
154
+ spec-compatibility mismatch, `doctor` is the verb that flags it.
155
+
156
+ > Last, toggle one extension off and back on so you see the state
157
+ > persists across CLI invocations. We'll use the same one you
158
+ > inspected above:
159
+
160
+ ```bash
161
+ sm plugins disable core/external-url-counter
162
+ sm plugins doctor
163
+ sm plugins enable core/external-url-counter
164
+ sm plugins doctor
165
+ ```
166
+
167
+ Expected: between the two `doctor` calls, the
168
+ `core/external-url-counter` row flips from `enabled` to
169
+ `disabled` and back. The change persists in the project DB; if
170
+ you restarted `sm`, the disabled state would still be there.
171
+
172
+ Mark `tour-3-explore: done`.
173
+
174
+ ## Tour wrap-up
175
+
176
+ > All set. You now know:
177
+ >
178
+ > - What plugins, extensions, bundles, and the six kinds are.
179
+ > - Four bundles ship pre-installed (`claude`, `gemini`,
180
+ > `agent-skills`, `core`).
181
+ > - How to list, inspect, diagnose, and toggle extensions from
182
+ > the CLI (and the same lives in the UI).
183
+ >
184
+ > If you want to dig deeper, the next menu option takes you into
185
+ > authoring your own plugin and into settings + view-slots. Or
186
+ > if you've seen enough, "I'm done for today" closes us out.
187
+ >
188
+ > Anything weird worth logging? If not, back to the menu.
189
+
190
+ Mark tour `plugins-tour: done` in `master-state.yml`, update the
191
+ matching harness task, return to the menu in `SKILL.md`.
192
+
193
+ ## Reference: how `sm` decides what to load
194
+
195
+ Not for the tester unless they ask. Cheat sheet for the agent:
196
+
197
+ - Built-in plugins live inside the CLI bundle and are always
198
+ discovered first.
199
+ - Project plugins live under `<cwd>/.skill-map/plugins/`; the
200
+ authoring tour uses this path. There is no user / global
201
+ scope, `-g/--global` and `~/.skill-map/plugins/` were removed
202
+ in v0.27.x.
203
+ - Load order: built-in → project (project ids that collide with
204
+ built-in are surfaced by `doctor`).
205
+ - `disable`/`enable` writes the state into the project DB; it
206
+ survives restarts.
207
+ - Escape hatch for one-off probing without committing a plugin to
208
+ the project: pass `--plugin-dir <path>` on the `sm plugins …`
209
+ verb family.
@@ -0,0 +1,170 @@
1
+ # Tour: settings + slots (step library, `settings-*` ids)
2
+
3
+ Step bodies for two tours: option 2 (`settings-and-consent`,
4
+ runs `settings-1-project` and `settings-2-local`) and the
5
+ single shared step `settings-6-contributions` borrowed by
6
+ option 3 (`build-and-configure`). The SKILL.md orchestrator
7
+ dispatches each `settings-*` id here; `authoring-*` ids it
8
+ dispatches to `tour-authoring.md`.
9
+
10
+ ## Precondition check
11
+
12
+ Same as the authoring step library: `.skill-map/` must exist in
13
+ the cwd (pre-flight step 4 of `SKILL.md` ran `sm init --no-scan`
14
+ and appended the master-tutorial's internal entries to
15
+ `.skillmapignore`, so this is the expected state). If
16
+ `.skill-map/` is missing, surface the bootstrap mismatch and
17
+ stop, do not try to re-init mid-tour.
18
+
19
+ ## Step `settings-1-project` — project settings (~2 min)
20
+
21
+ > Every project that runs `sm init` gets a `.skill-map/`
22
+ > directory with two settings files. The first one is
23
+ > `settings.json`, this is the **public** project settings, the
24
+ > file you commit to git.
25
+
26
+ ```bash
27
+ cat .skill-map/settings.json
28
+ ```
29
+
30
+ Expected output on a fresh init:
31
+
32
+ ```json
33
+ {
34
+ "schemaVersion": 1
35
+ }
36
+ ```
37
+
38
+ > Minimal on purpose. The CLI keeps the file lean and only adds
39
+ > keys when you change a setting. Schema version is there so the
40
+ > CLI can migrate the shape forward without surprising you.
41
+ >
42
+ > The settings UI in the browser (the `Settings` tab when `sm` is
43
+ > running) writes back into this file. Anything you change in
44
+ > there ends up here, commit it to share the choice with the
45
+ > team.
46
+
47
+ Mark `settings-1-project: done`.
48
+
49
+ ## Step `settings-2-local` — per-user overrides (~3 min)
50
+
51
+ > The second file is `settings.local.json`, which is **gitignored
52
+ > by default**. It exists for choices that should NOT travel
53
+ > across the team:
54
+ >
55
+ > - Whether you allowed `sm` to create `.sm` companion files in
56
+ > this project (the consent gate from the basic tutorial).
57
+ > - Personal token paths or credentials you do not want to
58
+ > commit.
59
+ > - Local preferences that depend on your dev environment.
60
+
61
+ ```bash
62
+ cat .skill-map/settings.local.json
63
+ ```
64
+
65
+ Expected on a fresh init:
66
+
67
+ ```json
68
+ {}
69
+ ```
70
+
71
+ > Empty until something writes to it. The first thing that
72
+ > typically lands is `allowEditSmFiles: true` after you accept
73
+ > the `.sm` prompt (the consent gate is a per-user, per-project
74
+ > choice, that's why it goes here).
75
+
76
+ Before the demo, give the tester one sentence of context about
77
+ what a `.sm` file actually is (the basic tutorial introduces it
78
+ in passing, here we anchor the concept):
79
+
80
+ > Every `.md` skill-map tracks gets a sibling `.sm` file (e.g.
81
+ > `notes/ideas.sm` next to `notes/ideas.md`) that carries **all
82
+ > of the tool's metadata about that markdown, so your `.md`
83
+ > stays clean and uncluttered**. Version, history, tags,
84
+ > annotations, anything that does not belong in the
85
+ > human-authored body lives in the `.sm`. The `.md` is content
86
+ > you write for Claude or humans, the `.sm` is bookkeeping the
87
+ > tool writes. They are ordinary source files, committed to git,
88
+ > and you'll see them often once you start using `sm bump` /
89
+ > `sm sidecar annotate` day to day.
90
+
91
+ If the tester wants to see it in action: ask them to run
92
+ `sm sidecar annotate notes/ideas.md`, accept the `[Y/n]` prompt
93
+ with `y`, and re-check the file:
94
+
95
+ ```bash
96
+ sm sidecar annotate notes/ideas.md
97
+ cat .skill-map/settings.local.json
98
+ ```
99
+
100
+ Expected: now contains `{"allowEditSmFiles": true}` (plus a
101
+ `notes/ideas.sm` file landed next to the markdown).
102
+
103
+ > The choice stuck. Next time `sm` wants to write a `.sm` in this
104
+ > project, it skips the prompt because your consent is on
105
+ > record. If you delete the file or move to a different project,
106
+ > the prompt comes back.
107
+
108
+ Mark `settings-2-local: done`.
109
+
110
+ ## Step `settings-6-contributions` — watch contributions land (~2 min)
111
+
112
+ > Last step. Let's see a contribution land in the inspector
113
+ > live. The fixture's `master-agent` declares `tools: [Read,
114
+ > Bash, Edit]`, which the `core/tools-count` extractor picks up.
115
+
116
+ If the tester does not have `sm` running, ask them to launch it
117
+ in their second terminal (same drill as the basic tutorial:
118
+ `sm`, copy the link from the output, open the browser, arrange
119
+ the screen). If `sm` is still running, leave it.
120
+
121
+ ```bash
122
+ sm
123
+ ```
124
+
125
+ Once the UI is open, ask the tester to:
126
+
127
+ > Click the `master-agent` node. The inspector opens on the
128
+ > right side. Look at the **header badge cluster** (just under
129
+ > the title): you should see a small chip from `tools-count`
130
+ > showing the value `3`.
131
+ >
132
+ > That chip is a plugin contribution. It landed in the slot
133
+ > `inspector.header.badge.counter`, the renderer is `NodeCounter`
134
+ > (same one your scaffold uses), the payload was `{ value: 3 }`.
135
+
136
+ If the `demo-highlight` plugin from the earlier authoring steps
137
+ of this tour is still installed, point the tester at the
138
+ contribution it emits too:
139
+
140
+ > The `demo-highlight` you scaffolded earlier in this tour also
141
+ > shows up: its chip lands on every node that has a TODO / FIXME
142
+ > / XXX in its body. Click `notes/ideas` to find it.
143
+
144
+ Have the tester change `master-agent`'s `tools` array (add or
145
+ remove one tool), save, and watch the chip refresh.
146
+
147
+ > Same flow as the basic tutorial's live UI: edit the markdown,
148
+ > watch the UI refresh. The difference is that the value flowed
149
+ > through a plugin (`core/tools-count`) and landed in a specific
150
+ > slot (`inspector.header.badge.counter`). You now know the full
151
+ > path from `.md` to UI chip.
152
+
153
+ Have them Ctrl+C the server when done.
154
+
155
+ Mark `settings-6-contributions: done`.
156
+
157
+ ## Reference: where each catalogue lives in the repo
158
+
159
+ Not for the tester unless they ask. Cheat sheet for the agent:
160
+
161
+ - **Slot catalogue (normative)**:
162
+ `spec/schemas/view-slots.schema.json` (enum + payload schemas).
163
+ - **Slot catalogue (UI mirror)**: `ui/src/app/slots/slot-config.ts`
164
+ (layout) and `ui/src/app/slots/slot-renderer-map.ts` (renderer
165
+ binding).
166
+ - **Input-types catalogue (normative)**: `spec/input-types.md`.
167
+ - **Plugin manifest schema**:
168
+ `spec/schemas/plugin-manifest.schema.json`.
169
+ - **Author tutorial**: `spec/plugin-author-guide.md`.
170
+ - **Slot annex for agents**: `context/view-slots.md`.
package/dist/cli.js CHANGED
@@ -2963,7 +2963,7 @@ var UPDATE_CHECK_TEXTS = {
2963
2963
  // package.json
2964
2964
  var package_default = {
2965
2965
  name: "@skill-map/cli",
2966
- version: "0.30.0",
2966
+ version: "0.31.0",
2967
2967
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
2968
2968
  license: "MIT",
2969
2969
  type: "module",
@@ -8081,9 +8081,9 @@ function providerKindFailure(opts, status, fileName, errDescription) {
8081
8081
  }
8082
8082
  };
8083
8083
  }
8084
- function isDirectorySafe(path, statSync11) {
8084
+ function isDirectorySafe(path, statSync12) {
8085
8085
  try {
8086
- return statSync11(path).isDirectory();
8086
+ return statSync12(path).isDirectory();
8087
8087
  } catch {
8088
8088
  return false;
8089
8089
  }
@@ -23748,43 +23748,37 @@ var STUB_COMMANDS = [
23748
23748
  ];
23749
23749
 
23750
23750
  // cli/commands/tutorial.ts
23751
- import { existsSync as existsSync29, readFileSync as readFileSync19 } from "fs";
23752
- import { writeFile as writeFile2 } from "fs/promises";
23751
+ import { cpSync as cpSync2, existsSync as existsSync29, mkdirSync as mkdirSync7, rmSync as rmSync2, statSync as statSync11 } from "fs";
23753
23752
  import { dirname as dirname19, join as join19, resolve as resolve36 } from "path";
23754
23753
  import { fileURLToPath as fileURLToPath6 } from "url";
23755
23754
  import { Command as Command37, Option as Option35 } from "clipanion";
23756
23755
 
23757
23756
  // cli/i18n/tutorial.texts.ts
23758
23757
  var TUTORIAL_TEXTS = {
23759
- // Success, written to stdout after `<cwd>/{{filename}}` is created.
23760
- // Multi-line layout: the two trigger phrases (English / Spanish) are
23761
- // indented and labelled so they're the most visible part of the
23762
- // output. The reminder above them surfaces the SKILL's language
23763
- // policy: the first message the tester writes to Claude sets the
23764
- // tutorial language for the rest of the session.
23765
- /**
23766
- * Success body. `glyph` is wrapped green at the call site; `cwd`
23767
- * renders relative to the user's cwd when it sits underneath. The
23768
- * `English` / `Español` labels print dim, the eye lands on the
23769
- * trigger phrases the user is going to copy / paste.
23770
- */
23771
- written: " {{glyph}} {{filename}} created at {{cwd}}\n\n Open Claude Code in this directory. Your first message sets\n the tutorial language for the rest of the session:\n\n {{enLabel}} run @{{filename}}\n {{esLabel}} ejecut\xE1 @{{filename}}\n",
23758
+ // Success, written to stdout after `<cwd>/{{target}}` is created.
23759
+ // The skill now lives at `.claude/skills/<slug>/`; Claude Code
23760
+ // auto-discovers it on the next boot, so the tester invokes by
23761
+ // speaking a trigger phrase rather than referencing the file path.
23762
+ // English / Spanish triggers are surfaced side by side and the
23763
+ // first phrase the tester types sets the tutorial language for the
23764
+ // rest of the session.
23765
+ 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",
23772
23766
  writtenLabelEn: "English",
23773
23767
  writtenLabelEs: "Espa\xF1ol",
23774
- // Refusal, `{{filename}}` already exists and `--force` was not set.
23768
+ // Refusal, `{{target}}` already exists and `--force` was not set.
23775
23769
  // Goes to stderr, exit code 2 (operational error per spec § Exit codes).
23776
23770
  // Mirrors the success body shape: glyph + headline, then a dim hint
23777
23771
  // line spelling the fix.
23778
- alreadyExists: "{{glyph}} {{filename}} already exists at {{cwd}}\n {{hint}}\n",
23779
- alreadyExistsHint: "Pass `--force` to overwrite.",
23772
+ alreadyExists: "{{glyph}} {{target}} already exists under {{cwd}}\n {{hint}}\n",
23773
+ alreadyExistsHint: "Pass `--force` to overwrite (deletes the existing folder first).",
23780
23774
  // Invalid `variant` positional argument. Goes to stderr, exit code 2.
23781
23775
  // Mirrors `alreadyExists`: glyph + headline + dim hint enumerating the
23782
23776
  // valid values.
23783
23777
  invalidVariant: "{{glyph}} sm tutorial: unknown variant '{{variant}}'\n {{hint}}\n",
23784
23778
  invalidVariantHint: "Valid values: tutorial (default), master.",
23785
- // I/O failure on write or on reading the bundled SKILL source.
23786
- writeFailed: "{{glyph}} sm tutorial: failed to write {{filename}}: {{message}}\n",
23787
- sourceMissing: "{{glyph}} sm tutorial: could not read the bundled tutorial ({{filename}}) from the install.\n {{hint}}\n",
23779
+ // I/O failure on write or on reading the bundled skill source.
23780
+ writeFailed: "{{glyph}} sm tutorial: failed to write {{target}}: {{message}}\n",
23781
+ sourceMissing: "{{glyph}} sm tutorial: could not read the bundled skill payload for {{target}} from the install.\n {{hint}}\n",
23788
23782
  sourceMissingHint: "Reinstall @skill-map/cli or report the bug."
23789
23783
  };
23790
23784
 
@@ -23793,41 +23787,45 @@ var VALID_VARIANTS = ["tutorial", "master"];
23793
23787
  var DEFAULT_VARIANT = "tutorial";
23794
23788
  var VARIANT_SPECS = {
23795
23789
  tutorial: {
23796
- filename: "sm-tutorial.md",
23797
- sourcePath: ".claude/skills/sm-tutorial/SKILL.md",
23798
- bundledName: "sm-tutorial.md"
23790
+ slug: "sm-tutorial",
23791
+ sourceDir: ".claude/skills/sm-tutorial",
23792
+ triggerEn: "start the tutorial",
23793
+ triggerEs: "arranquemos el tutorial"
23799
23794
  },
23800
23795
  master: {
23801
- filename: "sm-master.md",
23802
- sourcePath: ".claude/skills/sm-master/SKILL.md",
23803
- bundledName: "sm-master.md"
23796
+ slug: "sm-master",
23797
+ sourceDir: ".claude/skills/sm-master",
23798
+ triggerEn: "advanced tutorial",
23799
+ triggerEs: "tutorial maestro"
23804
23800
  }
23805
23801
  };
23806
23802
  var TutorialCommand = class extends SmCommand {
23807
23803
  static paths = [["tutorial"]];
23808
23804
  static usage = Command37.Usage({
23809
23805
  category: "Setup",
23810
- description: "Materialize an interactive tester tutorial (sm-tutorial.md or sm-master.md) in the current directory.",
23806
+ description: "Materialize an interactive tester tutorial as a Claude Code skill folder under `<cwd>/.claude/skills/`.",
23811
23807
  details: `
23812
- Drops the canonical SKILL.md content as ./sm-tutorial.md (default)
23813
- or ./sm-master.md (when invoked as \`sm tutorial master\`) so a
23814
- tester can open Claude Code in the cwd and load the file as a
23815
- skill by typing "ejecut\xE1 @sm-tutorial.md" (or "@sm-master.md").
23816
- Top-level only; no subdirectory is created.
23808
+ Drops the canonical skill directory (SKILL.md + any references/
23809
+ sub-folder) under \`<cwd>/.claude/skills/sm-tutorial/\` (default)
23810
+ or \`<cwd>/.claude/skills/sm-master/\` (when invoked as \`sm
23811
+ tutorial master\`). Claude Code auto-discovers the skill the
23812
+ next time it boots in this directory; the tester invokes it by
23813
+ speaking one of its trigger phrases.
23817
23814
 
23818
23815
  Does NOT require an initialized .skill-map/ project. Refuses to
23819
- overwrite the target file unless --force is passed. Valid values
23820
- for the positional argument are: tutorial (default), master.
23816
+ overwrite the target directory unless --force is passed. Valid
23817
+ values for the positional argument are: tutorial (default),
23818
+ master.
23821
23819
  `,
23822
23820
  examples: [
23823
- ["Materialize the basic tutorial in the cwd", "$0 tutorial"],
23824
- ["Materialize the advanced tutorial in the cwd", "$0 tutorial master"],
23825
- ["Overwrite an existing target file", "$0 tutorial --force"]
23821
+ ["Materialize the basic tutorial skill in the cwd", "$0 tutorial"],
23822
+ ["Materialize the advanced tutorial skill in the cwd", "$0 tutorial master"],
23823
+ ["Overwrite an existing target directory", "$0 tutorial --force"]
23826
23824
  ]
23827
23825
  });
23828
23826
  variant = Option35.String({ required: false });
23829
23827
  force = Option35.Boolean("--force", false, {
23830
- description: "Overwrite an existing target file without prompting."
23828
+ description: "Overwrite an existing target directory without prompting."
23831
23829
  });
23832
23830
  async run() {
23833
23831
  const ctx = defaultRuntimeContext();
@@ -23847,38 +23845,41 @@ var TutorialCommand = class extends SmCommand {
23847
23845
  }
23848
23846
  const variant = rawVariant ?? DEFAULT_VARIANT;
23849
23847
  const spec = VARIANT_SPECS[variant];
23850
- const target = join19(ctx.cwd, spec.filename);
23851
- if (await pathExists(target) && !this.force) {
23848
+ const targetDir = join19(ctx.cwd, ".claude", "skills", spec.slug);
23849
+ const targetDisplay = `.claude/skills/${spec.slug}/`;
23850
+ if (existsSync29(targetDir) && !this.force) {
23852
23851
  this.printer.error(
23853
23852
  tx(TUTORIAL_TEXTS.alreadyExists, {
23854
23853
  glyph: errGlyph,
23855
- filename: spec.filename,
23854
+ target: targetDisplay,
23856
23855
  cwd: stderrAnsi.dim(displayCwd(ctx.cwd)),
23857
23856
  hint: stderrAnsi.dim(TUTORIAL_TEXTS.alreadyExistsHint)
23858
23857
  })
23859
23858
  );
23860
23859
  return ExitCode.Error;
23861
23860
  }
23862
- let body;
23861
+ let sourceDir;
23863
23862
  try {
23864
- body = loadBundledTutorialText(variant);
23863
+ sourceDir = resolveSkillSourceDir(variant);
23865
23864
  } catch {
23866
23865
  this.printer.error(
23867
23866
  tx(TUTORIAL_TEXTS.sourceMissing, {
23868
23867
  glyph: errGlyph,
23869
- filename: spec.filename,
23868
+ target: targetDisplay,
23870
23869
  hint: stderrAnsi.dim(TUTORIAL_TEXTS.sourceMissingHint)
23871
23870
  })
23872
23871
  );
23873
23872
  return ExitCode.Error;
23874
23873
  }
23875
23874
  try {
23876
- await writeFile2(target, body);
23875
+ rmSync2(targetDir, { recursive: true, force: true });
23876
+ mkdirSync7(dirname19(targetDir), { recursive: true });
23877
+ cpSync2(sourceDir, targetDir, { recursive: true });
23877
23878
  } catch (err) {
23878
23879
  this.printer.error(
23879
23880
  tx(TUTORIAL_TEXTS.writeFailed, {
23880
23881
  glyph: errGlyph,
23881
- filename: spec.filename,
23882
+ target: targetDisplay,
23882
23883
  message: formatErrorMessage(err)
23883
23884
  })
23884
23885
  );
@@ -23894,10 +23895,13 @@ var TutorialCommand = class extends SmCommand {
23894
23895
  this.printer.data(
23895
23896
  tx(TUTORIAL_TEXTS.written, {
23896
23897
  glyph: ansi.green("\u2713"),
23897
- filename: spec.filename,
23898
+ slug: spec.slug,
23899
+ target: targetDisplay,
23898
23900
  cwd: ansi.dim(displayCwd(ctx.cwd)),
23899
23901
  enLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEn),
23900
- esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs)
23902
+ esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs),
23903
+ enTrigger: spec.triggerEn,
23904
+ esTrigger: spec.triggerEs
23901
23905
  })
23902
23906
  );
23903
23907
  return ExitCode.Ok;
@@ -23911,31 +23915,29 @@ function displayCwd(cwd) {
23911
23915
  if (segments.length === 0) return "./";
23912
23916
  return `./${segments[segments.length - 1]}/`;
23913
23917
  }
23914
- var cachedTutorials = /* @__PURE__ */ new Map();
23915
- function loadBundledTutorialText(variant) {
23916
- const cached = cachedTutorials.get(variant);
23918
+ var cachedSourceDirs = /* @__PURE__ */ new Map();
23919
+ function resolveSkillSourceDir(variant) {
23920
+ const cached = cachedSourceDirs.get(variant);
23917
23921
  if (cached !== void 0) return cached;
23918
- const body = readTutorialFromDisk(variant);
23919
- cachedTutorials.set(variant, body);
23920
- return body;
23921
- }
23922
- function readTutorialFromDisk(variant) {
23923
23922
  const spec = VARIANT_SPECS[variant];
23924
23923
  const here = dirname19(fileURLToPath6(import.meta.url));
23925
23924
  const candidates = [
23926
- // dev: src/cli/commands/ → repo-root .claude/skills/<slug>/SKILL.md
23927
- resolve36(here, "../../..", spec.sourcePath),
23928
- // bundled: dist/cli.js → dist/cli/tutorial/<filename> (sibling)
23929
- resolve36(here, "cli/tutorial", spec.bundledName),
23930
- // bundled fallback: any-depth → cli/tutorial/<filename>
23931
- resolve36(here, "../cli/tutorial", spec.bundledName)
23925
+ // dev: src/cli/commands/ → repo-root .claude/skills/<slug>/
23926
+ resolve36(here, "../../..", spec.sourceDir),
23927
+ // bundled: dist/cli.js → dist/cli/tutorial/<slug> (sibling)
23928
+ resolve36(here, "cli/tutorial", spec.slug),
23929
+ // bundled fallback: any-depth → cli/tutorial/<slug>
23930
+ resolve36(here, "../cli/tutorial", spec.slug)
23932
23931
  ];
23933
23932
  for (const candidate of candidates) {
23934
- if (existsSync29(candidate)) {
23935
- return readFileSync19(candidate, "utf8");
23933
+ if (existsSync29(candidate) && statSync11(candidate).isDirectory()) {
23934
+ cachedSourceDirs.set(variant, candidate);
23935
+ return candidate;
23936
23936
  }
23937
23937
  }
23938
- throw new Error(`SKILL.md not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`);
23938
+ throw new Error(
23939
+ `skill source directory not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`
23940
+ );
23939
23941
  }
23940
23942
 
23941
23943
  // cli/commands/version.ts