@skill-map/cli 0.29.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.
- package/dist/cli/tutorial/sm-master/references/fixture-templates.md +206 -0
- package/dist/cli/tutorial/sm-master/references/tour-authoring.md +296 -0
- package/dist/cli/tutorial/sm-master/references/tour-plugins.md +209 -0
- package/dist/cli/tutorial/sm-master/references/tour-settings.md +170 -0
- package/dist/cli.js +220 -86
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.js +1 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-DMSZOXER.js → chunk-LNRQ7VKE.js} +1 -1
- package/dist/ui/{chunk-EFKSD7PT.js → chunk-PVKIT7DW.js} +4 -4
- package/dist/ui/chunk-YQIWQVJ6.js +317 -0
- package/dist/ui/favicon-matrix.svg +15 -0
- package/dist/ui/index.html +12 -2
- package/dist/ui/main-N23S66NJ.js +2 -0
- package/dist/ui/{styles-CDN434T2.css → styles-2WO3KNOY.css} +1 -1
- package/package.json +2 -2
- package/dist/ui/chunk-BL7KARTN.js +0 -317
- package/dist/ui/main-LGW7AYEA.js +0 -2
- /package/dist/cli/tutorial/{sm-master.md → sm-master/SKILL.md} +0 -0
- /package/dist/cli/tutorial/{sm-tutorial.md → sm-tutorial/SKILL.md} +0 -0
package/dist/cli.js
CHANGED
|
@@ -775,6 +775,53 @@ function link(source, target) {
|
|
|
775
775
|
};
|
|
776
776
|
}
|
|
777
777
|
|
|
778
|
+
// kernel/util/strip-code-blocks.ts
|
|
779
|
+
var FENCE_RE = /^(?<indent> {0,3})(?<fence>`{3,}|~{3,})/;
|
|
780
|
+
function stripCodeBlocks(input) {
|
|
781
|
+
if (!input) return input;
|
|
782
|
+
const fenceless = stripFences(input);
|
|
783
|
+
return stripInline(fenceless);
|
|
784
|
+
}
|
|
785
|
+
function stripFences(input) {
|
|
786
|
+
const out = [];
|
|
787
|
+
const lines = input.split("\n");
|
|
788
|
+
let openFence = null;
|
|
789
|
+
for (const line of lines) {
|
|
790
|
+
if (openFence) {
|
|
791
|
+
const closer = matchClosingFence(line, openFence);
|
|
792
|
+
if (closer) {
|
|
793
|
+
out.push(blank(line));
|
|
794
|
+
openFence = null;
|
|
795
|
+
} else {
|
|
796
|
+
out.push(blank(line));
|
|
797
|
+
}
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
const open = FENCE_RE.exec(line);
|
|
801
|
+
if (open?.groups) {
|
|
802
|
+
openFence = open.groups["fence"];
|
|
803
|
+
out.push(blank(line));
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
out.push(line);
|
|
807
|
+
}
|
|
808
|
+
return out.join("\n");
|
|
809
|
+
}
|
|
810
|
+
function matchClosingFence(line, openFence) {
|
|
811
|
+
const m = FENCE_RE.exec(line);
|
|
812
|
+
if (!m?.groups) return false;
|
|
813
|
+
const fence = m.groups["fence"];
|
|
814
|
+
return fence[0] === openFence[0] && fence.length >= openFence.length;
|
|
815
|
+
}
|
|
816
|
+
function stripInline(input) {
|
|
817
|
+
return input.replace(/(`+)([\s\S]*?)\1/g, (_full, ticks, body) => {
|
|
818
|
+
return ticks.replace(/`/g, " ") + blank(body) + ticks.replace(/`/g, " ");
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
function blank(s) {
|
|
822
|
+
return s.replace(/[^\s]/g, " ");
|
|
823
|
+
}
|
|
824
|
+
|
|
778
825
|
// kernel/trigger-normalize.ts
|
|
779
826
|
function normalizeTrigger(source) {
|
|
780
827
|
let out = source.normalize("NFD");
|
|
@@ -787,21 +834,43 @@ function normalizeTrigger(source) {
|
|
|
787
834
|
|
|
788
835
|
// plugins/core/extractors/at-directive/index.ts
|
|
789
836
|
var ID2 = "at-directive";
|
|
790
|
-
var AT_RE = /(?:^|[^A-Za-z0-9_@])(@[a-z0-9][a-z0-9_
|
|
837
|
+
var AT_RE = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-z0-9_])?(?::[a-z0-9][a-z0-9_-]*)?)/gi;
|
|
838
|
+
var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
|
|
791
839
|
var atDirectiveExtractor = {
|
|
792
840
|
id: ID2,
|
|
793
841
|
pluginId: "core",
|
|
794
842
|
kind: "extractor",
|
|
795
843
|
version: "1.0.0",
|
|
796
|
-
description: "Detects
|
|
844
|
+
description: "Detects `@<token>` directives in a node's body. A bare handle (e.g. `@team`) becomes a `mentions` link; a file-flavoured token (e.g. `@docs/api.md`, `@./readme.md`) becomes a `references` link, matching how Claude Code / Gemini CLI / Cursor read the same syntax.",
|
|
797
845
|
scope: "body",
|
|
798
846
|
extract(ctx) {
|
|
799
|
-
const
|
|
800
|
-
|
|
847
|
+
const seenMentions = /* @__PURE__ */ new Set();
|
|
848
|
+
const seenReferences = /* @__PURE__ */ new Set();
|
|
849
|
+
const body = stripCodeBlocks(ctx.body);
|
|
850
|
+
for (const match of body.matchAll(AT_RE)) {
|
|
801
851
|
const original = match[1];
|
|
852
|
+
const bare = original.slice(1);
|
|
853
|
+
const isReference = bare.startsWith("./") || bare.startsWith("../") || bare.startsWith("/") || FILE_EXT_RE.test(bare);
|
|
854
|
+
if (isReference) {
|
|
855
|
+
const target = bare.replace(/^\.\//, "");
|
|
856
|
+
if (seenReferences.has(target)) continue;
|
|
857
|
+
seenReferences.add(target);
|
|
858
|
+
ctx.emitLink({
|
|
859
|
+
source: ctx.node.path,
|
|
860
|
+
target,
|
|
861
|
+
kind: "references",
|
|
862
|
+
confidence: "medium",
|
|
863
|
+
sources: [ID2],
|
|
864
|
+
trigger: {
|
|
865
|
+
originalTrigger: original,
|
|
866
|
+
normalizedTrigger: target.toLowerCase()
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
802
871
|
const normalized = normalizeTrigger(original);
|
|
803
|
-
if (
|
|
804
|
-
|
|
872
|
+
if (seenMentions.has(normalized)) continue;
|
|
873
|
+
seenMentions.add(normalized);
|
|
805
874
|
ctx.emitLink({
|
|
806
875
|
source: ctx.node.path,
|
|
807
876
|
target: original,
|
|
@@ -993,8 +1062,12 @@ var slashExtractor = {
|
|
|
993
1062
|
scope: "body",
|
|
994
1063
|
extract(ctx) {
|
|
995
1064
|
const seen = /* @__PURE__ */ new Set();
|
|
996
|
-
|
|
1065
|
+
const body = stripCodeBlocks(ctx.body);
|
|
1066
|
+
for (const match of body.matchAll(SLASH_RE)) {
|
|
997
1067
|
const original = match[1];
|
|
1068
|
+
const endIdx = (match.index ?? 0) + match[0].length;
|
|
1069
|
+
const nextChar = body[endIdx];
|
|
1070
|
+
if (nextChar && /[A-Za-z0-9_/-]/.test(nextChar)) continue;
|
|
998
1071
|
const normalized = normalizeTrigger(original);
|
|
999
1072
|
if (seen.has(normalized)) continue;
|
|
1000
1073
|
seen.add(normalized);
|
|
@@ -1176,7 +1249,7 @@ function tooltipFor(status) {
|
|
|
1176
1249
|
}
|
|
1177
1250
|
|
|
1178
1251
|
// plugins/core/analyzers/broken-ref/index.ts
|
|
1179
|
-
import { resolve } from "path";
|
|
1252
|
+
import { posix as pathPosix2, resolve } from "path";
|
|
1180
1253
|
|
|
1181
1254
|
// plugins/core/analyzers/broken-ref/text.ts
|
|
1182
1255
|
var BROKEN_REF_TEXTS = {
|
|
@@ -1185,7 +1258,13 @@ var BROKEN_REF_TEXTS = {
|
|
|
1185
1258
|
// Tooltips for the per-node view-contribution badges. Singular vs
|
|
1186
1259
|
// plural keeps the count grammar correct without a sub-template.
|
|
1187
1260
|
alertTooltipSingle: "This node has a broken reference. Open the inspector for details.",
|
|
1188
|
-
alertTooltipMany: "This node has {{count}} broken references. Open the inspector for details."
|
|
1261
|
+
alertTooltipMany: "This node has {{count}} broken references. Open the inspector for details.",
|
|
1262
|
+
// Fix-summary copy when the broken trigger has a same-named file on
|
|
1263
|
+
// disk that does not advertise `name:` in its frontmatter. Two
|
|
1264
|
+
// variants for single vs multiple candidates; same template family
|
|
1265
|
+
// as the alert tooltips above.
|
|
1266
|
+
hintSummarySingle: "Add `name: {{name}}` to the frontmatter of {{candidate}} so this reference resolves.",
|
|
1267
|
+
hintSummaryMany: "Add `name: {{name}}` to the frontmatter of one of these files so this reference resolves: {{candidates}}."
|
|
1189
1268
|
};
|
|
1190
1269
|
|
|
1191
1270
|
// plugins/core/analyzers/broken-ref/index.ts
|
|
@@ -1225,13 +1304,15 @@ var brokenRefAnalyzer = {
|
|
|
1225
1304
|
evaluate(ctx) {
|
|
1226
1305
|
const byPath3 = new Set(ctx.nodes.map((n) => n.path));
|
|
1227
1306
|
const byNormalizedName = indexByNormalizedName(ctx.nodes);
|
|
1307
|
+
const byBasenameWithoutName = indexByBasenameWithoutName(ctx.nodes);
|
|
1228
1308
|
const refIndex = ctx.referenceablePaths && ctx.referenceablePaths.size > 0 && ctx.cwd ? { paths: ctx.referenceablePaths, cwd: ctx.cwd } : null;
|
|
1229
1309
|
const issues = [];
|
|
1230
1310
|
const perNode = /* @__PURE__ */ new Map();
|
|
1231
1311
|
for (const link2 of ctx.links) {
|
|
1232
1312
|
if (isResolved(link2, byPath3, byNormalizedName)) continue;
|
|
1233
1313
|
if (refIndex && resolvesViaReferencePaths(link2, refIndex)) continue;
|
|
1234
|
-
|
|
1314
|
+
const candidates = findHintCandidates(link2, byBasenameWithoutName);
|
|
1315
|
+
issues.push(buildIssue(link2, candidates));
|
|
1235
1316
|
perNode.set(link2.source, (perNode.get(link2.source) ?? 0) + 1);
|
|
1236
1317
|
}
|
|
1237
1318
|
for (const [nodePath, count] of perNode) {
|
|
@@ -1251,8 +1332,13 @@ var brokenRefAnalyzer = {
|
|
|
1251
1332
|
return issues;
|
|
1252
1333
|
}
|
|
1253
1334
|
};
|
|
1254
|
-
function buildIssue(link2) {
|
|
1255
|
-
|
|
1335
|
+
function buildIssue(link2, hintCandidates = []) {
|
|
1336
|
+
const data = {
|
|
1337
|
+
target: link2.target,
|
|
1338
|
+
kind: link2.kind,
|
|
1339
|
+
trigger: link2.trigger?.normalizedTrigger ?? null
|
|
1340
|
+
};
|
|
1341
|
+
const issue = {
|
|
1256
1342
|
analyzerId: ID9,
|
|
1257
1343
|
severity: "warn",
|
|
1258
1344
|
nodeIds: [link2.source],
|
|
@@ -1261,12 +1347,28 @@ function buildIssue(link2) {
|
|
|
1261
1347
|
source: link2.source,
|
|
1262
1348
|
target: link2.target
|
|
1263
1349
|
}),
|
|
1264
|
-
data
|
|
1265
|
-
target: link2.target,
|
|
1266
|
-
kind: link2.kind,
|
|
1267
|
-
trigger: link2.trigger?.normalizedTrigger ?? null
|
|
1268
|
-
}
|
|
1350
|
+
data
|
|
1269
1351
|
};
|
|
1352
|
+
if (hintCandidates.length > 0) {
|
|
1353
|
+
const suggestedName = (link2.trigger?.normalizedTrigger ?? "").replace(/^[/@]/, "").trim();
|
|
1354
|
+
const candidatePaths = hintCandidates.map((n) => n.path);
|
|
1355
|
+
data["hint"] = {
|
|
1356
|
+
kind: "missing-frontmatter-name",
|
|
1357
|
+
suggestedName,
|
|
1358
|
+
candidates: candidatePaths
|
|
1359
|
+
};
|
|
1360
|
+
issue.fix = {
|
|
1361
|
+
summary: candidatePaths.length === 1 ? tx(BROKEN_REF_TEXTS.hintSummarySingle, {
|
|
1362
|
+
name: suggestedName,
|
|
1363
|
+
candidate: candidatePaths[0]
|
|
1364
|
+
}) : tx(BROKEN_REF_TEXTS.hintSummaryMany, {
|
|
1365
|
+
name: suggestedName,
|
|
1366
|
+
candidates: candidatePaths.join(", ")
|
|
1367
|
+
}),
|
|
1368
|
+
autofixable: false
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
return issue;
|
|
1270
1372
|
}
|
|
1271
1373
|
function resolvesViaReferencePaths(link2, refIndex) {
|
|
1272
1374
|
if (!isPathStyleLink(link2)) return false;
|
|
@@ -1285,6 +1387,36 @@ function indexByNormalizedName(nodes) {
|
|
|
1285
1387
|
}
|
|
1286
1388
|
return out;
|
|
1287
1389
|
}
|
|
1390
|
+
function basenameWithoutExt(path) {
|
|
1391
|
+
const base = pathPosix2.basename(path);
|
|
1392
|
+
const ext = pathPosix2.extname(base);
|
|
1393
|
+
return ext ? base.slice(0, -ext.length) : base;
|
|
1394
|
+
}
|
|
1395
|
+
function indexByBasenameWithoutName(nodes) {
|
|
1396
|
+
const out = /* @__PURE__ */ new Map();
|
|
1397
|
+
for (const node of nodes) {
|
|
1398
|
+
const raw = node.frontmatter?.["name"];
|
|
1399
|
+
const name = typeof raw === "string" ? raw : "";
|
|
1400
|
+
if (name) continue;
|
|
1401
|
+
const bare = basenameWithoutExt(node.path);
|
|
1402
|
+
if (!bare) continue;
|
|
1403
|
+
const key = normalizeTrigger(bare);
|
|
1404
|
+
if (!key) continue;
|
|
1405
|
+
const bucket = out.get(key) ?? [];
|
|
1406
|
+
bucket.push(node);
|
|
1407
|
+
out.set(key, bucket);
|
|
1408
|
+
}
|
|
1409
|
+
return out;
|
|
1410
|
+
}
|
|
1411
|
+
function findHintCandidates(link2, idx) {
|
|
1412
|
+
const normalized = link2.trigger?.normalizedTrigger;
|
|
1413
|
+
if (!normalized) return [];
|
|
1414
|
+
const sigil = normalized.charAt(0);
|
|
1415
|
+
if (sigil !== "/" && sigil !== "@") return [];
|
|
1416
|
+
const withoutSigil = normalized.slice(1).trim();
|
|
1417
|
+
if (!withoutSigil) return [];
|
|
1418
|
+
return idx.get(withoutSigil) ?? [];
|
|
1419
|
+
}
|
|
1288
1420
|
function isResolved(link2, byPath3, byNormalizedName) {
|
|
1289
1421
|
const normalized = link2.trigger?.normalizedTrigger;
|
|
1290
1422
|
if (normalized) {
|
|
@@ -2831,7 +2963,7 @@ var UPDATE_CHECK_TEXTS = {
|
|
|
2831
2963
|
// package.json
|
|
2832
2964
|
var package_default = {
|
|
2833
2965
|
name: "@skill-map/cli",
|
|
2834
|
-
version: "0.
|
|
2966
|
+
version: "0.31.0",
|
|
2835
2967
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
2836
2968
|
license: "MIT",
|
|
2837
2969
|
type: "module",
|
|
@@ -7949,9 +8081,9 @@ function providerKindFailure(opts, status, fileName, errDescription) {
|
|
|
7949
8081
|
}
|
|
7950
8082
|
};
|
|
7951
8083
|
}
|
|
7952
|
-
function isDirectorySafe(path,
|
|
8084
|
+
function isDirectorySafe(path, statSync12) {
|
|
7953
8085
|
try {
|
|
7954
|
-
return
|
|
8086
|
+
return statSync12(path).isDirectory();
|
|
7955
8087
|
} catch {
|
|
7956
8088
|
return false;
|
|
7957
8089
|
}
|
|
@@ -23616,43 +23748,37 @@ var STUB_COMMANDS = [
|
|
|
23616
23748
|
];
|
|
23617
23749
|
|
|
23618
23750
|
// cli/commands/tutorial.ts
|
|
23619
|
-
import { existsSync as existsSync29,
|
|
23620
|
-
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";
|
|
23621
23752
|
import { dirname as dirname19, join as join19, resolve as resolve36 } from "path";
|
|
23622
23753
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
23623
23754
|
import { Command as Command37, Option as Option35 } from "clipanion";
|
|
23624
23755
|
|
|
23625
23756
|
// cli/i18n/tutorial.texts.ts
|
|
23626
23757
|
var TUTORIAL_TEXTS = {
|
|
23627
|
-
// Success, written to stdout after `<cwd>/{{
|
|
23628
|
-
//
|
|
23629
|
-
//
|
|
23630
|
-
//
|
|
23631
|
-
//
|
|
23632
|
-
//
|
|
23633
|
-
|
|
23634
|
-
|
|
23635
|
-
* renders relative to the user's cwd when it sits underneath. The
|
|
23636
|
-
* `English` / `Español` labels print dim, the eye lands on the
|
|
23637
|
-
* trigger phrases the user is going to copy / paste.
|
|
23638
|
-
*/
|
|
23639
|
-
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",
|
|
23640
23766
|
writtenLabelEn: "English",
|
|
23641
23767
|
writtenLabelEs: "Espa\xF1ol",
|
|
23642
|
-
// Refusal, `{{
|
|
23768
|
+
// Refusal, `{{target}}` already exists and `--force` was not set.
|
|
23643
23769
|
// Goes to stderr, exit code 2 (operational error per spec § Exit codes).
|
|
23644
23770
|
// Mirrors the success body shape: glyph + headline, then a dim hint
|
|
23645
23771
|
// line spelling the fix.
|
|
23646
|
-
alreadyExists: "{{glyph}} {{
|
|
23647
|
-
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).",
|
|
23648
23774
|
// Invalid `variant` positional argument. Goes to stderr, exit code 2.
|
|
23649
23775
|
// Mirrors `alreadyExists`: glyph + headline + dim hint enumerating the
|
|
23650
23776
|
// valid values.
|
|
23651
23777
|
invalidVariant: "{{glyph}} sm tutorial: unknown variant '{{variant}}'\n {{hint}}\n",
|
|
23652
23778
|
invalidVariantHint: "Valid values: tutorial (default), master.",
|
|
23653
|
-
// I/O failure on write or on reading the bundled
|
|
23654
|
-
writeFailed: "{{glyph}} sm tutorial: failed to write {{
|
|
23655
|
-
sourceMissing: "{{glyph}} sm tutorial: could not read the bundled
|
|
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",
|
|
23656
23782
|
sourceMissingHint: "Reinstall @skill-map/cli or report the bug."
|
|
23657
23783
|
};
|
|
23658
23784
|
|
|
@@ -23661,41 +23787,45 @@ var VALID_VARIANTS = ["tutorial", "master"];
|
|
|
23661
23787
|
var DEFAULT_VARIANT = "tutorial";
|
|
23662
23788
|
var VARIANT_SPECS = {
|
|
23663
23789
|
tutorial: {
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
|
|
23790
|
+
slug: "sm-tutorial",
|
|
23791
|
+
sourceDir: ".claude/skills/sm-tutorial",
|
|
23792
|
+
triggerEn: "start the tutorial",
|
|
23793
|
+
triggerEs: "arranquemos el tutorial"
|
|
23667
23794
|
},
|
|
23668
23795
|
master: {
|
|
23669
|
-
|
|
23670
|
-
|
|
23671
|
-
|
|
23796
|
+
slug: "sm-master",
|
|
23797
|
+
sourceDir: ".claude/skills/sm-master",
|
|
23798
|
+
triggerEn: "advanced tutorial",
|
|
23799
|
+
triggerEs: "tutorial maestro"
|
|
23672
23800
|
}
|
|
23673
23801
|
};
|
|
23674
23802
|
var TutorialCommand = class extends SmCommand {
|
|
23675
23803
|
static paths = [["tutorial"]];
|
|
23676
23804
|
static usage = Command37.Usage({
|
|
23677
23805
|
category: "Setup",
|
|
23678
|
-
description: "Materialize an interactive tester tutorial
|
|
23806
|
+
description: "Materialize an interactive tester tutorial as a Claude Code skill folder under `<cwd>/.claude/skills/`.",
|
|
23679
23807
|
details: `
|
|
23680
|
-
Drops the canonical SKILL.md
|
|
23681
|
-
|
|
23682
|
-
|
|
23683
|
-
|
|
23684
|
-
|
|
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.
|
|
23685
23814
|
|
|
23686
23815
|
Does NOT require an initialized .skill-map/ project. Refuses to
|
|
23687
|
-
overwrite the target
|
|
23688
|
-
for the positional argument are: tutorial (default),
|
|
23816
|
+
overwrite the target directory unless --force is passed. Valid
|
|
23817
|
+
values for the positional argument are: tutorial (default),
|
|
23818
|
+
master.
|
|
23689
23819
|
`,
|
|
23690
23820
|
examples: [
|
|
23691
|
-
["Materialize the basic tutorial in the cwd", "$0 tutorial"],
|
|
23692
|
-
["Materialize the advanced tutorial in the cwd", "$0 tutorial master"],
|
|
23693
|
-
["Overwrite an existing target
|
|
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"]
|
|
23694
23824
|
]
|
|
23695
23825
|
});
|
|
23696
23826
|
variant = Option35.String({ required: false });
|
|
23697
23827
|
force = Option35.Boolean("--force", false, {
|
|
23698
|
-
description: "Overwrite an existing target
|
|
23828
|
+
description: "Overwrite an existing target directory without prompting."
|
|
23699
23829
|
});
|
|
23700
23830
|
async run() {
|
|
23701
23831
|
const ctx = defaultRuntimeContext();
|
|
@@ -23715,38 +23845,41 @@ var TutorialCommand = class extends SmCommand {
|
|
|
23715
23845
|
}
|
|
23716
23846
|
const variant = rawVariant ?? DEFAULT_VARIANT;
|
|
23717
23847
|
const spec = VARIANT_SPECS[variant];
|
|
23718
|
-
const
|
|
23719
|
-
|
|
23848
|
+
const targetDir = join19(ctx.cwd, ".claude", "skills", spec.slug);
|
|
23849
|
+
const targetDisplay = `.claude/skills/${spec.slug}/`;
|
|
23850
|
+
if (existsSync29(targetDir) && !this.force) {
|
|
23720
23851
|
this.printer.error(
|
|
23721
23852
|
tx(TUTORIAL_TEXTS.alreadyExists, {
|
|
23722
23853
|
glyph: errGlyph,
|
|
23723
|
-
|
|
23854
|
+
target: targetDisplay,
|
|
23724
23855
|
cwd: stderrAnsi.dim(displayCwd(ctx.cwd)),
|
|
23725
23856
|
hint: stderrAnsi.dim(TUTORIAL_TEXTS.alreadyExistsHint)
|
|
23726
23857
|
})
|
|
23727
23858
|
);
|
|
23728
23859
|
return ExitCode.Error;
|
|
23729
23860
|
}
|
|
23730
|
-
let
|
|
23861
|
+
let sourceDir;
|
|
23731
23862
|
try {
|
|
23732
|
-
|
|
23863
|
+
sourceDir = resolveSkillSourceDir(variant);
|
|
23733
23864
|
} catch {
|
|
23734
23865
|
this.printer.error(
|
|
23735
23866
|
tx(TUTORIAL_TEXTS.sourceMissing, {
|
|
23736
23867
|
glyph: errGlyph,
|
|
23737
|
-
|
|
23868
|
+
target: targetDisplay,
|
|
23738
23869
|
hint: stderrAnsi.dim(TUTORIAL_TEXTS.sourceMissingHint)
|
|
23739
23870
|
})
|
|
23740
23871
|
);
|
|
23741
23872
|
return ExitCode.Error;
|
|
23742
23873
|
}
|
|
23743
23874
|
try {
|
|
23744
|
-
|
|
23875
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
23876
|
+
mkdirSync7(dirname19(targetDir), { recursive: true });
|
|
23877
|
+
cpSync2(sourceDir, targetDir, { recursive: true });
|
|
23745
23878
|
} catch (err) {
|
|
23746
23879
|
this.printer.error(
|
|
23747
23880
|
tx(TUTORIAL_TEXTS.writeFailed, {
|
|
23748
23881
|
glyph: errGlyph,
|
|
23749
|
-
|
|
23882
|
+
target: targetDisplay,
|
|
23750
23883
|
message: formatErrorMessage(err)
|
|
23751
23884
|
})
|
|
23752
23885
|
);
|
|
@@ -23762,10 +23895,13 @@ var TutorialCommand = class extends SmCommand {
|
|
|
23762
23895
|
this.printer.data(
|
|
23763
23896
|
tx(TUTORIAL_TEXTS.written, {
|
|
23764
23897
|
glyph: ansi.green("\u2713"),
|
|
23765
|
-
|
|
23898
|
+
slug: spec.slug,
|
|
23899
|
+
target: targetDisplay,
|
|
23766
23900
|
cwd: ansi.dim(displayCwd(ctx.cwd)),
|
|
23767
23901
|
enLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEn),
|
|
23768
|
-
esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs)
|
|
23902
|
+
esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs),
|
|
23903
|
+
enTrigger: spec.triggerEn,
|
|
23904
|
+
esTrigger: spec.triggerEs
|
|
23769
23905
|
})
|
|
23770
23906
|
);
|
|
23771
23907
|
return ExitCode.Ok;
|
|
@@ -23779,31 +23915,29 @@ function displayCwd(cwd) {
|
|
|
23779
23915
|
if (segments.length === 0) return "./";
|
|
23780
23916
|
return `./${segments[segments.length - 1]}/`;
|
|
23781
23917
|
}
|
|
23782
|
-
var
|
|
23783
|
-
function
|
|
23784
|
-
const cached =
|
|
23918
|
+
var cachedSourceDirs = /* @__PURE__ */ new Map();
|
|
23919
|
+
function resolveSkillSourceDir(variant) {
|
|
23920
|
+
const cached = cachedSourceDirs.get(variant);
|
|
23785
23921
|
if (cached !== void 0) return cached;
|
|
23786
|
-
const body = readTutorialFromDisk(variant);
|
|
23787
|
-
cachedTutorials.set(variant, body);
|
|
23788
|
-
return body;
|
|
23789
|
-
}
|
|
23790
|
-
function readTutorialFromDisk(variant) {
|
|
23791
23922
|
const spec = VARIANT_SPECS[variant];
|
|
23792
23923
|
const here = dirname19(fileURLToPath6(import.meta.url));
|
|
23793
23924
|
const candidates = [
|
|
23794
|
-
// dev: src/cli/commands/ → repo-root .claude/skills/<slug>/
|
|
23795
|
-
resolve36(here, "../../..", spec.
|
|
23796
|
-
// bundled: dist/cli.js → dist/cli/tutorial/<
|
|
23797
|
-
resolve36(here, "cli/tutorial", spec.
|
|
23798
|
-
// bundled fallback: any-depth → cli/tutorial/<
|
|
23799
|
-
resolve36(here, "../cli/tutorial", spec.
|
|
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)
|
|
23800
23931
|
];
|
|
23801
23932
|
for (const candidate of candidates) {
|
|
23802
|
-
if (existsSync29(candidate)) {
|
|
23803
|
-
|
|
23933
|
+
if (existsSync29(candidate) && statSync11(candidate).isDirectory()) {
|
|
23934
|
+
cachedSourceDirs.set(variant, candidate);
|
|
23935
|
+
return candidate;
|
|
23804
23936
|
}
|
|
23805
23937
|
}
|
|
23806
|
-
throw new Error(
|
|
23938
|
+
throw new Error(
|
|
23939
|
+
`skill source directory not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`
|
|
23940
|
+
);
|
|
23807
23941
|
}
|
|
23808
23942
|
|
|
23809
23943
|
// cli/commands/version.ts
|