rshow 0.0.1 → 0.0.2

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/main.js CHANGED
@@ -1,8 +1,115 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import path21 from "node:path";
5
- import process15 from "node:process";
4
+ import path24 from "node:path";
5
+ import process17 from "node:process";
6
+
7
+ // src/binary.ts
8
+ import { mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
9
+ import { homedir } from "node:os";
10
+ import path from "node:path";
11
+ import process from "node:process";
12
+
13
+ // src/version.ts
14
+ import { readFileSync } from "node:fs";
15
+ import { fileURLToPath } from "node:url";
16
+ var VERSION = true ? "0.0.2" : versionFromPackage();
17
+
18
+ // src/binary.ts
19
+ var ASSET_MANIFEST = "assets.json";
20
+ var COMPLETE_MARKER = ".complete";
21
+ var sea;
22
+ function seaModule() {
23
+ if (sea === void 0) {
24
+ sea = import("node:sea").then(
25
+ (mod) => mod,
26
+ () => void 0
27
+ );
28
+ }
29
+ return sea;
30
+ }
31
+ async function isBinary() {
32
+ const mod = await seaModule();
33
+ if (mod === void 0 || typeof mod.isSea !== "function") return false;
34
+ try {
35
+ return mod.isSea();
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ function cacheRoot(env = process.env) {
41
+ const xdg = (env.XDG_CACHE_HOME ?? "").trim();
42
+ if (xdg !== "") return path.resolve(xdg);
43
+ const home = (env.HOME ?? "").trim();
44
+ return path.join(home !== "" ? home : homedir(), ".cache");
45
+ }
46
+ function binaryCacheDir(env = process.env, version = VERSION) {
47
+ return path.join(cacheRoot(env), "rshow", version);
48
+ }
49
+ async function exists(file) {
50
+ return await stat(file).catch(() => void 0) !== void 0;
51
+ }
52
+ async function extractAssets(bundle, dir) {
53
+ if (await exists(path.join(dir, COMPLETE_MARKER))) return;
54
+ const parent = path.dirname(dir);
55
+ await mkdir(parent, { recursive: true });
56
+ const tmp = await mkdtemp(path.join(parent, `${path.basename(dir)}.tmp-`));
57
+ try {
58
+ for (const key2 of bundle.keys) {
59
+ const file = path.join(tmp, ...key2.split("/"));
60
+ await mkdir(path.dirname(file), { recursive: true });
61
+ await writeFile(file, bundle.read(key2));
62
+ }
63
+ await writeFile(path.join(tmp, COMPLETE_MARKER), `${VERSION}
64
+ `);
65
+ await rm(dir, { recursive: true, force: true });
66
+ await rename(tmp, dir);
67
+ } catch (err) {
68
+ await rm(tmp, { recursive: true, force: true });
69
+ throw err;
70
+ }
71
+ }
72
+ function safeKey(key2) {
73
+ return key2 !== "" && !path.isAbsolute(key2) && !key2.split("/").includes("..");
74
+ }
75
+ function manifestKeys(read2) {
76
+ try {
77
+ const doc = JSON.parse(new TextDecoder().decode(read2(ASSET_MANIFEST)));
78
+ if (doc !== null && typeof doc === "object" && Array.isArray(doc.keys)) {
79
+ const keys = doc.keys.filter((key2) => typeof key2 === "string");
80
+ if (keys.length > 0) return keys.filter(safeKey);
81
+ }
82
+ } catch {
83
+ }
84
+ return [];
85
+ }
86
+ function bundleOf(mod) {
87
+ const raw = mod.getRawAsset;
88
+ const read2 = (key2) => new Uint8Array(raw(key2));
89
+ const withManifest = manifestKeys(read2);
90
+ return { keys: withManifest.length > 0 ? withManifest : (mod.getAssetKeys?.() ?? []).filter(safeKey), read: read2 };
91
+ }
92
+ async function unpack(env) {
93
+ if (!await isBinary()) return void 0;
94
+ const mod = await seaModule();
95
+ if (mod?.getRawAsset === void 0) throw new Error("this rshow binary carries no assets");
96
+ const dir = binaryCacheDir(env);
97
+ await extractAssets(bundleOf(mod), dir);
98
+ return dir;
99
+ }
100
+ var root;
101
+ function binaryRoot(env = process.env) {
102
+ if (root === void 0) root = unpack(env);
103
+ return root;
104
+ }
105
+ async function binaryThemeBases(env = process.env) {
106
+ const dir = await binaryRoot(env);
107
+ return dir === void 0 ? [] : [path.join(dir, "themes")];
108
+ }
109
+ async function binarySkillBases(env = process.env) {
110
+ const dir = await binaryRoot(env);
111
+ return dir === void 0 ? [] : [path.join(dir, "skills")];
112
+ }
6
113
 
7
114
  // src/exit.ts
8
115
  var EXIT = { ok: 0, usage: 2, auth: 4, api: 5, build: 5, check: 6, compare: 7 };
@@ -14,18 +121,18 @@ var UsageError = class extends Error {
14
121
  };
15
122
  var AuthError = class extends Error {
16
123
  status;
17
- constructor(message, status) {
124
+ constructor(message, status2) {
18
125
  super(message);
19
126
  this.name = "AuthError";
20
- this.status = status;
127
+ this.status = status2;
21
128
  }
22
129
  };
23
130
  var ApiError = class extends Error {
24
131
  status;
25
- constructor(message, status = 0) {
132
+ constructor(message, status2 = 0) {
26
133
  super(message);
27
134
  this.name = "ApiError";
28
- this.status = status;
135
+ this.status = status2;
29
136
  }
30
137
  };
31
138
  var BuildError = class extends Error {
@@ -41,20 +148,15 @@ var CheckError = class extends Error {
41
148
  }
42
149
  };
43
150
 
44
- // src/version.ts
45
- import { readFileSync } from "node:fs";
46
- import { fileURLToPath } from "node:url";
47
- var VERSION = true ? "0.0.1" : versionFromPackage();
48
-
49
151
  // src/client.ts
50
152
  function messageOf(err) {
51
153
  return err instanceof Error ? err.message : String(err);
52
154
  }
53
- function errorText(body, status) {
155
+ function errorText(body, status2) {
54
156
  if (body !== null && typeof body === "object" && typeof body.error === "string") {
55
157
  return body.error;
56
158
  }
57
- return `HTTP ${status}`;
159
+ return `HTTP ${status2}`;
58
160
  }
59
161
  function parseBody(text) {
60
162
  if (text.trim() === "") return void 0;
@@ -179,20 +281,20 @@ var Client = class {
179
281
 
180
282
  // src/config.ts
181
283
  import { readFile } from "node:fs/promises";
182
- import { homedir } from "node:os";
183
- import path from "node:path";
284
+ import { homedir as homedir2 } from "node:os";
285
+ import path2 from "node:path";
184
286
 
185
287
  // src/keychain.ts
186
288
  import { spawn } from "node:child_process";
187
- import process from "node:process";
289
+ import process2 from "node:process";
188
290
  var SERVICE = "rshow";
189
291
  var MISSING = 44;
190
292
  var NO_MACOS = "the keychain needs macOS; set RSHOW_TOKEN instead";
191
293
  function bin() {
192
- return process.env.RSHOW_SECURITY_BIN ?? "security";
294
+ return process2.env.RSHOW_SECURITY_BIN ?? "security";
193
295
  }
194
296
  function usable() {
195
- return process.platform === "darwin" || process.env.RSHOW_SECURITY_BIN !== void 0;
297
+ return process2.platform === "darwin" || process2.env.RSHOW_SECURITY_BIN !== void 0;
196
298
  }
197
299
  function run(args, input) {
198
300
  return new Promise((resolve2, reject) => {
@@ -322,11 +424,11 @@ function withoutSlash(value) {
322
424
  function globalConfigPath(env) {
323
425
  const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
324
426
  const home = (env.HOME ?? "").trim();
325
- const base = xdg !== "" ? xdg : path.join(home !== "" ? home : homedir(), ".config");
326
- return path.join(base, GLOBAL_DIR, GLOBAL_FILE);
427
+ const base = xdg !== "" ? xdg : path2.join(home !== "" ? home : homedir2(), ".config");
428
+ return path2.join(base, GLOBAL_DIR, GLOBAL_FILE);
327
429
  }
328
430
  function localConfigPath(dir) {
329
- return path.join(path.resolve(dir), LOCAL_FILE);
431
+ return path2.join(path2.resolve(dir), LOCAL_FILE);
330
432
  }
331
433
  function layerOf(file, value) {
332
434
  if (!isRecord(value)) throw new UsageError(`${file} must hold a JSON object`);
@@ -442,7 +544,7 @@ async function readToken(env, url) {
442
544
  }
443
545
 
444
546
  // src/output.ts
445
- import process2 from "node:process";
547
+ import process3 from "node:process";
446
548
  var Output = class {
447
549
  json;
448
550
  constructor(json) {
@@ -450,16 +552,16 @@ var Output = class {
450
552
  }
451
553
  data(value) {
452
554
  if (!this.json) return;
453
- process2.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
555
+ process3.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
454
556
  `);
455
557
  }
456
558
  line(text) {
457
559
  if (this.json) return;
458
- process2.stdout.write(`${text}
560
+ process3.stdout.write(`${text}
459
561
  `);
460
562
  }
461
563
  raw(text) {
462
- process2.stdout.write(`${text}
564
+ process3.stdout.write(`${text}
463
565
  `);
464
566
  }
465
567
  };
@@ -475,12 +577,13 @@ commands:
475
577
  preview <dir> serve a folder on 127.0.0.1 for a look, offline
476
578
  config [dir] print the merged config as JSON
477
579
  init --url u --art a write the global config file
478
- doctor [dir] check node, config, token, api, art and theme
580
+ doctor [dir] check node, config, token, api, art, theme, skills
479
581
  theme new <name> write a preset folder from a built-in theme
480
582
  theme check <dir> check a theme folder, preset or full
481
583
  theme preview <dir> build a theme, serve it on 127.0.0.1 for a look
482
584
  theme publish <dir> publish a theme folder as theme-<name>
483
585
  theme list list theme artifacts
586
+ skills <cmd> install, remove or show the agent skills, per target
484
587
  project <cmd> create, list, show, share or delete a project
485
588
  tag <slug> add or remove tags on an artifact
486
589
  list [filters] list artifacts you own or share
@@ -616,8 +719,9 @@ Prints the path it wrote and the next step: rshow login. --json prints
616
719
  var DOCTOR_USAGE = `usage: rshow doctor [dir] [--json]
617
720
 
618
721
  Checks the rshow version, the node version against the engines.node of the
619
- package, the config files, the token, the api, the art host and the theme, one
620
- line each: ok|warn|fail <check> <detail>. With [dir] it reads <dir>/rshow.json too.
722
+ package, the config files, the token, the api, the art host, the theme and the
723
+ skills, one line each: ok|warn|fail <check> <detail>. With [dir] it reads
724
+ <dir>/rshow.json too. A skill line warns and never fails.
621
725
  Network calls time out after 5 s. Exit 0 when no check failed, 6 when one did.
622
726
  The token is never printed.`;
623
727
  var THEME_USAGE = `usage: rshow theme <new|check|preview|publish|list> [options]
@@ -676,6 +780,54 @@ Prints /_themes/<name>@<n>/.`;
676
780
  var THEME_LIST_USAGE = `usage: rshow theme list
677
781
 
678
782
  Lists artifacts whose slug starts with theme- and their served version.`;
783
+ var SKILLS_USAGE = `usage: rshow skills <install|uninstall|status> [target ...] [options]
784
+
785
+ skills install [target ...] link or copy the three skills into an agent
786
+ skills uninstall [target ...] remove the links and copies rshow made
787
+ skills status [target ...] say what each target holds, per skill
788
+
789
+ A target is where an agent reads its skills from:
790
+ claude $HOME/.claude/skills
791
+ pi $HOME/.pi/agent/skills
792
+ codex $HOME/.codex/skills
793
+ With no target, every target whose parent folder exists is used.`;
794
+ var SKILLS_INSTALL_USAGE = `usage: rshow skills install [target ...] [--link|--copy] [--dry-run]
795
+
796
+ Puts the three skills rshow ships, rshow, rshow-slides and rshow-implement, in
797
+ the skills folder of every target, so that claude, pi and codex read them:
798
+ $HOME/.claude/skills, $HOME/.pi/agent/skills and $HOME/.codex/skills. With no
799
+ target, every target whose parent folder exists is used; naming one of claude,
800
+ pi and codex picks it, and a usage error names them when none is detected.
801
+ Each skill is a symlink to the folder rshow reads it from, so an upgrade of
802
+ rshow is nothing to do. --copy copies the folder and writes a .rshow-skill
803
+ marker in it, {"version": <rshow version>, "source": "rshow"}, which is how a
804
+ newer rshow knows the copy is its own. The default is a link, and a copy when
805
+ that folder sits in an npx cache, which npm may delete under a link, or when
806
+ rshow runs as a single executable, whose files live in a per-version cache.
807
+ A skill that is ours and current prints ok, and one of ours from another path or
808
+ another version is replaced and prints updated. Anything else, a folder, a file
809
+ or a link that rshow did not make, is left alone: skip goes to stderr, the rest
810
+ is still installed, and the run exits 6.
811
+ --dry-run prints what would happen, with would, and changes nothing.
812
+ Prints one line per skill, then installed <n> skills for <targets>; --json
813
+ prints {targets, actions: [{target, skill, action, path}]}.
814
+ RSHOW_SKILLS_DIR reads the skills from another folder.`;
815
+ var SKILLS_UNINSTALL_USAGE = `usage: rshow skills uninstall [target ...] [--dry-run]
816
+
817
+ Removes the skills rshow put in every target: a symlink into a skills folder
818
+ that holds rshow skills, and a copy that carries the .rshow-skill marker.
819
+ It removes nothing else, with no line and no error. With no target, every
820
+ target whose parent folder exists is used.
821
+ Prints removed <path> per skill, then removed <n> skills for <targets>, and
822
+ --dry-run prints what would happen, with would, and changes nothing.`;
823
+ var SKILLS_STATUS_USAGE = `usage: rshow skills status [target ...] [--json]
824
+
825
+ Prints one line per target and skill, <target> <skill> <state>, where the
826
+ state is link -> <path>, copy v<version>, missing or foreign. It prints nothing
827
+ for a target whose parent folder does not exist, and --json prints {targets,
828
+ actions: [{target, skill, action, path}]}.
829
+ A copy of another version also warns on stderr, because rshow skills install
830
+ replaces it.`;
679
831
  var LIST_USAGE = `usage: rshow list [--project p] [--tag t] [--q text]
680
832
 
681
833
  Lists artifacts you own or that are shared with you.
@@ -830,8 +982,8 @@ function usageLine(usage) {
830
982
  }
831
983
 
832
984
  // src/commands/publish.ts
833
- import { stat as stat3 } from "node:fs/promises";
834
- import path6 from "node:path";
985
+ import { stat as stat4 } from "node:fs/promises";
986
+ import path7 from "node:path";
835
987
 
836
988
  // src/args.ts
837
989
  import { parseArgs } from "node:util";
@@ -905,7 +1057,7 @@ function emailList(args, name) {
905
1057
 
906
1058
  // src/build.ts
907
1059
  import { spawn as spawn2 } from "node:child_process";
908
- import process3 from "node:process";
1060
+ import process4 from "node:process";
909
1061
  function messageOf4(err) {
910
1062
  return err instanceof Error ? err.message : String(err);
911
1063
  }
@@ -913,7 +1065,7 @@ function shellPath(env) {
913
1065
  const shell = (env.SHELL ?? "").trim();
914
1066
  return shell === "" ? "/bin/sh" : shell;
915
1067
  }
916
- async function runBuild(dir, command, env = process3.env) {
1068
+ async function runBuild(dir, command, env = process4.env) {
917
1069
  const code = await new Promise((resolve2, reject) => {
918
1070
  const child = spawn2(shellPath(env), ["-c", command], { cwd: dir, stdio: "inherit" });
919
1071
  child.once("error", reject);
@@ -927,12 +1079,12 @@ async function runBuild(dir, command, env = process3.env) {
927
1079
  }
928
1080
 
929
1081
  // src/check.ts
930
- import { readFile as readFile4, readdir, stat } from "node:fs/promises";
931
- import path4 from "node:path";
1082
+ import { readFile as readFile4, readdir, stat as stat2 } from "node:fs/promises";
1083
+ import path5 from "node:path";
932
1084
 
933
1085
  // src/mockup.ts
934
1086
  import { readFile as readFile2 } from "node:fs/promises";
935
- import path2 from "node:path";
1087
+ import path3 from "node:path";
936
1088
  var MAX_SCAN_BYTES = 4 * 1024 * 1024;
937
1089
  var HTML_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm"]);
938
1090
  var MIN_COLS = 80;
@@ -1127,7 +1279,7 @@ function htmlWarnings(rel, text) {
1127
1279
  async function mockupWarnings(entries) {
1128
1280
  const out = [];
1129
1281
  for (const entry of entries) {
1130
- const ext = path2.extname(entry.rel).toLowerCase();
1282
+ const ext = path3.extname(entry.rel).toLowerCase();
1131
1283
  if (!HTML_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES) continue;
1132
1284
  const text = await readFile2(entry.abs, "utf8").catch(() => void 0);
1133
1285
  if (text === void 0) continue;
@@ -1138,7 +1290,7 @@ async function mockupWarnings(entries) {
1138
1290
 
1139
1291
  // src/style.ts
1140
1292
  import { readFile as readFile3 } from "node:fs/promises";
1141
- import path3 from "node:path";
1293
+ import path4 from "node:path";
1142
1294
  var MAX_SCAN_BYTES2 = 4 * 1024 * 1024;
1143
1295
  var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css"]);
1144
1296
  var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
@@ -1535,7 +1687,7 @@ async function styleWarnings(entries) {
1535
1687
  const loaded = /* @__PURE__ */ new Set();
1536
1688
  const used = [];
1537
1689
  for (const entry of entries) {
1538
- const ext = path3.extname(entry.rel).toLowerCase();
1690
+ const ext = path4.extname(entry.rel).toLowerCase();
1539
1691
  if (!STYLE_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES2) continue;
1540
1692
  const text = await readFile3(entry.abs, "utf8").catch(() => void 0);
1541
1693
  if (text === void 0) continue;
@@ -1665,7 +1817,7 @@ function resolvePath(file, ref) {
1665
1817
  decoded = ref;
1666
1818
  }
1667
1819
  const wantsIndex = decoded.endsWith("/");
1668
- const base = path4.posix.dirname(file);
1820
+ const base = path5.posix.dirname(file);
1669
1821
  const parts2 = base === "." ? [] : base.split("/");
1670
1822
  for (const segment of decoded.split("/")) {
1671
1823
  if (segment === "" || segment === ".") continue;
@@ -1788,7 +1940,7 @@ function scanHtml(scan, file, text, facts) {
1788
1940
  scanConnect(scan, file, masked, at);
1789
1941
  }
1790
1942
  async function scanFile(scan, entry, facts) {
1791
- const ext = path4.extname(entry.rel).toLowerCase();
1943
+ const ext = path5.extname(entry.rel).toLowerCase();
1792
1944
  if (!SCAN_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES3) return;
1793
1945
  const text = await readFile4(entry.abs, "utf8");
1794
1946
  if (ext === ".html" || ext === ".htm") {
@@ -1805,7 +1957,7 @@ async function collect(scan, dir, prefix, entries) {
1805
1957
  for (const item of items) {
1806
1958
  if (item.name.startsWith(".") || SKIPPED_NAMES.has(item.name)) continue;
1807
1959
  const rel = prefix === "" ? item.name : `${prefix}/${item.name}`;
1808
- const abs = path4.join(dir, item.name);
1960
+ const abs = path5.join(dir, item.name);
1809
1961
  const problem = pathProblem(rel);
1810
1962
  if (problem !== void 0) add(scan, rel, 1, "error", problem);
1811
1963
  if (item.isDirectory()) {
@@ -1813,54 +1965,54 @@ async function collect(scan, dir, prefix, entries) {
1813
1965
  continue;
1814
1966
  }
1815
1967
  if (!item.isFile()) continue;
1816
- const info = await stat(abs).catch(() => void 0);
1968
+ const info = await stat2(abs).catch(() => void 0);
1817
1969
  if (info === void 0) continue;
1818
1970
  entries.push({ rel, abs, size: info.size });
1819
1971
  }
1820
1972
  }
1821
1973
  async function checkThemeFile(scan, key2, rel, where) {
1822
1974
  const url = `${scan.art}/_themes/${key2}/${rel}`;
1823
- let status;
1975
+ let status2;
1824
1976
  try {
1825
1977
  const reply = await scan.fetchImpl(url, { method: "HEAD" });
1826
- status = reply.status;
1978
+ status2 = reply.status;
1827
1979
  } catch (err) {
1828
1980
  add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${messageOf5(err)}`);
1829
1981
  return;
1830
1982
  }
1831
- if (status === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
1832
- else if (status < 200 || status > 399) {
1833
- add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: HTTP ${status}`);
1983
+ if (status2 === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
1984
+ else if (status2 < 200 || status2 > 399) {
1985
+ add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: HTTP ${status2}`);
1834
1986
  }
1835
1987
  }
1836
1988
  async function checkTheme(scan, key2, where) {
1837
1989
  if (scan.offline || scan.art === "") return;
1838
1990
  const url = `${scan.art}/_themes/${key2}/tokens.css`;
1839
- let status;
1991
+ let status2;
1840
1992
  try {
1841
1993
  const reply = await scan.fetchImpl(url, { method: "HEAD" });
1842
- status = reply.status;
1994
+ status2 = reply.status;
1843
1995
  } catch (err) {
1844
1996
  add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${messageOf5(err)}`);
1845
1997
  return;
1846
1998
  }
1847
- if (status === 404) {
1999
+ if (status2 === 404) {
1848
2000
  add(scan, where.file, where.line, "error", `theme ${key2} is not published (${url})`);
1849
2001
  return;
1850
2002
  }
1851
- if (status < 200 || status > 399) {
1852
- add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: HTTP ${status}`);
2003
+ if (status2 < 200 || status2 > 399) {
2004
+ add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: HTTP ${status2}`);
1853
2005
  return;
1854
2006
  }
1855
- if (status > 299) return;
2007
+ if (status2 > 299) return;
1856
2008
  for (const [rel, ref] of where.files) {
1857
2009
  if (rel === "tokens.css") continue;
1858
2010
  await checkThemeFile(scan, key2, rel, ref);
1859
2011
  }
1860
2012
  }
1861
2013
  async function checkFolder(input, opts = {}) {
1862
- const root = path4.resolve(input);
1863
- const info = await stat(root).catch(() => void 0);
2014
+ const root2 = path5.resolve(input);
2015
+ const info = await stat2(root2).catch(() => void 0);
1864
2016
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
1865
2017
  const scan = {
1866
2018
  files: /* @__PURE__ */ new Set(),
@@ -1871,7 +2023,7 @@ async function checkFolder(input, opts = {}) {
1871
2023
  themes: /* @__PURE__ */ new Map()
1872
2024
  };
1873
2025
  const entries = [];
1874
- await collect(scan, root, "", entries);
2026
+ await collect(scan, root2, "", entries);
1875
2027
  let bytes = 0;
1876
2028
  for (const entry of entries) {
1877
2029
  scan.files.add(entry.rel);
@@ -1952,7 +2104,7 @@ function reportCheck(out, result) {
1952
2104
  out.line(summaryLine(result));
1953
2105
  }
1954
2106
  async function enforceCheck(out, target, opts = {}) {
1955
- const info = await stat(target).catch(() => void 0);
2107
+ const info = await stat2(target).catch(() => void 0);
1956
2108
  if (info === void 0 || !info.isDirectory()) return;
1957
2109
  const result = await checkFolder(target, opts);
1958
2110
  if (result.findings.length > 0) reportCheck(out, result);
@@ -1964,8 +2116,8 @@ import { readFile as readFile6 } from "node:fs/promises";
1964
2116
 
1965
2117
  // src/files.ts
1966
2118
  import { createHash } from "node:crypto";
1967
- import { readdir as readdir2, readFile as readFile5, stat as stat2 } from "node:fs/promises";
1968
- import path5 from "node:path";
2119
+ import { readdir as readdir2, readFile as readFile5, stat as stat3 } from "node:fs/promises";
2120
+ import path6 from "node:path";
1969
2121
  var CONTENT_TYPES = {
1970
2122
  html: "text/html; charset=utf-8",
1971
2123
  css: "text/css; charset=utf-8",
@@ -1997,7 +2149,7 @@ var CONTENT_TYPES = {
1997
2149
  var DEFAULT_CONTENT_TYPE = "application/octet-stream";
1998
2150
  var SKIPPED_NAMES2 = /* @__PURE__ */ new Set(["node_modules"]);
1999
2151
  function contentTypeFor(filePath) {
2000
- const ext = path5.extname(filePath).replace(/^\./, "").toLowerCase();
2152
+ const ext = path6.extname(filePath).replace(/^\./, "").toLowerCase();
2001
2153
  return CONTENT_TYPES[ext] ?? DEFAULT_CONTENT_TYPE;
2002
2154
  }
2003
2155
  function sha256Hex(bytes) {
@@ -2013,7 +2165,7 @@ async function readEntry(abs, rel) {
2013
2165
  type: contentTypeFor(rel)
2014
2166
  };
2015
2167
  }
2016
- async function collectPaths(root) {
2168
+ async function collectPaths(root2) {
2017
2169
  const found = [];
2018
2170
  async function walk2(dir, prefix) {
2019
2171
  const entries = await readdir2(dir, { withFileTypes: true });
@@ -2022,7 +2174,7 @@ async function collectPaths(root) {
2022
2174
  if (entry.name.startsWith(".")) continue;
2023
2175
  if (SKIPPED_NAMES2.has(entry.name)) continue;
2024
2176
  const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
2025
- const abs = path5.join(dir, entry.name);
2177
+ const abs = path6.join(dir, entry.name);
2026
2178
  if (entry.isDirectory()) {
2027
2179
  await walk2(abs, rel);
2028
2180
  continue;
@@ -2031,22 +2183,22 @@ async function collectPaths(root) {
2031
2183
  found.push(rel);
2032
2184
  }
2033
2185
  }
2034
- await walk2(root, "");
2186
+ await walk2(root2, "");
2035
2187
  return found;
2036
2188
  }
2037
2189
  async function buildManifest(input, opts = {}) {
2038
- const root = path5.resolve(input);
2039
- const info = await stat2(root).catch(() => void 0);
2190
+ const root2 = path6.resolve(input);
2191
+ const info = await stat3(root2).catch(() => void 0);
2040
2192
  if (info === void 0) throw new UsageError(`no such file or folder: ${input}`);
2041
2193
  const entries = [];
2042
2194
  if (info.isFile()) {
2043
- entries.push(await readEntry(root, "index.html"));
2195
+ entries.push(await readEntry(root2, "index.html"));
2044
2196
  } else if (info.isDirectory()) {
2045
- const paths = await collectPaths(root);
2197
+ const paths = await collectPaths(root2);
2046
2198
  if (opts.requireIndex !== false && !paths.includes("index.html")) {
2047
2199
  throw new UsageError(`${input} has no index.html`);
2048
2200
  }
2049
- for (const rel of paths) entries.push(await readEntry(path5.join(root, rel), rel));
2201
+ for (const rel of paths) entries.push(await readEntry(path6.join(root2, rel), rel));
2050
2202
  } else {
2051
2203
  throw new UsageError(`not a file or folder: ${input}`);
2052
2204
  }
@@ -2192,7 +2344,7 @@ function sameFiles(local, remote) {
2192
2344
  const paths = Object.keys(local);
2193
2345
  const other = remote;
2194
2346
  if (paths.length !== Object.keys(other).length) return false;
2195
- return paths.every((path22) => other[path22]?.hash === local[path22]?.hash);
2347
+ return paths.every((path25) => other[path25]?.hash === local[path25]?.hash);
2196
2348
  }
2197
2349
  function reportUnchanged(out, result) {
2198
2350
  out.data({ unchanged: true, slug: result.slug, version: result.version.n });
@@ -2221,23 +2373,23 @@ function validateSlug(slug) {
2221
2373
  }
2222
2374
 
2223
2375
  // src/warn.ts
2224
- import process4 from "node:process";
2376
+ import process5 from "node:process";
2225
2377
  function warn2(message) {
2226
- process4.stderr.write(`rshow: warning: ${message}
2378
+ process5.stderr.write(`rshow: warning: ${message}
2227
2379
  `);
2228
2380
  }
2229
2381
 
2230
2382
  // src/commands/publish.ts
2231
2383
  async function directoryOf(input) {
2232
- const dir = path6.resolve(input);
2233
- const info = await stat3(dir).catch(() => void 0);
2384
+ const dir = path7.resolve(input);
2385
+ const info = await stat4(dir).catch(() => void 0);
2234
2386
  return info !== void 0 && info.isDirectory() ? dir : void 0;
2235
2387
  }
2236
2388
  async function targetOf(input, dir, built, out) {
2237
- if (dir === void 0) return path6.resolve(input);
2238
- if (built) return path6.resolve(dir, out);
2389
+ if (dir === void 0) return path7.resolve(input);
2390
+ if (built) return path7.resolve(dir, out);
2239
2391
  const local = await readConfigFile(localConfigPath(dir));
2240
- return local.out === void 0 ? dir : path6.resolve(dir, out);
2392
+ return local.out === void 0 ? dir : path7.resolve(dir, out);
2241
2393
  }
2242
2394
  async function run2(argv, ctx) {
2243
2395
  const args = parseCommandArgs(
@@ -2266,7 +2418,7 @@ async function run2(argv, ctx) {
2266
2418
  }
2267
2419
  const target = await targetOf(input, dir, build !== void 0, config.out);
2268
2420
  const slug = validateSlug(
2269
- stringValue(args, "slug") ?? config.slug ?? deriveSlug(path6.basename(path6.resolve(input)))
2421
+ stringValue(args, "slug") ?? config.slug ?? deriveSlug(path7.basename(path7.resolve(input)))
2270
2422
  );
2271
2423
  const title = stringValue(args, "title") ?? config.title;
2272
2424
  const label = stringValue(args, "label");
@@ -2301,8 +2453,8 @@ async function run2(argv, ctx) {
2301
2453
  }
2302
2454
 
2303
2455
  // src/commands/check.ts
2304
- import { stat as stat4 } from "node:fs/promises";
2305
- import path7 from "node:path";
2456
+ import { stat as stat5 } from "node:fs/promises";
2457
+ import path8 from "node:path";
2306
2458
  async function run3(argv, ctx) {
2307
2459
  const args = parseCommandArgs(
2308
2460
  argv,
@@ -2312,8 +2464,8 @@ async function run3(argv, ctx) {
2312
2464
  CHECK_USAGE
2313
2465
  );
2314
2466
  const input = args.positionals[0];
2315
- const dir = path7.resolve(input);
2316
- const info = await stat4(dir).catch(() => void 0);
2467
+ const dir = path8.resolve(input);
2468
+ const info = await stat5(dir).catch(() => void 0);
2317
2469
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
2318
2470
  const config = await ctx.config(dir);
2319
2471
  const offline = boolValue(args, "offline");
@@ -2336,21 +2488,21 @@ async function run3(argv, ctx) {
2336
2488
  }
2337
2489
 
2338
2490
  // src/commands/preview.ts
2339
- import { stat as stat7 } from "node:fs/promises";
2340
- import path10 from "node:path";
2491
+ import { stat as stat8 } from "node:fs/promises";
2492
+ import path11 from "node:path";
2341
2493
 
2342
2494
  // src/serve.ts
2343
2495
  import { watch } from "node:fs";
2344
- import { mkdtemp, readFile as readFile8, rm as rm2, stat as stat6, writeFile as writeFile2 } from "node:fs/promises";
2496
+ import { mkdtemp as mkdtemp2, readFile as readFile8, rm as rm3, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
2345
2497
  import { createHash as createHash2 } from "node:crypto";
2346
2498
  import { createServer } from "node:http";
2347
2499
  import { tmpdir } from "node:os";
2348
- import path9 from "node:path";
2349
- import process5 from "node:process";
2500
+ import path10 from "node:path";
2501
+ import process6 from "node:process";
2350
2502
 
2351
2503
  // src/themes.ts
2352
- import { cp, mkdir, readFile as readFile7, rm, stat as stat5, writeFile } from "node:fs/promises";
2353
- import path8 from "node:path";
2504
+ import { cp, mkdir as mkdir2, readFile as readFile7, rm as rm2, stat as stat6, writeFile as writeFile2 } from "node:fs/promises";
2505
+ import path9 from "node:path";
2354
2506
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2355
2507
 
2356
2508
  // src/color.ts
@@ -2682,11 +2834,21 @@ function flattenDtcg(tree) {
2682
2834
  }
2683
2835
 
2684
2836
  // src/themes.ts
2685
- var HERE = path8.dirname(fileURLToPath2(import.meta.url));
2686
- var BUILTIN_BASES = [
2687
- path8.resolve(HERE, "../../../themes"),
2688
- path8.resolve(HERE, "../themes")
2689
- ];
2837
+ function sourceDir() {
2838
+ try {
2839
+ const url = import.meta.url;
2840
+ return typeof url === "string" && url !== "" ? path9.dirname(fileURLToPath2(url)) : void 0;
2841
+ } catch {
2842
+ return void 0;
2843
+ }
2844
+ }
2845
+ var HERE = sourceDir();
2846
+ function nearSource(...segments) {
2847
+ if (HERE === void 0) return [];
2848
+ return [path9.resolve(HERE, "..", "..", "..", ...segments), path9.resolve(HERE, "..", ...segments)];
2849
+ }
2850
+ var BUILTIN_BASES = nearSource("themes");
2851
+ var BUILTIN_SKILL_BASES = nearSource("skills");
2690
2852
  var ROLE_TOKENS = ["--bg", "--fg", "--muted", "--accent", "--layer-01"];
2691
2853
  var TEXT_ROLES = ["--fg", "--muted", "--accent"];
2692
2854
  var GROUND_TOKENS = ["--bg", "--layer-01"];
@@ -2711,11 +2873,11 @@ function messageOf6(err) {
2711
2873
  return err instanceof Error ? err.message : String(err);
2712
2874
  }
2713
2875
  async function isFile(file) {
2714
- const info = await stat5(file).catch(() => void 0);
2876
+ const info = await stat6(file).catch(() => void 0);
2715
2877
  return info !== void 0 && info.isFile();
2716
2878
  }
2717
2879
  async function isDir(dir) {
2718
- const info = await stat5(dir).catch(() => void 0);
2880
+ const info = await stat6(dir).catch(() => void 0);
2719
2881
  return info !== void 0 && info.isDirectory();
2720
2882
  }
2721
2883
  function tokenMap(value) {
@@ -2750,7 +2912,7 @@ function themeValues(json) {
2750
2912
  };
2751
2913
  }
2752
2914
  async function readThemeJson(dir) {
2753
- const file = path8.join(dir, "theme.json");
2915
+ const file = path9.join(dir, "theme.json");
2754
2916
  let text;
2755
2917
  try {
2756
2918
  text = await readFile7(file, "utf8");
@@ -2769,12 +2931,18 @@ async function readThemeJson(dir) {
2769
2931
  }
2770
2932
  async function builtinThemeDir(name, bases = BUILTIN_BASES) {
2771
2933
  if (!THEME_NAME_PATTERN.test(name)) return void 0;
2772
- for (const base of bases) {
2773
- const dir = path8.join(base, name);
2934
+ for (const base of [...await binaryThemeBases(), ...bases]) {
2935
+ const dir = path9.join(base, name);
2774
2936
  if (await isDir(dir)) return dir;
2775
2937
  }
2776
2938
  return void 0;
2777
2939
  }
2940
+ async function builtinSkillsDir(bases = BUILTIN_SKILL_BASES) {
2941
+ for (const base of [...await binarySkillBases(), ...bases]) {
2942
+ if (await isDir(base)) return base;
2943
+ }
2944
+ return void 0;
2945
+ }
2778
2946
  function stripPrint(css) {
2779
2947
  let out = "";
2780
2948
  let index = 0;
@@ -2800,7 +2968,7 @@ function stripPrint(css) {
2800
2968
  return out;
2801
2969
  }
2802
2970
  async function themeTokenSets(dir) {
2803
- const file = path8.join(dir, "tokens.css");
2971
+ const file = path9.join(dir, "tokens.css");
2804
2972
  const text = await readFile7(file, "utf8").catch(() => void 0);
2805
2973
  return extractTokens(text === void 0 ? [] : [stripPrint(text)]);
2806
2974
  }
@@ -2823,9 +2991,9 @@ function declarations(values, indent) {
2823
2991
  function presetTokenCss(name, values) {
2824
2992
  const parts2 = [];
2825
2993
  const light = { ...values.tokens, ...values.light };
2826
- const root = declarations(light, " ");
2827
- if (root !== "") parts2.push(`:root {
2828
- ${root}
2994
+ const root2 = declarations(light, " ");
2995
+ if (root2 !== "") parts2.push(`:root {
2996
+ ${root2}
2829
2997
  }`);
2830
2998
  const dark = declarations(values.dark, " ");
2831
2999
  if (dark !== "") {
@@ -2854,7 +3022,7 @@ function presetFontCss(fonts) {
2854
3022
  lines.push(` font-weight: ${font.weight ?? "400"};`);
2855
3023
  lines.push(" font-display: swap;");
2856
3024
  const url = fontUrl(font.file);
2857
- const format = FONT_FORMATS[path8.extname(url).toLowerCase()];
3025
+ const format = FONT_FORMATS[path9.extname(url).toLowerCase()];
2858
3026
  const source = format === void 0 ? `url("${url}")` : `url("${url}") format("${format}")`;
2859
3027
  lines.push(` src: ${source};`);
2860
3028
  return `@font-face {
@@ -2881,13 +3049,13 @@ async function appendFile(file, addition) {
2881
3049
  if (addition === "") return;
2882
3050
  const before = await readFile7(file, "utf8").catch(() => "");
2883
3051
  const glue = before === "" || before.endsWith("\n") ? "" : "\n";
2884
- await writeFile(file, `${before}${glue}${addition}`);
3052
+ await writeFile2(file, `${before}${glue}${addition}`);
2885
3053
  }
2886
3054
  async function buildPreset(presetDir, outDir) {
2887
3055
  const preset = await readThemeJson(presetDir);
2888
3056
  const baseName = stringField(preset.json.extends);
2889
- await rm(outDir, { recursive: true, force: true });
2890
- await mkdir(outDir, { recursive: true });
3057
+ await rm2(outDir, { recursive: true, force: true });
3058
+ await mkdir2(outDir, { recursive: true });
2891
3059
  if (baseName === void 0) {
2892
3060
  await cp(presetDir, outDir, { recursive: true });
2893
3061
  return;
@@ -2898,15 +3066,15 @@ async function buildPreset(presetDir, outDir) {
2898
3066
  await cp(baseDir, outDir, { recursive: true });
2899
3067
  const values = themeValues(preset.json);
2900
3068
  const name = stringField(preset.json.name) ?? baseName;
2901
- await appendFile(path8.join(outDir, "tokens.css"), presetTokenCss(name, values));
3069
+ await appendFile(path9.join(outDir, "tokens.css"), presetTokenCss(name, values));
2902
3070
  const fonts = fontList(preset.json.fonts);
2903
3071
  if (fonts.length > 0) {
2904
- const from = path8.join(presetDir, "fonts");
2905
- if (await isDir(from)) await cp(from, path8.join(outDir, "fonts"), { recursive: true });
2906
- await appendFile(path8.join(outDir, "fonts.css"), presetFontCss(fonts));
3072
+ const from = path9.join(presetDir, "fonts");
3073
+ if (await isDir(from)) await cp(from, path9.join(outDir, "fonts"), { recursive: true });
3074
+ await appendFile(path9.join(outDir, "fonts.css"), presetFontCss(fonts));
2907
3075
  }
2908
3076
  const merged = mergeTheme(base.json, preset.json, baseName);
2909
- await writeFile(path8.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
3077
+ await writeFile2(path9.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
2910
3078
  `);
2911
3079
  }
2912
3080
  function keyLine(text, key2, mode) {
@@ -2921,8 +3089,8 @@ function keyLine(text, key2, mode) {
2921
3089
  }
2922
3090
  return 0;
2923
3091
  }
2924
- function finding(path22, line, severity, message) {
2925
- return { path: path22, line: Math.max(1, line), severity, message };
3092
+ function finding(path25, line, severity, message) {
3093
+ return { path: path25, line: Math.max(1, line), severity, message };
2926
3094
  }
2927
3095
  function resolveValue(name, map, fallback) {
2928
3096
  let current = map[name] ?? fallback[name];
@@ -3020,13 +3188,13 @@ async function checkPreset(presetDir) {
3020
3188
  }
3021
3189
  const fonts = fontList(preset.json.fonts);
3022
3190
  for (const font of fonts) {
3023
- const file = path8.join(presetDir, font.file);
3191
+ const file = path9.join(presetDir, font.file);
3024
3192
  if (!await isFile(file)) {
3025
3193
  findings.push(finding("theme.json", keyLine(text, "file"), "error", `missing font file: ${font.file}`));
3026
3194
  }
3027
3195
  }
3028
3196
  if (fonts.length > 0) {
3029
- const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path8.join(presetDir, "fonts", rel))));
3197
+ const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path9.join(presetDir, "fonts", rel))));
3030
3198
  if (!found.some(Boolean)) {
3031
3199
  findings.push(
3032
3200
  finding(
@@ -3144,14 +3312,14 @@ function parseValue(text) {
3144
3312
  function dataLimit(key2) {
3145
3313
  return key2.startsWith(SKETCH_DATA_KEY_PREFIX) ? MAX_SKETCH_DATA_BYTES : MAX_DATA_BYTES;
3146
3314
  }
3147
- function jsonReply(res, status, body, headers = {}) {
3148
- res.writeHead(status, { "content-type": JSON_TYPE, "cache-control": "no-store", ...headers });
3315
+ function jsonReply(res, status2, body, headers = {}) {
3316
+ res.writeHead(status2, { "content-type": JSON_TYPE, "cache-control": "no-store", ...headers });
3149
3317
  res.end(JSON.stringify(body));
3150
3318
  }
3151
- function jsonError(res, message, status) {
3152
- jsonReply(res, status, { error: message });
3319
+ function jsonError(res, message, status2) {
3320
+ jsonReply(res, status2, { error: message });
3153
3321
  }
3154
- async function resolveFile(root, pathname) {
3322
+ async function resolveFile(root2, pathname) {
3155
3323
  let decoded;
3156
3324
  try {
3157
3325
  decoded = decodeURIComponent(pathname);
@@ -3159,15 +3327,15 @@ async function resolveFile(root, pathname) {
3159
3327
  return void 0;
3160
3328
  }
3161
3329
  if (decoded.includes("\0")) return void 0;
3162
- const target = path9.resolve(root, decoded.replace(/^\/+/, ""));
3163
- const rel = path9.relative(root, target);
3164
- if (rel.startsWith("..") || path9.isAbsolute(rel)) return void 0;
3165
- const info = await stat6(target).catch(() => void 0);
3330
+ const target = path10.resolve(root2, decoded.replace(/^\/+/, ""));
3331
+ const rel = path10.relative(root2, target);
3332
+ if (rel.startsWith("..") || path10.isAbsolute(rel)) return void 0;
3333
+ const info = await stat7(target).catch(() => void 0);
3166
3334
  if (info === void 0) return void 0;
3167
3335
  if (info.isFile()) return target;
3168
3336
  if (!info.isDirectory()) return void 0;
3169
- const index = path9.join(target, "index.html");
3170
- const indexInfo = await stat6(index).catch(() => void 0);
3337
+ const index = path10.join(target, "index.html");
3338
+ const indexInfo = await stat7(index).catch(() => void 0);
3171
3339
  if (indexInfo === void 0 || !indexInfo.isFile()) return void 0;
3172
3340
  return index;
3173
3341
  }
@@ -3193,10 +3361,10 @@ async function proxy(art, pathname, method, res) {
3193
3361
  async function prepareTheme(dir) {
3194
3362
  const doc = await readThemeJson(dir);
3195
3363
  const declared = typeof doc.json.name === "string" ? doc.json.name.trim() : "";
3196
- const name = declared === "" ? path9.basename(dir) : declared;
3364
+ const name = declared === "" ? path10.basename(dir) : declared;
3197
3365
  const base = typeof doc.json.extends === "string" ? doc.json.extends.trim() : "";
3198
3366
  if (base === "") return { name, source: dir, built: dir, label: "local" };
3199
- const built = await mkdtemp(path9.join(tmpdir(), "rshow-theme-"));
3367
+ const built = await mkdtemp2(path10.join(tmpdir(), "rshow-theme-"));
3200
3368
  await buildPreset(dir, built);
3201
3369
  return { name, source: dir, built, label: "preset" };
3202
3370
  }
@@ -3267,9 +3435,9 @@ function createDataStore() {
3267
3435
  const values = /* @__PURE__ */ new Map();
3268
3436
  let dir;
3269
3437
  const persist = async () => {
3270
- if (dir === void 0) dir = await mkdtemp(path9.join(tmpdir(), "rshow-data-"));
3438
+ if (dir === void 0) dir = await mkdtemp2(path10.join(tmpdir(), "rshow-data-"));
3271
3439
  const doc = Object.fromEntries(values);
3272
- await writeFile2(path9.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
3440
+ await writeFile3(path10.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
3273
3441
  `);
3274
3442
  };
3275
3443
  return {
@@ -3283,7 +3451,7 @@ function createDataStore() {
3283
3451
  },
3284
3452
  close: async () => {
3285
3453
  if (dir === void 0) return;
3286
- await rm2(dir, { recursive: true, force: true });
3454
+ await rm3(dir, { recursive: true, force: true });
3287
3455
  dir = void 0;
3288
3456
  }
3289
3457
  };
@@ -3488,19 +3656,19 @@ async function handle(req, res, session) {
3488
3656
  }
3489
3657
  function closeOnSignals(preview2) {
3490
3658
  const stop = () => {
3491
- preview2.close().catch(() => void 0).finally(() => process5.exit(0));
3659
+ preview2.close().catch(() => void 0).finally(() => process6.exit(0));
3492
3660
  };
3493
- process5.once("SIGINT", stop);
3494
- process5.once("SIGTERM", stop);
3661
+ process6.once("SIGINT", stop);
3662
+ process6.once("SIGTERM", stop);
3495
3663
  }
3496
3664
  async function startPreview(opts) {
3497
- const dir = path9.resolve(opts.dir);
3665
+ const dir = path10.resolve(opts.dir);
3498
3666
  const art = (opts.art ?? "").replace(/\/+$/, "");
3499
3667
  const host = opts.host ?? "127.0.0.1";
3500
- const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path9.resolve(opts.theme));
3501
- const root = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
3668
+ const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path10.resolve(opts.theme));
3669
+ const root2 = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
3502
3670
  const session = {
3503
- root,
3671
+ root: root2,
3504
3672
  art,
3505
3673
  offline: opts.offline === true,
3506
3674
  reload: opts.reload !== false,
@@ -3509,8 +3677,8 @@ async function startPreview(opts) {
3509
3677
  clients: /* @__PURE__ */ new Set(),
3510
3678
  builtin: /* @__PURE__ */ new Map()
3511
3679
  };
3512
- const targets = [{ dir: root, rebuild: false }];
3513
- if (theme !== void 0 && theme.source !== root) targets.push({ dir: theme.source, rebuild: true });
3680
+ const targets = [{ dir: root2, rebuild: false }];
3681
+ if (theme !== void 0 && theme.source !== root2) targets.push({ dir: theme.source, rebuild: true });
3514
3682
  const watchers = startWatchers(session, targets);
3515
3683
  const server = createServer((req, res) => {
3516
3684
  handle(req, res, session).catch((err) => {
@@ -3525,7 +3693,7 @@ async function startPreview(opts) {
3525
3693
  session.clients.clear();
3526
3694
  await session.store.close();
3527
3695
  if (theme !== void 0 && theme.built !== theme.source) {
3528
- await rm2(theme.built, { recursive: true, force: true });
3696
+ await rm3(theme.built, { recursive: true, force: true });
3529
3697
  }
3530
3698
  };
3531
3699
  try {
@@ -3565,8 +3733,8 @@ function portOf(value) {
3565
3733
  return port;
3566
3734
  }
3567
3735
  async function requireFolder(input, what) {
3568
- const dir = path10.resolve(input);
3569
- const info = await stat7(dir).catch(() => void 0);
3736
+ const dir = path11.resolve(input);
3737
+ const info = await stat8(dir).catch(() => void 0);
3570
3738
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such ${what}: ${input}`);
3571
3739
  return dir;
3572
3740
  }
@@ -3652,9 +3820,9 @@ async function run5(argv, ctx) {
3652
3820
  }
3653
3821
 
3654
3822
  // src/commands/init.ts
3655
- import { mkdir as mkdir2, readFile as readFile9, writeFile as writeFile3 } from "node:fs/promises";
3656
- import path11 from "node:path";
3657
- import process6 from "node:process";
3823
+ import { mkdir as mkdir3, readFile as readFile9, writeFile as writeFile4 } from "node:fs/promises";
3824
+ import path12 from "node:path";
3825
+ import process7 from "node:process";
3658
3826
  var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
3659
3827
  function messageOf9(err) {
3660
3828
  return err instanceof Error ? err.message : String(err);
@@ -3706,7 +3874,7 @@ async function run6(argv, ctx) {
3706
3874
  const url = serverUrl(stringValue(args, "url"), "url");
3707
3875
  const art = serverUrl(stringValue(args, "art"), "art");
3708
3876
  const theme = (stringValue(args, "theme") ?? "").trim();
3709
- const file = globalConfigPath(process6.env);
3877
+ const file = globalConfigPath(process7.env);
3710
3878
  const current = await readConfigFile(file);
3711
3879
  const same = (have, want) => have === void 0 || have.replace(/\/+$/, "") === want;
3712
3880
  if (!boolValue(args, "force")) {
@@ -3721,8 +3889,8 @@ async function run6(argv, ctx) {
3721
3889
  raw.url = url;
3722
3890
  raw.art = art;
3723
3891
  if (theme !== "") raw.theme = theme;
3724
- await mkdir2(path11.dirname(file), { recursive: true });
3725
- await writeFile3(file, `${JSON.stringify(raw, null, 2)}
3892
+ await mkdir3(path12.dirname(file), { recursive: true });
3893
+ await writeFile4(file, `${JSON.stringify(raw, null, 2)}
3726
3894
  `, "utf8");
3727
3895
  ctx.out.data({ path: file, url, art });
3728
3896
  ctx.out.line(`wrote ${file}`);
@@ -3730,24 +3898,226 @@ async function run6(argv, ctx) {
3730
3898
  }
3731
3899
 
3732
3900
  // src/commands/doctor.ts
3733
- import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
3734
- import path12 from "node:path";
3735
- import process7 from "node:process";
3901
+ import { readFile as readFile11, stat as stat10 } from "node:fs/promises";
3902
+ import path14 from "node:path";
3903
+ import process8 from "node:process";
3736
3904
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3905
+
3906
+ // src/skills.ts
3907
+ import { cp as cp2, lstat, mkdir as mkdir4, readFile as readFile10, readdir as readdir3, readlink, rm as rm4, stat as stat9, symlink, writeFile as writeFile5 } from "node:fs/promises";
3908
+ import { homedir as homedir3 } from "node:os";
3909
+ import path13 from "node:path";
3910
+ var SKILL_TARGETS = ["claude", "pi", "codex"];
3911
+ var SKILL_MARKER = ".rshow-skill";
3912
+ var SKILL_SOURCE = "rshow";
3913
+ var TARGET_SEGMENTS = {
3914
+ claude: [".claude"],
3915
+ pi: [".pi", "agent"],
3916
+ codex: [".codex"]
3917
+ };
3918
+ async function isDir2(dir) {
3919
+ const info = await stat9(dir).catch(() => void 0);
3920
+ return info !== void 0 && info.isDirectory();
3921
+ }
3922
+ async function isFile2(file) {
3923
+ const info = await stat9(file).catch(() => void 0);
3924
+ return info !== void 0 && info.isFile();
3925
+ }
3926
+ function isUnder(file, dir) {
3927
+ const rel = path13.relative(dir, file);
3928
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path13.sep}`) && !path13.isAbsolute(rel);
3929
+ }
3930
+ function homeDir(env) {
3931
+ const home = (env.HOME ?? "").trim();
3932
+ return home !== "" ? home : homedir3();
3933
+ }
3934
+ function targetParent(home, target) {
3935
+ return path13.join(home, ...TARGET_SEGMENTS[target]);
3936
+ }
3937
+ function targetSkillsDir(home, target) {
3938
+ return path13.join(targetParent(home, target), "skills");
3939
+ }
3940
+ async function detectedTargets(home) {
3941
+ const out = [];
3942
+ for (const target of SKILL_TARGETS) {
3943
+ if (await isDir2(targetParent(home, target))) out.push(target);
3944
+ }
3945
+ return out;
3946
+ }
3947
+ function parseTargets(names) {
3948
+ const out = [];
3949
+ for (const name of names) {
3950
+ const target = SKILL_TARGETS.find((item) => item === name);
3951
+ if (target === void 0) {
3952
+ throw new UsageError(`unknown target: ${name} (use ${SKILL_TARGETS.join(", ")})`);
3953
+ }
3954
+ if (!out.includes(target)) out.push(target);
3955
+ }
3956
+ return out;
3957
+ }
3958
+ function targetHint() {
3959
+ return SKILL_TARGETS.join(", ");
3960
+ }
3961
+ function defaultModeOf(dir) {
3962
+ return dir.split(path13.sep).join("/").includes("/_npx/") ? "copy" : "link";
3963
+ }
3964
+ async function skillsSource(env) {
3965
+ const override = (env.RSHOW_SKILLS_DIR ?? "").trim();
3966
+ let dir;
3967
+ if (override !== "") {
3968
+ dir = path13.resolve(override);
3969
+ if (!await isDir2(dir)) throw new UsageError(`RSHOW_SKILLS_DIR is not a folder: ${dir}`);
3970
+ } else {
3971
+ dir = await builtinSkillsDir();
3972
+ }
3973
+ if (dir === void 0) return void 0;
3974
+ const skills = [];
3975
+ for (const entry of await readdir3(dir, { withFileTypes: true }).catch(() => [])) {
3976
+ if (entry.isDirectory() && await isFile2(path13.join(dir, entry.name, "SKILL.md"))) skills.push(entry.name);
3977
+ }
3978
+ skills.sort();
3979
+ return { dir, mode: await isBinary() ? "copy" : defaultModeOf(dir), skills };
3980
+ }
3981
+ function isOurLink(target, source) {
3982
+ const resolved = path13.resolve(target);
3983
+ if (isUnder(resolved, path13.resolve(source))) return true;
3984
+ return /(^|\/)rshow\/skills\//.test(resolved.split(path13.sep).join("/"));
3985
+ }
3986
+ async function linkTargetOf(dst) {
3987
+ const value = await readlink(dst);
3988
+ return path13.isAbsolute(value) ? value : path13.resolve(path13.dirname(dst), value);
3989
+ }
3990
+ async function readMarker(dir) {
3991
+ const text = await readFile10(path13.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
3992
+ if (text === void 0) return void 0;
3993
+ let parsed;
3994
+ try {
3995
+ parsed = JSON.parse(text);
3996
+ } catch {
3997
+ return void 0;
3998
+ }
3999
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
4000
+ const doc = parsed;
4001
+ if (doc.source !== SKILL_SOURCE || typeof doc.version !== "string" || doc.version === "") return void 0;
4002
+ return { version: doc.version, source: SKILL_SOURCE };
4003
+ }
4004
+ async function inspectSkill(dst, source, skill) {
4005
+ const info = await lstat(dst).catch(() => void 0);
4006
+ if (info === void 0) return { kind: "missing" };
4007
+ if (info.isSymbolicLink()) {
4008
+ const link = await linkTargetOf(dst);
4009
+ if (!isOurLink(link, source)) return { kind: "foreign" };
4010
+ return { kind: "link", path: link, current: path13.resolve(link) === path13.join(path13.resolve(source), skill) };
4011
+ }
4012
+ if (info.isDirectory()) {
4013
+ const marker = await readMarker(dst);
4014
+ return marker === void 0 ? { kind: "foreign" } : { kind: "copy", version: marker.version };
4015
+ }
4016
+ return { kind: "foreign" };
4017
+ }
4018
+ function skillIsCurrent(state, version) {
4019
+ if (state.kind === "link") return true;
4020
+ if (state.kind === "copy") return state.version === version;
4021
+ return false;
4022
+ }
4023
+ function skillStateLabel(state) {
4024
+ switch (state.kind) {
4025
+ case "missing":
4026
+ return "missing";
4027
+ case "foreign":
4028
+ return "foreign";
4029
+ case "link":
4030
+ return `link -> ${state.path}`;
4031
+ case "copy":
4032
+ return `copy v${state.version}`;
4033
+ }
4034
+ }
4035
+ function actionFor(state, version) {
4036
+ if (state.kind === "missing") return "installed";
4037
+ if (state.kind === "foreign") return "skip";
4038
+ if (state.kind === "link") return state.current ? "ok" : "updated";
4039
+ return state.version === version ? "ok" : "updated";
4040
+ }
4041
+ async function writeSkill(dst, src, mode, version) {
4042
+ if (mode === "link") {
4043
+ await symlink(src, dst);
4044
+ return;
4045
+ }
4046
+ await cp2(src, dst, { recursive: true });
4047
+ const marker = { version, source: SKILL_SOURCE };
4048
+ await writeFile5(path13.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
4049
+ `, "utf8");
4050
+ }
4051
+ async function installSkills(options) {
4052
+ const entries = [];
4053
+ for (const target of options.targets) {
4054
+ const dir = targetSkillsDir(options.home, target);
4055
+ if (!options.dryRun) await mkdir4(dir, { recursive: true });
4056
+ for (const skill of options.source.skills) {
4057
+ const dst = path13.join(dir, skill);
4058
+ const src = path13.join(options.source.dir, skill);
4059
+ const state = await inspectSkill(dst, options.source.dir, skill);
4060
+ const action = actionFor(state, options.version);
4061
+ if (!options.dryRun && (action === "installed" || action === "updated")) {
4062
+ if (action === "updated") await rm4(dst, { recursive: true, force: true });
4063
+ await writeSkill(dst, src, options.mode, options.version);
4064
+ }
4065
+ entries.push({ target, skill, action, path: dst });
4066
+ }
4067
+ }
4068
+ return entries;
4069
+ }
4070
+ async function uninstallSkills(options) {
4071
+ const entries = [];
4072
+ for (const target of options.targets) {
4073
+ const dir = targetSkillsDir(options.home, target);
4074
+ for (const skill of (await readdir3(dir).catch(() => [])).sort()) {
4075
+ const dst = path13.join(dir, skill);
4076
+ const state = await inspectSkill(dst, options.source.dir, skill);
4077
+ if (state.kind !== "link" && state.kind !== "copy") continue;
4078
+ if (!options.dryRun) await rm4(dst, { recursive: true, force: true });
4079
+ entries.push({ target, skill, action: "removed", path: dst });
4080
+ }
4081
+ }
4082
+ return entries;
4083
+ }
4084
+ async function skillsStatus(options) {
4085
+ const out = [];
4086
+ for (const target of options.targets) {
4087
+ const dir = targetSkillsDir(options.home, target);
4088
+ for (const skill of options.source.skills) {
4089
+ const dst = path13.join(dir, skill);
4090
+ out.push({ target, skill, state: await inspectSkill(dst, options.source.dir, skill), path: dst });
4091
+ }
4092
+ }
4093
+ return out;
4094
+ }
4095
+
4096
+ // src/commands/doctor.ts
3737
4097
  var TIMEOUT_MS = 5e3;
3738
4098
  function messageOf10(err) {
3739
4099
  return err instanceof Error ? err.message : String(err);
3740
4100
  }
3741
- async function isFile2(file) {
3742
- const info = await stat8(file).catch(() => void 0);
4101
+ async function isFile3(file) {
4102
+ const info = await stat10(file).catch(() => void 0);
3743
4103
  return info !== void 0 && info.isFile();
3744
4104
  }
4105
+ function sourceDir2() {
4106
+ try {
4107
+ const url = import.meta.url;
4108
+ return typeof url === "string" && url !== "" ? path14.dirname(fileURLToPath3(url)) : void 0;
4109
+ } catch {
4110
+ return void 0;
4111
+ }
4112
+ }
3745
4113
  async function packageJsonPath() {
3746
- let dir = path12.dirname(fileURLToPath3(import.meta.url));
4114
+ const start = sourceDir2();
4115
+ if (start === void 0) return void 0;
4116
+ let dir = start;
3747
4117
  for (let i = 0; i < 6; i += 1) {
3748
- const file = path12.join(dir, "package.json");
3749
- if (await isFile2(file)) return file;
3750
- const up = path12.dirname(dir);
4118
+ const file = path14.join(dir, "package.json");
4119
+ if (await isFile3(file)) return file;
4120
+ const up = path14.dirname(dir);
3751
4121
  if (up === dir) break;
3752
4122
  dir = up;
3753
4123
  }
@@ -3771,23 +4141,26 @@ function minimumOf(range) {
3771
4141
  }
3772
4142
  async function nodeCheck() {
3773
4143
  const name = "node";
3774
- const current = process7.versions.node;
4144
+ const current = process8.versions.node;
3775
4145
  const file = await packageJsonPath();
3776
- if (file === void 0) return { name, status: "warn", detail: `v${current} (package.json not found)` };
4146
+ if (file === void 0) {
4147
+ if (await isBinary()) return { name, status: "ok", detail: `v${current} (inside the binary)` };
4148
+ return { name, status: "warn", detail: `v${current} (package.json not found)` };
4149
+ }
3777
4150
  let range;
3778
4151
  try {
3779
- const body = JSON.parse(await readFile10(file, "utf8"));
4152
+ const body = JSON.parse(await readFile11(file, "utf8"));
3780
4153
  range = typeof body.engines?.node === "string" ? body.engines.node : "";
3781
4154
  } catch (err) {
3782
4155
  return { name, status: "warn", detail: `v${current} (${messageOf10(err)})` };
3783
4156
  }
3784
4157
  const needed = minimumOf(range);
3785
4158
  if (needed === void 0) return { name, status: "warn", detail: `v${current} (no engines.node)` };
3786
- const status = atLeast(versionParts(current), needed) ? "ok" : "warn";
3787
- return { name, status, detail: `v${current} (needs ${range})` };
4159
+ const status2 = atLeast(versionParts(current), needed) ? "ok" : "warn";
4160
+ return { name, status: status2, detail: `v${current} (needs ${range})` };
3788
4161
  }
3789
4162
  async function tokenOf(url) {
3790
- const env = (process7.env.RSHOW_TOKEN ?? "").trim();
4163
+ const env = (process8.env.RSHOW_TOKEN ?? "").trim();
3791
4164
  if (env !== "") return { token: env, source: "env" };
3792
4165
  if (url === void 0) return { source: "none" };
3793
4166
  try {
@@ -3872,12 +4245,28 @@ async function themeCheck(config) {
3872
4245
  return { name, status: "warn", detail: messageOf10(err) };
3873
4246
  }
3874
4247
  }
4248
+ async function skillsCheck() {
4249
+ const name = "skills";
4250
+ const home = homeDir(process8.env);
4251
+ const targets = await detectedTargets(home);
4252
+ if (targets.length === 0) return { name, status: "ok", detail: "no target detected" };
4253
+ const source = await skillsSource(process8.env).catch(() => void 0);
4254
+ if (source === void 0) return { name, status: "warn", detail: "no skills folder next to rshow" };
4255
+ const list3 = await skillsStatus({ home, source, targets });
4256
+ const problems = [];
4257
+ for (const target of targets) {
4258
+ const bad = list3.filter((item) => item.target === target && !skillIsCurrent(item.state, VERSION)).map((item) => `${item.skill} ${skillStateLabel(item.state)}`);
4259
+ if (bad.length > 0) problems.push(`${target}: ${bad.join(", ")}`);
4260
+ }
4261
+ if (problems.length === 0) return { name, status: "ok", detail: targets.join(", ") };
4262
+ return { name, status: "warn", detail: `${problems.join("; ")} (run rshow skills install)` };
4263
+ }
3875
4264
  async function run7(argv, ctx) {
3876
4265
  const args = parseCommandArgs(argv, {}, 0, 1, DOCTOR_USAGE);
3877
4266
  const dir = args.positionals[0];
3878
4267
  const found = [];
3879
- for (const file of [globalConfigPath(process7.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
3880
- if (file !== void 0 && await isFile2(file)) found.push(file);
4268
+ for (const file of [globalConfigPath(process8.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
4269
+ if (file !== void 0 && await isFile3(file)) found.push(file);
3881
4270
  }
3882
4271
  let config;
3883
4272
  let configError;
@@ -3888,7 +4277,7 @@ async function run7(argv, ctx) {
3888
4277
  }
3889
4278
  const token = await tokenOf(config?.url);
3890
4279
  const checks = [
3891
- { name: "rshow", status: "ok", detail: VERSION },
4280
+ { name: "rshow", status: "ok", detail: await isBinary() ? `${VERSION} (binary)` : VERSION },
3892
4281
  await nodeCheck(),
3893
4282
  configCheck(found, config, configError),
3894
4283
  tokenCheck(token),
@@ -3897,19 +4286,20 @@ async function run7(argv, ctx) {
3897
4286
  ];
3898
4287
  const theme = await themeCheck(config);
3899
4288
  if (theme !== void 0) checks.push(theme);
4289
+ checks.push(await skillsCheck());
3900
4290
  ctx.out.data({ checks });
3901
4291
  for (const check2 of checks) ctx.out.line(`${check2.status} ${check2.name} ${check2.detail}`);
3902
4292
  return checks.some((check2) => check2.status === "fail") ? EXIT.check : EXIT.ok;
3903
4293
  }
3904
4294
 
3905
4295
  // src/commands/theme.ts
3906
- import { mkdir as mkdir3, mkdtemp as mkdtemp2, rm as rm3, stat as stat10, writeFile as writeFile4 } from "node:fs/promises";
4296
+ import { mkdir as mkdir5, mkdtemp as mkdtemp3, rm as rm5, stat as stat12, writeFile as writeFile6 } from "node:fs/promises";
3907
4297
  import { tmpdir as tmpdir2 } from "node:os";
3908
- import path14 from "node:path";
4298
+ import path16 from "node:path";
3909
4299
 
3910
4300
  // src/theme.ts
3911
- import { readFile as readFile11, stat as stat9 } from "node:fs/promises";
3912
- import path13 from "node:path";
4301
+ import { readFile as readFile12, stat as stat11 } from "node:fs/promises";
4302
+ import path15 from "node:path";
3913
4303
  function themeSlug(name) {
3914
4304
  return `theme-${name}`;
3915
4305
  }
@@ -3917,10 +4307,10 @@ function themeUrl(name, n) {
3917
4307
  return `/_themes/${name}@${n}/`;
3918
4308
  }
3919
4309
  async function readThemeMeta(dir) {
3920
- const file = path13.join(dir, "theme.json");
4310
+ const file = path15.join(dir, "theme.json");
3921
4311
  let text;
3922
4312
  try {
3923
- text = await readFile11(file, "utf8");
4313
+ text = await readFile12(file, "utf8");
3924
4314
  } catch (err) {
3925
4315
  if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
3926
4316
  throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -3941,8 +4331,8 @@ async function readThemeMeta(dir) {
3941
4331
  return { name };
3942
4332
  }
3943
4333
  async function requireThemeDir(input) {
3944
- const dir = path13.resolve(input);
3945
- const info = await stat9(dir).catch(() => void 0);
4334
+ const dir = path15.resolve(input);
4335
+ const info = await stat11(dir).catch(() => void 0);
3946
4336
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
3947
4337
  return dir;
3948
4338
  }
@@ -3950,7 +4340,7 @@ async function requireThemeDir(input) {
3950
4340
  // src/commands/theme.ts
3951
4341
  async function presetCheck(dir) {
3952
4342
  const findings = await checkPreset(dir);
3953
- const info = await stat10(path14.join(dir, "theme.json")).catch(() => void 0);
4343
+ const info = await stat12(path16.join(dir, "theme.json")).catch(() => void 0);
3954
4344
  const errors = findings.filter((finding2) => finding2.severity === "error").length;
3955
4345
  return { findings, errors, warnings: findings.length - errors, files: 1, bytes: info?.size ?? 0 };
3956
4346
  }
@@ -3986,7 +4376,7 @@ async function publish(argv, ctx) {
3986
4376
  let target = dir;
3987
4377
  let temp;
3988
4378
  if (preset) {
3989
- temp = await mkdtemp2(path14.join(tmpdir2(), "rshow-theme-"));
4379
+ temp = await mkdtemp3(path16.join(tmpdir2(), "rshow-theme-"));
3990
4380
  await buildPreset(dir, temp);
3991
4381
  target = temp;
3992
4382
  }
@@ -4032,7 +4422,7 @@ async function publish(argv, ctx) {
4032
4422
  reportPublish(ctx.out, { ...result, artifact });
4033
4423
  ctx.out.line(url);
4034
4424
  } finally {
4035
- if (temp !== void 0) await rm3(temp, { recursive: true, force: true });
4425
+ if (temp !== void 0) await rm5(temp, { recursive: true, force: true });
4036
4426
  }
4037
4427
  }
4038
4428
  async function list(argv, ctx) {
@@ -4074,14 +4464,14 @@ async function create(argv, ctx) {
4074
4464
  const from = stringValue(args, "from") ?? "default";
4075
4465
  const baseDir = await builtinThemeDir(from);
4076
4466
  if (baseDir === void 0) throw new UsageError(`no built-in theme: ${from}`);
4077
- const parent = path14.resolve(stringValue(args, "dir") ?? ".");
4078
- const target = path14.join(parent, name);
4079
- const exists = await stat10(target).catch(() => void 0);
4080
- if (exists !== void 0) throw new UsageError(`${target} already exists`);
4467
+ const parent = path16.resolve(stringValue(args, "dir") ?? ".");
4468
+ const target = path16.join(parent, name);
4469
+ const exists2 = await stat12(target).catch(() => void 0);
4470
+ if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
4081
4471
  const values = await baseRoleValues(baseDir, ROLE_TOKENS);
4082
4472
  const doc = { name, extends: from, tokens: {}, modeTokens: values };
4083
- await mkdir3(target, { recursive: true });
4084
- await writeFile4(path14.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
4473
+ await mkdir5(target, { recursive: true });
4474
+ await writeFile6(path16.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
4085
4475
  `);
4086
4476
  ctx.out.data({ path: target, name, extends: from });
4087
4477
  ctx.out.line(target);
@@ -4147,8 +4537,124 @@ async function run8(argv, ctx) {
4147
4537
  throw new UsageError(`unknown theme command: ${sub} (try: ${usageLine(THEME_USAGE)})`);
4148
4538
  }
4149
4539
 
4150
- // src/commands/list.ts
4540
+ // src/commands/skills.ts
4541
+ import path17 from "node:path";
4542
+ import process9 from "node:process";
4543
+ var CHANGE_ACTIONS = /* @__PURE__ */ new Set(["installed", "updated", "removed"]);
4544
+ async function sourceOf() {
4545
+ const source = await skillsSource(process9.env);
4546
+ if (source === void 0) {
4547
+ throw new UsageError(`no skills folder next to rshow (set RSHOW_SKILLS_DIR to one)`);
4548
+ }
4549
+ return source;
4550
+ }
4551
+ async function targetsWithDetection(names, home) {
4552
+ const chosen = parseTargets(names);
4553
+ if (chosen.length > 0) return chosen;
4554
+ return detectedTargets(home);
4555
+ }
4556
+ function noTargetsError(home) {
4557
+ return new UsageError(
4558
+ `no target detected (looked for ${path17.join(home, ".claude")}, ${path17.join(home, ".pi/agent")} and ${path17.join(home, ".codex")}); name one of ${targetHint()}`
4559
+ );
4560
+ }
4561
+ function actionLine(entry, dryRun) {
4562
+ const would = dryRun && CHANGE_ACTIONS.has(entry.action) ? "would " : "";
4563
+ return `${would}${entry.action} ${entry.path}`;
4564
+ }
4565
+ function countOf(entries) {
4566
+ return entries.filter((entry) => entry.action !== "skip").length;
4567
+ }
4568
+ var DRY_RUN_VERB = { installed: "install", removed: "remove" };
4569
+ function summaryLine2(verb, entries, targets, dryRun) {
4570
+ const phrase = dryRun ? `would ${DRY_RUN_VERB[verb] ?? verb}` : verb;
4571
+ return `${phrase} ${countOf(entries)} skills for ${targets.join(", ")}`;
4572
+ }
4573
+ async function install(argv, ctx) {
4574
+ const args = parseCommandArgs(
4575
+ argv,
4576
+ { link: { type: "boolean" }, copy: { type: "boolean" }, "dry-run": { type: "boolean" } },
4577
+ 0,
4578
+ 3,
4579
+ SKILLS_INSTALL_USAGE
4580
+ );
4581
+ const link = boolValue(args, "link");
4582
+ const copy = boolValue(args, "copy");
4583
+ if (link && copy) throw new UsageError(`--link and --copy cannot be used together (try: ${usageLine(SKILLS_INSTALL_USAGE)})`);
4584
+ const home = homeDir(process9.env);
4585
+ const targets = await targetsWithDetection(args.positionals, home);
4586
+ if (targets.length === 0) throw noTargetsError(home);
4587
+ const source = await sourceOf();
4588
+ const mode = link ? "link" : copy ? "copy" : source.mode;
4589
+ const dryRun = boolValue(args, "dry-run");
4590
+ const entries = await installSkills({ home, source, targets, mode, dryRun, version: VERSION });
4591
+ ctx.out.data({ targets, actions: entries });
4592
+ const skipped = [];
4593
+ for (const entry of entries) {
4594
+ if (entry.action === "skip") skipped.push(entry.path);
4595
+ else ctx.out.line(actionLine(entry, dryRun));
4596
+ }
4597
+ ctx.out.line(summaryLine2("installed", entries, targets, dryRun));
4598
+ for (const dst of skipped) process9.stderr.write(`skip ${dst}: exists and is not from rshow
4599
+ `);
4600
+ return skipped.length > 0 ? EXIT.check : EXIT.ok;
4601
+ }
4602
+ async function uninstall(argv, ctx) {
4603
+ const args = parseCommandArgs(argv, { "dry-run": { type: "boolean" } }, 0, 3, SKILLS_UNINSTALL_USAGE);
4604
+ const home = homeDir(process9.env);
4605
+ const targets = await targetsWithDetection(args.positionals, home);
4606
+ if (targets.length === 0) return EXIT.ok;
4607
+ const source = await sourceOf();
4608
+ const dryRun = boolValue(args, "dry-run");
4609
+ const entries = await uninstallSkills({ home, source, targets, dryRun });
4610
+ ctx.out.data({ targets, actions: entries });
4611
+ for (const entry of entries) ctx.out.line(actionLine(entry, dryRun));
4612
+ ctx.out.line(summaryLine2("removed", entries, targets, dryRun));
4613
+ return EXIT.ok;
4614
+ }
4615
+ function actionOf(item) {
4616
+ if (item.state.kind === "link") return { action: "link", link: item.state.path };
4617
+ if (item.state.kind === "copy") return { action: "copy", version: item.state.version };
4618
+ return { action: item.state.kind };
4619
+ }
4620
+ async function status(argv, ctx) {
4621
+ const args = parseCommandArgs(argv, {}, 0, 3, SKILLS_STATUS_USAGE);
4622
+ const home = homeDir(process9.env);
4623
+ const targets = await targetsWithDetection(args.positionals, home);
4624
+ if (targets.length === 0) return EXIT.ok;
4625
+ const source = await sourceOf();
4626
+ const list3 = await skillsStatus({ home, source, targets });
4627
+ const targetWidth = Math.max(...targets.map((target) => target.length));
4628
+ const skillWidth = Math.max(...source.skills.map((skill) => skill.length));
4629
+ ctx.out.data({
4630
+ targets,
4631
+ actions: list3.map((item) => ({ target: item.target, skill: item.skill, path: item.path, ...actionOf(item) }))
4632
+ });
4633
+ for (const item of list3) {
4634
+ const name = `${item.target.padEnd(targetWidth)} ${item.skill.padEnd(skillWidth)}`;
4635
+ ctx.out.line(`${name} ${skillStateLabel(item.state)}`);
4636
+ if (item.state.kind === "copy" && !skillIsCurrent(item.state, VERSION)) {
4637
+ warn2(`${item.path} is a copy of v${item.state.version}, this rshow is ${VERSION}: run rshow skills install`);
4638
+ }
4639
+ }
4640
+ return EXIT.ok;
4641
+ }
4151
4642
  async function run9(argv, ctx) {
4643
+ const [sub, ...rest] = argv;
4644
+ switch (sub) {
4645
+ case "install":
4646
+ return install(rest, ctx);
4647
+ case "uninstall":
4648
+ return uninstall(rest, ctx);
4649
+ case "status":
4650
+ return status(rest, ctx);
4651
+ default:
4652
+ throw new UsageError(`skills needs a subcommand (try: ${usageLine(SKILLS_USAGE)})`);
4653
+ }
4654
+ }
4655
+
4656
+ // src/commands/list.ts
4657
+ async function run10(argv, ctx) {
4152
4658
  const args = parseCommandArgs(
4153
4659
  argv,
4154
4660
  { project: { type: "string" }, tag: { type: "string" }, q: { type: "string" } },
@@ -4181,7 +4687,7 @@ async function run9(argv, ctx) {
4181
4687
  }
4182
4688
 
4183
4689
  // src/commands/show.ts
4184
- async function run10(argv, ctx) {
4690
+ async function run11(argv, ctx) {
4185
4691
  const args = parseCommandArgs(argv, {}, 1, 1, SHOW_USAGE);
4186
4692
  const slug = validateSlug(args.positionals[0]);
4187
4693
  const api = await ctx.api();
@@ -4214,7 +4720,7 @@ async function run10(argv, ctx) {
4214
4720
 
4215
4721
  // src/commands/share.ts
4216
4722
  var MODES = /* @__PURE__ */ new Set(["private", "people", "public"]);
4217
- async function run11(argv, ctx) {
4723
+ async function run12(argv, ctx) {
4218
4724
  const args = parseCommandArgs(
4219
4725
  argv,
4220
4726
  {
@@ -4254,7 +4760,7 @@ async function run11(argv, ctx) {
4254
4760
  }
4255
4761
 
4256
4762
  // src/commands/pin.ts
4257
- async function run12(argv, ctx) {
4763
+ async function run13(argv, ctx) {
4258
4764
  const args = parseCommandArgs(argv, {}, 2, 2, PIN_USAGE);
4259
4765
  const slug = validateSlug(args.positionals[0]);
4260
4766
  const target = args.positionals[1];
@@ -4276,7 +4782,7 @@ async function run12(argv, ctx) {
4276
4782
  }
4277
4783
 
4278
4784
  // src/commands/delete.ts
4279
- async function run13(argv, ctx) {
4785
+ async function run14(argv, ctx) {
4280
4786
  const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, DELETE_USAGE);
4281
4787
  const slug = validateSlug(args.positionals[0]);
4282
4788
  if (!boolValue(args, "yes")) throw new UsageError(`delete needs --yes (try: ${usageLine(DELETE_USAGE)})`);
@@ -4401,7 +4907,7 @@ async function remove2(argv, ctx) {
4401
4907
  ctx.out.data(null);
4402
4908
  ctx.out.line(`deleted: ${slug}`);
4403
4909
  }
4404
- async function run14(argv, ctx) {
4910
+ async function run15(argv, ctx) {
4405
4911
  const [sub, ...rest] = argv;
4406
4912
  if (sub === void 0) {
4407
4913
  throw new UsageError(`expected a project command (try: ${usageLine(PROJECT_USAGE)})`);
@@ -4415,7 +4921,7 @@ async function run14(argv, ctx) {
4415
4921
  }
4416
4922
 
4417
4923
  // src/commands/tag.ts
4418
- async function run15(argv, ctx) {
4924
+ async function run16(argv, ctx) {
4419
4925
  const args = parseCommandArgs(
4420
4926
  argv,
4421
4927
  { add: { type: "string", multiple: true }, remove: { type: "string", multiple: true } },
@@ -4439,20 +4945,20 @@ async function run15(argv, ctx) {
4439
4945
  }
4440
4946
 
4441
4947
  // src/commands/handoff.ts
4442
- import process11 from "node:process";
4948
+ import process13 from "node:process";
4443
4949
 
4444
4950
  // src/handoff.ts
4445
- import { mkdir as mkdir4, stat as stat11, writeFile as writeFile6 } from "node:fs/promises";
4446
- import path17 from "node:path";
4447
- import process10 from "node:process";
4951
+ import { mkdir as mkdir6, stat as stat13, writeFile as writeFile8 } from "node:fs/promises";
4952
+ import path20 from "node:path";
4953
+ import process12 from "node:process";
4448
4954
 
4449
4955
  // src/cdp.ts
4450
4956
  import { spawn as spawn3 } from "node:child_process";
4451
4957
  import { existsSync, rmSync } from "node:fs";
4452
- import { mkdtemp as mkdtemp3, rm as rm4 } from "node:fs/promises";
4958
+ import { mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
4453
4959
  import { tmpdir as tmpdir3 } from "node:os";
4454
- import path15 from "node:path";
4455
- import process8 from "node:process";
4960
+ import path18 from "node:path";
4961
+ import process10 from "node:process";
4456
4962
  var CHROME_CANDIDATES = [
4457
4963
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
4458
4964
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
@@ -4472,9 +4978,9 @@ var LOAD_TIMEOUT_MS = 2e4;
4472
4978
  var liveBrowsers = /* @__PURE__ */ new Map();
4473
4979
  var signalHandlersInstalled = false;
4474
4980
  function killBrowser(child, signal) {
4475
- if (process8.platform !== "win32" && child.pid !== void 0) {
4981
+ if (process10.platform !== "win32" && child.pid !== void 0) {
4476
4982
  try {
4477
- process8.kill(-child.pid, signal);
4983
+ process10.kill(-child.pid, signal);
4478
4984
  return;
4479
4985
  } catch {
4480
4986
  }
@@ -4499,24 +5005,24 @@ function reapBrowsers() {
4499
5005
  }
4500
5006
  function onInterrupt() {
4501
5007
  reapBrowsers();
4502
- process8.exit(130);
5008
+ process10.exit(130);
4503
5009
  }
4504
5010
  function onTerminate() {
4505
5011
  reapBrowsers();
4506
- process8.exit(143);
5012
+ process10.exit(143);
4507
5013
  }
4508
5014
  function dropSignalHandlers() {
4509
5015
  if (!signalHandlersInstalled) return;
4510
5016
  signalHandlersInstalled = false;
4511
- process8.off("SIGINT", onInterrupt);
4512
- process8.off("SIGTERM", onTerminate);
5017
+ process10.off("SIGINT", onInterrupt);
5018
+ process10.off("SIGTERM", onTerminate);
4513
5019
  }
4514
5020
  function trackBrowser(child, profile) {
4515
5021
  liveBrowsers.set(child, profile);
4516
5022
  if (!signalHandlersInstalled) {
4517
5023
  signalHandlersInstalled = true;
4518
- process8.on("SIGINT", onInterrupt);
4519
- process8.on("SIGTERM", onTerminate);
5024
+ process10.on("SIGINT", onInterrupt);
5025
+ process10.on("SIGTERM", onTerminate);
4520
5026
  }
4521
5027
  }
4522
5028
  function forgetBrowser(child) {
@@ -4527,20 +5033,20 @@ var exitHookInstalled = false;
4527
5033
  function installExitHook() {
4528
5034
  if (exitHookInstalled) return;
4529
5035
  exitHookInstalled = true;
4530
- process8.once("exit", reapBrowsers);
5036
+ process10.once("exit", reapBrowsers);
4531
5037
  }
4532
5038
  function messageOf11(err) {
4533
5039
  return err instanceof Error ? err.message : String(err);
4534
5040
  }
4535
5041
  function whichInPath(name, pathValue) {
4536
- for (const dir of String(pathValue ?? "").split(path15.delimiter)) {
5042
+ for (const dir of String(pathValue ?? "").split(path18.delimiter)) {
4537
5043
  if (dir === "") continue;
4538
- const file = path15.join(dir, name);
5044
+ const file = path18.join(dir, name);
4539
5045
  if (existsSync(file)) return file;
4540
5046
  }
4541
5047
  return void 0;
4542
5048
  }
4543
- function findChrome(env = process8.env) {
5049
+ function findChrome(env = process10.env) {
4544
5050
  const explicit = (env.CHROME ?? "").trim();
4545
5051
  if (explicit !== "") {
4546
5052
  if (existsSync(explicit)) return explicit;
@@ -4711,7 +5217,7 @@ var Chrome = class _Chrome {
4711
5217
  static async launch(opts = {}) {
4712
5218
  const binary = opts.binary ?? findChrome();
4713
5219
  if (binary === void 0) throw new UsageError("no Chrome or Chromium found, set CHROME");
4714
- const profile = await mkdtemp3(path15.join(tmpdir3(), "rshow-chrome-"));
5220
+ const profile = await mkdtemp4(path18.join(tmpdir3(), "rshow-chrome-"));
4715
5221
  const child = spawn3(
4716
5222
  binary,
4717
5223
  [
@@ -4731,7 +5237,7 @@ var Chrome = class _Chrome {
4731
5237
  "--window-size=1440,900",
4732
5238
  "about:blank"
4733
5239
  ],
4734
- { stdio: ["ignore", "pipe", "pipe"], detached: process8.platform !== "win32" }
5240
+ { stdio: ["ignore", "pipe", "pipe"], detached: process10.platform !== "win32" }
4735
5241
  );
4736
5242
  installExitHook();
4737
5243
  trackBrowser(child, profile);
@@ -4742,7 +5248,7 @@ var Chrome = class _Chrome {
4742
5248
  } catch (err) {
4743
5249
  forgetBrowser(child);
4744
5250
  killBrowser(child, "SIGKILL");
4745
- await rm4(profile, { recursive: true, force: true }).catch(() => {
5251
+ await rm6(profile, { recursive: true, force: true }).catch(() => {
4746
5252
  });
4747
5253
  if (err instanceof ApiError || err instanceof UsageError) throw err;
4748
5254
  throw new ApiError(`cannot start chrome: ${messageOf11(err)}`);
@@ -4757,7 +5263,7 @@ var Chrome = class _Chrome {
4757
5263
  killBrowser(this.child, "SIGTERM");
4758
5264
  await delay(50);
4759
5265
  if (this.child.exitCode === null) killBrowser(this.child, "SIGKILL");
4760
- await rm4(this.profile, PROFILE_REMOVE).catch(() => {
5266
+ await rm6(this.profile, PROFILE_REMOVE).catch(() => {
4761
5267
  });
4762
5268
  forgetBrowser(this.child);
4763
5269
  }
@@ -4863,9 +5369,9 @@ var Page = class _Page {
4863
5369
  };
4864
5370
 
4865
5371
  // src/map.ts
4866
- import { readFile as readFile12, writeFile as writeFile5 } from "node:fs/promises";
4867
- import path16 from "node:path";
4868
- import process9 from "node:process";
5372
+ import { readFile as readFile13, writeFile as writeFile7 } from "node:fs/promises";
5373
+ import path19 from "node:path";
5374
+ import process11 from "node:process";
4869
5375
  var MAP_FILE = "rshow.map.json";
4870
5376
  function isRecord5(value) {
4871
5377
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -4881,12 +5387,12 @@ function textMap(file, key2, value) {
4881
5387
  return out;
4882
5388
  }
4883
5389
  function mapPath(dir) {
4884
- return path16.resolve(dir ?? process9.cwd(), MAP_FILE);
5390
+ return path19.resolve(dir ?? process11.cwd(), MAP_FILE);
4885
5391
  }
4886
5392
  async function readMapFile(file) {
4887
5393
  let text;
4888
5394
  try {
4889
- text = await readFile12(file, "utf8");
5395
+ text = await readFile13(file, "utf8");
4890
5396
  } catch (err) {
4891
5397
  if (err.code === "ENOENT") return void 0;
4892
5398
  throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -4905,7 +5411,7 @@ async function readMapFile(file) {
4905
5411
  };
4906
5412
  }
4907
5413
  async function writeMapFile(file, map) {
4908
- await writeFile5(file, `${JSON.stringify(sortMap(map), null, 2)}
5414
+ await writeFile7(file, `${JSON.stringify(sortMap(map), null, 2)}
4909
5415
  `);
4910
5416
  }
4911
5417
  function sortRecord(value) {
@@ -5349,14 +5855,14 @@ function parseTarget(input) {
5349
5855
  if (remote !== null) {
5350
5856
  return { kind: "remote", slug: remote[1], version: Number(remote[2]) };
5351
5857
  }
5352
- const dir = path17.resolve(input);
5353
- return { kind: "local", dir, slug: deriveSlug(path17.basename(dir)) || "handoff" };
5858
+ const dir = path20.resolve(input);
5859
+ return { kind: "local", dir, slug: deriveSlug(path20.basename(dir)) || "handoff" };
5354
5860
  }
5355
5861
  async function requireLocalDir(target) {
5356
5862
  if (target.kind !== "local") throw new UsageError("expected a local folder");
5357
- const info = await stat11(target.dir).catch(() => void 0);
5863
+ const info = await stat13(target.dir).catch(() => void 0);
5358
5864
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${target.dir}`);
5359
- const index = await stat11(path17.join(target.dir, "index.html")).catch(() => void 0);
5865
+ const index = await stat13(path20.join(target.dir, "index.html")).catch(() => void 0);
5360
5866
  if (index === void 0 || !index.isFile()) throw new UsageError(`${target.dir} has no index.html`);
5361
5867
  return target.dir;
5362
5868
  }
@@ -5371,10 +5877,10 @@ function safeName(text) {
5371
5877
  return cleaned === "" ? "screen" : cleaned;
5372
5878
  }
5373
5879
  function displayPath(target) {
5374
- const resolved = path17.resolve(target);
5375
- const relative = path17.relative(process10.cwd(), resolved);
5880
+ const resolved = path20.resolve(target);
5881
+ const relative = path20.relative(process12.cwd(), resolved);
5376
5882
  if (relative === "") return ".";
5377
- if (relative.startsWith("..") || path17.isAbsolute(relative)) return resolved;
5883
+ if (relative.startsWith("..") || path20.isAbsolute(relative)) return resolved;
5378
5884
  return relative;
5379
5885
  }
5380
5886
  function baselineName(screen, state, width, mode) {
@@ -5535,7 +6041,7 @@ async function runHandoff(target, opts) {
5535
6041
  const version = target.kind === "remote" ? target.version : null;
5536
6042
  const slug = target.slug;
5537
6043
  const url = target.kind === "remote" ? `${art}/${slug}/v${version}/` : "";
5538
- const out = path17.resolve(opts.out);
6044
+ const out = path20.resolve(opts.out);
5539
6045
  let closePreview;
5540
6046
  let pageUrl = url;
5541
6047
  if (target.kind === "remote") {
@@ -5550,8 +6056,8 @@ async function runHandoff(target, opts) {
5550
6056
  const styleTexts = await collectStyleTexts(pageUrl, fetchImpl);
5551
6057
  const tokens = extractTokens(styleTexts);
5552
6058
  say(`tokens: ${tokens.light.length} light, ${tokens.dark.length} dark`);
5553
- await mkdir4(path17.join(out, "baseline"), { recursive: true });
5554
- await writeFile6(path17.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
6059
+ await mkdir6(path20.join(out, "baseline"), { recursive: true });
6060
+ await writeFile8(path20.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
5555
6061
  `);
5556
6062
  const screens = [];
5557
6063
  const baselines = [];
@@ -5602,7 +6108,7 @@ async function runHandoff(target, opts) {
5602
6108
  }
5603
6109
  const png = await capture(page, screen);
5604
6110
  const name = baselineName(screen.id, state, width, mode);
5605
- await writeFile6(path17.join(out, "baseline", name), png);
6111
+ await writeFile8(path20.join(out, "baseline", name), png);
5606
6112
  baselines.push({ screen: screen.id, state, width, mode, file: `baseline/${name}` });
5607
6113
  }
5608
6114
  }
@@ -5631,7 +6137,7 @@ async function runHandoff(target, opts) {
5631
6137
  screens,
5632
6138
  baselines
5633
6139
  };
5634
- await writeFile6(path17.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
6140
+ await writeFile8(path20.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
5635
6141
  `);
5636
6142
  const mapFile = MAP_FILE;
5637
6143
  let mapWritten = false;
@@ -5650,7 +6156,7 @@ async function runHandoff(target, opts) {
5650
6156
  createdAt,
5651
6157
  mapFile
5652
6158
  });
5653
- await writeFile6(path17.join(out, "HANDOFF.md"), markdown);
6159
+ await writeFile8(path20.join(out, "HANDOFF.md"), markdown);
5654
6160
  return {
5655
6161
  out,
5656
6162
  url: pageUrl,
@@ -5691,7 +6197,7 @@ function modeList(text) {
5691
6197
  }
5692
6198
  return out;
5693
6199
  }
5694
- async function run16(argv, ctx) {
6200
+ async function run17(argv, ctx) {
5695
6201
  const args = parseCommandArgs(
5696
6202
  argv,
5697
6203
  { out: { type: "string" }, widths: { type: "string" }, modes: { type: "string" } },
@@ -5714,7 +6220,7 @@ async function run16(argv, ctx) {
5714
6220
  widths: widthList(stringValue(args, "widths")),
5715
6221
  modes: modeList(stringValue(args, "modes")),
5716
6222
  art: config.art ?? "",
5717
- chrome: process11.env.CHROME,
6223
+ chrome: process13.env.CHROME,
5718
6224
  onProgress: (line) => ctx.out.line(line)
5719
6225
  });
5720
6226
  const display = displayPath(result.out);
@@ -5740,11 +6246,11 @@ async function run16(argv, ctx) {
5740
6246
  }
5741
6247
 
5742
6248
  // src/commands/compare.ts
5743
- import process13 from "node:process";
6249
+ import process15 from "node:process";
5744
6250
 
5745
6251
  // src/compare.ts
5746
- import { copyFile, mkdir as mkdir5, readFile as readFile14, writeFile as writeFile7 } from "node:fs/promises";
5747
- import path19 from "node:path";
6252
+ import { copyFile, mkdir as mkdir7, readFile as readFile15, writeFile as writeFile9 } from "node:fs/promises";
6253
+ import path22 from "node:path";
5748
6254
 
5749
6255
  // src/diff.ts
5750
6256
  var MAX_Y = 35215;
@@ -5906,10 +6412,10 @@ function diffImages(before, after, opts = {}) {
5906
6412
 
5907
6413
  // src/native.ts
5908
6414
  import { spawn as spawn4 } from "node:child_process";
5909
- import { readFile as readFile13, unlink } from "node:fs/promises";
6415
+ import { readFile as readFile14, unlink } from "node:fs/promises";
5910
6416
  import { tmpdir as tmpdir4 } from "node:os";
5911
- import path18 from "node:path";
5912
- import process12 from "node:process";
6417
+ import path21 from "node:path";
6418
+ import process14 from "node:process";
5913
6419
  var FILE_PREFIX = "file:";
5914
6420
  var WINDOW_PREFIX = "window:";
5915
6421
  var TMUX_PREFIX = "tmux:";
@@ -5938,9 +6444,9 @@ function isNative(source) {
5938
6444
  }
5939
6445
  async function captureNative(source, mapDir, deps = {}) {
5940
6446
  if (source.kind === "file") {
5941
- const file = path18.resolve(mapDir, source.file);
6447
+ const file = path21.resolve(mapDir, source.file);
5942
6448
  try {
5943
- return new Uint8Array(await readFile13(file));
6449
+ return new Uint8Array(await readFile14(file));
5944
6450
  } catch {
5945
6451
  throw new UsageError(`no screenshot at ${source.file}, which is relative to rshow.map.json`);
5946
6452
  }
@@ -6011,9 +6517,9 @@ var systemRunner = {
6011
6517
  })
6012
6518
  };
6013
6519
  async function captureWindow(title, deps = {}) {
6014
- const platform = deps.platform ?? process12.platform;
6520
+ const platform = deps.platform ?? process14.platform;
6015
6521
  const runner = deps.runner ?? systemRunner;
6016
- const scratch = path18.join(tmpdir4(), `rshow-window-${process12.pid}-${Date.now()}.png`);
6522
+ const scratch = path21.join(tmpdir4(), `rshow-window-${process14.pid}-${Date.now()}.png`);
6017
6523
  const wrap = (err) => err instanceof UsageError ? err : new UsageError(`window: ${title}: ${err instanceof Error ? err.message : String(err)}; ${NATIVE_HINT}`);
6018
6524
  try {
6019
6525
  if (platform === "darwin") {
@@ -6029,7 +6535,7 @@ async function captureWindow(title, deps = {}) {
6029
6535
  } else {
6030
6536
  throw new UsageError(`window: capture is not supported on ${platform}; ${NATIVE_HINT}`);
6031
6537
  }
6032
- return new Uint8Array(await readFile13(scratch));
6538
+ return new Uint8Array(await readFile14(scratch));
6033
6539
  } catch (err) {
6034
6540
  throw wrap(err);
6035
6541
  } finally {
@@ -6843,9 +7349,9 @@ var EMPTY_MAP = { components: {}, tokens: {}, screens: {} };
6843
7349
  async function readJson(file, what) {
6844
7350
  let text;
6845
7351
  try {
6846
- text = await readFile14(file, "utf8");
7352
+ text = await readFile15(file, "utf8");
6847
7353
  } catch {
6848
- throw new UsageError(`${what} is missing ${path19.basename(file)} (run rshow handoff first)`);
7354
+ throw new UsageError(`${what} is missing ${path22.basename(file)} (run rshow handoff first)`);
6849
7355
  }
6850
7356
  try {
6851
7357
  return JSON.parse(text);
@@ -6854,13 +7360,13 @@ async function readJson(file, what) {
6854
7360
  }
6855
7361
  }
6856
7362
  async function readHandoff(dir) {
6857
- const spec = await readJson(path19.join(dir, "spec.json"), dir);
7363
+ const spec = await readJson(path22.join(dir, "spec.json"), dir);
6858
7364
  if (spec === null || typeof spec !== "object" || !Array.isArray(spec.screens)) {
6859
7365
  throw new UsageError(`${dir}/spec.json does not hold a handoff spec`);
6860
7366
  }
6861
7367
  let tokens = { light: [], dark: [] };
6862
7368
  try {
6863
- tokens = flattenDtcg(JSON.parse(await readFile14(path19.join(dir, "tokens.json"), "utf8")));
7369
+ tokens = flattenDtcg(JSON.parse(await readFile15(path22.join(dir, "tokens.json"), "utf8")));
6864
7370
  } catch {
6865
7371
  tokens = { light: [], dark: [] };
6866
7372
  }
@@ -6883,15 +7389,15 @@ function clipOf(info, screenId) {
6883
7389
  async function runCompare(opts) {
6884
7390
  const say = opts.onProgress ?? (() => {
6885
7391
  });
6886
- const dir = path19.resolve(opts.against);
7392
+ const dir = path22.resolve(opts.against);
6887
7393
  const { spec, tokens } = await readHandoff(dir);
6888
- const mapFile = opts.map === void 0 ? mapPath() : path19.resolve(opts.map);
7394
+ const mapFile = opts.map === void 0 ? mapPath() : path22.resolve(opts.map);
6889
7395
  const map = await readMapFile(mapFile) ?? EMPTY_MAP;
6890
- const mapDir = path19.dirname(mapFile);
6891
- const reportDir = path19.join(dir, "compare-report");
6892
- const imagesDir = path19.join(reportDir, "images");
6893
- await mkdir5(imagesDir, { recursive: true });
6894
- await mkdir5(path19.join(dir, "diff"), { recursive: true });
7396
+ const mapDir = path22.dirname(mapFile);
7397
+ const reportDir = path22.join(dir, "compare-report");
7398
+ const imagesDir = path22.join(reportDir, "images");
7399
+ await mkdir7(imagesDir, { recursive: true });
7400
+ await mkdir7(path22.join(dir, "diff"), { recursive: true });
6895
7401
  const results = [];
6896
7402
  const collected = [];
6897
7403
  let skipped = 0;
@@ -6926,16 +7432,16 @@ async function runCompare(opts) {
6926
7432
  throw new UsageError(`tmux capture failed for ${target}: ${err instanceof Error ? err.message : String(err)}`);
6927
7433
  });
6928
7434
  const text = opts.tuiColors ? captured : stripAnsi(captured);
6929
- const baselineFile = path19.join(dir, "baseline", `${safeName(screen)}.txt`);
7435
+ const baselineFile = path22.join(dir, "baseline", `${safeName(screen)}.txt`);
6930
7436
  let expected;
6931
7437
  try {
6932
- expected = await readFile14(baselineFile, "utf8");
7438
+ expected = await readFile15(baselineFile, "utf8");
6933
7439
  } catch {
6934
7440
  expected = void 0;
6935
7441
  }
6936
7442
  if (expected === void 0) {
6937
- await mkdir5(path19.dirname(baselineFile), { recursive: true });
6938
- await writeFile7(baselineFile, text);
7443
+ await mkdir7(path22.dirname(baselineFile), { recursive: true });
7444
+ await writeFile9(baselineFile, text);
6939
7445
  results.push({
6940
7446
  screen,
6941
7447
  state: "default",
@@ -7010,7 +7516,7 @@ async function runCompare(opts) {
7010
7516
  }
7011
7517
  let expectedImage;
7012
7518
  try {
7013
- expectedImage = decodePng(new Uint8Array(await readFile14(path19.join(dir, baseline.file))));
7519
+ expectedImage = decodePng(new Uint8Array(await readFile15(path22.join(dir, baseline.file))));
7014
7520
  } catch {
7015
7521
  results.push({ ...base, note: `missing baseline ${baseline.file}` });
7016
7522
  continue;
@@ -7022,10 +7528,10 @@ async function runCompare(opts) {
7022
7528
  threshold: opts.threshold
7023
7529
  });
7024
7530
  const diffPng = encodePng(diff.image);
7025
- await writeFile7(path19.join(imagesDir, `${stem}-diff.png`), diffPng);
7026
- await writeFile7(path19.join(dir, "diff", `${stem}.png`), diffPng);
7027
- await writeFile7(path19.join(imagesDir, `${stem}-capture.png`), encodePng(scaled));
7028
- await copyFile(path19.join(dir, baseline.file), path19.join(imagesDir, `${stem}-baseline.png`));
7531
+ await writeFile9(path22.join(imagesDir, `${stem}-diff.png`), diffPng);
7532
+ await writeFile9(path22.join(dir, "diff", `${stem}.png`), diffPng);
7533
+ await writeFile9(path22.join(imagesDir, `${stem}-capture.png`), encodePng(scaled));
7534
+ await copyFile(path22.join(dir, baseline.file), path22.join(imagesDir, `${stem}-baseline.png`));
7029
7535
  const expected = screenSpec === void 0 ? [] : normativeColors(screenSpec, tokens, baseline.state, baseline.mode);
7030
7536
  const palette = checkPalette(extractPalette(scaled), expected);
7031
7537
  const pixelOk = diff.ratio <= opts.nativeMaxDiffRatio;
@@ -7070,7 +7576,7 @@ async function runCompare(opts) {
7070
7576
  });
7071
7577
  }
7072
7578
  if (screenSpec !== void 0) {
7073
- await writeFile7(path19.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
7579
+ await writeFile9(path22.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
7074
7580
  `);
7075
7581
  }
7076
7582
  }
@@ -7127,12 +7633,12 @@ async function runCompare(opts) {
7127
7633
  }
7128
7634
  const clip = clipOf(info, screenId) ? await screenClip(page, screenId) : null;
7129
7635
  const actual = await page.screenshot(clip ?? void 0);
7130
- const actualFile = path19.join(imagesDir, `${stem}-actual.png`);
7131
- await writeFile7(actualFile, actual);
7132
- const baselineFile = path19.join(dir, baseline.file);
7636
+ const actualFile = path22.join(imagesDir, `${stem}-actual.png`);
7637
+ await writeFile9(actualFile, actual);
7638
+ const baselineFile = path22.join(dir, baseline.file);
7133
7639
  let expected;
7134
7640
  try {
7135
- expected = new Uint8Array(await readFile14(baselineFile));
7641
+ expected = new Uint8Array(await readFile15(baselineFile));
7136
7642
  } catch {
7137
7643
  results.push({
7138
7644
  screen: baseline.screen,
@@ -7147,14 +7653,14 @@ async function runCompare(opts) {
7147
7653
  });
7148
7654
  continue;
7149
7655
  }
7150
- const baselineCopy = path19.join(imagesDir, `${stem}-baseline.png`);
7656
+ const baselineCopy = path22.join(imagesDir, `${stem}-baseline.png`);
7151
7657
  await copyFile(baselineFile, baselineCopy);
7152
7658
  const left = decodePng(expected);
7153
7659
  const right = decodePng(actual);
7154
7660
  const diff = diffImages(left, right, { threshold: opts.threshold });
7155
7661
  const diffPng = encodePng(diff.image);
7156
- await writeFile7(path19.join(imagesDir, `${stem}-diff.png`), diffPng);
7157
- await writeFile7(path19.join(dir, "diff", `${stem}.png`), diffPng);
7662
+ await writeFile9(path22.join(imagesDir, `${stem}-diff.png`), diffPng);
7663
+ await writeFile9(path22.join(dir, "diff", `${stem}.png`), diffPng);
7158
7664
  const note = left.width === right.width && left.height === right.height ? void 0 : `size ${left.width}x${left.height} against ${right.width}x${right.height}`;
7159
7665
  results.push({
7160
7666
  screen: baseline.screen,
@@ -7223,8 +7729,8 @@ async function runCompare(opts) {
7223
7729
  const checked = results.filter((item) => item.source !== "").length;
7224
7730
  const review = results.filter((item) => item.status === "review").length;
7225
7731
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
7226
- await writeFile7(
7227
- path19.join(reportDir, "report.json"),
7732
+ await writeFile9(
7733
+ path22.join(reportDir, "report.json"),
7228
7734
  `${JSON.stringify(
7229
7735
  {
7230
7736
  schema: 1,
@@ -7260,8 +7766,8 @@ async function runCompare(opts) {
7260
7766
  )}
7261
7767
  `
7262
7768
  );
7263
- await writeFile7(
7264
- path19.join(reportDir, "index.html"),
7769
+ await writeFile9(
7770
+ path22.join(reportDir, "index.html"),
7265
7771
  reportHtml({
7266
7772
  spec,
7267
7773
  results,
@@ -7314,15 +7820,15 @@ function reportHtml(input) {
7314
7820
  const nativeLimit = input.nativeMaxDiffRatio ?? 0.15;
7315
7821
  const statusOf = (item) => item.status ?? (item.pass ? "pass" : "fail");
7316
7822
  const rows = results.map((item) => {
7317
- const status = statusOf(item);
7318
- const mark = status === "pass" ? '<span class="pass">pass</span>' : status === "review" ? '<span class="review">review</span>' : '<span class="fail">fail</span>';
7823
+ const status2 = statusOf(item);
7824
+ const mark = status2 === "pass" ? '<span class="pass">pass</span>' : status2 === "review" ? '<span class="review">review</span>' : '<span class="fail">fail</span>';
7319
7825
  const ratio2 = item.diffRatio === void 0 ? "-" : item.diffRatio.toFixed(4);
7320
7826
  const rubric = item.rubric === void 0 ? "" : ` <a href="${escapeHtml(item.rubric)}">rubric</a>`;
7321
7827
  return `<tr><td>${escapeHtml(item.screen)}</td><td>${escapeHtml(item.state)}</td><td>${escapeHtml(item.mode)}</td><td>${item.width === 0 ? "-" : item.width}</td><td>${mark}</td><td>${ratio2}</td><td>${item.mismatches.length}</td><td>${escapeHtml(item.note ?? "")}${rubric}</td></tr>`;
7322
7828
  }).join("\n");
7323
7829
  const sections = results.filter((item) => item.images !== void 0 || item.mismatches.length > 0).map((item) => {
7324
- const status = statusOf(item);
7325
- const badge = `<span class="badge ${status}">${status}</span>`;
7830
+ const status2 = statusOf(item);
7831
+ const badge = `<span class="badge ${status2}">${status2}</span>`;
7326
7832
  const captureCaption = item.kind === "native" ? "capture" : "actual";
7327
7833
  const shots = item.images === void 0 ? "" : `<div class="shots">
7328
7834
  <figure><img src="${escapeHtml(item.images.baseline)}" alt="baseline"><figcaption>baseline</figcaption></figure>
@@ -7382,7 +7888,7 @@ function ratioValue(text, name, fallback) {
7382
7888
  }
7383
7889
  return value;
7384
7890
  }
7385
- async function run17(argv, ctx) {
7891
+ async function run18(argv, ctx) {
7386
7892
  const args = parseCommandArgs(
7387
7893
  argv,
7388
7894
  {
@@ -7412,7 +7918,7 @@ async function run17(argv, ctx) {
7412
7918
  DEFAULT_NATIVE_MAX_DIFF_RATIO
7413
7919
  ),
7414
7920
  tuiColors: boolValue(args, "tui-colors"),
7415
- chrome: process13.env.CHROME,
7921
+ chrome: process15.env.CHROME,
7416
7922
  onProgress: (line) => ctx.out.line(line)
7417
7923
  });
7418
7924
  const failing = result.results.filter((item) => !item.pass);
@@ -7427,8 +7933,8 @@ async function run17(argv, ctx) {
7427
7933
  const where = item.width === 0 ? item.mode : `${item.mode} ${item.width}`;
7428
7934
  const ratio2 = item.diffRatio === void 0 ? "" : ` diff ${item.diffRatio.toFixed(4)}`;
7429
7935
  const note = item.note === void 0 ? "" : ` (${item.note})`;
7430
- const status = item.status ?? (item.pass ? "pass" : "fail");
7431
- ctx.out.line(`${status} ${item.screen} ${item.state} ${where}${ratio2}${note}`);
7936
+ const status2 = item.status ?? (item.pass ? "pass" : "fail");
7937
+ ctx.out.line(`${status2} ${item.screen} ${item.state} ${where}${ratio2}${note}`);
7432
7938
  for (const mismatch of item.mismatches.slice(0, 20)) {
7433
7939
  ctx.out.line(
7434
7940
  ` mismatch ${item.screen} ${item.state} ${mismatch.component}: ${mismatch.property} expected ${mismatch.expected} actual ${mismatch.actual}`
@@ -7460,8 +7966,8 @@ async function run17(argv, ctx) {
7460
7966
  }
7461
7967
 
7462
7968
  // src/commands/docs.ts
7463
- import { mkdir as mkdir6, readFile as readFile15, rm as rm5, writeFile as writeFile8 } from "node:fs/promises";
7464
- import path20 from "node:path";
7969
+ import { mkdir as mkdir8, readFile as readFile16, rm as rm7, writeFile as writeFile10 } from "node:fs/promises";
7970
+ import path23 from "node:path";
7465
7971
  var USAGE = {
7466
7972
  publish: PUBLISH_USAGE,
7467
7973
  check: CHECK_USAGE,
@@ -7477,6 +7983,10 @@ var USAGE = {
7477
7983
  "theme preview": THEME_PREVIEW_USAGE,
7478
7984
  "theme publish": THEME_PUBLISH_USAGE,
7479
7985
  "theme list": THEME_LIST_USAGE,
7986
+ skills: SKILLS_USAGE,
7987
+ "skills install": SKILLS_INSTALL_USAGE,
7988
+ "skills uninstall": SKILLS_UNINSTALL_USAGE,
7989
+ "skills status": SKILLS_STATUS_USAGE,
7480
7990
  project: PROJECT_USAGE,
7481
7991
  "project create": PROJECT_CREATE_USAGE,
7482
7992
  "project list": PROJECT_LIST_USAGE,
@@ -7498,7 +8008,7 @@ var WHEN = {
7498
8008
  auth: "the API refused the token with 401 or 403.",
7499
8009
  api: "the API returned another error.",
7500
8010
  build: "the `--build` command failed.",
7501
- check: "`rshow check`, `rshow publish` or `rshow theme publish` found an error.",
8011
+ check: "`rshow check`, `rshow publish` or `rshow theme publish` found an error, or `rshow skills install` found a skill folder that is not from rshow.",
7502
8012
  compare: "`rshow compare` found a difference."
7503
8013
  };
7504
8014
  var ARG_WORD = /^(?:<[^>]+>|\[[^\]]*\]|[a-z0-9-]+@v<n>|--?\S+)$/;
@@ -7736,7 +8246,7 @@ function heading(label) {
7736
8246
  async function themeTokensPage() {
7737
8247
  const dir = await builtinThemeDir("default");
7738
8248
  if (dir === void 0) throw new Error("no built-in theme: default");
7739
- const css = await readFile15(path20.join(dir, "tokens.css"), "utf8");
8249
+ const css = await readFile16(path23.join(dir, "tokens.css"), "utf8");
7740
8250
  const end = css.indexOf("*/");
7741
8251
  const roles = parseRoles(end < 0 ? css : css.slice(0, end));
7742
8252
  const sections = parseSections(rootBlock(css));
@@ -7784,45 +8294,45 @@ function exitCodesPage() {
7784
8294
  `;
7785
8295
  }
7786
8296
  async function generate(out) {
7787
- const dir = path20.join(out, "commands");
7788
- await rm5(dir, { recursive: true, force: true });
7789
- await mkdir6(dir, { recursive: true });
8297
+ const dir = path23.join(out, "commands");
8298
+ await rm7(dir, { recursive: true, force: true });
8299
+ await mkdir8(dir, { recursive: true });
7790
8300
  const rows = commandRows();
7791
8301
  const files = [];
7792
8302
  for (const row of rows) {
7793
- const file = path20.join(dir, row.file);
7794
- await writeFile8(file, commandPage(row, rows));
8303
+ const file = path23.join(dir, row.file);
8304
+ await writeFile10(file, commandPage(row, rows));
7795
8305
  files.push(file);
7796
8306
  }
7797
- const index = path20.join(dir, "index.md");
7798
- await writeFile8(index, indexPage(rows));
8307
+ const index = path23.join(dir, "index.md");
8308
+ await writeFile10(index, indexPage(rows));
7799
8309
  files.push(index);
7800
8310
  for (const [name, text] of [
7801
8311
  ["theme-tokens.md", await themeTokensPage()],
7802
8312
  ["exit-codes.md", exitCodesPage()]
7803
8313
  ]) {
7804
- const file = path20.join(out, name);
7805
- await writeFile8(file, text);
8314
+ const file = path23.join(out, name);
8315
+ await writeFile10(file, text);
7806
8316
  files.push(file);
7807
8317
  }
7808
- return { commands: rows.length, files: files.map((file) => path20.relative(out, file)).sort() };
8318
+ return { commands: rows.length, files: files.map((file) => path23.relative(out, file)).sort() };
7809
8319
  }
7810
- async function run18(argv, ctx) {
8320
+ async function run19(argv, ctx) {
7811
8321
  const args = parseCommandArgs(argv, { out: { type: "string" } }, 0, 0, DOCS_USAGE);
7812
8322
  const out = stringValue(args, "out");
7813
8323
  if (out === void 0) throw new UsageError(`docs needs --out <dir> (try: ${usageLine(DOCS_USAGE)})`);
7814
8324
  const result = await generate(out);
7815
8325
  ctx.out.data(result);
7816
- ctx.out.line(`wrote ${result.commands} command pages and the index under ${path20.join(out, "commands")}`);
7817
- ctx.out.line(`wrote ${path20.join(out, "theme-tokens.md")}`);
7818
- ctx.out.line(`wrote ${path20.join(out, "exit-codes.md")}`);
8326
+ ctx.out.line(`wrote ${result.commands} command pages and the index under ${path23.join(out, "commands")}`);
8327
+ ctx.out.line(`wrote ${path23.join(out, "theme-tokens.md")}`);
8328
+ ctx.out.line(`wrote ${path23.join(out, "exit-codes.md")}`);
7819
8329
  }
7820
8330
 
7821
8331
  // src/commands/login.ts
7822
- import process14 from "node:process";
8332
+ import process16 from "node:process";
7823
8333
  async function hiddenLine() {
7824
- process14.stderr.write("token: ");
7825
- const stdin = process14.stdin;
8334
+ process16.stderr.write("token: ");
8335
+ const stdin = process16.stdin;
7826
8336
  stdin.setRawMode(true);
7827
8337
  stdin.resume();
7828
8338
  stdin.setEncoding("utf8");
@@ -7832,7 +8342,7 @@ async function hiddenLine() {
7832
8342
  stdin.off("data", onData);
7833
8343
  stdin.setRawMode(false);
7834
8344
  stdin.pause();
7835
- process14.stderr.write("\n");
8345
+ process16.stderr.write("\n");
7836
8346
  if (err === void 0) resolve2(text);
7837
8347
  else reject(err);
7838
8348
  };
@@ -7855,16 +8365,16 @@ async function hiddenLine() {
7855
8365
  }
7856
8366
  async function pipedText() {
7857
8367
  const chunks = [];
7858
- for await (const chunk2 of process14.stdin) chunks.push(chunk2);
8368
+ for await (const chunk2 of process16.stdin) chunks.push(chunk2);
7859
8369
  return Buffer.concat(chunks).toString("utf8").trim();
7860
8370
  }
7861
8371
  async function tokenFromStdin() {
7862
- const token = process14.stdin.isTTY === true ? await hiddenLine() : await pipedText();
8372
+ const token = process16.stdin.isTTY === true ? await hiddenLine() : await pipedText();
7863
8373
  if (token === "") throw new UsageError("expected a token on stdin");
7864
8374
  if (!token.startsWith(TOKEN_PREFIX)) throw new UsageError(`that is not an rshow token: it must start with ${TOKEN_PREFIX}`);
7865
8375
  return token;
7866
8376
  }
7867
- async function run19(argv, ctx) {
8377
+ async function run20(argv, ctx) {
7868
8378
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGIN_USAGE);
7869
8379
  const settings = await ctx.config();
7870
8380
  const given = (stringValue(args, "url") ?? "").trim();
@@ -7879,7 +8389,7 @@ async function run19(argv, ctx) {
7879
8389
  }
7880
8390
 
7881
8391
  // src/commands/logout.ts
7882
- async function run20(argv, ctx) {
8392
+ async function run21(argv, ctx) {
7883
8393
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
7884
8394
  const settings = await ctx.config();
7885
8395
  const given = (stringValue(args, "url") ?? "").trim();
@@ -7892,28 +8402,29 @@ async function run20(argv, ctx) {
7892
8402
  var COMMANDS = {
7893
8403
  publish: { usage: PUBLISH_USAGE, run: run2 },
7894
8404
  check: { usage: CHECK_USAGE, run: run3 },
7895
- handoff: { usage: HANDOFF_USAGE, run: run16 },
7896
- compare: { usage: COMPARE_USAGE, run: run17 },
8405
+ handoff: { usage: HANDOFF_USAGE, run: run17 },
8406
+ compare: { usage: COMPARE_USAGE, run: run18 },
7897
8407
  preview: { usage: PREVIEW_USAGE, run: run4 },
7898
8408
  config: { usage: CONFIG_USAGE, run: run5 },
7899
8409
  init: { usage: INIT_USAGE, run: run6 },
7900
8410
  doctor: { usage: DOCTOR_USAGE, run: run7 },
7901
8411
  theme: { usage: THEME_USAGE, run: run8 },
7902
- project: { usage: PROJECT_USAGE, run: run14 },
7903
- tag: { usage: TAG_USAGE, run: run15 },
7904
- list: { usage: LIST_USAGE, run: run9 },
7905
- show: { usage: SHOW_USAGE, run: run10 },
7906
- share: { usage: SHARE_USAGE, run: run11 },
7907
- pin: { usage: PIN_USAGE, run: run12 },
7908
- delete: { usage: DELETE_USAGE, run: run13 },
7909
- login: { usage: LOGIN_USAGE, run: run19 },
7910
- logout: { usage: LOGOUT_USAGE, run: run20 },
8412
+ skills: { usage: SKILLS_USAGE, run: run9 },
8413
+ project: { usage: PROJECT_USAGE, run: run15 },
8414
+ tag: { usage: TAG_USAGE, run: run16 },
8415
+ list: { usage: LIST_USAGE, run: run10 },
8416
+ show: { usage: SHOW_USAGE, run: run11 },
8417
+ share: { usage: SHARE_USAGE, run: run12 },
8418
+ pin: { usage: PIN_USAGE, run: run13 },
8419
+ delete: { usage: DELETE_USAGE, run: run14 },
8420
+ login: { usage: LOGIN_USAGE, run: run20 },
8421
+ logout: { usage: LOGOUT_USAGE, run: run21 },
7911
8422
  // Kept out of the general help: the docs site runs it, a person rarely does.
7912
- docs: { usage: DOCS_USAGE, run: run18 }
8423
+ docs: { usage: DOCS_USAGE, run: run19 }
7913
8424
  };
7914
8425
  function fail(err) {
7915
8426
  const message = err instanceof Error ? err.message : String(err);
7916
- process15.stderr.write(`rshow: ${message}
8427
+ process17.stderr.write(`rshow: ${message}
7917
8428
  `);
7918
8429
  if (err instanceof UsageError) return EXIT.usage;
7919
8430
  if (err instanceof CheckError) return EXIT.check;
@@ -7930,17 +8441,18 @@ async function main(argv) {
7930
8441
  const name = args.find((arg) => !arg.startsWith("-"));
7931
8442
  const spec = name === void 0 ? void 0 : COMMANDS[name];
7932
8443
  if (wantsVersion) {
7933
- process15.stdout.write(`rshow ${VERSION}
8444
+ const suffix = await isBinary() ? " (binary)" : "";
8445
+ process17.stdout.write(`rshow ${VERSION}${suffix}
7934
8446
  `);
7935
8447
  return EXIT.ok;
7936
8448
  }
7937
8449
  if (wantsHelp) {
7938
- process15.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
8450
+ process17.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
7939
8451
  `);
7940
8452
  return EXIT.ok;
7941
8453
  }
7942
8454
  if (name === void 0) {
7943
- process15.stderr.write(`${GENERAL_USAGE}
8455
+ process17.stderr.write(`${GENERAL_USAGE}
7944
8456
  `);
7945
8457
  return EXIT.usage;
7946
8458
  }
@@ -7951,12 +8463,12 @@ async function main(argv) {
7951
8463
  const rest = [...args.slice(0, index), ...args.slice(index + 1)];
7952
8464
  const configs = /* @__PURE__ */ new Map();
7953
8465
  const clients = /* @__PURE__ */ new Map();
7954
- const keyOf = (dir) => dir === void 0 ? "" : path21.resolve(dir);
8466
+ const keyOf = (dir) => dir === void 0 ? "" : path24.resolve(dir);
7955
8467
  const configFor = (dir) => {
7956
8468
  const key2 = keyOf(dir);
7957
8469
  let pending = configs.get(key2);
7958
8470
  if (pending === void 0) {
7959
- pending = loadConfig(process15.env, dir);
8471
+ pending = loadConfig(process17.env, dir);
7960
8472
  configs.set(key2, pending);
7961
8473
  }
7962
8474
  return pending;
@@ -7969,7 +8481,7 @@ async function main(argv) {
7969
8481
  if (cached !== void 0) return cached;
7970
8482
  const settings = await configFor(dir);
7971
8483
  const url = requireUrl(settings);
7972
- const client = new Client(url, await readToken(process15.env, url));
8484
+ const client = new Client(url, await readToken(process17.env, url));
7973
8485
  clients.set(key2, client);
7974
8486
  return client;
7975
8487
  },
@@ -7978,4 +8490,11 @@ async function main(argv) {
7978
8490
  const code = await spec.run(rest, ctx);
7979
8491
  return code ?? EXIT.ok;
7980
8492
  }
7981
- process15.exitCode = await main(process15.argv.slice(2)).catch(fail);
8493
+ main(process17.argv.slice(2)).then(
8494
+ (code) => {
8495
+ process17.exitCode = code;
8496
+ },
8497
+ (err) => {
8498
+ process17.exitCode = fail(err);
8499
+ }
8500
+ );