rshow 0.0.2 → 0.0.4

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,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import path24 from "node:path";
5
- import process17 from "node:process";
4
+ import path25 from "node:path";
5
+ import process23 from "node:process";
6
6
 
7
7
  // src/binary.ts
8
8
  import { mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -13,7 +13,7 @@ import process from "node:process";
13
13
  // src/version.ts
14
14
  import { readFileSync } from "node:fs";
15
15
  import { fileURLToPath } from "node:url";
16
- var VERSION = true ? "0.0.2" : versionFromPackage();
16
+ var VERSION = true ? "0.0.4" : versionFromPackage();
17
17
 
18
18
  // src/binary.ts
19
19
  var ASSET_MANIFEST = "assets.json";
@@ -72,9 +72,9 @@ async function extractAssets(bundle, dir) {
72
72
  function safeKey(key2) {
73
73
  return key2 !== "" && !path.isAbsolute(key2) && !key2.split("/").includes("..");
74
74
  }
75
- function manifestKeys(read2) {
75
+ function manifestKeys(read4) {
76
76
  try {
77
- const doc = JSON.parse(new TextDecoder().decode(read2(ASSET_MANIFEST)));
77
+ const doc = JSON.parse(new TextDecoder().decode(read4(ASSET_MANIFEST)));
78
78
  if (doc !== null && typeof doc === "object" && Array.isArray(doc.keys)) {
79
79
  const keys = doc.keys.filter((key2) => typeof key2 === "string");
80
80
  if (keys.length > 0) return keys.filter(safeKey);
@@ -85,9 +85,9 @@ function manifestKeys(read2) {
85
85
  }
86
86
  function bundleOf(mod) {
87
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 };
88
+ const read4 = (key2) => new Uint8Array(raw(key2));
89
+ const withManifest = manifestKeys(read4);
90
+ return { keys: withManifest.length > 0 ? withManifest : (mod.getAssetKeys?.() ?? []).filter(safeKey), read: read4 };
91
91
  }
92
92
  async function unpack(env) {
93
93
  if (!await isBinary()) return void 0;
@@ -148,10 +148,145 @@ var CheckError = class extends Error {
148
148
  }
149
149
  };
150
150
 
151
- // src/client.ts
151
+ // src/network.ts
152
+ import { readFile } from "node:fs/promises";
153
+ import process3 from "node:process";
154
+ import tls from "node:tls";
155
+
156
+ // src/warn.ts
157
+ import process2 from "node:process";
158
+ function warn(message) {
159
+ process2.stderr.write(`rshow: warning: ${message}
160
+ `);
161
+ }
162
+
163
+ // src/network.ts
164
+ var PROXY_VARIABLES = ["https_proxy", "HTTPS_PROXY", "http_proxy", "HTTP_PROXY"];
165
+ var TRUST_HINT = "your network may inspect HTTPS: set RSHOW_EXTRA_CA_FILE to its root certificate, or check that the system store has it";
166
+ var PROXY_HINT = "if a proxy stands between you and that host, set https_proxy and run with NODE_USE_ENV_PROXY=1";
167
+ var DNS_HINT = "check the url in rshow config and your DNS";
168
+ var TRUSTED = { reason: "certificate not trusted", hint: TRUST_HINT };
169
+ var REASONS = {
170
+ UNABLE_TO_GET_ISSUER_CERT_LOCALLY: TRUSTED,
171
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: TRUSTED,
172
+ SELF_SIGNED_CERT_IN_CHAIN: TRUSTED,
173
+ DEPTH_ZERO_SELF_SIGNED_CERT: TRUSTED,
174
+ CERT_HAS_EXPIRED: TRUSTED,
175
+ ERR_TLS_CERT_ALTNAME_INVALID: TRUSTED,
176
+ ENOTFOUND: { reason: "host not found", hint: DNS_HINT },
177
+ EAI_AGAIN: { reason: "host not found", hint: DNS_HINT },
178
+ ECONNREFUSED: { reason: "connection refused" },
179
+ ETIMEDOUT: { reason: "connection timed out", proxyHint: true },
180
+ UND_ERR_CONNECT_TIMEOUT: { reason: "connection timed out", proxyHint: true },
181
+ ECONNRESET: { reason: "connection reset", proxyHint: true }
182
+ };
152
183
  function messageOf(err) {
153
184
  return err instanceof Error ? err.message : String(err);
154
185
  }
186
+ function pemCertificatesOf(text) {
187
+ return text.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g) ?? [];
188
+ }
189
+ async function extraCertificates(env) {
190
+ const file = (env.RSHOW_EXTRA_CA_FILE ?? "").trim();
191
+ if (file === "") return [];
192
+ let text;
193
+ try {
194
+ text = await readFile(file, "utf8");
195
+ } catch (err) {
196
+ warn(`cannot read RSHOW_EXTRA_CA_FILE ${file}: ${messageOf(err)}`);
197
+ return [];
198
+ }
199
+ const certs = pemCertificatesOf(text);
200
+ if (certs.length === 0) warn(`RSHOW_EXTRA_CA_FILE ${file} holds no PEM certificate`);
201
+ return certs;
202
+ }
203
+ async function installCaCertificates(env = process3.env) {
204
+ const extra = await extraCertificates(env);
205
+ const api = tls;
206
+ if (typeof api.getCACertificates !== "function" || typeof api.setDefaultCACertificates !== "function") return;
207
+ try {
208
+ const certs = [...api.getCACertificates("default")];
209
+ if (env.RSHOW_SYSTEM_CA !== "0") certs.push(...api.getCACertificates("system"));
210
+ certs.push(...extra);
211
+ api.setDefaultCACertificates([...new Set(certs)]);
212
+ } catch {
213
+ }
214
+ }
215
+ function proxyVariableOf(env) {
216
+ return PROXY_VARIABLES.find((name) => (env[name] ?? "").trim() !== "");
217
+ }
218
+ function envProxyEnabled(env) {
219
+ const flag = (env.NODE_USE_ENV_PROXY ?? "").trim();
220
+ if (flag !== "" && flag !== "0") return true;
221
+ return process3.execArgv.includes("--use-env-proxy");
222
+ }
223
+ var proxyNoticed = false;
224
+ function noticeProxy(env) {
225
+ if (proxyNoticed) return;
226
+ proxyNoticed = true;
227
+ const name = proxyVariableOf(env);
228
+ if (name === void 0 || envProxyEnabled(env)) return;
229
+ warn(`${name} is set; run with NODE_USE_ENV_PROXY=1 (Node 24+) so rshow uses it`);
230
+ }
231
+ var installed;
232
+ function readyNetwork(env = process3.env) {
233
+ installed ??= installCaCertificates(env);
234
+ noticeProxy(env);
235
+ return installed;
236
+ }
237
+ function codesOf(err, depth = 0) {
238
+ if (depth > 5 || err === null || typeof err !== "object") return [];
239
+ const out = [];
240
+ const code = err.code;
241
+ if (typeof code === "string" && code !== "") out.push(code);
242
+ const inner = err.errors;
243
+ if (Array.isArray(inner)) for (const one of inner) out.push(...codesOf(one, depth + 1));
244
+ out.push(...codesOf(err.cause, depth + 1));
245
+ return out;
246
+ }
247
+ function deepestMessageOf(err, depth = 0) {
248
+ if (depth > 5 || err === null || typeof err !== "object") return "";
249
+ const cause = deepestMessageOf(err.cause, depth + 1);
250
+ if (cause !== "") return cause;
251
+ const message = messageOf(err);
252
+ return message === "" ? "" : message;
253
+ }
254
+ function hostOf(url) {
255
+ try {
256
+ return new URL(url).host;
257
+ } catch {
258
+ return url;
259
+ }
260
+ }
261
+ function faultOf(err, env = process3.env) {
262
+ const codes = codesOf(err).reverse();
263
+ for (const code of codes) {
264
+ const known = REASONS[code];
265
+ if (known === void 0) continue;
266
+ const hint = known.proxyHint === true ? proxyVariableOf(env) === void 0 ? PROXY_HINT : void 0 : known.hint;
267
+ return hint === void 0 ? { reason: known.reason } : { reason: known.reason, hint };
268
+ }
269
+ return { reason: codes[0] ?? deepestMessageOf(err) };
270
+ }
271
+ function networkDetail(err, url, env = process3.env) {
272
+ return `network error: ${hostOf(url)}: ${faultOf(err, env).reason}`;
273
+ }
274
+ function networkText(err, url, env = process3.env) {
275
+ const fault = faultOf(err, env);
276
+ const line = `network error: ${hostOf(url)}: ${fault.reason}`;
277
+ return fault.hint === void 0 ? line : `${line}
278
+ hint: ${fault.hint}`;
279
+ }
280
+ async function sendRequest(fetchImpl, url, init, env = process3.env) {
281
+ await readyNetwork(env);
282
+ try {
283
+ return await fetchImpl(url, init);
284
+ } catch (err) {
285
+ throw new ApiError(networkText(err, url, env));
286
+ }
287
+ }
288
+
289
+ // src/client.ts
155
290
  function errorText(body, status2) {
156
291
  if (body !== null && typeof body === "object" && typeof body.error === "string") {
157
292
  return body.error;
@@ -189,12 +324,7 @@ var Client = class {
189
324
  headers["content-type"] = opts.contentType ?? "application/octet-stream";
190
325
  body = opts.bytes;
191
326
  }
192
- let res;
193
- try {
194
- res = await this.fetchImpl(this.baseUrl + pathname, { method, headers, body });
195
- } catch (err) {
196
- throw new ApiError(`network error: ${messageOf(err)}`);
197
- }
327
+ const res = await sendRequest(this.fetchImpl, this.baseUrl + pathname, { method, headers, body });
198
328
  const text = await res.text();
199
329
  const parsed = parseBody(text);
200
330
  if (opts.allow404 === true && res.status === 404) return { status: 404, body: void 0 };
@@ -280,73 +410,9 @@ var Client = class {
280
410
  };
281
411
 
282
412
  // src/config.ts
283
- import { readFile } from "node:fs/promises";
284
- import { homedir as homedir2 } from "node:os";
285
- import path2 from "node:path";
286
-
287
- // src/keychain.ts
288
- import { spawn } from "node:child_process";
289
- import process2 from "node:process";
290
- var SERVICE = "rshow";
291
- var MISSING = 44;
292
- var NO_MACOS = "the keychain needs macOS; set RSHOW_TOKEN instead";
293
- function bin() {
294
- return process2.env.RSHOW_SECURITY_BIN ?? "security";
295
- }
296
- function usable() {
297
- return process2.platform === "darwin" || process2.env.RSHOW_SECURITY_BIN !== void 0;
298
- }
299
- function run(args, input) {
300
- return new Promise((resolve2, reject) => {
301
- const child = spawn(bin(), args, { stdio: ["pipe", "pipe", "pipe"] });
302
- let stdout = "";
303
- let stderr = "";
304
- child.stdout.setEncoding("utf8");
305
- child.stdout.on("data", (chunk2) => {
306
- stdout += chunk2;
307
- });
308
- child.stderr.setEncoding("utf8");
309
- child.stderr.on("data", (chunk2) => {
310
- stderr += chunk2;
311
- });
312
- child.stdin.on("error", () => {
313
- });
314
- child.on("error", (err) => {
315
- if (err.code === "ENOENT") resolve2({ code: -1, stdout, stderr, absent: true });
316
- else reject(new ApiError(`keychain: ${err.message}`));
317
- });
318
- child.on("close", (code) => resolve2({ code: code ?? -1, stdout, stderr, absent: false }));
319
- child.stdin.end(input ?? "");
320
- });
321
- }
322
- function failure(doing, result, secret) {
323
- const text = secret === void 0 ? result.stderr : result.stderr.split(secret).join("[redacted]");
324
- const detail = text.trim();
325
- return new ApiError(`${doing}: ${detail === "" ? `exit ${result.code}` : detail}`);
326
- }
327
- async function save(url, token) {
328
- if (!usable()) throw new UsageError(NO_MACOS);
329
- const line = `add-generic-password -U -s ${SERVICE} -a ${url} -w ${token}
330
- `;
331
- const result = await run(["-i"], line);
332
- if (result.code !== 0) throw failure("cannot save the token in the keychain", result, token);
333
- if (await read(url) !== token) throw failure("cannot save the token in the keychain", result, token);
334
- }
335
- async function read(url) {
336
- if (!usable()) return void 0;
337
- const result = await run(["find-generic-password", "-s", SERVICE, "-a", url, "-w"]);
338
- if (result.absent || result.code === MISSING) return void 0;
339
- if (result.code !== 0) throw failure("cannot read the token from the keychain", result);
340
- const token = result.stdout.trim();
341
- return token === "" ? void 0 : token;
342
- }
343
- async function remove(url) {
344
- if (!usable()) throw new UsageError(NO_MACOS);
345
- const result = await run(["delete-generic-password", "-s", SERVICE, "-a", url]);
346
- if (result.absent || result.code === MISSING) return false;
347
- if (result.code !== 0) throw failure("cannot remove the token from the keychain", result);
348
- return true;
349
- }
413
+ import { readFile as readFile3 } from "node:fs/promises";
414
+ import { homedir as homedir3 } from "node:os";
415
+ import path3 from "node:path";
350
416
 
351
417
  // ../shared/src/index.ts
352
418
  var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
@@ -393,8 +459,8 @@ function tagsOf(value) {
393
459
  if (!Array.isArray(value)) return [];
394
460
  return value.filter((tag) => typeof tag === "string").map((tag) => tag.toLowerCase());
395
461
  }
396
- function withTags(current, add2, remove3) {
397
- const next = unique(tagsOf(current)).filter((tag) => !remove3.includes(tag));
462
+ function withTags(current, add2, remove5) {
463
+ const next = unique(tagsOf(current)).filter((tag) => !remove5.includes(tag));
398
464
  for (const tag of add2) {
399
465
  if (!next.includes(tag)) next.push(tag);
400
466
  }
@@ -406,16 +472,326 @@ function sameTags(left, right) {
406
472
  return a.length === b.length && a.every((tag, index) => tag === b[index]);
407
473
  }
408
474
 
475
+ // src/tokenstore.ts
476
+ import process8 from "node:process";
477
+
478
+ // src/credfile.ts
479
+ import { chmod, mkdir as mkdir2, readFile as readFile2, rename as rename2, stat as stat2, unlink, writeFile as writeFile2 } from "node:fs/promises";
480
+ import { homedir as homedir2 } from "node:os";
481
+ import path2 from "node:path";
482
+ import process4 from "node:process";
483
+ var DIR = "rshow";
484
+ var FILE = "credentials";
485
+ var VERSION2 = 1;
486
+ var FILE_MODE = 384;
487
+ var DIR_MODE = 448;
488
+ var SHARED = 63;
489
+ var fixed = /* @__PURE__ */ new Set();
490
+ function messageOf2(err) {
491
+ return err instanceof Error ? err.message : String(err);
492
+ }
493
+ function isRecord(value) {
494
+ return value !== null && typeof value === "object" && !Array.isArray(value);
495
+ }
496
+ function credentialsPath(env) {
497
+ const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
498
+ const home = (env.HOME ?? "").trim();
499
+ const base = xdg !== "" ? xdg : path2.join(home !== "" ? home : homedir2(), ".config");
500
+ return path2.join(base, DIR, FILE);
501
+ }
502
+ async function tighten(file) {
503
+ await chmod(file, FILE_MODE).catch(() => {
504
+ });
505
+ if (fixed.has(file)) return;
506
+ fixed.add(file);
507
+ warn(`${file} was readable by group or other; its mode is now 600`);
508
+ }
509
+ async function load(file) {
510
+ const info = await stat2(file).catch(() => void 0);
511
+ if (info === void 0) return {};
512
+ if ((info.mode & SHARED) !== 0) await tighten(file);
513
+ let text;
514
+ try {
515
+ text = await readFile2(file, "utf8");
516
+ } catch (err) {
517
+ if (err.code === "ENOENT") return {};
518
+ throw new UsageError(`cannot read ${file}: ${messageOf2(err)}`);
519
+ }
520
+ let parsed;
521
+ try {
522
+ parsed = JSON.parse(text);
523
+ } catch {
524
+ throw new UsageError(`invalid JSON in ${file}`);
525
+ }
526
+ if (!isRecord(parsed)) throw new UsageError(`${file} must hold a JSON object`);
527
+ if (parsed.version !== void 0 && parsed.version !== VERSION2) {
528
+ throw new UsageError(`${file}: version must be ${VERSION2}`);
529
+ }
530
+ const raw = parsed.tokens ?? {};
531
+ if (!isRecord(raw)) throw new UsageError(`${file}: tokens must be an object`);
532
+ const tokens = {};
533
+ for (const [url, token] of Object.entries(raw)) {
534
+ if (typeof token !== "string") throw new UsageError(`${file}: tokens.${url} must be a string`);
535
+ tokens[url] = token;
536
+ }
537
+ return tokens;
538
+ }
539
+ async function store(file, tokens) {
540
+ const dir = path2.dirname(file);
541
+ let created;
542
+ try {
543
+ created = await mkdir2(dir, { recursive: true, mode: DIR_MODE });
544
+ } catch (err) {
545
+ throw new UsageError(`cannot make ${dir}: ${messageOf2(err)}`);
546
+ }
547
+ if (created !== void 0) await chmod(dir, DIR_MODE).catch(() => {
548
+ });
549
+ const temp = path2.join(dir, `.${path2.basename(file)}.${process4.pid}.${Date.now()}.tmp`);
550
+ const body = `${JSON.stringify({ version: VERSION2, tokens }, null, 2)}
551
+ `;
552
+ try {
553
+ await writeFile2(temp, body, { mode: FILE_MODE, flag: "wx" });
554
+ await chmod(temp, FILE_MODE);
555
+ await rename2(temp, file);
556
+ } catch (err) {
557
+ await unlink(temp).catch(() => {
558
+ });
559
+ throw new UsageError(`cannot write ${file}: ${messageOf2(err)}`);
560
+ }
561
+ }
562
+ async function read(file, url) {
563
+ return (await load(file))[url];
564
+ }
565
+ async function save(file, url, token) {
566
+ const tokens = await load(file);
567
+ tokens[url] = token;
568
+ await store(file, tokens);
569
+ }
570
+ async function remove(file, url) {
571
+ const tokens = await load(file);
572
+ if (tokens[url] === void 0) return false;
573
+ delete tokens[url];
574
+ if (Object.keys(tokens).length === 0) {
575
+ await unlink(file).catch((err) => {
576
+ if (err.code !== "ENOENT") throw new UsageError(`cannot remove ${file}: ${messageOf2(err)}`);
577
+ });
578
+ return true;
579
+ }
580
+ await store(file, tokens);
581
+ return true;
582
+ }
583
+
584
+ // src/keychain.ts
585
+ import process6 from "node:process";
586
+
587
+ // src/tool.ts
588
+ import { spawn } from "node:child_process";
589
+ import process5 from "node:process";
590
+ function childEnv(env) {
591
+ const out = { ...process5.env };
592
+ for (const [key2, value] of Object.entries(env ?? {})) {
593
+ if (value === void 0) delete out[key2];
594
+ else out[key2] = value;
595
+ }
596
+ return out;
597
+ }
598
+ function runTool(bin3, args, input, env) {
599
+ return new Promise((resolve2, reject) => {
600
+ const child = spawn(bin3, args, { stdio: ["pipe", "pipe", "pipe"], env: childEnv(env) });
601
+ let stdout = "";
602
+ let stderr = "";
603
+ child.stdout.setEncoding("utf8");
604
+ child.stdout.on("data", (chunk2) => {
605
+ stdout += chunk2;
606
+ });
607
+ child.stderr.setEncoding("utf8");
608
+ child.stderr.on("data", (chunk2) => {
609
+ stderr += chunk2;
610
+ });
611
+ child.stdin.on("error", () => {
612
+ });
613
+ child.on("error", (err) => {
614
+ if (err.code === "ENOENT") resolve2({ code: -1, stdout, stderr, absent: true });
615
+ else reject(new ApiError(`${bin3}: ${err.message}`));
616
+ });
617
+ child.on("close", (code) => resolve2({ code: code ?? -1, stdout, stderr, absent: false }));
618
+ child.stdin.end(input ?? "");
619
+ });
620
+ }
621
+ function toolFailure(doing, result, secret) {
622
+ const text = secret === void 0 ? result.stderr : result.stderr.split(secret).join("[redacted]");
623
+ const detail = text.trim();
624
+ return new ApiError(`${doing}: ${detail === "" ? `exit ${result.code}` : detail}`);
625
+ }
626
+
627
+ // src/keychain.ts
628
+ var SERVICE = "rshow";
629
+ var MISSING = 44;
630
+ function bin(env) {
631
+ const set = (env.RSHOW_SECURITY_BIN ?? "").trim();
632
+ return set === "" ? "security" : set;
633
+ }
634
+ function canSave(platform = process6.platform, securityBin = process6.env.RSHOW_SECURITY_BIN) {
635
+ return platform === "darwin" || (securityBin ?? "").trim() !== "";
636
+ }
637
+ async function save2(url, token, env = process6.env) {
638
+ if (!canSave(process6.platform, env.RSHOW_SECURITY_BIN)) {
639
+ throw new UsageError("the keychain needs macOS; set RSHOW_TOKEN instead");
640
+ }
641
+ const line = `add-generic-password -U -s ${SERVICE} -a ${url} -w ${token}
642
+ `;
643
+ const result = await runTool(bin(env), ["-i"], line, env);
644
+ if (result.code !== 0) throw toolFailure("cannot save the token in the keychain", result, token);
645
+ if (await read2(url, env) !== token) {
646
+ throw toolFailure("cannot save the token in the keychain", result, token);
647
+ }
648
+ }
649
+ async function read2(url, env = process6.env) {
650
+ if (!canSave(process6.platform, env.RSHOW_SECURITY_BIN)) return void 0;
651
+ const args = ["find-generic-password", "-s", SERVICE, "-a", url, "-w"];
652
+ const result = await runTool(bin(env), args, void 0, env);
653
+ if (result.absent || result.code === MISSING) return void 0;
654
+ if (result.code !== 0) throw toolFailure("cannot read the token from the keychain", result);
655
+ const token = result.stdout.trim();
656
+ return token === "" ? void 0 : token;
657
+ }
658
+ async function remove2(url, env = process6.env) {
659
+ if (!canSave(process6.platform, env.RSHOW_SECURITY_BIN)) return false;
660
+ const args = ["delete-generic-password", "-s", SERVICE, "-a", url];
661
+ const result = await runTool(bin(env), args, void 0, env);
662
+ if (result.absent || result.code === MISSING) return false;
663
+ if (result.code !== 0) throw toolFailure("cannot remove the token from the keychain", result);
664
+ return true;
665
+ }
666
+
667
+ // src/keyring.ts
668
+ import process7 from "node:process";
669
+ var SERVICE2 = "rshow";
670
+ var PROBE_SERVICE = "rshow-probe";
671
+ var PROBE_ACCOUNT = "probe";
672
+ var NOT_FOUND = 1;
673
+ function bin2(env) {
674
+ const set = (env.RSHOW_SECRET_TOOL_BIN ?? "").trim();
675
+ return set === "" ? "secret-tool" : set;
676
+ }
677
+ function canUse(env, platform = process7.platform) {
678
+ return platform === "linux" || (env.RSHOW_SECRET_TOOL_BIN ?? "").trim() !== "";
679
+ }
680
+ async function usable(env = process7.env) {
681
+ const args = ["lookup", "service", PROBE_SERVICE, "account", PROBE_ACCOUNT];
682
+ const result = await runTool(bin2(env), args, void 0, env);
683
+ return !result.absent && (result.code === 0 || result.code === NOT_FOUND);
684
+ }
685
+ async function save3(url, token, env = process7.env) {
686
+ const args = ["store", "--label", `rshow ${url}`, "service", SERVICE2, "account", url];
687
+ const result = await runTool(bin2(env), args, token, env);
688
+ if (result.code !== 0) throw toolFailure("cannot save the token in the keyring", result, token);
689
+ if (await read3(url, env) !== token) {
690
+ throw toolFailure("cannot save the token in the keyring", result, token);
691
+ }
692
+ }
693
+ async function read3(url, env = process7.env) {
694
+ const args = ["lookup", "service", SERVICE2, "account", url];
695
+ const result = await runTool(bin2(env), args, void 0, env);
696
+ if (result.absent || result.code === NOT_FOUND) return void 0;
697
+ if (result.code !== 0) throw toolFailure("cannot read the token from the keyring", result);
698
+ const token = result.stdout.trim();
699
+ return token === "" ? void 0 : token;
700
+ }
701
+ async function remove3(url, env = process7.env) {
702
+ const args = ["clear", "service", SERVICE2, "account", url];
703
+ const result = await runTool(bin2(env), args, void 0, env);
704
+ if (result.absent || result.code === NOT_FOUND) return false;
705
+ if (result.code !== 0) throw toolFailure("cannot remove the token from the keyring", result);
706
+ return true;
707
+ }
708
+
709
+ // src/tokenstore.ts
710
+ var BACKENDS = ["keychain", "keyring", "file"];
711
+ function storeOf(backend, env) {
712
+ if (backend === "keychain") {
713
+ return {
714
+ backend,
715
+ detail: "keychain",
716
+ description: "the macOS Keychain",
717
+ read: (url) => read2(url, env),
718
+ save: (url, token) => save2(url, token, env),
719
+ remove: (url) => remove2(url, env)
720
+ };
721
+ }
722
+ if (backend === "keyring") {
723
+ return {
724
+ backend,
725
+ detail: "keyring",
726
+ description: "the Linux keyring",
727
+ read: (url) => read3(url, env),
728
+ save: (url, token) => save3(url, token, env),
729
+ remove: (url) => remove3(url, env)
730
+ };
731
+ }
732
+ const file = credentialsPath(env);
733
+ return {
734
+ backend,
735
+ detail: `file ${file}`,
736
+ description: `${file} (mode 600)`,
737
+ read: (url) => read(file, url),
738
+ save: (url, token) => save(file, url, token),
739
+ remove: (url) => remove(file, url)
740
+ };
741
+ }
742
+ function forced(env) {
743
+ const text = (env.RSHOW_TOKEN_STORE ?? "").trim();
744
+ if (text === "") return void 0;
745
+ for (const backend of BACKENDS) {
746
+ if (backend === text) return backend;
747
+ }
748
+ throw new UsageError(`RSHOW_TOKEN_STORE must be keychain, keyring or file, not ${text}`);
749
+ }
750
+ async function choose(env = process8.env, platform = process8.platform) {
751
+ const forcedBackend = forced(env);
752
+ if (forcedBackend !== void 0) return storeOf(forcedBackend, env);
753
+ if (canSave(platform, env.RSHOW_SECURITY_BIN)) return storeOf("keychain", env);
754
+ if (canUse(env, platform) && await usable(env)) return storeOf("keyring", env);
755
+ return storeOf("file", env);
756
+ }
757
+ function fileStore(env = process8.env) {
758
+ return storeOf("file", env);
759
+ }
760
+ async function every(env = process8.env, platform = process8.platform) {
761
+ const stores = [];
762
+ if (canSave(platform, env.RSHOW_SECURITY_BIN)) stores.push(storeOf("keychain", env));
763
+ if (canUse(env, platform) && await usable(env)) stores.push(storeOf("keyring", env));
764
+ stores.push(storeOf("file", env));
765
+ return stores;
766
+ }
767
+ async function find(env, url, platform = process8.platform) {
768
+ const forcedBackend = forced(env);
769
+ const stores = forcedBackend === void 0 ? await every(env, platform) : [storeOf(forcedBackend, env)];
770
+ let failed = false;
771
+ let firstError;
772
+ for (const store2 of stores) {
773
+ try {
774
+ const token = await store2.read(url);
775
+ if (token !== void 0) return { store: store2, token };
776
+ } catch (err) {
777
+ if (!failed) firstError = err;
778
+ failed = true;
779
+ }
780
+ }
781
+ if (failed) throw firstError;
782
+ return void 0;
783
+ }
784
+
409
785
  // src/config.ts
410
786
  var DEFAULT_OUT = "dist";
411
787
  var GLOBAL_DIR = "rshow";
412
788
  var GLOBAL_FILE = "config.json";
413
789
  var LOCAL_FILE = "rshow.json";
414
790
  var NO_SERVER = "no server configured: run rshow init --url <admin-url> --art <viewer-url>";
415
- function messageOf2(err) {
791
+ function messageOf3(err) {
416
792
  return err instanceof Error ? err.message : String(err);
417
793
  }
418
- function isRecord(value) {
794
+ function isRecord2(value) {
419
795
  return value !== null && typeof value === "object" && !Array.isArray(value);
420
796
  }
421
797
  function withoutSlash(value) {
@@ -424,14 +800,14 @@ function withoutSlash(value) {
424
800
  function globalConfigPath(env) {
425
801
  const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
426
802
  const home = (env.HOME ?? "").trim();
427
- const base = xdg !== "" ? xdg : path2.join(home !== "" ? home : homedir2(), ".config");
428
- return path2.join(base, GLOBAL_DIR, GLOBAL_FILE);
803
+ const base = xdg !== "" ? xdg : path3.join(home !== "" ? home : homedir3(), ".config");
804
+ return path3.join(base, GLOBAL_DIR, GLOBAL_FILE);
429
805
  }
430
806
  function localConfigPath(dir) {
431
- return path2.join(path2.resolve(dir), LOCAL_FILE);
807
+ return path3.join(path3.resolve(dir), LOCAL_FILE);
432
808
  }
433
809
  function layerOf(file, value) {
434
- if (!isRecord(value)) throw new UsageError(`${file} must hold a JSON object`);
810
+ if (!isRecord2(value)) throw new UsageError(`${file} must hold a JSON object`);
435
811
  const fieldOf = (key2) => {
436
812
  const raw = value[key2];
437
813
  if (raw === void 0 || raw === null) return void 0;
@@ -451,7 +827,7 @@ function layerOf(file, value) {
451
827
  };
452
828
  const rawTokens = value.tokens;
453
829
  if (rawTokens !== void 0 && rawTokens !== null) {
454
- if (!isRecord(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
830
+ if (!isRecord2(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
455
831
  const tokens = {};
456
832
  for (const [key2, raw] of Object.entries(rawTokens)) {
457
833
  if (typeof raw !== "string") throw new UsageError(`${file}: tokens.${key2} must be a string`);
@@ -473,10 +849,10 @@ function layerOf(file, value) {
473
849
  async function readConfigFile(file) {
474
850
  let text;
475
851
  try {
476
- text = await readFile(file, "utf8");
852
+ text = await readFile3(file, "utf8");
477
853
  } catch (err) {
478
854
  if (err.code === "ENOENT") return {};
479
- throw new UsageError(`cannot read ${file}: ${messageOf2(err)}`);
855
+ throw new UsageError(`cannot read ${file}: ${messageOf3(err)}`);
480
856
  }
481
857
  let parsed;
482
858
  try {
@@ -538,13 +914,13 @@ function requireArt(config) {
538
914
  async function readToken(env, url) {
539
915
  const token = (env.RSHOW_TOKEN ?? "").trim();
540
916
  if (token !== "") return token;
541
- const saved = await read(url);
542
- if (saved !== void 0) return saved;
917
+ const found = await find(env, url);
918
+ if (found !== void 0) return found.token;
543
919
  throw new UsageError("no token: run rshow login, or set RSHOW_TOKEN");
544
920
  }
545
921
 
546
922
  // src/output.ts
547
- import process3 from "node:process";
923
+ import process9 from "node:process";
548
924
  var Output = class {
549
925
  json;
550
926
  constructor(json) {
@@ -552,16 +928,16 @@ var Output = class {
552
928
  }
553
929
  data(value) {
554
930
  if (!this.json) return;
555
- process3.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
931
+ process9.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
556
932
  `);
557
933
  }
558
934
  line(text) {
559
935
  if (this.json) return;
560
- process3.stdout.write(`${text}
936
+ process9.stdout.write(`${text}
561
937
  `);
562
938
  }
563
939
  raw(text) {
564
- process3.stdout.write(`${text}
940
+ process9.stdout.write(`${text}
565
941
  `);
566
942
  }
567
943
  };
@@ -591,7 +967,7 @@ commands:
591
967
  share <slug> set mode or change shares
592
968
  pin <slug> <n|live> serve version n, or live for the newest
593
969
  delete <slug> --yes delete an artifact
594
- login [--url u] save the API token in the macOS Keychain
970
+ login [--url u] save the API token for this account
595
971
  logout [--url u] remove the saved token for a server
596
972
 
597
973
  global:
@@ -602,9 +978,13 @@ global:
602
978
  environment:
603
979
  RSHOW_URL API base URL, overrides the config file
604
980
  RSHOW_ART art base URL, overrides the config file
605
- RSHOW_TOKEN the API token, else the token saved by rshow login
606
- in the macOS Keychain; never read from a file
607
- XDG_CONFIG_HOME where to find rshow/config.json (default ~/.config)
981
+ RSHOW_TOKEN the API token, else the token rshow login saved in
982
+ the macOS Keychain, the Linux keyring or a 0600 file
983
+ RSHOW_TOKEN_STORE force the keychain, keyring or file token store
984
+ XDG_CONFIG_HOME where rshow keeps config.json and credentials
985
+ RSHOW_SYSTEM_CA set to 0 to trust only the roots node bundles
986
+ RSHOW_EXTRA_CA_FILE a PEM file of extra roots, such as a cluster's CA
987
+ NODE_USE_ENV_PROXY set to 1 so fetch uses https_proxy (Node 24+)
608
988
 
609
989
  Start here: rshow init --url <admin-url> --art <viewer-url>, then rshow login.
610
990
 
@@ -702,8 +1082,8 @@ files win. Run rshow init to write url and art.
702
1082
  A theme with no version, or @latest, is looked up on the art host and printed
703
1083
  as <name>@<n>; when that fails a warning goes to stderr. --offline skips it, and
704
1084
  so does a config with no art host.
705
- The token is RSHOW_TOKEN, else the token saved by rshow login in the macOS
706
- Keychain; never read from a file.`;
1085
+ The token is RSHOW_TOKEN, else the token rshow login saved in the macOS
1086
+ Keychain, the Linux keyring or a file with mode 600.`;
707
1087
  var INIT_USAGE = `usage: rshow init --url <admin-url> --art <viewer-url> [--theme <name>] [--force]
708
1088
 
709
1089
  Writes the global config file that rshow reads first:
@@ -723,6 +1103,7 @@ package, the config files, the token, the api, the art host, the theme and the
723
1103
  skills, one line each: ok|warn|fail <check> <detail>. With [dir] it reads
724
1104
  <dir>/rshow.json too. A skill line warns and never fails.
725
1105
  Network calls time out after 5 s. Exit 0 when no check failed, 6 when one did.
1106
+ The token line names its source: env, keychain, keyring, or file and its path.
726
1107
  The token is never printed.`;
727
1108
  var THEME_USAGE = `usage: rshow theme <new|check|preview|publish|list> [options]
728
1109
 
@@ -849,14 +1230,19 @@ var DELETE_USAGE = `usage: rshow delete <slug> --yes
849
1230
  Deletes the artifact and needs --yes.`;
850
1231
  var LOGIN_USAGE = `usage: rshow login [--url u]
851
1232
 
852
- Reads the token from stdin, checks it against the API, then saves it in the
853
- macOS Keychain. On a terminal it asks for the token with echo off, so it is
854
- never typed on a command line or pasted anywhere. The API base URL is the
855
- account, so each server keeps its own token; --url logs in to another one.
856
- RSHOW_TOKEN still wins over the saved token.`;
1233
+ Reads the token from stdin, checks it with the API, then saves it in the macOS
1234
+ Keychain, in the Linux keyring, or in $XDG_CONFIG_HOME/rshow/credentials with
1235
+ mode 600: the first store this machine has, and it reads it back to confirm.
1236
+ On a terminal it asks for the token with echo off, so it is never typed on a
1237
+ command line or pasted anywhere. The API base URL is the account, so each
1238
+ server keeps its own token; --url logs in to another one.
1239
+ Prints the email, the scope and where the token went.
1240
+ RSHOW_TOKEN still wins over the saved token, and RSHOW_TOKEN_STORE forces one
1241
+ of keychain, keyring or file.`;
857
1242
  var LOGOUT_USAGE = `usage: rshow logout [--url u]
858
1243
 
859
- Removes the token this server has in the macOS Keychain.`;
1244
+ Removes the token this server has from every store that holds it: the macOS
1245
+ Keychain, the Linux keyring and the 0600 credentials file.`;
860
1246
  var PROJECT_USAGE = `usage: rshow project <create|list|show|share|delete> [options]
861
1247
 
862
1248
  project create <slug> [--title t] [--description d]
@@ -982,14 +1368,14 @@ function usageLine(usage) {
982
1368
  }
983
1369
 
984
1370
  // src/commands/publish.ts
985
- import { stat as stat4 } from "node:fs/promises";
986
- import path7 from "node:path";
1371
+ import { stat as stat5 } from "node:fs/promises";
1372
+ import path8 from "node:path";
987
1373
 
988
1374
  // src/args.ts
989
1375
  import { parseArgs } from "node:util";
990
1376
  var LIST_FLAGS = /* @__PURE__ */ new Set(["--add", "--editor", "--remove", "--tag"]);
991
1377
  var LOWERCASE_TAG = new RegExp(TAG_PATTERN.source, "i");
992
- function messageOf3(err) {
1378
+ function messageOf4(err) {
993
1379
  return err instanceof Error ? err.message : String(err);
994
1380
  }
995
1381
  function isListValue(token) {
@@ -1025,7 +1411,7 @@ function parseCommandArgs(argv, options, minPositionals, maxPositionals, hint) {
1025
1411
  try {
1026
1412
  parsed = parseArgs({ args: expanded, options, allowPositionals: true, strict: true });
1027
1413
  } catch (err) {
1028
- throw new UsageError(`${messageOf3(err)} (try: ${usageLine(hint)})`);
1414
+ throw new UsageError(`${messageOf4(err)} (try: ${usageLine(hint)})`);
1029
1415
  }
1030
1416
  const count = parsed.positionals.length;
1031
1417
  if (count < minPositionals || count > maxPositionals) {
@@ -1057,21 +1443,21 @@ function emailList(args, name) {
1057
1443
 
1058
1444
  // src/build.ts
1059
1445
  import { spawn as spawn2 } from "node:child_process";
1060
- import process4 from "node:process";
1061
- function messageOf4(err) {
1446
+ import process10 from "node:process";
1447
+ function messageOf5(err) {
1062
1448
  return err instanceof Error ? err.message : String(err);
1063
1449
  }
1064
1450
  function shellPath(env) {
1065
1451
  const shell = (env.SHELL ?? "").trim();
1066
1452
  return shell === "" ? "/bin/sh" : shell;
1067
1453
  }
1068
- async function runBuild(dir, command, env = process4.env) {
1454
+ async function runBuild(dir, command, env = process10.env) {
1069
1455
  const code = await new Promise((resolve2, reject) => {
1070
1456
  const child = spawn2(shellPath(env), ["-c", command], { cwd: dir, stdio: "inherit" });
1071
1457
  child.once("error", reject);
1072
1458
  child.once("close", (value) => resolve2(value));
1073
1459
  }).catch((err) => {
1074
- throw new BuildError(`build failed: ${messageOf4(err)}`);
1460
+ throw new BuildError(`build failed: ${messageOf5(err)}`);
1075
1461
  });
1076
1462
  if (code !== 0) {
1077
1463
  throw new BuildError(`build failed: ${code === null ? "killed by a signal" : `exit ${code}`}`);
@@ -1079,12 +1465,12 @@ async function runBuild(dir, command, env = process4.env) {
1079
1465
  }
1080
1466
 
1081
1467
  // src/check.ts
1082
- import { readFile as readFile4, readdir, stat as stat2 } from "node:fs/promises";
1083
- import path5 from "node:path";
1468
+ import { readFile as readFile6, readdir, stat as stat3 } from "node:fs/promises";
1469
+ import path6 from "node:path";
1084
1470
 
1085
1471
  // src/mockup.ts
1086
- import { readFile as readFile2 } from "node:fs/promises";
1087
- import path3 from "node:path";
1472
+ import { readFile as readFile4 } from "node:fs/promises";
1473
+ import path4 from "node:path";
1088
1474
  var MAX_SCAN_BYTES = 4 * 1024 * 1024;
1089
1475
  var HTML_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm"]);
1090
1476
  var MIN_COLS = 80;
@@ -1154,20 +1540,20 @@ function idOf(value) {
1154
1540
  const id = String(value ?? "").trim().replace(/^#/, "");
1155
1541
  return id === "" ? void 0 : id;
1156
1542
  }
1157
- function warn(scan, line, message) {
1543
+ function warn2(scan, line, message) {
1158
1544
  scan.out.push({ path: scan.path, line, message });
1159
1545
  }
1160
1546
  function keyHint(scan, masked, from, to, line) {
1161
1547
  const hit = TERMINAL_KEYS.exec(masked.slice(from, to).replace(ANY_TAG, ""));
1162
1548
  if (hit === null) return;
1163
- warn(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
1549
+ warn2(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
1164
1550
  }
1165
1551
  function sizeGate(scan, attrs, line) {
1166
1552
  if (attrs.has("data-allow-small")) return;
1167
1553
  const cols = sizeOf(attrs, "data-cols", MIN_COLS);
1168
1554
  const rows = sizeOf(attrs, "data-rows", MIN_ROWS);
1169
1555
  if (cols >= MIN_COLS && rows >= MIN_ROWS) return;
1170
- warn(scan, line, `tui below the 80x24 minimum (${cols}x${rows}), or mark it with data-allow-small`);
1556
+ warn2(scan, line, `tui below the 80x24 minimum (${cols}x${rows}), or mark it with data-allow-small`);
1171
1557
  }
1172
1558
  function walk(scan, masked) {
1173
1559
  const stack = [];
@@ -1247,25 +1633,25 @@ function resolve(scan) {
1247
1633
  ids.add(item.id);
1248
1634
  const count = (seen.get(item.id) ?? 0) + 1;
1249
1635
  seen.set(item.id, count);
1250
- if (count === 2) warn(scan, item.line, `duplicate artboard id: ${item.id}`);
1636
+ if (count === 2) warn2(scan, item.line, `duplicate artboard id: ${item.id}`);
1251
1637
  }
1252
1638
  const unknown = /* @__PURE__ */ new Set();
1253
1639
  for (const flow of scan.flows) {
1254
1640
  if (ids.has(flow.id) || unknown.has(flow.id)) continue;
1255
1641
  unknown.add(flow.id);
1256
- warn(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
1642
+ warn2(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
1257
1643
  }
1258
1644
  for (const item of scan.boards) {
1259
- if (!item.title) warn(scan, item.line, `artboard ${item.label} has no data-title`);
1645
+ if (!item.title) warn2(scan, item.line, `artboard ${item.label} has no data-title`);
1260
1646
  for (const block of item.blocks) {
1261
1647
  const missing = block.states.filter((state) => state !== "default" && !item.states.includes(state));
1262
1648
  if (missing.length === 0) continue;
1263
- warn(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
1649
+ warn2(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
1264
1650
  }
1265
1651
  for (const state of item.states) {
1266
1652
  if (state === "default") continue;
1267
1653
  if (item.blocks.some((block) => block.states.includes(state))) continue;
1268
- warn(scan, item.line, `artboard ${item.label} lists state "${state}" that no block uses`);
1654
+ warn2(scan, item.line, `artboard ${item.label} lists state "${state}" that no block uses`);
1269
1655
  }
1270
1656
  }
1271
1657
  }
@@ -1279,9 +1665,9 @@ function htmlWarnings(rel, text) {
1279
1665
  async function mockupWarnings(entries) {
1280
1666
  const out = [];
1281
1667
  for (const entry of entries) {
1282
- const ext = path3.extname(entry.rel).toLowerCase();
1668
+ const ext = path4.extname(entry.rel).toLowerCase();
1283
1669
  if (!HTML_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES) continue;
1284
- const text = await readFile2(entry.abs, "utf8").catch(() => void 0);
1670
+ const text = await readFile4(entry.abs, "utf8").catch(() => void 0);
1285
1671
  if (text === void 0) continue;
1286
1672
  out.push(...htmlWarnings(entry.rel, text));
1287
1673
  }
@@ -1289,8 +1675,8 @@ async function mockupWarnings(entries) {
1289
1675
  }
1290
1676
 
1291
1677
  // src/style.ts
1292
- import { readFile as readFile3 } from "node:fs/promises";
1293
- import path4 from "node:path";
1678
+ import { readFile as readFile5 } from "node:fs/promises";
1679
+ import path5 from "node:path";
1294
1680
  var MAX_SCAN_BYTES2 = 4 * 1024 * 1024;
1295
1681
  var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css"]);
1296
1682
  var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
@@ -1687,9 +2073,9 @@ async function styleWarnings(entries) {
1687
2073
  const loaded = /* @__PURE__ */ new Set();
1688
2074
  const used = [];
1689
2075
  for (const entry of entries) {
1690
- const ext = path4.extname(entry.rel).toLowerCase();
2076
+ const ext = path5.extname(entry.rel).toLowerCase();
1691
2077
  if (!STYLE_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES2) continue;
1692
- const text = await readFile3(entry.abs, "utf8").catch(() => void 0);
2078
+ const text = await readFile5(entry.abs, "utf8").catch(() => void 0);
1693
2079
  if (text === void 0) continue;
1694
2080
  collectLoaded(text, loaded);
1695
2081
  if (stylePathSkipped(entry.rel)) continue;
@@ -1740,9 +2126,6 @@ var CONNECT_PATTERNS = [
1740
2126
  pattern: /\.open\s*\(\s*["'`][A-Za-z]+["'`]\s*,\s*["'`](https?:\/\/[^"'`\s)]+)/g
1741
2127
  }
1742
2128
  ];
1743
- function messageOf5(err) {
1744
- return err instanceof Error ? err.message : String(err);
1745
- }
1746
2129
  function add(scan, file, line, severity, message) {
1747
2130
  scan.findings.push({ path: file, line, severity, message });
1748
2131
  }
@@ -1793,7 +2176,7 @@ function attributes2(raw) {
1793
2176
  }
1794
2177
  return found;
1795
2178
  }
1796
- function hostOf(value) {
2179
+ function hostOf2(value) {
1797
2180
  if (!EXTERNAL.test(value) || IGNORED.test(value)) return void 0;
1798
2181
  try {
1799
2182
  const host = new URL(value, BASE_URL).hostname;
@@ -1817,7 +2200,7 @@ function resolvePath(file, ref) {
1817
2200
  decoded = ref;
1818
2201
  }
1819
2202
  const wantsIndex = decoded.endsWith("/");
1820
- const base = path5.posix.dirname(file);
2203
+ const base = path6.posix.dirname(file);
1821
2204
  const parts2 = base === "." ? [] : base.split("/");
1822
2205
  for (const segment of decoded.split("/")) {
1823
2206
  if (segment === "" || segment === ".") continue;
@@ -1868,7 +2251,7 @@ function resolveRef(scan, file, raw, line) {
1868
2251
  }
1869
2252
  function checkScript(scan, file, line, attrs) {
1870
2253
  const value = (attrs.get("src") ?? "").trim();
1871
- const host = hostOf(value);
2254
+ const host = hostOf2(value);
1872
2255
  if (host === void 0) return;
1873
2256
  if (!SCRIPT_HOSTS.includes(host)) {
1874
2257
  add(scan, file, line, "error", `script host not allowed: ${value} (allowed: ${SCRIPT_HOSTS.join(", ")})`);
@@ -1884,7 +2267,7 @@ function checkLink(scan, file, line, attrs) {
1884
2267
  const rel = (attrs.get("rel") ?? "").toLowerCase().split(/\s+/);
1885
2268
  if (!rel.includes("stylesheet")) return;
1886
2269
  const value = (attrs.get("href") ?? "").trim();
1887
- const host = hostOf(value);
2270
+ const host = hostOf2(value);
1888
2271
  if (host === void 0 || STYLE_HOSTS.includes(host)) return;
1889
2272
  add(scan, file, line, "error", `stylesheet host not allowed: ${value} (allowed: ${STYLE_HOSTS.join(", ")})`);
1890
2273
  }
@@ -1940,9 +2323,9 @@ function scanHtml(scan, file, text, facts) {
1940
2323
  scanConnect(scan, file, masked, at);
1941
2324
  }
1942
2325
  async function scanFile(scan, entry, facts) {
1943
- const ext = path5.extname(entry.rel).toLowerCase();
2326
+ const ext = path6.extname(entry.rel).toLowerCase();
1944
2327
  if (!SCAN_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES3) return;
1945
- const text = await readFile4(entry.abs, "utf8");
2328
+ const text = await readFile6(entry.abs, "utf8");
1946
2329
  if (ext === ".html" || ext === ".htm") {
1947
2330
  scanHtml(scan, entry.rel, text, facts);
1948
2331
  return;
@@ -1957,7 +2340,7 @@ async function collect(scan, dir, prefix, entries) {
1957
2340
  for (const item of items) {
1958
2341
  if (item.name.startsWith(".") || SKIPPED_NAMES.has(item.name)) continue;
1959
2342
  const rel = prefix === "" ? item.name : `${prefix}/${item.name}`;
1960
- const abs = path5.join(dir, item.name);
2343
+ const abs = path6.join(dir, item.name);
1961
2344
  const problem = pathProblem(rel);
1962
2345
  if (problem !== void 0) add(scan, rel, 1, "error", problem);
1963
2346
  if (item.isDirectory()) {
@@ -1965,7 +2348,7 @@ async function collect(scan, dir, prefix, entries) {
1965
2348
  continue;
1966
2349
  }
1967
2350
  if (!item.isFile()) continue;
1968
- const info = await stat2(abs).catch(() => void 0);
2351
+ const info = await stat3(abs).catch(() => void 0);
1969
2352
  if (info === void 0) continue;
1970
2353
  entries.push({ rel, abs, size: info.size });
1971
2354
  }
@@ -1973,11 +2356,12 @@ async function collect(scan, dir, prefix, entries) {
1973
2356
  async function checkThemeFile(scan, key2, rel, where) {
1974
2357
  const url = `${scan.art}/_themes/${key2}/${rel}`;
1975
2358
  let status2;
2359
+ await readyNetwork();
1976
2360
  try {
1977
2361
  const reply = await scan.fetchImpl(url, { method: "HEAD" });
1978
2362
  status2 = reply.status;
1979
2363
  } catch (err) {
1980
- add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${messageOf5(err)}`);
2364
+ add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${networkDetail(err, url)}`);
1981
2365
  return;
1982
2366
  }
1983
2367
  if (status2 === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
@@ -1989,11 +2373,12 @@ async function checkTheme(scan, key2, where) {
1989
2373
  if (scan.offline || scan.art === "") return;
1990
2374
  const url = `${scan.art}/_themes/${key2}/tokens.css`;
1991
2375
  let status2;
2376
+ await readyNetwork();
1992
2377
  try {
1993
2378
  const reply = await scan.fetchImpl(url, { method: "HEAD" });
1994
2379
  status2 = reply.status;
1995
2380
  } catch (err) {
1996
- add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${messageOf5(err)}`);
2381
+ add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${networkDetail(err, url)}`);
1997
2382
  return;
1998
2383
  }
1999
2384
  if (status2 === 404) {
@@ -2011,8 +2396,8 @@ async function checkTheme(scan, key2, where) {
2011
2396
  }
2012
2397
  }
2013
2398
  async function checkFolder(input, opts = {}) {
2014
- const root2 = path5.resolve(input);
2015
- const info = await stat2(root2).catch(() => void 0);
2399
+ const root2 = path6.resolve(input);
2400
+ const info = await stat3(root2).catch(() => void 0);
2016
2401
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
2017
2402
  const scan = {
2018
2403
  files: /* @__PURE__ */ new Set(),
@@ -2104,7 +2489,7 @@ function reportCheck(out, result) {
2104
2489
  out.line(summaryLine(result));
2105
2490
  }
2106
2491
  async function enforceCheck(out, target, opts = {}) {
2107
- const info = await stat2(target).catch(() => void 0);
2492
+ const info = await stat3(target).catch(() => void 0);
2108
2493
  if (info === void 0 || !info.isDirectory()) return;
2109
2494
  const result = await checkFolder(target, opts);
2110
2495
  if (result.findings.length > 0) reportCheck(out, result);
@@ -2112,12 +2497,12 @@ async function enforceCheck(out, target, opts = {}) {
2112
2497
  }
2113
2498
 
2114
2499
  // src/publish.ts
2115
- import { readFile as readFile6 } from "node:fs/promises";
2500
+ import { readFile as readFile8 } from "node:fs/promises";
2116
2501
 
2117
2502
  // src/files.ts
2118
2503
  import { createHash } from "node:crypto";
2119
- import { readdir as readdir2, readFile as readFile5, stat as stat3 } from "node:fs/promises";
2120
- import path6 from "node:path";
2504
+ import { readdir as readdir2, readFile as readFile7, stat as stat4 } from "node:fs/promises";
2505
+ import path7 from "node:path";
2121
2506
  var CONTENT_TYPES = {
2122
2507
  html: "text/html; charset=utf-8",
2123
2508
  css: "text/css; charset=utf-8",
@@ -2149,14 +2534,14 @@ var CONTENT_TYPES = {
2149
2534
  var DEFAULT_CONTENT_TYPE = "application/octet-stream";
2150
2535
  var SKIPPED_NAMES2 = /* @__PURE__ */ new Set(["node_modules"]);
2151
2536
  function contentTypeFor(filePath) {
2152
- const ext = path6.extname(filePath).replace(/^\./, "").toLowerCase();
2537
+ const ext = path7.extname(filePath).replace(/^\./, "").toLowerCase();
2153
2538
  return CONTENT_TYPES[ext] ?? DEFAULT_CONTENT_TYPE;
2154
2539
  }
2155
2540
  function sha256Hex(bytes) {
2156
2541
  return createHash("sha256").update(bytes).digest("hex");
2157
2542
  }
2158
2543
  async function readEntry(abs, rel) {
2159
- const bytes = await readFile5(abs);
2544
+ const bytes = await readFile7(abs);
2160
2545
  return {
2161
2546
  path: rel,
2162
2547
  file: abs,
@@ -2174,7 +2559,7 @@ async function collectPaths(root2) {
2174
2559
  if (entry.name.startsWith(".")) continue;
2175
2560
  if (SKIPPED_NAMES2.has(entry.name)) continue;
2176
2561
  const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
2177
- const abs = path6.join(dir, entry.name);
2562
+ const abs = path7.join(dir, entry.name);
2178
2563
  if (entry.isDirectory()) {
2179
2564
  await walk2(abs, rel);
2180
2565
  continue;
@@ -2187,8 +2572,8 @@ async function collectPaths(root2) {
2187
2572
  return found;
2188
2573
  }
2189
2574
  async function buildManifest(input, opts = {}) {
2190
- const root2 = path6.resolve(input);
2191
- const info = await stat3(root2).catch(() => void 0);
2575
+ const root2 = path7.resolve(input);
2576
+ const info = await stat4(root2).catch(() => void 0);
2192
2577
  if (info === void 0) throw new UsageError(`no such file or folder: ${input}`);
2193
2578
  const entries = [];
2194
2579
  if (info.isFile()) {
@@ -2198,7 +2583,7 @@ async function buildManifest(input, opts = {}) {
2198
2583
  if (opts.requireIndex !== false && !paths.includes("index.html")) {
2199
2584
  throw new UsageError(`${input} has no index.html`);
2200
2585
  }
2201
- for (const rel of paths) entries.push(await readEntry(path6.join(root2, rel), rel));
2586
+ for (const rel of paths) entries.push(await readEntry(path7.join(root2, rel), rel));
2202
2587
  } else {
2203
2588
  throw new UsageError(`not a file or folder: ${input}`);
2204
2589
  }
@@ -2316,7 +2701,7 @@ async function publishTarget(api, target, opts) {
2316
2701
  let uploaded = 0;
2317
2702
  await runPool(missing, UPLOAD_CONCURRENCY, async (hash) => {
2318
2703
  const entry = byHash.get(hash);
2319
- await api.putBlob(hash, await readFile6(entry.file));
2704
+ await api.putBlob(hash, await readFile8(entry.file));
2320
2705
  uploaded += 1;
2321
2706
  });
2322
2707
  const version = await api.createVersion(opts.slug, { label: opts.label, files: manifest.files });
@@ -2344,7 +2729,7 @@ function sameFiles(local, remote) {
2344
2729
  const paths = Object.keys(local);
2345
2730
  const other = remote;
2346
2731
  if (paths.length !== Object.keys(other).length) return false;
2347
- return paths.every((path25) => other[path25]?.hash === local[path25]?.hash);
2732
+ return paths.every((path26) => other[path26]?.hash === local[path26]?.hash);
2348
2733
  }
2349
2734
  function reportUnchanged(out, result) {
2350
2735
  out.data({ unchanged: true, slug: result.slug, version: result.version.n });
@@ -2372,26 +2757,19 @@ function validateSlug(slug) {
2372
2757
  return slug;
2373
2758
  }
2374
2759
 
2375
- // src/warn.ts
2376
- import process5 from "node:process";
2377
- function warn2(message) {
2378
- process5.stderr.write(`rshow: warning: ${message}
2379
- `);
2380
- }
2381
-
2382
2760
  // src/commands/publish.ts
2383
2761
  async function directoryOf(input) {
2384
- const dir = path7.resolve(input);
2385
- const info = await stat4(dir).catch(() => void 0);
2762
+ const dir = path8.resolve(input);
2763
+ const info = await stat5(dir).catch(() => void 0);
2386
2764
  return info !== void 0 && info.isDirectory() ? dir : void 0;
2387
2765
  }
2388
2766
  async function targetOf(input, dir, built, out) {
2389
- if (dir === void 0) return path7.resolve(input);
2390
- if (built) return path7.resolve(dir, out);
2767
+ if (dir === void 0) return path8.resolve(input);
2768
+ if (built) return path8.resolve(dir, out);
2391
2769
  const local = await readConfigFile(localConfigPath(dir));
2392
- return local.out === void 0 ? dir : path7.resolve(dir, out);
2770
+ return local.out === void 0 ? dir : path8.resolve(dir, out);
2393
2771
  }
2394
- async function run2(argv, ctx) {
2772
+ async function run(argv, ctx) {
2395
2773
  const args = parseCommandArgs(
2396
2774
  argv,
2397
2775
  {
@@ -2418,7 +2796,7 @@ async function run2(argv, ctx) {
2418
2796
  }
2419
2797
  const target = await targetOf(input, dir, build !== void 0, config.out);
2420
2798
  const slug = validateSlug(
2421
- stringValue(args, "slug") ?? config.slug ?? deriveSlug(path7.basename(path7.resolve(input)))
2799
+ stringValue(args, "slug") ?? config.slug ?? deriveSlug(path8.basename(path8.resolve(input)))
2422
2800
  );
2423
2801
  const title = stringValue(args, "title") ?? config.title;
2424
2802
  const label = stringValue(args, "label");
@@ -2429,7 +2807,7 @@ async function run2(argv, ctx) {
2429
2807
  const opts = { slug, title, label, project, tags, force: boolValue(args, "force") };
2430
2808
  if (!boolValue(args, "no-check")) {
2431
2809
  opts.beforeUpload = () => {
2432
- if (config.art === void 0) warn2("no art host configured; skipping theme checks");
2810
+ if (config.art === void 0) warn("no art host configured; skipping theme checks");
2433
2811
  return enforceCheck(ctx.out, target, { art: config.art, offline: config.art === void 0 });
2434
2812
  };
2435
2813
  }
@@ -2453,9 +2831,9 @@ async function run2(argv, ctx) {
2453
2831
  }
2454
2832
 
2455
2833
  // src/commands/check.ts
2456
- import { stat as stat5 } from "node:fs/promises";
2457
- import path8 from "node:path";
2458
- async function run3(argv, ctx) {
2834
+ import { stat as stat6 } from "node:fs/promises";
2835
+ import path9 from "node:path";
2836
+ async function run2(argv, ctx) {
2459
2837
  const args = parseCommandArgs(
2460
2838
  argv,
2461
2839
  { offline: { type: "boolean" }, "no-style": { type: "boolean" } },
@@ -2464,13 +2842,13 @@ async function run3(argv, ctx) {
2464
2842
  CHECK_USAGE
2465
2843
  );
2466
2844
  const input = args.positionals[0];
2467
- const dir = path8.resolve(input);
2468
- const info = await stat5(dir).catch(() => void 0);
2845
+ const dir = path9.resolve(input);
2846
+ const info = await stat6(dir).catch(() => void 0);
2469
2847
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
2470
2848
  const config = await ctx.config(dir);
2471
2849
  const offline = boolValue(args, "offline");
2472
2850
  const art = config.art;
2473
- if (art === void 0 && !offline) warn2("no art host configured; skipping theme checks");
2851
+ if (art === void 0 && !offline) warn("no art host configured; skipping theme checks");
2474
2852
  const result = await checkFolder(dir, {
2475
2853
  art,
2476
2854
  offline: offline || art === void 0,
@@ -2488,21 +2866,21 @@ async function run3(argv, ctx) {
2488
2866
  }
2489
2867
 
2490
2868
  // src/commands/preview.ts
2491
- import { stat as stat8 } from "node:fs/promises";
2492
- import path11 from "node:path";
2869
+ import { stat as stat9 } from "node:fs/promises";
2870
+ import path12 from "node:path";
2493
2871
 
2494
2872
  // src/serve.ts
2495
2873
  import { watch } from "node:fs";
2496
- import { mkdtemp as mkdtemp2, readFile as readFile8, rm as rm3, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
2874
+ import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm3, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
2497
2875
  import { createHash as createHash2 } from "node:crypto";
2498
2876
  import { createServer } from "node:http";
2499
2877
  import { tmpdir } from "node:os";
2500
- import path10 from "node:path";
2501
- import process6 from "node:process";
2878
+ import path11 from "node:path";
2879
+ import process11 from "node:process";
2502
2880
 
2503
2881
  // src/themes.ts
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";
2882
+ import { cp, mkdir as mkdir3, readFile as readFile9, rm as rm2, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
2883
+ import path10 from "node:path";
2506
2884
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2507
2885
 
2508
2886
  // src/color.ts
@@ -2837,7 +3215,7 @@ function flattenDtcg(tree) {
2837
3215
  function sourceDir() {
2838
3216
  try {
2839
3217
  const url = import.meta.url;
2840
- return typeof url === "string" && url !== "" ? path9.dirname(fileURLToPath2(url)) : void 0;
3218
+ return typeof url === "string" && url !== "" ? path10.dirname(fileURLToPath2(url)) : void 0;
2841
3219
  } catch {
2842
3220
  return void 0;
2843
3221
  }
@@ -2845,7 +3223,7 @@ function sourceDir() {
2845
3223
  var HERE = sourceDir();
2846
3224
  function nearSource(...segments) {
2847
3225
  if (HERE === void 0) return [];
2848
- return [path9.resolve(HERE, "..", "..", "..", ...segments), path9.resolve(HERE, "..", ...segments)];
3226
+ return [path10.resolve(HERE, "..", "..", "..", ...segments), path10.resolve(HERE, "..", ...segments)];
2849
3227
  }
2850
3228
  var BUILTIN_BASES = nearSource("themes");
2851
3229
  var BUILTIN_SKILL_BASES = nearSource("skills");
@@ -2863,7 +3241,7 @@ var FONT_FORMATS = {
2863
3241
  ".ttf": "truetype",
2864
3242
  ".otf": "opentype"
2865
3243
  };
2866
- function isRecord2(value) {
3244
+ function isRecord3(value) {
2867
3245
  return value !== null && typeof value === "object" && !Array.isArray(value);
2868
3246
  }
2869
3247
  function stringField(value) {
@@ -2873,16 +3251,16 @@ function messageOf6(err) {
2873
3251
  return err instanceof Error ? err.message : String(err);
2874
3252
  }
2875
3253
  async function isFile(file) {
2876
- const info = await stat6(file).catch(() => void 0);
3254
+ const info = await stat7(file).catch(() => void 0);
2877
3255
  return info !== void 0 && info.isFile();
2878
3256
  }
2879
3257
  async function isDir(dir) {
2880
- const info = await stat6(dir).catch(() => void 0);
3258
+ const info = await stat7(dir).catch(() => void 0);
2881
3259
  return info !== void 0 && info.isDirectory();
2882
3260
  }
2883
3261
  function tokenMap(value) {
2884
3262
  const out = {};
2885
- if (!isRecord2(value)) return out;
3263
+ if (!isRecord3(value)) return out;
2886
3264
  for (const [name, raw] of Object.entries(value)) {
2887
3265
  if (typeof raw === "string" && raw.trim() !== "") out[name] = raw.trim();
2888
3266
  }
@@ -2892,7 +3270,7 @@ function fontList(value) {
2892
3270
  if (!Array.isArray(value)) return [];
2893
3271
  const out = [];
2894
3272
  for (const item of value) {
2895
- if (!isRecord2(item)) continue;
3273
+ if (!isRecord3(item)) continue;
2896
3274
  const family = stringField(item.family);
2897
3275
  const file = stringField(item.file);
2898
3276
  if (family === void 0 || file === void 0) continue;
@@ -2903,8 +3281,8 @@ function fontList(value) {
2903
3281
  return out;
2904
3282
  }
2905
3283
  function themeValues(json) {
2906
- const doc = isRecord2(json) ? json : {};
2907
- const modes = isRecord2(doc.modeTokens) ? doc.modeTokens : {};
3284
+ const doc = isRecord3(json) ? json : {};
3285
+ const modes = isRecord3(doc.modeTokens) ? doc.modeTokens : {};
2908
3286
  return {
2909
3287
  tokens: tokenMap(doc.tokens),
2910
3288
  light: tokenMap(modes.light),
@@ -2912,10 +3290,10 @@ function themeValues(json) {
2912
3290
  };
2913
3291
  }
2914
3292
  async function readThemeJson(dir) {
2915
- const file = path9.join(dir, "theme.json");
3293
+ const file = path10.join(dir, "theme.json");
2916
3294
  let text;
2917
3295
  try {
2918
- text = await readFile7(file, "utf8");
3296
+ text = await readFile9(file, "utf8");
2919
3297
  } catch (err) {
2920
3298
  if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
2921
3299
  throw new UsageError(`cannot read ${file}: ${messageOf6(err)}`);
@@ -2926,13 +3304,13 @@ async function readThemeJson(dir) {
2926
3304
  } catch {
2927
3305
  throw new UsageError(`invalid JSON in ${file}`);
2928
3306
  }
2929
- if (!isRecord2(parsed)) throw new UsageError(`${file} must hold a JSON object`);
3307
+ if (!isRecord3(parsed)) throw new UsageError(`${file} must hold a JSON object`);
2930
3308
  return { dir, text, json: parsed };
2931
3309
  }
2932
3310
  async function builtinThemeDir(name, bases = BUILTIN_BASES) {
2933
3311
  if (!THEME_NAME_PATTERN.test(name)) return void 0;
2934
3312
  for (const base of [...await binaryThemeBases(), ...bases]) {
2935
- const dir = path9.join(base, name);
3313
+ const dir = path10.join(base, name);
2936
3314
  if (await isDir(dir)) return dir;
2937
3315
  }
2938
3316
  return void 0;
@@ -2968,8 +3346,8 @@ function stripPrint(css) {
2968
3346
  return out;
2969
3347
  }
2970
3348
  async function themeTokenSets(dir) {
2971
- const file = path9.join(dir, "tokens.css");
2972
- const text = await readFile7(file, "utf8").catch(() => void 0);
3349
+ const file = path10.join(dir, "tokens.css");
3350
+ const text = await readFile9(file, "utf8").catch(() => void 0);
2973
3351
  return extractTokens(text === void 0 ? [] : [stripPrint(text)]);
2974
3352
  }
2975
3353
  function declMap(list3) {
@@ -3022,7 +3400,7 @@ function presetFontCss(fonts) {
3022
3400
  lines.push(` font-weight: ${font.weight ?? "400"};`);
3023
3401
  lines.push(" font-display: swap;");
3024
3402
  const url = fontUrl(font.file);
3025
- const format = FONT_FORMATS[path9.extname(url).toLowerCase()];
3403
+ const format = FONT_FORMATS[path10.extname(url).toLowerCase()];
3026
3404
  const source = format === void 0 ? `url("${url}")` : `url("${url}") format("${format}")`;
3027
3405
  lines.push(` src: ${source};`);
3028
3406
  return `@font-face {
@@ -3047,15 +3425,15 @@ function mergeTheme(base, preset, baseName) {
3047
3425
  }
3048
3426
  async function appendFile(file, addition) {
3049
3427
  if (addition === "") return;
3050
- const before = await readFile7(file, "utf8").catch(() => "");
3428
+ const before = await readFile9(file, "utf8").catch(() => "");
3051
3429
  const glue = before === "" || before.endsWith("\n") ? "" : "\n";
3052
- await writeFile2(file, `${before}${glue}${addition}`);
3430
+ await writeFile3(file, `${before}${glue}${addition}`);
3053
3431
  }
3054
3432
  async function buildPreset(presetDir, outDir) {
3055
3433
  const preset = await readThemeJson(presetDir);
3056
3434
  const baseName = stringField(preset.json.extends);
3057
3435
  await rm2(outDir, { recursive: true, force: true });
3058
- await mkdir2(outDir, { recursive: true });
3436
+ await mkdir3(outDir, { recursive: true });
3059
3437
  if (baseName === void 0) {
3060
3438
  await cp(presetDir, outDir, { recursive: true });
3061
3439
  return;
@@ -3066,15 +3444,15 @@ async function buildPreset(presetDir, outDir) {
3066
3444
  await cp(baseDir, outDir, { recursive: true });
3067
3445
  const values = themeValues(preset.json);
3068
3446
  const name = stringField(preset.json.name) ?? baseName;
3069
- await appendFile(path9.join(outDir, "tokens.css"), presetTokenCss(name, values));
3447
+ await appendFile(path10.join(outDir, "tokens.css"), presetTokenCss(name, values));
3070
3448
  const fonts = fontList(preset.json.fonts);
3071
3449
  if (fonts.length > 0) {
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));
3450
+ const from = path10.join(presetDir, "fonts");
3451
+ if (await isDir(from)) await cp(from, path10.join(outDir, "fonts"), { recursive: true });
3452
+ await appendFile(path10.join(outDir, "fonts.css"), presetFontCss(fonts));
3075
3453
  }
3076
3454
  const merged = mergeTheme(base.json, preset.json, baseName);
3077
- await writeFile2(path9.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
3455
+ await writeFile3(path10.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
3078
3456
  `);
3079
3457
  }
3080
3458
  function keyLine(text, key2, mode) {
@@ -3089,8 +3467,8 @@ function keyLine(text, key2, mode) {
3089
3467
  }
3090
3468
  return 0;
3091
3469
  }
3092
- function finding(path25, line, severity, message) {
3093
- return { path: path25, line: Math.max(1, line), severity, message };
3470
+ function finding(path26, line, severity, message) {
3471
+ return { path: path26, line: Math.max(1, line), severity, message };
3094
3472
  }
3095
3473
  function resolveValue(name, map, fallback) {
3096
3474
  let current = map[name] ?? fallback[name];
@@ -3188,13 +3566,13 @@ async function checkPreset(presetDir) {
3188
3566
  }
3189
3567
  const fonts = fontList(preset.json.fonts);
3190
3568
  for (const font of fonts) {
3191
- const file = path9.join(presetDir, font.file);
3569
+ const file = path10.join(presetDir, font.file);
3192
3570
  if (!await isFile(file)) {
3193
3571
  findings.push(finding("theme.json", keyLine(text, "file"), "error", `missing font file: ${font.file}`));
3194
3572
  }
3195
3573
  }
3196
3574
  if (fonts.length > 0) {
3197
- const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path9.join(presetDir, "fonts", rel))));
3575
+ const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path10.join(presetDir, "fonts", rel))));
3198
3576
  if (!found.some(Boolean)) {
3199
3577
  findings.push(
3200
3578
  finding(
@@ -3292,7 +3670,7 @@ var SCRIPT_TYPE = "text/javascript; charset=utf-8";
3292
3670
  function messageOf7(err) {
3293
3671
  return err instanceof Error ? err.message : String(err);
3294
3672
  }
3295
- function isRecord3(value) {
3673
+ function isRecord4(value) {
3296
3674
  return value !== null && typeof value === "object" && !Array.isArray(value);
3297
3675
  }
3298
3676
  function etagOf(text) {
@@ -3327,25 +3705,26 @@ async function resolveFile(root2, pathname) {
3327
3705
  return void 0;
3328
3706
  }
3329
3707
  if (decoded.includes("\0")) return 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);
3708
+ const target = path11.resolve(root2, decoded.replace(/^\/+/, ""));
3709
+ const rel = path11.relative(root2, target);
3710
+ if (rel.startsWith("..") || path11.isAbsolute(rel)) return void 0;
3711
+ const info = await stat8(target).catch(() => void 0);
3334
3712
  if (info === void 0) return void 0;
3335
3713
  if (info.isFile()) return target;
3336
3714
  if (!info.isDirectory()) return void 0;
3337
- const index = path10.join(target, "index.html");
3338
- const indexInfo = await stat7(index).catch(() => void 0);
3715
+ const index = path11.join(target, "index.html");
3716
+ const indexInfo = await stat8(index).catch(() => void 0);
3339
3717
  if (indexInfo === void 0 || !indexInfo.isFile()) return void 0;
3340
3718
  return index;
3341
3719
  }
3342
3720
  async function proxy(art, pathname, method, res) {
3343
3721
  let reply;
3722
+ await readyNetwork();
3344
3723
  try {
3345
3724
  reply = await fetch(art + pathname, { method: method === "HEAD" ? "HEAD" : "GET" });
3346
3725
  } catch (err) {
3347
3726
  res.writeHead(502, { "content-type": TEXT_TYPE, "x-rshow-source": "proxy" });
3348
- res.end(`proxy error: ${messageOf7(err)}
3727
+ res.end(`proxy error: ${networkText(err, art + pathname)}
3349
3728
  `);
3350
3729
  return;
3351
3730
  }
@@ -3361,10 +3740,10 @@ async function proxy(art, pathname, method, res) {
3361
3740
  async function prepareTheme(dir) {
3362
3741
  const doc = await readThemeJson(dir);
3363
3742
  const declared = typeof doc.json.name === "string" ? doc.json.name.trim() : "";
3364
- const name = declared === "" ? path10.basename(dir) : declared;
3743
+ const name = declared === "" ? path11.basename(dir) : declared;
3365
3744
  const base = typeof doc.json.extends === "string" ? doc.json.extends.trim() : "";
3366
3745
  if (base === "") return { name, source: dir, built: dir, label: "local" };
3367
- const built = await mkdtemp2(path10.join(tmpdir(), "rshow-theme-"));
3746
+ const built = await mkdtemp2(path11.join(tmpdir(), "rshow-theme-"));
3368
3747
  await buildPreset(dir, built);
3369
3748
  return { name, source: dir, built, label: "preset" };
3370
3749
  }
@@ -3435,9 +3814,9 @@ function createDataStore() {
3435
3814
  const values = /* @__PURE__ */ new Map();
3436
3815
  let dir;
3437
3816
  const persist = async () => {
3438
- if (dir === void 0) dir = await mkdtemp2(path10.join(tmpdir(), "rshow-data-"));
3817
+ if (dir === void 0) dir = await mkdtemp2(path11.join(tmpdir(), "rshow-data-"));
3439
3818
  const doc = Object.fromEntries(values);
3440
- await writeFile3(path10.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
3819
+ await writeFile4(path11.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
3441
3820
  `);
3442
3821
  };
3443
3822
  return {
@@ -3483,9 +3862,9 @@ async function readBody(req, limit) {
3483
3862
  function valueText(value) {
3484
3863
  return JSON.stringify(value === void 0 ? null : value);
3485
3864
  }
3486
- async function handleData(req, res, store, key2, method) {
3865
+ async function handleData(req, res, store2, key2, method) {
3487
3866
  if (method === "GET" || method === "HEAD") {
3488
- const entry = store.get(key2);
3867
+ const entry = store2.get(key2);
3489
3868
  if (entry === void 0) {
3490
3869
  jsonError(res, "not found", 404);
3491
3870
  return;
@@ -3526,12 +3905,12 @@ async function handleData(req, res, store, key2, method) {
3526
3905
  jsonError(res, "expected a JSON body", 400);
3527
3906
  return;
3528
3907
  }
3529
- if (!isRecord3(parsed) || !("value" in parsed)) {
3908
+ if (!isRecord4(parsed) || !("value" in parsed)) {
3530
3909
  jsonError(res, "expected {value}", 400);
3531
3910
  return;
3532
3911
  }
3533
3912
  const text = valueText(parsed.value);
3534
- const existing = store.get(key2);
3913
+ const existing = store2.get(key2);
3535
3914
  const ifMatch = req.headers["if-match"] ?? null;
3536
3915
  if (ifMatch !== null && ifMatch.trim() !== "") {
3537
3916
  if (existing === void 0 || !etagMatches(ifMatch, etagOf(existing.text))) {
@@ -3539,7 +3918,7 @@ async function handleData(req, res, store, key2, method) {
3539
3918
  return;
3540
3919
  }
3541
3920
  }
3542
- if (existing === void 0 && store.count() >= MAX_DATA_KEYS) {
3921
+ if (existing === void 0 && store2.count() >= MAX_DATA_KEYS) {
3543
3922
  jsonError(res, "too many keys for this artifact", 409);
3544
3923
  return;
3545
3924
  }
@@ -3547,7 +3926,7 @@ async function handleData(req, res, store, key2, method) {
3547
3926
  jsonError(res, "value too large", 413);
3548
3927
  return;
3549
3928
  }
3550
- const written = await store.write(key2, text);
3929
+ const written = await store2.write(key2, text);
3551
3930
  jsonReply(res, 200, { ok: true, key: key2, updated_at: written.updated_at }, { etag: written.etag });
3552
3931
  }
3553
3932
  function subscribe(res, session) {
@@ -3578,7 +3957,7 @@ function injectReload(html) {
3578
3957
  ${html.slice(at)}`;
3579
3958
  }
3580
3959
  async function sendFile(res, method, session, file, headers = {}) {
3581
- const bytes = await readFile8(file);
3960
+ const bytes = await readFile10(file);
3582
3961
  const type = contentTypeFor(file);
3583
3962
  const all = { "content-type": type, "cache-control": "no-store", ...headers };
3584
3963
  if (method === "HEAD") {
@@ -3603,7 +3982,7 @@ function startWatchers(session, targets) {
3603
3982
  const done = rebuild ? buildPreset(theme.source, theme.built) : Promise.resolve();
3604
3983
  done.then(
3605
3984
  () => broadcast(session),
3606
- (err) => warn2(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
3985
+ (err) => warn(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
3607
3986
  );
3608
3987
  }, DEBOUNCE_MS);
3609
3988
  timer.unref();
@@ -3613,7 +3992,7 @@ function startWatchers(session, targets) {
3613
3992
  watcher.on("error", () => void 0);
3614
3993
  watchers.push(watcher);
3615
3994
  } catch (err) {
3616
- warn2(`cannot watch ${target.dir}: ${messageOf7(err)}`);
3995
+ warn(`cannot watch ${target.dir}: ${messageOf7(err)}`);
3617
3996
  }
3618
3997
  }
3619
3998
  return watchers;
@@ -3656,16 +4035,16 @@ async function handle(req, res, session) {
3656
4035
  }
3657
4036
  function closeOnSignals(preview2) {
3658
4037
  const stop = () => {
3659
- preview2.close().catch(() => void 0).finally(() => process6.exit(0));
4038
+ preview2.close().catch(() => void 0).finally(() => process11.exit(0));
3660
4039
  };
3661
- process6.once("SIGINT", stop);
3662
- process6.once("SIGTERM", stop);
4040
+ process11.once("SIGINT", stop);
4041
+ process11.once("SIGTERM", stop);
3663
4042
  }
3664
4043
  async function startPreview(opts) {
3665
- const dir = path10.resolve(opts.dir);
4044
+ const dir = path11.resolve(opts.dir);
3666
4045
  const art = (opts.art ?? "").replace(/\/+$/, "");
3667
4046
  const host = opts.host ?? "127.0.0.1";
3668
- const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path10.resolve(opts.theme));
4047
+ const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path11.resolve(opts.theme));
3669
4048
  const root2 = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
3670
4049
  const session = {
3671
4050
  root: root2,
@@ -3733,12 +4112,12 @@ function portOf(value) {
3733
4112
  return port;
3734
4113
  }
3735
4114
  async function requireFolder(input, what) {
3736
- const dir = path11.resolve(input);
3737
- const info = await stat8(dir).catch(() => void 0);
4115
+ const dir = path12.resolve(input);
4116
+ const info = await stat9(dir).catch(() => void 0);
3738
4117
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such ${what}: ${input}`);
3739
4118
  return dir;
3740
4119
  }
3741
- async function run4(argv, ctx) {
4120
+ async function run3(argv, ctx) {
3742
4121
  const args = parseCommandArgs(
3743
4122
  argv,
3744
4123
  {
@@ -3757,7 +4136,7 @@ async function run4(argv, ctx) {
3757
4136
  const offline = boolValue(args, "offline");
3758
4137
  const port = portOf(stringValue(args, "port"));
3759
4138
  const config = await ctx.config(dir);
3760
- if (config.art === void 0 && !offline) warn2("no art host configured; skipping the /_themes proxy");
4139
+ if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
3761
4140
  const preview2 = await startPreview({
3762
4141
  dir,
3763
4142
  art: offline ? "" : config.art ?? "",
@@ -3781,7 +4160,14 @@ function themeRefOf(theme) {
3781
4160
  return { name: theme.slice(0, at), version: theme.slice(at + 1) };
3782
4161
  }
3783
4162
  async function fetchVersion(art, name) {
3784
- const reply = await fetch(`${art}/_themes/${name}.json`, { signal: AbortSignal.timeout(THEME_TIMEOUT_MS) });
4163
+ const url = `${art}/_themes/${name}.json`;
4164
+ await readyNetwork();
4165
+ let reply;
4166
+ try {
4167
+ reply = await fetch(url, { signal: AbortSignal.timeout(THEME_TIMEOUT_MS) });
4168
+ } catch (err) {
4169
+ throw new Error(networkDetail(err, url));
4170
+ }
3785
4171
  if (reply.status !== 200) throw new Error(`HTTP ${reply.status}`);
3786
4172
  const body = await reply.json();
3787
4173
  const version = body?.version;
@@ -3800,17 +4186,17 @@ async function resolveTheme2(config) {
3800
4186
  const ref = themeRefOf(theme);
3801
4187
  if (ref.version !== void 0 && ref.version !== "latest") return config;
3802
4188
  if (config.art === void 0) {
3803
- warn2("no art host configured; skipping theme version lookup");
4189
+ warn("no art host configured; skipping theme version lookup");
3804
4190
  return config;
3805
4191
  }
3806
4192
  try {
3807
4193
  return { ...config, theme: await resolvedTheme(config.art, theme) };
3808
4194
  } catch (err) {
3809
- warn2(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
4195
+ warn(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
3810
4196
  return config.theme === void 0 ? config : { ...config, theme: ref.name };
3811
4197
  }
3812
4198
  }
3813
- async function run5(argv, ctx) {
4199
+ async function run4(argv, ctx) {
3814
4200
  const args = parseCommandArgs(argv, { offline: { type: "boolean" } }, 0, 1, CONFIG_USAGE);
3815
4201
  const dir = args.positionals[0];
3816
4202
  const loaded = await ctx.config(dir);
@@ -3820,14 +4206,14 @@ async function run5(argv, ctx) {
3820
4206
  }
3821
4207
 
3822
4208
  // src/commands/init.ts
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";
4209
+ import { mkdir as mkdir4, readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
4210
+ import path13 from "node:path";
4211
+ import process12 from "node:process";
3826
4212
  var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
3827
4213
  function messageOf9(err) {
3828
4214
  return err instanceof Error ? err.message : String(err);
3829
4215
  }
3830
- function isRecord4(value) {
4216
+ function isRecord5(value) {
3831
4217
  return value !== null && typeof value === "object" && !Array.isArray(value);
3832
4218
  }
3833
4219
  function serverUrl(value, flag) {
@@ -3849,16 +4235,16 @@ function serverUrl(value, flag) {
3849
4235
  async function rawConfig(file) {
3850
4236
  let text;
3851
4237
  try {
3852
- text = await readFile9(file, "utf8");
4238
+ text = await readFile11(file, "utf8");
3853
4239
  } catch (err) {
3854
4240
  if (err.code === "ENOENT") return {};
3855
4241
  throw new UsageError(`cannot read ${file}: ${messageOf9(err)}`);
3856
4242
  }
3857
4243
  const parsed = JSON.parse(text);
3858
- if (!isRecord4(parsed)) throw new UsageError(`${file} must hold a JSON object`);
4244
+ if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
3859
4245
  return parsed;
3860
4246
  }
3861
- async function run6(argv, ctx) {
4247
+ async function run5(argv, ctx) {
3862
4248
  const args = parseCommandArgs(
3863
4249
  argv,
3864
4250
  {
@@ -3874,7 +4260,7 @@ async function run6(argv, ctx) {
3874
4260
  const url = serverUrl(stringValue(args, "url"), "url");
3875
4261
  const art = serverUrl(stringValue(args, "art"), "art");
3876
4262
  const theme = (stringValue(args, "theme") ?? "").trim();
3877
- const file = globalConfigPath(process7.env);
4263
+ const file = globalConfigPath(process12.env);
3878
4264
  const current = await readConfigFile(file);
3879
4265
  const same = (have, want) => have === void 0 || have.replace(/\/+$/, "") === want;
3880
4266
  if (!boolValue(args, "force")) {
@@ -3889,8 +4275,8 @@ async function run6(argv, ctx) {
3889
4275
  raw.url = url;
3890
4276
  raw.art = art;
3891
4277
  if (theme !== "") raw.theme = theme;
3892
- await mkdir3(path12.dirname(file), { recursive: true });
3893
- await writeFile4(file, `${JSON.stringify(raw, null, 2)}
4278
+ await mkdir4(path13.dirname(file), { recursive: true });
4279
+ await writeFile5(file, `${JSON.stringify(raw, null, 2)}
3894
4280
  `, "utf8");
3895
4281
  ctx.out.data({ path: file, url, art });
3896
4282
  ctx.out.line(`wrote ${file}`);
@@ -3898,15 +4284,15 @@ async function run6(argv, ctx) {
3898
4284
  }
3899
4285
 
3900
4286
  // src/commands/doctor.ts
3901
- import { readFile as readFile11, stat as stat10 } from "node:fs/promises";
3902
- import path14 from "node:path";
3903
- import process8 from "node:process";
4287
+ import { readFile as readFile13, stat as stat11 } from "node:fs/promises";
4288
+ import path15 from "node:path";
4289
+ import process13 from "node:process";
3904
4290
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3905
4291
 
3906
4292
  // 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";
4293
+ import { cp as cp2, lstat, mkdir as mkdir5, readFile as readFile12, readdir as readdir3, readlink, rm as rm4, stat as stat10, symlink, writeFile as writeFile6 } from "node:fs/promises";
4294
+ import { homedir as homedir4 } from "node:os";
4295
+ import path14 from "node:path";
3910
4296
  var SKILL_TARGETS = ["claude", "pi", "codex"];
3911
4297
  var SKILL_MARKER = ".rshow-skill";
3912
4298
  var SKILL_SOURCE = "rshow";
@@ -3916,26 +4302,26 @@ var TARGET_SEGMENTS = {
3916
4302
  codex: [".codex"]
3917
4303
  };
3918
4304
  async function isDir2(dir) {
3919
- const info = await stat9(dir).catch(() => void 0);
4305
+ const info = await stat10(dir).catch(() => void 0);
3920
4306
  return info !== void 0 && info.isDirectory();
3921
4307
  }
3922
4308
  async function isFile2(file) {
3923
- const info = await stat9(file).catch(() => void 0);
4309
+ const info = await stat10(file).catch(() => void 0);
3924
4310
  return info !== void 0 && info.isFile();
3925
4311
  }
3926
4312
  function isUnder(file, dir) {
3927
- const rel = path13.relative(dir, file);
3928
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${path13.sep}`) && !path13.isAbsolute(rel);
4313
+ const rel = path14.relative(dir, file);
4314
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
3929
4315
  }
3930
4316
  function homeDir(env) {
3931
4317
  const home = (env.HOME ?? "").trim();
3932
- return home !== "" ? home : homedir3();
4318
+ return home !== "" ? home : homedir4();
3933
4319
  }
3934
4320
  function targetParent(home, target) {
3935
- return path13.join(home, ...TARGET_SEGMENTS[target]);
4321
+ return path14.join(home, ...TARGET_SEGMENTS[target]);
3936
4322
  }
3937
4323
  function targetSkillsDir(home, target) {
3938
- return path13.join(targetParent(home, target), "skills");
4324
+ return path14.join(targetParent(home, target), "skills");
3939
4325
  }
3940
4326
  async function detectedTargets(home) {
3941
4327
  const out = [];
@@ -3959,13 +4345,13 @@ function targetHint() {
3959
4345
  return SKILL_TARGETS.join(", ");
3960
4346
  }
3961
4347
  function defaultModeOf(dir) {
3962
- return dir.split(path13.sep).join("/").includes("/_npx/") ? "copy" : "link";
4348
+ return dir.split(path14.sep).join("/").includes("/_npx/") ? "copy" : "link";
3963
4349
  }
3964
4350
  async function skillsSource(env) {
3965
4351
  const override = (env.RSHOW_SKILLS_DIR ?? "").trim();
3966
4352
  let dir;
3967
4353
  if (override !== "") {
3968
- dir = path13.resolve(override);
4354
+ dir = path14.resolve(override);
3969
4355
  if (!await isDir2(dir)) throw new UsageError(`RSHOW_SKILLS_DIR is not a folder: ${dir}`);
3970
4356
  } else {
3971
4357
  dir = await builtinSkillsDir();
@@ -3973,22 +4359,22 @@ async function skillsSource(env) {
3973
4359
  if (dir === void 0) return void 0;
3974
4360
  const skills = [];
3975
4361
  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);
4362
+ if (entry.isDirectory() && await isFile2(path14.join(dir, entry.name, "SKILL.md"))) skills.push(entry.name);
3977
4363
  }
3978
4364
  skills.sort();
3979
4365
  return { dir, mode: await isBinary() ? "copy" : defaultModeOf(dir), skills };
3980
4366
  }
3981
4367
  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("/"));
4368
+ const resolved = path14.resolve(target);
4369
+ if (isUnder(resolved, path14.resolve(source))) return true;
4370
+ return /(^|\/)rshow\/skills\//.test(resolved.split(path14.sep).join("/"));
3985
4371
  }
3986
4372
  async function linkTargetOf(dst) {
3987
4373
  const value = await readlink(dst);
3988
- return path13.isAbsolute(value) ? value : path13.resolve(path13.dirname(dst), value);
4374
+ return path14.isAbsolute(value) ? value : path14.resolve(path14.dirname(dst), value);
3989
4375
  }
3990
4376
  async function readMarker(dir) {
3991
- const text = await readFile10(path13.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
4377
+ const text = await readFile12(path14.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
3992
4378
  if (text === void 0) return void 0;
3993
4379
  let parsed;
3994
4380
  try {
@@ -4007,7 +4393,7 @@ async function inspectSkill(dst, source, skill) {
4007
4393
  if (info.isSymbolicLink()) {
4008
4394
  const link = await linkTargetOf(dst);
4009
4395
  if (!isOurLink(link, source)) return { kind: "foreign" };
4010
- return { kind: "link", path: link, current: path13.resolve(link) === path13.join(path13.resolve(source), skill) };
4396
+ return { kind: "link", path: link, current: path14.resolve(link) === path14.join(path14.resolve(source), skill) };
4011
4397
  }
4012
4398
  if (info.isDirectory()) {
4013
4399
  const marker = await readMarker(dst);
@@ -4045,17 +4431,17 @@ async function writeSkill(dst, src, mode, version) {
4045
4431
  }
4046
4432
  await cp2(src, dst, { recursive: true });
4047
4433
  const marker = { version, source: SKILL_SOURCE };
4048
- await writeFile5(path13.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
4434
+ await writeFile6(path14.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
4049
4435
  `, "utf8");
4050
4436
  }
4051
4437
  async function installSkills(options) {
4052
4438
  const entries = [];
4053
4439
  for (const target of options.targets) {
4054
4440
  const dir = targetSkillsDir(options.home, target);
4055
- if (!options.dryRun) await mkdir4(dir, { recursive: true });
4441
+ if (!options.dryRun) await mkdir5(dir, { recursive: true });
4056
4442
  for (const skill of options.source.skills) {
4057
- const dst = path13.join(dir, skill);
4058
- const src = path13.join(options.source.dir, skill);
4443
+ const dst = path14.join(dir, skill);
4444
+ const src = path14.join(options.source.dir, skill);
4059
4445
  const state = await inspectSkill(dst, options.source.dir, skill);
4060
4446
  const action = actionFor(state, options.version);
4061
4447
  if (!options.dryRun && (action === "installed" || action === "updated")) {
@@ -4072,7 +4458,7 @@ async function uninstallSkills(options) {
4072
4458
  for (const target of options.targets) {
4073
4459
  const dir = targetSkillsDir(options.home, target);
4074
4460
  for (const skill of (await readdir3(dir).catch(() => [])).sort()) {
4075
- const dst = path13.join(dir, skill);
4461
+ const dst = path14.join(dir, skill);
4076
4462
  const state = await inspectSkill(dst, options.source.dir, skill);
4077
4463
  if (state.kind !== "link" && state.kind !== "copy") continue;
4078
4464
  if (!options.dryRun) await rm4(dst, { recursive: true, force: true });
@@ -4086,7 +4472,7 @@ async function skillsStatus(options) {
4086
4472
  for (const target of options.targets) {
4087
4473
  const dir = targetSkillsDir(options.home, target);
4088
4474
  for (const skill of options.source.skills) {
4089
- const dst = path13.join(dir, skill);
4475
+ const dst = path14.join(dir, skill);
4090
4476
  out.push({ target, skill, state: await inspectSkill(dst, options.source.dir, skill), path: dst });
4091
4477
  }
4092
4478
  }
@@ -4099,13 +4485,13 @@ function messageOf10(err) {
4099
4485
  return err instanceof Error ? err.message : String(err);
4100
4486
  }
4101
4487
  async function isFile3(file) {
4102
- const info = await stat10(file).catch(() => void 0);
4488
+ const info = await stat11(file).catch(() => void 0);
4103
4489
  return info !== void 0 && info.isFile();
4104
4490
  }
4105
4491
  function sourceDir2() {
4106
4492
  try {
4107
4493
  const url = import.meta.url;
4108
- return typeof url === "string" && url !== "" ? path14.dirname(fileURLToPath3(url)) : void 0;
4494
+ return typeof url === "string" && url !== "" ? path15.dirname(fileURLToPath3(url)) : void 0;
4109
4495
  } catch {
4110
4496
  return void 0;
4111
4497
  }
@@ -4115,9 +4501,9 @@ async function packageJsonPath() {
4115
4501
  if (start === void 0) return void 0;
4116
4502
  let dir = start;
4117
4503
  for (let i = 0; i < 6; i += 1) {
4118
- const file = path14.join(dir, "package.json");
4504
+ const file = path15.join(dir, "package.json");
4119
4505
  if (await isFile3(file)) return file;
4120
- const up = path14.dirname(dir);
4506
+ const up = path15.dirname(dir);
4121
4507
  if (up === dir) break;
4122
4508
  dir = up;
4123
4509
  }
@@ -4141,7 +4527,7 @@ function minimumOf(range) {
4141
4527
  }
4142
4528
  async function nodeCheck() {
4143
4529
  const name = "node";
4144
- const current = process8.versions.node;
4530
+ const current = process13.versions.node;
4145
4531
  const file = await packageJsonPath();
4146
4532
  if (file === void 0) {
4147
4533
  if (await isBinary()) return { name, status: "ok", detail: `v${current} (inside the binary)` };
@@ -4149,7 +4535,7 @@ async function nodeCheck() {
4149
4535
  }
4150
4536
  let range;
4151
4537
  try {
4152
- const body = JSON.parse(await readFile11(file, "utf8"));
4538
+ const body = JSON.parse(await readFile13(file, "utf8"));
4153
4539
  range = typeof body.engines?.node === "string" ? body.engines.node : "";
4154
4540
  } catch (err) {
4155
4541
  return { name, status: "warn", detail: `v${current} (${messageOf10(err)})` };
@@ -4160,20 +4546,21 @@ async function nodeCheck() {
4160
4546
  return { name, status: status2, detail: `v${current} (needs ${range})` };
4161
4547
  }
4162
4548
  async function tokenOf(url) {
4163
- const env = (process8.env.RSHOW_TOKEN ?? "").trim();
4549
+ const env = (process13.env.RSHOW_TOKEN ?? "").trim();
4164
4550
  if (env !== "") return { token: env, source: "env" };
4165
- if (url === void 0) return { source: "none" };
4551
+ if (url === void 0) return {};
4166
4552
  try {
4167
- const saved = await read(url);
4168
- if (saved !== void 0) return { token: saved, source: "keychain" };
4169
- return { source: "none" };
4553
+ const found = await find(process13.env, url);
4554
+ if (found !== void 0) return { token: found.token, source: found.store.detail };
4555
+ return {};
4170
4556
  } catch (err) {
4171
- return { source: "none", error: messageOf10(err) };
4557
+ return { error: messageOf10(err) };
4172
4558
  }
4173
4559
  }
4174
4560
  async function getJson(url, token) {
4175
4561
  const headers = {};
4176
4562
  if (token !== void 0) headers.authorization = `Bearer ${token}`;
4563
+ await readyNetwork();
4177
4564
  const reply = await fetch(url, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
4178
4565
  if (reply.status !== 200) return { status: reply.status };
4179
4566
  const body = await reply.json().catch(() => void 0);
@@ -4199,7 +4586,7 @@ function tokenCheck(token) {
4199
4586
  const reason = token.error === void 0 ? "" : ` (${token.error})`;
4200
4587
  return { name, status: "fail", detail: `none${reason}: run rshow login, or set RSHOW_TOKEN` };
4201
4588
  }
4202
- return { name, status: "ok", detail: `source: ${token.source}` };
4589
+ return { name, status: "ok", detail: `source: ${token.source ?? "none"}` };
4203
4590
  }
4204
4591
  async function apiCheck(url, token) {
4205
4592
  const name = "api";
@@ -4213,7 +4600,7 @@ async function apiCheck(url, token) {
4213
4600
  const scope = typeof body.scope === "string" ? body.scope : "";
4214
4601
  return { name, status: "ok", detail: `${email} (scope ${scope})` };
4215
4602
  } catch (err) {
4216
- return { name, status: "fail", detail: messageOf10(err) };
4603
+ return { name, status: "fail", detail: networkDetail(err, `${url}/api/me`) };
4217
4604
  }
4218
4605
  }
4219
4606
  async function artCheck(art) {
@@ -4228,7 +4615,7 @@ async function artCheck(art) {
4228
4615
  }
4229
4616
  return { name, status: "ok", detail: `version ${version}` };
4230
4617
  } catch (err) {
4231
- return { name, status: "warn", detail: messageOf10(err) };
4618
+ return { name, status: "warn", detail: networkDetail(err, `${art}/_themes/default.json`) };
4232
4619
  }
4233
4620
  }
4234
4621
  async function themeCheck(config) {
@@ -4247,10 +4634,10 @@ async function themeCheck(config) {
4247
4634
  }
4248
4635
  async function skillsCheck() {
4249
4636
  const name = "skills";
4250
- const home = homeDir(process8.env);
4637
+ const home = homeDir(process13.env);
4251
4638
  const targets = await detectedTargets(home);
4252
4639
  if (targets.length === 0) return { name, status: "ok", detail: "no target detected" };
4253
- const source = await skillsSource(process8.env).catch(() => void 0);
4640
+ const source = await skillsSource(process13.env).catch(() => void 0);
4254
4641
  if (source === void 0) return { name, status: "warn", detail: "no skills folder next to rshow" };
4255
4642
  const list3 = await skillsStatus({ home, source, targets });
4256
4643
  const problems = [];
@@ -4261,11 +4648,11 @@ async function skillsCheck() {
4261
4648
  if (problems.length === 0) return { name, status: "ok", detail: targets.join(", ") };
4262
4649
  return { name, status: "warn", detail: `${problems.join("; ")} (run rshow skills install)` };
4263
4650
  }
4264
- async function run7(argv, ctx) {
4651
+ async function run6(argv, ctx) {
4265
4652
  const args = parseCommandArgs(argv, {}, 0, 1, DOCTOR_USAGE);
4266
4653
  const dir = args.positionals[0];
4267
4654
  const found = [];
4268
- for (const file of [globalConfigPath(process8.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
4655
+ for (const file of [globalConfigPath(process13.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
4269
4656
  if (file !== void 0 && await isFile3(file)) found.push(file);
4270
4657
  }
4271
4658
  let config;
@@ -4293,13 +4680,13 @@ async function run7(argv, ctx) {
4293
4680
  }
4294
4681
 
4295
4682
  // src/commands/theme.ts
4296
- import { mkdir as mkdir5, mkdtemp as mkdtemp3, rm as rm5, stat as stat12, writeFile as writeFile6 } from "node:fs/promises";
4683
+ import { mkdir as mkdir6, mkdtemp as mkdtemp3, rm as rm5, stat as stat13, writeFile as writeFile7 } from "node:fs/promises";
4297
4684
  import { tmpdir as tmpdir2 } from "node:os";
4298
- import path16 from "node:path";
4685
+ import path17 from "node:path";
4299
4686
 
4300
4687
  // src/theme.ts
4301
- import { readFile as readFile12, stat as stat11 } from "node:fs/promises";
4302
- import path15 from "node:path";
4688
+ import { readFile as readFile14, stat as stat12 } from "node:fs/promises";
4689
+ import path16 from "node:path";
4303
4690
  function themeSlug(name) {
4304
4691
  return `theme-${name}`;
4305
4692
  }
@@ -4307,10 +4694,10 @@ function themeUrl(name, n) {
4307
4694
  return `/_themes/${name}@${n}/`;
4308
4695
  }
4309
4696
  async function readThemeMeta(dir) {
4310
- const file = path15.join(dir, "theme.json");
4697
+ const file = path16.join(dir, "theme.json");
4311
4698
  let text;
4312
4699
  try {
4313
- text = await readFile12(file, "utf8");
4700
+ text = await readFile14(file, "utf8");
4314
4701
  } catch (err) {
4315
4702
  if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
4316
4703
  throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -4331,8 +4718,8 @@ async function readThemeMeta(dir) {
4331
4718
  return { name };
4332
4719
  }
4333
4720
  async function requireThemeDir(input) {
4334
- const dir = path15.resolve(input);
4335
- const info = await stat11(dir).catch(() => void 0);
4721
+ const dir = path16.resolve(input);
4722
+ const info = await stat12(dir).catch(() => void 0);
4336
4723
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
4337
4724
  return dir;
4338
4725
  }
@@ -4340,7 +4727,7 @@ async function requireThemeDir(input) {
4340
4727
  // src/commands/theme.ts
4341
4728
  async function presetCheck(dir) {
4342
4729
  const findings = await checkPreset(dir);
4343
- const info = await stat12(path16.join(dir, "theme.json")).catch(() => void 0);
4730
+ const info = await stat13(path17.join(dir, "theme.json")).catch(() => void 0);
4344
4731
  const errors = findings.filter((finding2) => finding2.severity === "error").length;
4345
4732
  return { findings, errors, warnings: findings.length - errors, files: 1, bytes: info?.size ?? 0 };
4346
4733
  }
@@ -4369,14 +4756,14 @@ async function publish(argv, ctx) {
4369
4756
  if (!boolValue(args, "no-check")) {
4370
4757
  if (preset) await enforcePresetCheck(ctx.out, dir);
4371
4758
  else {
4372
- if (config.art === void 0) warn2("no art host configured; skipping theme checks");
4759
+ if (config.art === void 0) warn("no art host configured; skipping theme checks");
4373
4760
  await enforceCheck(ctx.out, dir, { art: config.art, offline: config.art === void 0 });
4374
4761
  }
4375
4762
  }
4376
4763
  let target = dir;
4377
4764
  let temp;
4378
4765
  if (preset) {
4379
- temp = await mkdtemp3(path16.join(tmpdir2(), "rshow-theme-"));
4766
+ temp = await mkdtemp3(path17.join(tmpdir2(), "rshow-theme-"));
4380
4767
  await buildPreset(dir, temp);
4381
4768
  target = temp;
4382
4769
  }
@@ -4464,14 +4851,14 @@ async function create(argv, ctx) {
4464
4851
  const from = stringValue(args, "from") ?? "default";
4465
4852
  const baseDir = await builtinThemeDir(from);
4466
4853
  if (baseDir === void 0) throw new UsageError(`no built-in theme: ${from}`);
4467
- const parent = path16.resolve(stringValue(args, "dir") ?? ".");
4468
- const target = path16.join(parent, name);
4469
- const exists2 = await stat12(target).catch(() => void 0);
4854
+ const parent = path17.resolve(stringValue(args, "dir") ?? ".");
4855
+ const target = path17.join(parent, name);
4856
+ const exists2 = await stat13(target).catch(() => void 0);
4470
4857
  if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
4471
4858
  const values = await baseRoleValues(baseDir, ROLE_TOKENS);
4472
4859
  const doc = { name, extends: from, tokens: {}, modeTokens: values };
4473
- await mkdir5(target, { recursive: true });
4474
- await writeFile6(path16.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
4860
+ await mkdir6(target, { recursive: true });
4861
+ await writeFile7(path17.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
4475
4862
  `);
4476
4863
  ctx.out.data({ path: target, name, extends: from });
4477
4864
  ctx.out.line(target);
@@ -4506,7 +4893,7 @@ async function preview(argv, ctx) {
4506
4893
  const port = portOf(stringValue(args, "port"));
4507
4894
  const offline = boolValue(args, "offline");
4508
4895
  const config = await ctx.config(dir);
4509
- if (config.art === void 0 && !offline) warn2("no art host configured; skipping the /_themes proxy");
4896
+ if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
4510
4897
  const server = await startPreview({
4511
4898
  dir,
4512
4899
  theme: dir,
@@ -4520,7 +4907,7 @@ async function preview(argv, ctx) {
4520
4907
  ctx.out.raw(`${server.url}/index.html`);
4521
4908
  return EXIT.ok;
4522
4909
  }
4523
- async function run8(argv, ctx) {
4910
+ async function run7(argv, ctx) {
4524
4911
  const [sub, ...rest] = argv;
4525
4912
  if (sub === void 0) throw new UsageError(`expected a theme command (try: ${usageLine(THEME_USAGE)})`);
4526
4913
  if (sub === "new") return create(rest, ctx);
@@ -4538,11 +4925,11 @@ async function run8(argv, ctx) {
4538
4925
  }
4539
4926
 
4540
4927
  // src/commands/skills.ts
4541
- import path17 from "node:path";
4542
- import process9 from "node:process";
4928
+ import path18 from "node:path";
4929
+ import process14 from "node:process";
4543
4930
  var CHANGE_ACTIONS = /* @__PURE__ */ new Set(["installed", "updated", "removed"]);
4544
4931
  async function sourceOf() {
4545
- const source = await skillsSource(process9.env);
4932
+ const source = await skillsSource(process14.env);
4546
4933
  if (source === void 0) {
4547
4934
  throw new UsageError(`no skills folder next to rshow (set RSHOW_SKILLS_DIR to one)`);
4548
4935
  }
@@ -4555,7 +4942,7 @@ async function targetsWithDetection(names, home) {
4555
4942
  }
4556
4943
  function noTargetsError(home) {
4557
4944
  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()}`
4945
+ `no target detected (looked for ${path18.join(home, ".claude")}, ${path18.join(home, ".pi/agent")} and ${path18.join(home, ".codex")}); name one of ${targetHint()}`
4559
4946
  );
4560
4947
  }
4561
4948
  function actionLine(entry, dryRun) {
@@ -4581,7 +4968,7 @@ async function install(argv, ctx) {
4581
4968
  const link = boolValue(args, "link");
4582
4969
  const copy = boolValue(args, "copy");
4583
4970
  if (link && copy) throw new UsageError(`--link and --copy cannot be used together (try: ${usageLine(SKILLS_INSTALL_USAGE)})`);
4584
- const home = homeDir(process9.env);
4971
+ const home = homeDir(process14.env);
4585
4972
  const targets = await targetsWithDetection(args.positionals, home);
4586
4973
  if (targets.length === 0) throw noTargetsError(home);
4587
4974
  const source = await sourceOf();
@@ -4595,13 +4982,13 @@ async function install(argv, ctx) {
4595
4982
  else ctx.out.line(actionLine(entry, dryRun));
4596
4983
  }
4597
4984
  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
4985
+ for (const dst of skipped) process14.stderr.write(`skip ${dst}: exists and is not from rshow
4599
4986
  `);
4600
4987
  return skipped.length > 0 ? EXIT.check : EXIT.ok;
4601
4988
  }
4602
4989
  async function uninstall(argv, ctx) {
4603
4990
  const args = parseCommandArgs(argv, { "dry-run": { type: "boolean" } }, 0, 3, SKILLS_UNINSTALL_USAGE);
4604
- const home = homeDir(process9.env);
4991
+ const home = homeDir(process14.env);
4605
4992
  const targets = await targetsWithDetection(args.positionals, home);
4606
4993
  if (targets.length === 0) return EXIT.ok;
4607
4994
  const source = await sourceOf();
@@ -4619,7 +5006,7 @@ function actionOf(item) {
4619
5006
  }
4620
5007
  async function status(argv, ctx) {
4621
5008
  const args = parseCommandArgs(argv, {}, 0, 3, SKILLS_STATUS_USAGE);
4622
- const home = homeDir(process9.env);
5009
+ const home = homeDir(process14.env);
4623
5010
  const targets = await targetsWithDetection(args.positionals, home);
4624
5011
  if (targets.length === 0) return EXIT.ok;
4625
5012
  const source = await sourceOf();
@@ -4634,12 +5021,12 @@ async function status(argv, ctx) {
4634
5021
  const name = `${item.target.padEnd(targetWidth)} ${item.skill.padEnd(skillWidth)}`;
4635
5022
  ctx.out.line(`${name} ${skillStateLabel(item.state)}`);
4636
5023
  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`);
5024
+ warn(`${item.path} is a copy of v${item.state.version}, this rshow is ${VERSION}: run rshow skills install`);
4638
5025
  }
4639
5026
  }
4640
5027
  return EXIT.ok;
4641
5028
  }
4642
- async function run9(argv, ctx) {
5029
+ async function run8(argv, ctx) {
4643
5030
  const [sub, ...rest] = argv;
4644
5031
  switch (sub) {
4645
5032
  case "install":
@@ -4654,7 +5041,7 @@ async function run9(argv, ctx) {
4654
5041
  }
4655
5042
 
4656
5043
  // src/commands/list.ts
4657
- async function run10(argv, ctx) {
5044
+ async function run9(argv, ctx) {
4658
5045
  const args = parseCommandArgs(
4659
5046
  argv,
4660
5047
  { project: { type: "string" }, tag: { type: "string" }, q: { type: "string" } },
@@ -4687,7 +5074,7 @@ async function run10(argv, ctx) {
4687
5074
  }
4688
5075
 
4689
5076
  // src/commands/show.ts
4690
- async function run11(argv, ctx) {
5077
+ async function run10(argv, ctx) {
4691
5078
  const args = parseCommandArgs(argv, {}, 1, 1, SHOW_USAGE);
4692
5079
  const slug = validateSlug(args.positionals[0]);
4693
5080
  const api = await ctx.api();
@@ -4720,7 +5107,7 @@ async function run11(argv, ctx) {
4720
5107
 
4721
5108
  // src/commands/share.ts
4722
5109
  var MODES = /* @__PURE__ */ new Set(["private", "people", "public"]);
4723
- async function run12(argv, ctx) {
5110
+ async function run11(argv, ctx) {
4724
5111
  const args = parseCommandArgs(
4725
5112
  argv,
4726
5113
  {
@@ -4760,7 +5147,7 @@ async function run12(argv, ctx) {
4760
5147
  }
4761
5148
 
4762
5149
  // src/commands/pin.ts
4763
- async function run13(argv, ctx) {
5150
+ async function run12(argv, ctx) {
4764
5151
  const args = parseCommandArgs(argv, {}, 2, 2, PIN_USAGE);
4765
5152
  const slug = validateSlug(args.positionals[0]);
4766
5153
  const target = args.positionals[1];
@@ -4782,7 +5169,7 @@ async function run13(argv, ctx) {
4782
5169
  }
4783
5170
 
4784
5171
  // src/commands/delete.ts
4785
- async function run14(argv, ctx) {
5172
+ async function run13(argv, ctx) {
4786
5173
  const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, DELETE_USAGE);
4787
5174
  const slug = validateSlug(args.positionals[0]);
4788
5175
  if (!boolValue(args, "yes")) throw new UsageError(`delete needs --yes (try: ${usageLine(DELETE_USAGE)})`);
@@ -4896,7 +5283,7 @@ async function share(argv, ctx) {
4896
5283
  for (const member of members) ctx.out.line(`member ${member.email} ${member.role}`);
4897
5284
  for (const email of removals) ctx.out.line(`removed ${email}`);
4898
5285
  }
4899
- async function remove2(argv, ctx) {
5286
+ async function remove4(argv, ctx) {
4900
5287
  const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, PROJECT_DELETE_USAGE);
4901
5288
  const slug = validateSlug(args.positionals[0]);
4902
5289
  if (!boolValue(args, "yes")) {
@@ -4907,7 +5294,7 @@ async function remove2(argv, ctx) {
4907
5294
  ctx.out.data(null);
4908
5295
  ctx.out.line(`deleted: ${slug}`);
4909
5296
  }
4910
- async function run15(argv, ctx) {
5297
+ async function run14(argv, ctx) {
4911
5298
  const [sub, ...rest] = argv;
4912
5299
  if (sub === void 0) {
4913
5300
  throw new UsageError(`expected a project command (try: ${usageLine(PROJECT_USAGE)})`);
@@ -4916,12 +5303,12 @@ async function run15(argv, ctx) {
4916
5303
  if (sub === "list") return list2(rest, ctx);
4917
5304
  if (sub === "show") return show(rest, ctx);
4918
5305
  if (sub === "share") return share(rest, ctx);
4919
- if (sub === "delete") return remove2(rest, ctx);
5306
+ if (sub === "delete") return remove4(rest, ctx);
4920
5307
  throw new UsageError(`unknown project command: ${sub} (try: ${usageLine(PROJECT_USAGE)})`);
4921
5308
  }
4922
5309
 
4923
5310
  // src/commands/tag.ts
4924
- async function run16(argv, ctx) {
5311
+ async function run15(argv, ctx) {
4925
5312
  const args = parseCommandArgs(
4926
5313
  argv,
4927
5314
  { add: { type: "string", multiple: true }, remove: { type: "string", multiple: true } },
@@ -4931,13 +5318,13 @@ async function run16(argv, ctx) {
4931
5318
  );
4932
5319
  const slug = validateSlug(args.positionals[0]);
4933
5320
  const add2 = normalizeTags(stringList(args, "add"));
4934
- const remove3 = normalizeTags(stringList(args, "remove"));
4935
- if (add2.length === 0 && remove3.length === 0) {
5321
+ const remove5 = normalizeTags(stringList(args, "remove"));
5322
+ if (add2.length === 0 && remove5.length === 0) {
4936
5323
  throw new UsageError(`nothing to change (try: ${usageLine(TAG_USAGE)})`);
4937
5324
  }
4938
5325
  const api = await ctx.api();
4939
5326
  const current = await api.artifact(slug);
4940
- const tags = withTags(tagsOf(current?.tags), add2, remove3);
5327
+ const tags = withTags(tagsOf(current?.tags), add2, remove5);
4941
5328
  const artifact = await api.patchArtifact(slug, { tags });
4942
5329
  ctx.out.data({ artifact, tags });
4943
5330
  ctx.out.line(`slug: ${slug}`);
@@ -4945,20 +5332,20 @@ async function run16(argv, ctx) {
4945
5332
  }
4946
5333
 
4947
5334
  // src/commands/handoff.ts
4948
- import process13 from "node:process";
5335
+ import process18 from "node:process";
4949
5336
 
4950
5337
  // src/handoff.ts
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";
5338
+ import { mkdir as mkdir7, stat as stat14, writeFile as writeFile9 } from "node:fs/promises";
5339
+ import path21 from "node:path";
5340
+ import process17 from "node:process";
4954
5341
 
4955
5342
  // src/cdp.ts
4956
5343
  import { spawn as spawn3 } from "node:child_process";
4957
5344
  import { existsSync, rmSync } from "node:fs";
4958
5345
  import { mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
4959
5346
  import { tmpdir as tmpdir3 } from "node:os";
4960
- import path18 from "node:path";
4961
- import process10 from "node:process";
5347
+ import path19 from "node:path";
5348
+ import process15 from "node:process";
4962
5349
  var CHROME_CANDIDATES = [
4963
5350
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
4964
5351
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
@@ -4978,9 +5365,9 @@ var LOAD_TIMEOUT_MS = 2e4;
4978
5365
  var liveBrowsers = /* @__PURE__ */ new Map();
4979
5366
  var signalHandlersInstalled = false;
4980
5367
  function killBrowser(child, signal) {
4981
- if (process10.platform !== "win32" && child.pid !== void 0) {
5368
+ if (process15.platform !== "win32" && child.pid !== void 0) {
4982
5369
  try {
4983
- process10.kill(-child.pid, signal);
5370
+ process15.kill(-child.pid, signal);
4984
5371
  return;
4985
5372
  } catch {
4986
5373
  }
@@ -5005,24 +5392,24 @@ function reapBrowsers() {
5005
5392
  }
5006
5393
  function onInterrupt() {
5007
5394
  reapBrowsers();
5008
- process10.exit(130);
5395
+ process15.exit(130);
5009
5396
  }
5010
5397
  function onTerminate() {
5011
5398
  reapBrowsers();
5012
- process10.exit(143);
5399
+ process15.exit(143);
5013
5400
  }
5014
5401
  function dropSignalHandlers() {
5015
5402
  if (!signalHandlersInstalled) return;
5016
5403
  signalHandlersInstalled = false;
5017
- process10.off("SIGINT", onInterrupt);
5018
- process10.off("SIGTERM", onTerminate);
5404
+ process15.off("SIGINT", onInterrupt);
5405
+ process15.off("SIGTERM", onTerminate);
5019
5406
  }
5020
5407
  function trackBrowser(child, profile) {
5021
5408
  liveBrowsers.set(child, profile);
5022
5409
  if (!signalHandlersInstalled) {
5023
5410
  signalHandlersInstalled = true;
5024
- process10.on("SIGINT", onInterrupt);
5025
- process10.on("SIGTERM", onTerminate);
5411
+ process15.on("SIGINT", onInterrupt);
5412
+ process15.on("SIGTERM", onTerminate);
5026
5413
  }
5027
5414
  }
5028
5415
  function forgetBrowser(child) {
@@ -5033,20 +5420,20 @@ var exitHookInstalled = false;
5033
5420
  function installExitHook() {
5034
5421
  if (exitHookInstalled) return;
5035
5422
  exitHookInstalled = true;
5036
- process10.once("exit", reapBrowsers);
5423
+ process15.once("exit", reapBrowsers);
5037
5424
  }
5038
5425
  function messageOf11(err) {
5039
5426
  return err instanceof Error ? err.message : String(err);
5040
5427
  }
5041
5428
  function whichInPath(name, pathValue) {
5042
- for (const dir of String(pathValue ?? "").split(path18.delimiter)) {
5429
+ for (const dir of String(pathValue ?? "").split(path19.delimiter)) {
5043
5430
  if (dir === "") continue;
5044
- const file = path18.join(dir, name);
5431
+ const file = path19.join(dir, name);
5045
5432
  if (existsSync(file)) return file;
5046
5433
  }
5047
5434
  return void 0;
5048
5435
  }
5049
- function findChrome(env = process10.env) {
5436
+ function findChrome(env = process15.env) {
5050
5437
  const explicit = (env.CHROME ?? "").trim();
5051
5438
  if (explicit !== "") {
5052
5439
  if (existsSync(explicit)) return explicit;
@@ -5217,7 +5604,7 @@ var Chrome = class _Chrome {
5217
5604
  static async launch(opts = {}) {
5218
5605
  const binary = opts.binary ?? findChrome();
5219
5606
  if (binary === void 0) throw new UsageError("no Chrome or Chromium found, set CHROME");
5220
- const profile = await mkdtemp4(path18.join(tmpdir3(), "rshow-chrome-"));
5607
+ const profile = await mkdtemp4(path19.join(tmpdir3(), "rshow-chrome-"));
5221
5608
  const child = spawn3(
5222
5609
  binary,
5223
5610
  [
@@ -5237,7 +5624,7 @@ var Chrome = class _Chrome {
5237
5624
  "--window-size=1440,900",
5238
5625
  "about:blank"
5239
5626
  ],
5240
- { stdio: ["ignore", "pipe", "pipe"], detached: process10.platform !== "win32" }
5627
+ { stdio: ["ignore", "pipe", "pipe"], detached: process15.platform !== "win32" }
5241
5628
  );
5242
5629
  installExitHook();
5243
5630
  trackBrowser(child, profile);
@@ -5369,16 +5756,16 @@ var Page = class _Page {
5369
5756
  };
5370
5757
 
5371
5758
  // src/map.ts
5372
- import { readFile as readFile13, writeFile as writeFile7 } from "node:fs/promises";
5373
- import path19 from "node:path";
5374
- import process11 from "node:process";
5759
+ import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
5760
+ import path20 from "node:path";
5761
+ import process16 from "node:process";
5375
5762
  var MAP_FILE = "rshow.map.json";
5376
- function isRecord5(value) {
5763
+ function isRecord6(value) {
5377
5764
  return value !== null && typeof value === "object" && !Array.isArray(value);
5378
5765
  }
5379
5766
  function textMap(file, key2, value) {
5380
5767
  if (value === void 0 || value === null) return {};
5381
- if (!isRecord5(value)) throw new UsageError(`${file}: ${key2} must be an object`);
5768
+ if (!isRecord6(value)) throw new UsageError(`${file}: ${key2} must be an object`);
5382
5769
  const out = {};
5383
5770
  for (const [name, target] of Object.entries(value)) {
5384
5771
  if (typeof target !== "string") throw new UsageError(`${file}: ${key2}.${name} must be a string`);
@@ -5387,12 +5774,12 @@ function textMap(file, key2, value) {
5387
5774
  return out;
5388
5775
  }
5389
5776
  function mapPath(dir) {
5390
- return path19.resolve(dir ?? process11.cwd(), MAP_FILE);
5777
+ return path20.resolve(dir ?? process16.cwd(), MAP_FILE);
5391
5778
  }
5392
5779
  async function readMapFile(file) {
5393
5780
  let text;
5394
5781
  try {
5395
- text = await readFile13(file, "utf8");
5782
+ text = await readFile15(file, "utf8");
5396
5783
  } catch (err) {
5397
5784
  if (err.code === "ENOENT") return void 0;
5398
5785
  throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -5403,7 +5790,7 @@ async function readMapFile(file) {
5403
5790
  } catch {
5404
5791
  throw new UsageError(`invalid JSON in ${file}`);
5405
5792
  }
5406
- if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
5793
+ if (!isRecord6(parsed)) throw new UsageError(`${file} must hold a JSON object`);
5407
5794
  return {
5408
5795
  components: textMap(file, "components", parsed.components),
5409
5796
  tokens: textMap(file, "tokens", parsed.tokens),
@@ -5411,7 +5798,7 @@ async function readMapFile(file) {
5411
5798
  };
5412
5799
  }
5413
5800
  async function writeMapFile(file, map) {
5414
- await writeFile7(file, `${JSON.stringify(sortMap(map), null, 2)}
5801
+ await writeFile8(file, `${JSON.stringify(sortMap(map), null, 2)}
5415
5802
  `);
5416
5803
  }
5417
5804
  function sortRecord(value) {
@@ -5855,14 +6242,14 @@ function parseTarget(input) {
5855
6242
  if (remote !== null) {
5856
6243
  return { kind: "remote", slug: remote[1], version: Number(remote[2]) };
5857
6244
  }
5858
- const dir = path20.resolve(input);
5859
- return { kind: "local", dir, slug: deriveSlug(path20.basename(dir)) || "handoff" };
6245
+ const dir = path21.resolve(input);
6246
+ return { kind: "local", dir, slug: deriveSlug(path21.basename(dir)) || "handoff" };
5860
6247
  }
5861
6248
  async function requireLocalDir(target) {
5862
6249
  if (target.kind !== "local") throw new UsageError("expected a local folder");
5863
- const info = await stat13(target.dir).catch(() => void 0);
6250
+ const info = await stat14(target.dir).catch(() => void 0);
5864
6251
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${target.dir}`);
5865
- const index = await stat13(path20.join(target.dir, "index.html")).catch(() => void 0);
6252
+ const index = await stat14(path21.join(target.dir, "index.html")).catch(() => void 0);
5866
6253
  if (index === void 0 || !index.isFile()) throw new UsageError(`${target.dir} has no index.html`);
5867
6254
  return target.dir;
5868
6255
  }
@@ -5877,10 +6264,10 @@ function safeName(text) {
5877
6264
  return cleaned === "" ? "screen" : cleaned;
5878
6265
  }
5879
6266
  function displayPath(target) {
5880
- const resolved = path20.resolve(target);
5881
- const relative = path20.relative(process12.cwd(), resolved);
6267
+ const resolved = path21.resolve(target);
6268
+ const relative = path21.relative(process17.cwd(), resolved);
5882
6269
  if (relative === "") return ".";
5883
- if (relative.startsWith("..") || path20.isAbsolute(relative)) return resolved;
6270
+ if (relative.startsWith("..") || path21.isAbsolute(relative)) return resolved;
5884
6271
  return relative;
5885
6272
  }
5886
6273
  function baselineName(screen, state, width, mode) {
@@ -5915,10 +6302,11 @@ async function collectStyleTexts(url, fetchImpl = fetch) {
5915
6302
  }
5916
6303
  async function assertPublic(url, art, fetchImpl = fetch) {
5917
6304
  let reply;
6305
+ await readyNetwork();
5918
6306
  try {
5919
6307
  reply = await fetchImpl(url, { headers: { accept: "text/html" }, redirect: "follow" });
5920
6308
  } catch (err) {
5921
- throw new UsageError(`cannot reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
6309
+ throw new UsageError(`cannot reach ${url}: ${networkDetail(err, url)}`);
5922
6310
  }
5923
6311
  const host = new URL(art).host;
5924
6312
  const wandered = new URL(reply.url === "" ? url : reply.url).host !== host;
@@ -6041,7 +6429,7 @@ async function runHandoff(target, opts) {
6041
6429
  const version = target.kind === "remote" ? target.version : null;
6042
6430
  const slug = target.slug;
6043
6431
  const url = target.kind === "remote" ? `${art}/${slug}/v${version}/` : "";
6044
- const out = path20.resolve(opts.out);
6432
+ const out = path21.resolve(opts.out);
6045
6433
  let closePreview;
6046
6434
  let pageUrl = url;
6047
6435
  if (target.kind === "remote") {
@@ -6056,8 +6444,8 @@ async function runHandoff(target, opts) {
6056
6444
  const styleTexts = await collectStyleTexts(pageUrl, fetchImpl);
6057
6445
  const tokens = extractTokens(styleTexts);
6058
6446
  say(`tokens: ${tokens.light.length} light, ${tokens.dark.length} dark`);
6059
- await mkdir6(path20.join(out, "baseline"), { recursive: true });
6060
- await writeFile8(path20.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
6447
+ await mkdir7(path21.join(out, "baseline"), { recursive: true });
6448
+ await writeFile9(path21.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
6061
6449
  `);
6062
6450
  const screens = [];
6063
6451
  const baselines = [];
@@ -6108,7 +6496,7 @@ async function runHandoff(target, opts) {
6108
6496
  }
6109
6497
  const png = await capture(page, screen);
6110
6498
  const name = baselineName(screen.id, state, width, mode);
6111
- await writeFile8(path20.join(out, "baseline", name), png);
6499
+ await writeFile9(path21.join(out, "baseline", name), png);
6112
6500
  baselines.push({ screen: screen.id, state, width, mode, file: `baseline/${name}` });
6113
6501
  }
6114
6502
  }
@@ -6137,7 +6525,7 @@ async function runHandoff(target, opts) {
6137
6525
  screens,
6138
6526
  baselines
6139
6527
  };
6140
- await writeFile8(path20.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
6528
+ await writeFile9(path21.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
6141
6529
  `);
6142
6530
  const mapFile = MAP_FILE;
6143
6531
  let mapWritten = false;
@@ -6156,7 +6544,7 @@ async function runHandoff(target, opts) {
6156
6544
  createdAt,
6157
6545
  mapFile
6158
6546
  });
6159
- await writeFile8(path20.join(out, "HANDOFF.md"), markdown);
6547
+ await writeFile9(path21.join(out, "HANDOFF.md"), markdown);
6160
6548
  return {
6161
6549
  out,
6162
6550
  url: pageUrl,
@@ -6197,7 +6585,7 @@ function modeList(text) {
6197
6585
  }
6198
6586
  return out;
6199
6587
  }
6200
- async function run17(argv, ctx) {
6588
+ async function run16(argv, ctx) {
6201
6589
  const args = parseCommandArgs(
6202
6590
  argv,
6203
6591
  { out: { type: "string" }, widths: { type: "string" }, modes: { type: "string" } },
@@ -6213,14 +6601,14 @@ async function run17(argv, ctx) {
6213
6601
  requireUrl(config);
6214
6602
  requireArt(config);
6215
6603
  } else if (config.art === void 0) {
6216
- warn2("no art host configured; skipping the /_themes proxy");
6604
+ warn("no art host configured; skipping the /_themes proxy");
6217
6605
  }
6218
6606
  const result = await runHandoff(target, {
6219
6607
  out,
6220
6608
  widths: widthList(stringValue(args, "widths")),
6221
6609
  modes: modeList(stringValue(args, "modes")),
6222
6610
  art: config.art ?? "",
6223
- chrome: process13.env.CHROME,
6611
+ chrome: process18.env.CHROME,
6224
6612
  onProgress: (line) => ctx.out.line(line)
6225
6613
  });
6226
6614
  const display = displayPath(result.out);
@@ -6246,11 +6634,11 @@ async function run17(argv, ctx) {
6246
6634
  }
6247
6635
 
6248
6636
  // src/commands/compare.ts
6249
- import process15 from "node:process";
6637
+ import process20 from "node:process";
6250
6638
 
6251
6639
  // src/compare.ts
6252
- import { copyFile, mkdir as mkdir7, readFile as readFile15, writeFile as writeFile9 } from "node:fs/promises";
6253
- import path22 from "node:path";
6640
+ import { copyFile, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile10 } from "node:fs/promises";
6641
+ import path23 from "node:path";
6254
6642
 
6255
6643
  // src/diff.ts
6256
6644
  var MAX_Y = 35215;
@@ -6412,10 +6800,10 @@ function diffImages(before, after, opts = {}) {
6412
6800
 
6413
6801
  // src/native.ts
6414
6802
  import { spawn as spawn4 } from "node:child_process";
6415
- import { readFile as readFile14, unlink } from "node:fs/promises";
6803
+ import { readFile as readFile16, unlink as unlink2 } from "node:fs/promises";
6416
6804
  import { tmpdir as tmpdir4 } from "node:os";
6417
- import path21 from "node:path";
6418
- import process14 from "node:process";
6805
+ import path22 from "node:path";
6806
+ import process19 from "node:process";
6419
6807
  var FILE_PREFIX = "file:";
6420
6808
  var WINDOW_PREFIX = "window:";
6421
6809
  var TMUX_PREFIX = "tmux:";
@@ -6444,9 +6832,9 @@ function isNative(source) {
6444
6832
  }
6445
6833
  async function captureNative(source, mapDir, deps = {}) {
6446
6834
  if (source.kind === "file") {
6447
- const file = path21.resolve(mapDir, source.file);
6835
+ const file = path22.resolve(mapDir, source.file);
6448
6836
  try {
6449
- return new Uint8Array(await readFile14(file));
6837
+ return new Uint8Array(await readFile16(file));
6450
6838
  } catch {
6451
6839
  throw new UsageError(`no screenshot at ${source.file}, which is relative to rshow.map.json`);
6452
6840
  }
@@ -6494,8 +6882,8 @@ function noWindow(title, seen) {
6494
6882
  return new UsageError(`no window matches ${JSON.stringify(title)} (${list3}); use ${WINDOW_PREFIX} with an owner name or a title, or ${NATIVE_HINT}`);
6495
6883
  }
6496
6884
  var systemRunner = {
6497
- run: (bin2, args) => new Promise((resolve2, reject) => {
6498
- const child = spawn4(bin2, [...args], { stdio: ["ignore", "pipe", "pipe"] });
6885
+ run: (bin3, args) => new Promise((resolve2, reject) => {
6886
+ const child = spawn4(bin3, [...args], { stdio: ["ignore", "pipe", "pipe"] });
6499
6887
  let stdout = "";
6500
6888
  let stderr = "";
6501
6889
  child.stdout?.setEncoding("utf8");
@@ -6506,10 +6894,10 @@ var systemRunner = {
6506
6894
  child.stderr?.on("data", (chunk2) => {
6507
6895
  stderr += chunk2;
6508
6896
  });
6509
- child.on("error", (err) => reject(new UsageError(`cannot run ${bin2}: ${err.message}`)));
6897
+ child.on("error", (err) => reject(new UsageError(`cannot run ${bin3}: ${err.message}`)));
6510
6898
  child.on("close", (code) => {
6511
6899
  if (code !== 0) {
6512
- reject(new UsageError(stderr.trim() === "" ? `${bin2} exited ${code}` : `${bin2}: ${stderr.trim()}`));
6900
+ reject(new UsageError(stderr.trim() === "" ? `${bin3} exited ${code}` : `${bin3}: ${stderr.trim()}`));
6513
6901
  return;
6514
6902
  }
6515
6903
  resolve2(stdout);
@@ -6517,9 +6905,9 @@ var systemRunner = {
6517
6905
  })
6518
6906
  };
6519
6907
  async function captureWindow(title, deps = {}) {
6520
- const platform = deps.platform ?? process14.platform;
6908
+ const platform = deps.platform ?? process19.platform;
6521
6909
  const runner = deps.runner ?? systemRunner;
6522
- const scratch = path21.join(tmpdir4(), `rshow-window-${process14.pid}-${Date.now()}.png`);
6910
+ const scratch = path22.join(tmpdir4(), `rshow-window-${process19.pid}-${Date.now()}.png`);
6523
6911
  const wrap = (err) => err instanceof UsageError ? err : new UsageError(`window: ${title}: ${err instanceof Error ? err.message : String(err)}; ${NATIVE_HINT}`);
6524
6912
  try {
6525
6913
  if (platform === "darwin") {
@@ -6535,11 +6923,11 @@ async function captureWindow(title, deps = {}) {
6535
6923
  } else {
6536
6924
  throw new UsageError(`window: capture is not supported on ${platform}; ${NATIVE_HINT}`);
6537
6925
  }
6538
- return new Uint8Array(await readFile14(scratch));
6926
+ return new Uint8Array(await readFile16(scratch));
6539
6927
  } catch (err) {
6540
6928
  throw wrap(err);
6541
6929
  } finally {
6542
- await unlink(scratch).catch(() => {
6930
+ await unlink2(scratch).catch(() => {
6543
6931
  });
6544
6932
  }
6545
6933
  }
@@ -6576,23 +6964,23 @@ function extractPalette(image, k = 8) {
6576
6964
  const g = image.data[pos + 1];
6577
6965
  const b = image.data[pos + 2];
6578
6966
  const key2 = r >> BIN_SHIFT << BITS * 2 | g >> BIN_SHIFT << BITS | b >> BIN_SHIFT;
6579
- const bin2 = bins.get(key2);
6580
- if (bin2 === void 0) bins.set(key2, { count: 1, r, g, b });
6967
+ const bin3 = bins.get(key2);
6968
+ if (bin3 === void 0) bins.set(key2, { count: 1, r, g, b });
6581
6969
  else {
6582
- bin2.count += 1;
6583
- bin2.r += r;
6584
- bin2.g += g;
6585
- bin2.b += b;
6970
+ bin3.count += 1;
6971
+ bin3.r += r;
6972
+ bin3.g += g;
6973
+ bin3.b += b;
6586
6974
  }
6587
6975
  used += 1;
6588
6976
  }
6589
6977
  }
6590
6978
  if (used === 0) return [];
6591
6979
  const seeds = [...bins.values()].sort((left, right) => right.count - left.count).slice(0, k);
6592
- let centers = seeds.map((bin2) => ({ r: bin2.r / bin2.count, g: bin2.g / bin2.count, b: bin2.b / bin2.count }));
6593
- const points = [...bins.values()].map((bin2) => ({
6594
- color: { r: bin2.r / bin2.count, g: bin2.g / bin2.count, b: bin2.b / bin2.count },
6595
- weight: bin2.count
6980
+ let centers = seeds.map((bin3) => ({ r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count }));
6981
+ const points = [...bins.values()].map((bin3) => ({
6982
+ color: { r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count },
6983
+ weight: bin3.count
6596
6984
  }));
6597
6985
  for (let round2 = 0; round2 < ROUNDS && centers.length > 1; round2 += 1) {
6598
6986
  const sums = centers.map(() => ({ r: 0, g: 0, b: 0, weight: 0 }));
@@ -7307,9 +7695,9 @@ function compareGrid(expected, actual) {
7307
7695
  }
7308
7696
  return { rows, differing, pass: differing.length === 0 };
7309
7697
  }
7310
- function captureTmux(target, bin2 = "tmux") {
7698
+ function captureTmux(target, bin3 = "tmux") {
7311
7699
  return new Promise((resolve2, reject) => {
7312
- const child = spawn5(bin2, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
7700
+ const child = spawn5(bin3, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
7313
7701
  let stdout = "";
7314
7702
  let stderr = "";
7315
7703
  child.stdout?.setEncoding("utf8");
@@ -7321,11 +7709,11 @@ function captureTmux(target, bin2 = "tmux") {
7321
7709
  stderr += chunk2;
7322
7710
  });
7323
7711
  child.on("error", (err) => {
7324
- reject(new ApiError(`cannot run ${bin2}: ${err.message}`));
7712
+ reject(new ApiError(`cannot run ${bin3}: ${err.message}`));
7325
7713
  });
7326
7714
  child.on("close", (code) => {
7327
7715
  if (code !== 0) {
7328
- reject(new ApiError(stderr.trim() === "" ? `${bin2} exited ${code}` : stderr.trim()));
7716
+ reject(new ApiError(stderr.trim() === "" ? `${bin3} exited ${code}` : stderr.trim()));
7329
7717
  return;
7330
7718
  }
7331
7719
  resolve2(stdout);
@@ -7349,9 +7737,9 @@ var EMPTY_MAP = { components: {}, tokens: {}, screens: {} };
7349
7737
  async function readJson(file, what) {
7350
7738
  let text;
7351
7739
  try {
7352
- text = await readFile15(file, "utf8");
7740
+ text = await readFile17(file, "utf8");
7353
7741
  } catch {
7354
- throw new UsageError(`${what} is missing ${path22.basename(file)} (run rshow handoff first)`);
7742
+ throw new UsageError(`${what} is missing ${path23.basename(file)} (run rshow handoff first)`);
7355
7743
  }
7356
7744
  try {
7357
7745
  return JSON.parse(text);
@@ -7360,13 +7748,13 @@ async function readJson(file, what) {
7360
7748
  }
7361
7749
  }
7362
7750
  async function readHandoff(dir) {
7363
- const spec = await readJson(path22.join(dir, "spec.json"), dir);
7751
+ const spec = await readJson(path23.join(dir, "spec.json"), dir);
7364
7752
  if (spec === null || typeof spec !== "object" || !Array.isArray(spec.screens)) {
7365
7753
  throw new UsageError(`${dir}/spec.json does not hold a handoff spec`);
7366
7754
  }
7367
7755
  let tokens = { light: [], dark: [] };
7368
7756
  try {
7369
- tokens = flattenDtcg(JSON.parse(await readFile15(path22.join(dir, "tokens.json"), "utf8")));
7757
+ tokens = flattenDtcg(JSON.parse(await readFile17(path23.join(dir, "tokens.json"), "utf8")));
7370
7758
  } catch {
7371
7759
  tokens = { light: [], dark: [] };
7372
7760
  }
@@ -7389,15 +7777,15 @@ function clipOf(info, screenId) {
7389
7777
  async function runCompare(opts) {
7390
7778
  const say = opts.onProgress ?? (() => {
7391
7779
  });
7392
- const dir = path22.resolve(opts.against);
7780
+ const dir = path23.resolve(opts.against);
7393
7781
  const { spec, tokens } = await readHandoff(dir);
7394
- const mapFile = opts.map === void 0 ? mapPath() : path22.resolve(opts.map);
7782
+ const mapFile = opts.map === void 0 ? mapPath() : path23.resolve(opts.map);
7395
7783
  const map = await readMapFile(mapFile) ?? EMPTY_MAP;
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 });
7784
+ const mapDir = path23.dirname(mapFile);
7785
+ const reportDir = path23.join(dir, "compare-report");
7786
+ const imagesDir = path23.join(reportDir, "images");
7787
+ await mkdir8(imagesDir, { recursive: true });
7788
+ await mkdir8(path23.join(dir, "diff"), { recursive: true });
7401
7789
  const results = [];
7402
7790
  const collected = [];
7403
7791
  let skipped = 0;
@@ -7432,16 +7820,16 @@ async function runCompare(opts) {
7432
7820
  throw new UsageError(`tmux capture failed for ${target}: ${err instanceof Error ? err.message : String(err)}`);
7433
7821
  });
7434
7822
  const text = opts.tuiColors ? captured : stripAnsi(captured);
7435
- const baselineFile = path22.join(dir, "baseline", `${safeName(screen)}.txt`);
7823
+ const baselineFile = path23.join(dir, "baseline", `${safeName(screen)}.txt`);
7436
7824
  let expected;
7437
7825
  try {
7438
- expected = await readFile15(baselineFile, "utf8");
7826
+ expected = await readFile17(baselineFile, "utf8");
7439
7827
  } catch {
7440
7828
  expected = void 0;
7441
7829
  }
7442
7830
  if (expected === void 0) {
7443
- await mkdir7(path22.dirname(baselineFile), { recursive: true });
7444
- await writeFile9(baselineFile, text);
7831
+ await mkdir8(path23.dirname(baselineFile), { recursive: true });
7832
+ await writeFile10(baselineFile, text);
7445
7833
  results.push({
7446
7834
  screen,
7447
7835
  state: "default",
@@ -7486,12 +7874,12 @@ async function runCompare(opts) {
7486
7874
  const raw = (map.screens[screen] ?? "").trim();
7487
7875
  say(`capture: ${raw}`);
7488
7876
  let capture2;
7489
- let failure2;
7877
+ let failure;
7490
7878
  try {
7491
7879
  capture2 = decodePng(await captureNative(group.source, mapDir));
7492
7880
  } catch (err) {
7493
7881
  if (err instanceof UsageError) throw err;
7494
- failure2 = err instanceof Error ? err.message : String(err);
7882
+ failure = err instanceof Error ? err.message : String(err);
7495
7883
  }
7496
7884
  const screenSpec = spec.screens.find((item) => item.id === screen);
7497
7885
  const rubricFile = nativeName(screen);
@@ -7510,13 +7898,13 @@ async function runCompare(opts) {
7510
7898
  rubric: rubricFile,
7511
7899
  mismatches: []
7512
7900
  };
7513
- if (failure2 !== void 0 || capture2 === void 0) {
7514
- results.push({ ...base, note: `capture failed: ${failure2 ?? "no pixels"}` });
7901
+ if (failure !== void 0 || capture2 === void 0) {
7902
+ results.push({ ...base, note: `capture failed: ${failure ?? "no pixels"}` });
7515
7903
  continue;
7516
7904
  }
7517
7905
  let expectedImage;
7518
7906
  try {
7519
- expectedImage = decodePng(new Uint8Array(await readFile15(path22.join(dir, baseline.file))));
7907
+ expectedImage = decodePng(new Uint8Array(await readFile17(path23.join(dir, baseline.file))));
7520
7908
  } catch {
7521
7909
  results.push({ ...base, note: `missing baseline ${baseline.file}` });
7522
7910
  continue;
@@ -7528,10 +7916,10 @@ async function runCompare(opts) {
7528
7916
  threshold: opts.threshold
7529
7917
  });
7530
7918
  const diffPng = encodePng(diff.image);
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`));
7919
+ await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
7920
+ await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
7921
+ await writeFile10(path23.join(imagesDir, `${stem}-capture.png`), encodePng(scaled));
7922
+ await copyFile(path23.join(dir, baseline.file), path23.join(imagesDir, `${stem}-baseline.png`));
7535
7923
  const expected = screenSpec === void 0 ? [] : normativeColors(screenSpec, tokens, baseline.state, baseline.mode);
7536
7924
  const palette = checkPalette(extractPalette(scaled), expected);
7537
7925
  const pixelOk = diff.ratio <= opts.nativeMaxDiffRatio;
@@ -7576,7 +7964,7 @@ async function runCompare(opts) {
7576
7964
  });
7577
7965
  }
7578
7966
  if (screenSpec !== void 0) {
7579
- await writeFile9(path22.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
7967
+ await writeFile10(path23.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
7580
7968
  `);
7581
7969
  }
7582
7970
  }
@@ -7633,12 +8021,12 @@ async function runCompare(opts) {
7633
8021
  }
7634
8022
  const clip = clipOf(info, screenId) ? await screenClip(page, screenId) : null;
7635
8023
  const actual = await page.screenshot(clip ?? void 0);
7636
- const actualFile = path22.join(imagesDir, `${stem}-actual.png`);
7637
- await writeFile9(actualFile, actual);
7638
- const baselineFile = path22.join(dir, baseline.file);
8024
+ const actualFile = path23.join(imagesDir, `${stem}-actual.png`);
8025
+ await writeFile10(actualFile, actual);
8026
+ const baselineFile = path23.join(dir, baseline.file);
7639
8027
  let expected;
7640
8028
  try {
7641
- expected = new Uint8Array(await readFile15(baselineFile));
8029
+ expected = new Uint8Array(await readFile17(baselineFile));
7642
8030
  } catch {
7643
8031
  results.push({
7644
8032
  screen: baseline.screen,
@@ -7653,14 +8041,14 @@ async function runCompare(opts) {
7653
8041
  });
7654
8042
  continue;
7655
8043
  }
7656
- const baselineCopy = path22.join(imagesDir, `${stem}-baseline.png`);
8044
+ const baselineCopy = path23.join(imagesDir, `${stem}-baseline.png`);
7657
8045
  await copyFile(baselineFile, baselineCopy);
7658
8046
  const left = decodePng(expected);
7659
8047
  const right = decodePng(actual);
7660
8048
  const diff = diffImages(left, right, { threshold: opts.threshold });
7661
8049
  const diffPng = encodePng(diff.image);
7662
- await writeFile9(path22.join(imagesDir, `${stem}-diff.png`), diffPng);
7663
- await writeFile9(path22.join(dir, "diff", `${stem}.png`), diffPng);
8050
+ await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
8051
+ await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
7664
8052
  const note = left.width === right.width && left.height === right.height ? void 0 : `size ${left.width}x${left.height} against ${right.width}x${right.height}`;
7665
8053
  results.push({
7666
8054
  screen: baseline.screen,
@@ -7729,8 +8117,8 @@ async function runCompare(opts) {
7729
8117
  const checked = results.filter((item) => item.source !== "").length;
7730
8118
  const review = results.filter((item) => item.status === "review").length;
7731
8119
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
7732
- await writeFile9(
7733
- path22.join(reportDir, "report.json"),
8120
+ await writeFile10(
8121
+ path23.join(reportDir, "report.json"),
7734
8122
  `${JSON.stringify(
7735
8123
  {
7736
8124
  schema: 1,
@@ -7766,8 +8154,8 @@ async function runCompare(opts) {
7766
8154
  )}
7767
8155
  `
7768
8156
  );
7769
- await writeFile9(
7770
- path22.join(reportDir, "index.html"),
8157
+ await writeFile10(
8158
+ path23.join(reportDir, "index.html"),
7771
8159
  reportHtml({
7772
8160
  spec,
7773
8161
  results,
@@ -7888,7 +8276,7 @@ function ratioValue(text, name, fallback) {
7888
8276
  }
7889
8277
  return value;
7890
8278
  }
7891
- async function run18(argv, ctx) {
8279
+ async function run17(argv, ctx) {
7892
8280
  const args = parseCommandArgs(
7893
8281
  argv,
7894
8282
  {
@@ -7918,7 +8306,7 @@ async function run18(argv, ctx) {
7918
8306
  DEFAULT_NATIVE_MAX_DIFF_RATIO
7919
8307
  ),
7920
8308
  tuiColors: boolValue(args, "tui-colors"),
7921
- chrome: process15.env.CHROME,
8309
+ chrome: process20.env.CHROME,
7922
8310
  onProgress: (line) => ctx.out.line(line)
7923
8311
  });
7924
8312
  const failing = result.results.filter((item) => !item.pass);
@@ -7966,8 +8354,8 @@ async function run18(argv, ctx) {
7966
8354
  }
7967
8355
 
7968
8356
  // src/commands/docs.ts
7969
- import { mkdir as mkdir8, readFile as readFile16, rm as rm7, writeFile as writeFile10 } from "node:fs/promises";
7970
- import path23 from "node:path";
8357
+ import { mkdir as mkdir9, readFile as readFile18, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
8358
+ import path24 from "node:path";
7971
8359
  var USAGE = {
7972
8360
  publish: PUBLISH_USAGE,
7973
8361
  check: CHECK_USAGE,
@@ -8246,7 +8634,7 @@ function heading(label) {
8246
8634
  async function themeTokensPage() {
8247
8635
  const dir = await builtinThemeDir("default");
8248
8636
  if (dir === void 0) throw new Error("no built-in theme: default");
8249
- const css = await readFile16(path23.join(dir, "tokens.css"), "utf8");
8637
+ const css = await readFile18(path24.join(dir, "tokens.css"), "utf8");
8250
8638
  const end = css.indexOf("*/");
8251
8639
  const roles = parseRoles(end < 0 ? css : css.slice(0, end));
8252
8640
  const sections = parseSections(rootBlock(css));
@@ -8294,45 +8682,45 @@ function exitCodesPage() {
8294
8682
  `;
8295
8683
  }
8296
8684
  async function generate(out) {
8297
- const dir = path23.join(out, "commands");
8685
+ const dir = path24.join(out, "commands");
8298
8686
  await rm7(dir, { recursive: true, force: true });
8299
- await mkdir8(dir, { recursive: true });
8687
+ await mkdir9(dir, { recursive: true });
8300
8688
  const rows = commandRows();
8301
8689
  const files = [];
8302
8690
  for (const row of rows) {
8303
- const file = path23.join(dir, row.file);
8304
- await writeFile10(file, commandPage(row, rows));
8691
+ const file = path24.join(dir, row.file);
8692
+ await writeFile11(file, commandPage(row, rows));
8305
8693
  files.push(file);
8306
8694
  }
8307
- const index = path23.join(dir, "index.md");
8308
- await writeFile10(index, indexPage(rows));
8695
+ const index = path24.join(dir, "index.md");
8696
+ await writeFile11(index, indexPage(rows));
8309
8697
  files.push(index);
8310
8698
  for (const [name, text] of [
8311
8699
  ["theme-tokens.md", await themeTokensPage()],
8312
8700
  ["exit-codes.md", exitCodesPage()]
8313
8701
  ]) {
8314
- const file = path23.join(out, name);
8315
- await writeFile10(file, text);
8702
+ const file = path24.join(out, name);
8703
+ await writeFile11(file, text);
8316
8704
  files.push(file);
8317
8705
  }
8318
- return { commands: rows.length, files: files.map((file) => path23.relative(out, file)).sort() };
8706
+ return { commands: rows.length, files: files.map((file) => path24.relative(out, file)).sort() };
8319
8707
  }
8320
- async function run19(argv, ctx) {
8708
+ async function run18(argv, ctx) {
8321
8709
  const args = parseCommandArgs(argv, { out: { type: "string" } }, 0, 0, DOCS_USAGE);
8322
8710
  const out = stringValue(args, "out");
8323
8711
  if (out === void 0) throw new UsageError(`docs needs --out <dir> (try: ${usageLine(DOCS_USAGE)})`);
8324
8712
  const result = await generate(out);
8325
8713
  ctx.out.data(result);
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")}`);
8714
+ ctx.out.line(`wrote ${result.commands} command pages and the index under ${path24.join(out, "commands")}`);
8715
+ ctx.out.line(`wrote ${path24.join(out, "theme-tokens.md")}`);
8716
+ ctx.out.line(`wrote ${path24.join(out, "exit-codes.md")}`);
8329
8717
  }
8330
8718
 
8331
8719
  // src/commands/login.ts
8332
- import process16 from "node:process";
8720
+ import process21 from "node:process";
8333
8721
  async function hiddenLine() {
8334
- process16.stderr.write("token: ");
8335
- const stdin = process16.stdin;
8722
+ process21.stderr.write("token: ");
8723
+ const stdin = process21.stdin;
8336
8724
  stdin.setRawMode(true);
8337
8725
  stdin.resume();
8338
8726
  stdin.setEncoding("utf8");
@@ -8342,7 +8730,7 @@ async function hiddenLine() {
8342
8730
  stdin.off("data", onData);
8343
8731
  stdin.setRawMode(false);
8344
8732
  stdin.pause();
8345
- process16.stderr.write("\n");
8733
+ process21.stderr.write("\n");
8346
8734
  if (err === void 0) resolve2(text);
8347
8735
  else reject(err);
8348
8736
  };
@@ -8365,16 +8753,27 @@ async function hiddenLine() {
8365
8753
  }
8366
8754
  async function pipedText() {
8367
8755
  const chunks = [];
8368
- for await (const chunk2 of process16.stdin) chunks.push(chunk2);
8756
+ for await (const chunk2 of process21.stdin) chunks.push(chunk2);
8369
8757
  return Buffer.concat(chunks).toString("utf8").trim();
8370
8758
  }
8371
8759
  async function tokenFromStdin() {
8372
- const token = process16.stdin.isTTY === true ? await hiddenLine() : await pipedText();
8760
+ const token = process21.stdin.isTTY === true ? await hiddenLine() : await pipedText();
8373
8761
  if (token === "") throw new UsageError("expected a token on stdin");
8374
8762
  if (!token.startsWith(TOKEN_PREFIX)) throw new UsageError(`that is not an rshow token: it must start with ${TOKEN_PREFIX}`);
8375
8763
  return token;
8376
8764
  }
8377
- async function run20(argv, ctx) {
8765
+ async function saveIn(store2, url, token) {
8766
+ await store2.save(url, token);
8767
+ if (await store2.read(url) !== token) {
8768
+ throw new ApiError(`cannot save the token in ${store2.description}`);
8769
+ }
8770
+ }
8771
+ function reasonOf(err) {
8772
+ const text = (err instanceof Error ? err.message : String(err)).replace(/^cannot save the token in the keyring: /, "");
8773
+ const lines = text.split("\n").map((part) => part.trim()).filter((part) => part !== "");
8774
+ return lines.at(-1) ?? "";
8775
+ }
8776
+ async function run19(argv, ctx, platform = process21.platform) {
8378
8777
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGIN_USAGE);
8379
8778
  const settings = await ctx.config();
8380
8779
  const given = (stringValue(args, "url") ?? "").trim();
@@ -8383,48 +8782,69 @@ async function run20(argv, ctx) {
8383
8782
  const me = (await new Client(url, token).send("GET", "/api/me")).body;
8384
8783
  const email = typeof me?.email === "string" ? me.email : "";
8385
8784
  const scope = typeof me?.scope === "string" ? me.scope : "";
8386
- await save(url, token);
8785
+ let store2 = await choose(process21.env, platform);
8786
+ if (store2.backend === "keyring" && forced(process21.env) === void 0) {
8787
+ try {
8788
+ await saveIn(store2, url, token);
8789
+ } catch (err) {
8790
+ warn(`the Linux keyring cannot save the token (${reasonOf(err)}); saving it in a file`);
8791
+ await store2.remove(url).catch(() => {
8792
+ });
8793
+ store2 = fileStore(process21.env);
8794
+ await saveIn(store2, url, token);
8795
+ }
8796
+ } else {
8797
+ await saveIn(store2, url, token);
8798
+ }
8387
8799
  ctx.out.data({ email, scope, url });
8388
- ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}`);
8800
+ ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}, saved in ${store2.description}`);
8389
8801
  }
8390
8802
 
8391
8803
  // src/commands/logout.ts
8392
- async function run21(argv, ctx) {
8804
+ import process22 from "node:process";
8805
+ async function run20(argv, ctx, platform = process22.platform) {
8393
8806
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
8394
8807
  const settings = await ctx.config();
8395
8808
  const given = (stringValue(args, "url") ?? "").trim();
8396
8809
  const url = (given === "" ? requireUrl(settings) : given).replace(/\/+$/, "");
8397
- const removed = await remove(url);
8398
- ctx.out.line(removed ? `logged out of ${url}` : `no saved token for ${url}`);
8810
+ const removed = [];
8811
+ for (const store2 of await every(process22.env, platform)) {
8812
+ if (await store2.remove(url)) removed.push(store2.description);
8813
+ }
8814
+ if (removed.length === 0) {
8815
+ ctx.out.line(`no saved token for ${url}`);
8816
+ return;
8817
+ }
8818
+ ctx.out.line(`logged out of ${url}, removed from ${removed.join(" and ")}`);
8399
8819
  }
8400
8820
 
8401
8821
  // src/main.ts
8402
8822
  var COMMANDS = {
8403
- publish: { usage: PUBLISH_USAGE, run: run2 },
8404
- check: { usage: CHECK_USAGE, run: run3 },
8405
- handoff: { usage: HANDOFF_USAGE, run: run17 },
8406
- compare: { usage: COMPARE_USAGE, run: run18 },
8407
- preview: { usage: PREVIEW_USAGE, run: run4 },
8408
- config: { usage: CONFIG_USAGE, run: run5 },
8409
- init: { usage: INIT_USAGE, run: run6 },
8410
- doctor: { usage: DOCTOR_USAGE, run: run7 },
8411
- theme: { usage: THEME_USAGE, run: run8 },
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 },
8823
+ publish: { usage: PUBLISH_USAGE, run },
8824
+ check: { usage: CHECK_USAGE, run: run2 },
8825
+ handoff: { usage: HANDOFF_USAGE, run: run16 },
8826
+ compare: { usage: COMPARE_USAGE, run: run17 },
8827
+ preview: { usage: PREVIEW_USAGE, run: run3 },
8828
+ config: { usage: CONFIG_USAGE, run: run4 },
8829
+ init: { usage: INIT_USAGE, run: run5 },
8830
+ doctor: { usage: DOCTOR_USAGE, run: run6 },
8831
+ theme: { usage: THEME_USAGE, run: run7 },
8832
+ skills: { usage: SKILLS_USAGE, run: run8 },
8833
+ project: { usage: PROJECT_USAGE, run: run14 },
8834
+ tag: { usage: TAG_USAGE, run: run15 },
8835
+ list: { usage: LIST_USAGE, run: run9 },
8836
+ show: { usage: SHOW_USAGE, run: run10 },
8837
+ share: { usage: SHARE_USAGE, run: run11 },
8838
+ pin: { usage: PIN_USAGE, run: run12 },
8839
+ delete: { usage: DELETE_USAGE, run: run13 },
8840
+ login: { usage: LOGIN_USAGE, run: run19 },
8841
+ logout: { usage: LOGOUT_USAGE, run: run20 },
8422
8842
  // Kept out of the general help: the docs site runs it, a person rarely does.
8423
- docs: { usage: DOCS_USAGE, run: run19 }
8843
+ docs: { usage: DOCS_USAGE, run: run18 }
8424
8844
  };
8425
8845
  function fail(err) {
8426
8846
  const message = err instanceof Error ? err.message : String(err);
8427
- process17.stderr.write(`rshow: ${message}
8847
+ process23.stderr.write(`rshow: ${message}
8428
8848
  `);
8429
8849
  if (err instanceof UsageError) return EXIT.usage;
8430
8850
  if (err instanceof CheckError) return EXIT.check;
@@ -8442,17 +8862,17 @@ async function main(argv) {
8442
8862
  const spec = name === void 0 ? void 0 : COMMANDS[name];
8443
8863
  if (wantsVersion) {
8444
8864
  const suffix = await isBinary() ? " (binary)" : "";
8445
- process17.stdout.write(`rshow ${VERSION}${suffix}
8865
+ process23.stdout.write(`rshow ${VERSION}${suffix}
8446
8866
  `);
8447
8867
  return EXIT.ok;
8448
8868
  }
8449
8869
  if (wantsHelp) {
8450
- process17.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
8870
+ process23.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
8451
8871
  `);
8452
8872
  return EXIT.ok;
8453
8873
  }
8454
8874
  if (name === void 0) {
8455
- process17.stderr.write(`${GENERAL_USAGE}
8875
+ process23.stderr.write(`${GENERAL_USAGE}
8456
8876
  `);
8457
8877
  return EXIT.usage;
8458
8878
  }
@@ -8463,12 +8883,12 @@ async function main(argv) {
8463
8883
  const rest = [...args.slice(0, index), ...args.slice(index + 1)];
8464
8884
  const configs = /* @__PURE__ */ new Map();
8465
8885
  const clients = /* @__PURE__ */ new Map();
8466
- const keyOf = (dir) => dir === void 0 ? "" : path24.resolve(dir);
8886
+ const keyOf = (dir) => dir === void 0 ? "" : path25.resolve(dir);
8467
8887
  const configFor = (dir) => {
8468
8888
  const key2 = keyOf(dir);
8469
8889
  let pending = configs.get(key2);
8470
8890
  if (pending === void 0) {
8471
- pending = loadConfig(process17.env, dir);
8891
+ pending = loadConfig(process23.env, dir);
8472
8892
  configs.set(key2, pending);
8473
8893
  }
8474
8894
  return pending;
@@ -8481,7 +8901,7 @@ async function main(argv) {
8481
8901
  if (cached !== void 0) return cached;
8482
8902
  const settings = await configFor(dir);
8483
8903
  const url = requireUrl(settings);
8484
- const client = new Client(url, await readToken(process17.env, url));
8904
+ const client = new Client(url, await readToken(process23.env, url));
8485
8905
  clients.set(key2, client);
8486
8906
  return client;
8487
8907
  },
@@ -8490,11 +8910,11 @@ async function main(argv) {
8490
8910
  const code = await spec.run(rest, ctx);
8491
8911
  return code ?? EXIT.ok;
8492
8912
  }
8493
- main(process17.argv.slice(2)).then(
8913
+ main(process23.argv.slice(2)).then(
8494
8914
  (code) => {
8495
- process17.exitCode = code;
8915
+ process23.exitCode = code;
8496
8916
  },
8497
8917
  (err) => {
8498
- process17.exitCode = fail(err);
8918
+ process23.exitCode = fail(err);
8499
8919
  }
8500
8920
  );