rshow 0.0.2 → 0.0.3

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.3" : 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,306 @@ 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
+ async function every(env = process8.env, platform = process8.platform) {
758
+ const stores = [];
759
+ if (canSave(platform, env.RSHOW_SECURITY_BIN)) stores.push(storeOf("keychain", env));
760
+ if (canUse(env, platform) && await usable(env)) stores.push(storeOf("keyring", env));
761
+ stores.push(storeOf("file", env));
762
+ return stores;
763
+ }
764
+
409
765
  // src/config.ts
410
766
  var DEFAULT_OUT = "dist";
411
767
  var GLOBAL_DIR = "rshow";
412
768
  var GLOBAL_FILE = "config.json";
413
769
  var LOCAL_FILE = "rshow.json";
414
770
  var NO_SERVER = "no server configured: run rshow init --url <admin-url> --art <viewer-url>";
415
- function messageOf2(err) {
771
+ function messageOf3(err) {
416
772
  return err instanceof Error ? err.message : String(err);
417
773
  }
418
- function isRecord(value) {
774
+ function isRecord2(value) {
419
775
  return value !== null && typeof value === "object" && !Array.isArray(value);
420
776
  }
421
777
  function withoutSlash(value) {
@@ -424,14 +780,14 @@ function withoutSlash(value) {
424
780
  function globalConfigPath(env) {
425
781
  const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
426
782
  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);
783
+ const base = xdg !== "" ? xdg : path3.join(home !== "" ? home : homedir3(), ".config");
784
+ return path3.join(base, GLOBAL_DIR, GLOBAL_FILE);
429
785
  }
430
786
  function localConfigPath(dir) {
431
- return path2.join(path2.resolve(dir), LOCAL_FILE);
787
+ return path3.join(path3.resolve(dir), LOCAL_FILE);
432
788
  }
433
789
  function layerOf(file, value) {
434
- if (!isRecord(value)) throw new UsageError(`${file} must hold a JSON object`);
790
+ if (!isRecord2(value)) throw new UsageError(`${file} must hold a JSON object`);
435
791
  const fieldOf = (key2) => {
436
792
  const raw = value[key2];
437
793
  if (raw === void 0 || raw === null) return void 0;
@@ -451,7 +807,7 @@ function layerOf(file, value) {
451
807
  };
452
808
  const rawTokens = value.tokens;
453
809
  if (rawTokens !== void 0 && rawTokens !== null) {
454
- if (!isRecord(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
810
+ if (!isRecord2(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
455
811
  const tokens = {};
456
812
  for (const [key2, raw] of Object.entries(rawTokens)) {
457
813
  if (typeof raw !== "string") throw new UsageError(`${file}: tokens.${key2} must be a string`);
@@ -473,10 +829,10 @@ function layerOf(file, value) {
473
829
  async function readConfigFile(file) {
474
830
  let text;
475
831
  try {
476
- text = await readFile(file, "utf8");
832
+ text = await readFile3(file, "utf8");
477
833
  } catch (err) {
478
834
  if (err.code === "ENOENT") return {};
479
- throw new UsageError(`cannot read ${file}: ${messageOf2(err)}`);
835
+ throw new UsageError(`cannot read ${file}: ${messageOf3(err)}`);
480
836
  }
481
837
  let parsed;
482
838
  try {
@@ -538,13 +894,14 @@ function requireArt(config) {
538
894
  async function readToken(env, url) {
539
895
  const token = (env.RSHOW_TOKEN ?? "").trim();
540
896
  if (token !== "") return token;
541
- const saved = await read(url);
897
+ const store2 = await choose(env);
898
+ const saved = await store2.read(url);
542
899
  if (saved !== void 0) return saved;
543
900
  throw new UsageError("no token: run rshow login, or set RSHOW_TOKEN");
544
901
  }
545
902
 
546
903
  // src/output.ts
547
- import process3 from "node:process";
904
+ import process9 from "node:process";
548
905
  var Output = class {
549
906
  json;
550
907
  constructor(json) {
@@ -552,16 +909,16 @@ var Output = class {
552
909
  }
553
910
  data(value) {
554
911
  if (!this.json) return;
555
- process3.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
912
+ process9.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
556
913
  `);
557
914
  }
558
915
  line(text) {
559
916
  if (this.json) return;
560
- process3.stdout.write(`${text}
917
+ process9.stdout.write(`${text}
561
918
  `);
562
919
  }
563
920
  raw(text) {
564
- process3.stdout.write(`${text}
921
+ process9.stdout.write(`${text}
565
922
  `);
566
923
  }
567
924
  };
@@ -591,7 +948,7 @@ commands:
591
948
  share <slug> set mode or change shares
592
949
  pin <slug> <n|live> serve version n, or live for the newest
593
950
  delete <slug> --yes delete an artifact
594
- login [--url u] save the API token in the macOS Keychain
951
+ login [--url u] save the API token for this account
595
952
  logout [--url u] remove the saved token for a server
596
953
 
597
954
  global:
@@ -602,9 +959,13 @@ global:
602
959
  environment:
603
960
  RSHOW_URL API base URL, overrides the config file
604
961
  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)
962
+ RSHOW_TOKEN the API token, else the token rshow login saved in
963
+ the macOS Keychain, the Linux keyring or a 0600 file
964
+ RSHOW_TOKEN_STORE force the keychain, keyring or file token store
965
+ XDG_CONFIG_HOME where rshow keeps config.json and credentials
966
+ RSHOW_SYSTEM_CA set to 0 to trust only the roots node bundles
967
+ RSHOW_EXTRA_CA_FILE a PEM file of extra roots, such as a cluster's CA
968
+ NODE_USE_ENV_PROXY set to 1 so fetch uses https_proxy (Node 24+)
608
969
 
609
970
  Start here: rshow init --url <admin-url> --art <viewer-url>, then rshow login.
610
971
 
@@ -702,8 +1063,8 @@ files win. Run rshow init to write url and art.
702
1063
  A theme with no version, or @latest, is looked up on the art host and printed
703
1064
  as <name>@<n>; when that fails a warning goes to stderr. --offline skips it, and
704
1065
  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.`;
1066
+ The token is RSHOW_TOKEN, else the token rshow login saved in the macOS
1067
+ Keychain, the Linux keyring or a file with mode 600.`;
707
1068
  var INIT_USAGE = `usage: rshow init --url <admin-url> --art <viewer-url> [--theme <name>] [--force]
708
1069
 
709
1070
  Writes the global config file that rshow reads first:
@@ -723,6 +1084,7 @@ package, the config files, the token, the api, the art host, the theme and the
723
1084
  skills, one line each: ok|warn|fail <check> <detail>. With [dir] it reads
724
1085
  <dir>/rshow.json too. A skill line warns and never fails.
725
1086
  Network calls time out after 5 s. Exit 0 when no check failed, 6 when one did.
1087
+ The token line names its source: env, keychain, keyring, or file and its path.
726
1088
  The token is never printed.`;
727
1089
  var THEME_USAGE = `usage: rshow theme <new|check|preview|publish|list> [options]
728
1090
 
@@ -849,14 +1211,19 @@ var DELETE_USAGE = `usage: rshow delete <slug> --yes
849
1211
  Deletes the artifact and needs --yes.`;
850
1212
  var LOGIN_USAGE = `usage: rshow login [--url u]
851
1213
 
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.`;
1214
+ Reads the token from stdin, checks it with the API, then saves it in the macOS
1215
+ Keychain, in the Linux keyring, or in $XDG_CONFIG_HOME/rshow/credentials with
1216
+ mode 600: the first store this machine has, and it reads it back to confirm.
1217
+ On a terminal it asks for the token with echo off, so it is never typed on a
1218
+ command line or pasted anywhere. The API base URL is the account, so each
1219
+ server keeps its own token; --url logs in to another one.
1220
+ Prints the email, the scope and where the token went.
1221
+ RSHOW_TOKEN still wins over the saved token, and RSHOW_TOKEN_STORE forces one
1222
+ of keychain, keyring or file.`;
857
1223
  var LOGOUT_USAGE = `usage: rshow logout [--url u]
858
1224
 
859
- Removes the token this server has in the macOS Keychain.`;
1225
+ Removes the token this server has from every store that holds it: the macOS
1226
+ Keychain, the Linux keyring and the 0600 credentials file.`;
860
1227
  var PROJECT_USAGE = `usage: rshow project <create|list|show|share|delete> [options]
861
1228
 
862
1229
  project create <slug> [--title t] [--description d]
@@ -982,14 +1349,14 @@ function usageLine(usage) {
982
1349
  }
983
1350
 
984
1351
  // src/commands/publish.ts
985
- import { stat as stat4 } from "node:fs/promises";
986
- import path7 from "node:path";
1352
+ import { stat as stat5 } from "node:fs/promises";
1353
+ import path8 from "node:path";
987
1354
 
988
1355
  // src/args.ts
989
1356
  import { parseArgs } from "node:util";
990
1357
  var LIST_FLAGS = /* @__PURE__ */ new Set(["--add", "--editor", "--remove", "--tag"]);
991
1358
  var LOWERCASE_TAG = new RegExp(TAG_PATTERN.source, "i");
992
- function messageOf3(err) {
1359
+ function messageOf4(err) {
993
1360
  return err instanceof Error ? err.message : String(err);
994
1361
  }
995
1362
  function isListValue(token) {
@@ -1025,7 +1392,7 @@ function parseCommandArgs(argv, options, minPositionals, maxPositionals, hint) {
1025
1392
  try {
1026
1393
  parsed = parseArgs({ args: expanded, options, allowPositionals: true, strict: true });
1027
1394
  } catch (err) {
1028
- throw new UsageError(`${messageOf3(err)} (try: ${usageLine(hint)})`);
1395
+ throw new UsageError(`${messageOf4(err)} (try: ${usageLine(hint)})`);
1029
1396
  }
1030
1397
  const count = parsed.positionals.length;
1031
1398
  if (count < minPositionals || count > maxPositionals) {
@@ -1057,21 +1424,21 @@ function emailList(args, name) {
1057
1424
 
1058
1425
  // src/build.ts
1059
1426
  import { spawn as spawn2 } from "node:child_process";
1060
- import process4 from "node:process";
1061
- function messageOf4(err) {
1427
+ import process10 from "node:process";
1428
+ function messageOf5(err) {
1062
1429
  return err instanceof Error ? err.message : String(err);
1063
1430
  }
1064
1431
  function shellPath(env) {
1065
1432
  const shell = (env.SHELL ?? "").trim();
1066
1433
  return shell === "" ? "/bin/sh" : shell;
1067
1434
  }
1068
- async function runBuild(dir, command, env = process4.env) {
1435
+ async function runBuild(dir, command, env = process10.env) {
1069
1436
  const code = await new Promise((resolve2, reject) => {
1070
1437
  const child = spawn2(shellPath(env), ["-c", command], { cwd: dir, stdio: "inherit" });
1071
1438
  child.once("error", reject);
1072
1439
  child.once("close", (value) => resolve2(value));
1073
1440
  }).catch((err) => {
1074
- throw new BuildError(`build failed: ${messageOf4(err)}`);
1441
+ throw new BuildError(`build failed: ${messageOf5(err)}`);
1075
1442
  });
1076
1443
  if (code !== 0) {
1077
1444
  throw new BuildError(`build failed: ${code === null ? "killed by a signal" : `exit ${code}`}`);
@@ -1079,12 +1446,12 @@ async function runBuild(dir, command, env = process4.env) {
1079
1446
  }
1080
1447
 
1081
1448
  // src/check.ts
1082
- import { readFile as readFile4, readdir, stat as stat2 } from "node:fs/promises";
1083
- import path5 from "node:path";
1449
+ import { readFile as readFile6, readdir, stat as stat3 } from "node:fs/promises";
1450
+ import path6 from "node:path";
1084
1451
 
1085
1452
  // src/mockup.ts
1086
- import { readFile as readFile2 } from "node:fs/promises";
1087
- import path3 from "node:path";
1453
+ import { readFile as readFile4 } from "node:fs/promises";
1454
+ import path4 from "node:path";
1088
1455
  var MAX_SCAN_BYTES = 4 * 1024 * 1024;
1089
1456
  var HTML_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm"]);
1090
1457
  var MIN_COLS = 80;
@@ -1154,20 +1521,20 @@ function idOf(value) {
1154
1521
  const id = String(value ?? "").trim().replace(/^#/, "");
1155
1522
  return id === "" ? void 0 : id;
1156
1523
  }
1157
- function warn(scan, line, message) {
1524
+ function warn2(scan, line, message) {
1158
1525
  scan.out.push({ path: scan.path, line, message });
1159
1526
  }
1160
1527
  function keyHint(scan, masked, from, to, line) {
1161
1528
  const hit = TERMINAL_KEYS.exec(masked.slice(from, to).replace(ANY_TAG, ""));
1162
1529
  if (hit === null) return;
1163
- warn(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
1530
+ warn2(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
1164
1531
  }
1165
1532
  function sizeGate(scan, attrs, line) {
1166
1533
  if (attrs.has("data-allow-small")) return;
1167
1534
  const cols = sizeOf(attrs, "data-cols", MIN_COLS);
1168
1535
  const rows = sizeOf(attrs, "data-rows", MIN_ROWS);
1169
1536
  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`);
1537
+ warn2(scan, line, `tui below the 80x24 minimum (${cols}x${rows}), or mark it with data-allow-small`);
1171
1538
  }
1172
1539
  function walk(scan, masked) {
1173
1540
  const stack = [];
@@ -1247,25 +1614,25 @@ function resolve(scan) {
1247
1614
  ids.add(item.id);
1248
1615
  const count = (seen.get(item.id) ?? 0) + 1;
1249
1616
  seen.set(item.id, count);
1250
- if (count === 2) warn(scan, item.line, `duplicate artboard id: ${item.id}`);
1617
+ if (count === 2) warn2(scan, item.line, `duplicate artboard id: ${item.id}`);
1251
1618
  }
1252
1619
  const unknown = /* @__PURE__ */ new Set();
1253
1620
  for (const flow of scan.flows) {
1254
1621
  if (ids.has(flow.id) || unknown.has(flow.id)) continue;
1255
1622
  unknown.add(flow.id);
1256
- warn(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
1623
+ warn2(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
1257
1624
  }
1258
1625
  for (const item of scan.boards) {
1259
- if (!item.title) warn(scan, item.line, `artboard ${item.label} has no data-title`);
1626
+ if (!item.title) warn2(scan, item.line, `artboard ${item.label} has no data-title`);
1260
1627
  for (const block of item.blocks) {
1261
1628
  const missing = block.states.filter((state) => state !== "default" && !item.states.includes(state));
1262
1629
  if (missing.length === 0) continue;
1263
- warn(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
1630
+ warn2(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
1264
1631
  }
1265
1632
  for (const state of item.states) {
1266
1633
  if (state === "default") continue;
1267
1634
  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`);
1635
+ warn2(scan, item.line, `artboard ${item.label} lists state "${state}" that no block uses`);
1269
1636
  }
1270
1637
  }
1271
1638
  }
@@ -1279,9 +1646,9 @@ function htmlWarnings(rel, text) {
1279
1646
  async function mockupWarnings(entries) {
1280
1647
  const out = [];
1281
1648
  for (const entry of entries) {
1282
- const ext = path3.extname(entry.rel).toLowerCase();
1649
+ const ext = path4.extname(entry.rel).toLowerCase();
1283
1650
  if (!HTML_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES) continue;
1284
- const text = await readFile2(entry.abs, "utf8").catch(() => void 0);
1651
+ const text = await readFile4(entry.abs, "utf8").catch(() => void 0);
1285
1652
  if (text === void 0) continue;
1286
1653
  out.push(...htmlWarnings(entry.rel, text));
1287
1654
  }
@@ -1289,8 +1656,8 @@ async function mockupWarnings(entries) {
1289
1656
  }
1290
1657
 
1291
1658
  // src/style.ts
1292
- import { readFile as readFile3 } from "node:fs/promises";
1293
- import path4 from "node:path";
1659
+ import { readFile as readFile5 } from "node:fs/promises";
1660
+ import path5 from "node:path";
1294
1661
  var MAX_SCAN_BYTES2 = 4 * 1024 * 1024;
1295
1662
  var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css"]);
1296
1663
  var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
@@ -1687,9 +2054,9 @@ async function styleWarnings(entries) {
1687
2054
  const loaded = /* @__PURE__ */ new Set();
1688
2055
  const used = [];
1689
2056
  for (const entry of entries) {
1690
- const ext = path4.extname(entry.rel).toLowerCase();
2057
+ const ext = path5.extname(entry.rel).toLowerCase();
1691
2058
  if (!STYLE_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES2) continue;
1692
- const text = await readFile3(entry.abs, "utf8").catch(() => void 0);
2059
+ const text = await readFile5(entry.abs, "utf8").catch(() => void 0);
1693
2060
  if (text === void 0) continue;
1694
2061
  collectLoaded(text, loaded);
1695
2062
  if (stylePathSkipped(entry.rel)) continue;
@@ -1740,9 +2107,6 @@ var CONNECT_PATTERNS = [
1740
2107
  pattern: /\.open\s*\(\s*["'`][A-Za-z]+["'`]\s*,\s*["'`](https?:\/\/[^"'`\s)]+)/g
1741
2108
  }
1742
2109
  ];
1743
- function messageOf5(err) {
1744
- return err instanceof Error ? err.message : String(err);
1745
- }
1746
2110
  function add(scan, file, line, severity, message) {
1747
2111
  scan.findings.push({ path: file, line, severity, message });
1748
2112
  }
@@ -1793,7 +2157,7 @@ function attributes2(raw) {
1793
2157
  }
1794
2158
  return found;
1795
2159
  }
1796
- function hostOf(value) {
2160
+ function hostOf2(value) {
1797
2161
  if (!EXTERNAL.test(value) || IGNORED.test(value)) return void 0;
1798
2162
  try {
1799
2163
  const host = new URL(value, BASE_URL).hostname;
@@ -1817,7 +2181,7 @@ function resolvePath(file, ref) {
1817
2181
  decoded = ref;
1818
2182
  }
1819
2183
  const wantsIndex = decoded.endsWith("/");
1820
- const base = path5.posix.dirname(file);
2184
+ const base = path6.posix.dirname(file);
1821
2185
  const parts2 = base === "." ? [] : base.split("/");
1822
2186
  for (const segment of decoded.split("/")) {
1823
2187
  if (segment === "" || segment === ".") continue;
@@ -1868,7 +2232,7 @@ function resolveRef(scan, file, raw, line) {
1868
2232
  }
1869
2233
  function checkScript(scan, file, line, attrs) {
1870
2234
  const value = (attrs.get("src") ?? "").trim();
1871
- const host = hostOf(value);
2235
+ const host = hostOf2(value);
1872
2236
  if (host === void 0) return;
1873
2237
  if (!SCRIPT_HOSTS.includes(host)) {
1874
2238
  add(scan, file, line, "error", `script host not allowed: ${value} (allowed: ${SCRIPT_HOSTS.join(", ")})`);
@@ -1884,7 +2248,7 @@ function checkLink(scan, file, line, attrs) {
1884
2248
  const rel = (attrs.get("rel") ?? "").toLowerCase().split(/\s+/);
1885
2249
  if (!rel.includes("stylesheet")) return;
1886
2250
  const value = (attrs.get("href") ?? "").trim();
1887
- const host = hostOf(value);
2251
+ const host = hostOf2(value);
1888
2252
  if (host === void 0 || STYLE_HOSTS.includes(host)) return;
1889
2253
  add(scan, file, line, "error", `stylesheet host not allowed: ${value} (allowed: ${STYLE_HOSTS.join(", ")})`);
1890
2254
  }
@@ -1940,9 +2304,9 @@ function scanHtml(scan, file, text, facts) {
1940
2304
  scanConnect(scan, file, masked, at);
1941
2305
  }
1942
2306
  async function scanFile(scan, entry, facts) {
1943
- const ext = path5.extname(entry.rel).toLowerCase();
2307
+ const ext = path6.extname(entry.rel).toLowerCase();
1944
2308
  if (!SCAN_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES3) return;
1945
- const text = await readFile4(entry.abs, "utf8");
2309
+ const text = await readFile6(entry.abs, "utf8");
1946
2310
  if (ext === ".html" || ext === ".htm") {
1947
2311
  scanHtml(scan, entry.rel, text, facts);
1948
2312
  return;
@@ -1957,7 +2321,7 @@ async function collect(scan, dir, prefix, entries) {
1957
2321
  for (const item of items) {
1958
2322
  if (item.name.startsWith(".") || SKIPPED_NAMES.has(item.name)) continue;
1959
2323
  const rel = prefix === "" ? item.name : `${prefix}/${item.name}`;
1960
- const abs = path5.join(dir, item.name);
2324
+ const abs = path6.join(dir, item.name);
1961
2325
  const problem = pathProblem(rel);
1962
2326
  if (problem !== void 0) add(scan, rel, 1, "error", problem);
1963
2327
  if (item.isDirectory()) {
@@ -1965,7 +2329,7 @@ async function collect(scan, dir, prefix, entries) {
1965
2329
  continue;
1966
2330
  }
1967
2331
  if (!item.isFile()) continue;
1968
- const info = await stat2(abs).catch(() => void 0);
2332
+ const info = await stat3(abs).catch(() => void 0);
1969
2333
  if (info === void 0) continue;
1970
2334
  entries.push({ rel, abs, size: info.size });
1971
2335
  }
@@ -1973,11 +2337,12 @@ async function collect(scan, dir, prefix, entries) {
1973
2337
  async function checkThemeFile(scan, key2, rel, where) {
1974
2338
  const url = `${scan.art}/_themes/${key2}/${rel}`;
1975
2339
  let status2;
2340
+ await readyNetwork();
1976
2341
  try {
1977
2342
  const reply = await scan.fetchImpl(url, { method: "HEAD" });
1978
2343
  status2 = reply.status;
1979
2344
  } catch (err) {
1980
- add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${messageOf5(err)}`);
2345
+ add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${networkDetail(err, url)}`);
1981
2346
  return;
1982
2347
  }
1983
2348
  if (status2 === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
@@ -1989,11 +2354,12 @@ async function checkTheme(scan, key2, where) {
1989
2354
  if (scan.offline || scan.art === "") return;
1990
2355
  const url = `${scan.art}/_themes/${key2}/tokens.css`;
1991
2356
  let status2;
2357
+ await readyNetwork();
1992
2358
  try {
1993
2359
  const reply = await scan.fetchImpl(url, { method: "HEAD" });
1994
2360
  status2 = reply.status;
1995
2361
  } catch (err) {
1996
- add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${messageOf5(err)}`);
2362
+ add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${networkDetail(err, url)}`);
1997
2363
  return;
1998
2364
  }
1999
2365
  if (status2 === 404) {
@@ -2011,8 +2377,8 @@ async function checkTheme(scan, key2, where) {
2011
2377
  }
2012
2378
  }
2013
2379
  async function checkFolder(input, opts = {}) {
2014
- const root2 = path5.resolve(input);
2015
- const info = await stat2(root2).catch(() => void 0);
2380
+ const root2 = path6.resolve(input);
2381
+ const info = await stat3(root2).catch(() => void 0);
2016
2382
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
2017
2383
  const scan = {
2018
2384
  files: /* @__PURE__ */ new Set(),
@@ -2104,7 +2470,7 @@ function reportCheck(out, result) {
2104
2470
  out.line(summaryLine(result));
2105
2471
  }
2106
2472
  async function enforceCheck(out, target, opts = {}) {
2107
- const info = await stat2(target).catch(() => void 0);
2473
+ const info = await stat3(target).catch(() => void 0);
2108
2474
  if (info === void 0 || !info.isDirectory()) return;
2109
2475
  const result = await checkFolder(target, opts);
2110
2476
  if (result.findings.length > 0) reportCheck(out, result);
@@ -2112,12 +2478,12 @@ async function enforceCheck(out, target, opts = {}) {
2112
2478
  }
2113
2479
 
2114
2480
  // src/publish.ts
2115
- import { readFile as readFile6 } from "node:fs/promises";
2481
+ import { readFile as readFile8 } from "node:fs/promises";
2116
2482
 
2117
2483
  // src/files.ts
2118
2484
  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";
2485
+ import { readdir as readdir2, readFile as readFile7, stat as stat4 } from "node:fs/promises";
2486
+ import path7 from "node:path";
2121
2487
  var CONTENT_TYPES = {
2122
2488
  html: "text/html; charset=utf-8",
2123
2489
  css: "text/css; charset=utf-8",
@@ -2149,14 +2515,14 @@ var CONTENT_TYPES = {
2149
2515
  var DEFAULT_CONTENT_TYPE = "application/octet-stream";
2150
2516
  var SKIPPED_NAMES2 = /* @__PURE__ */ new Set(["node_modules"]);
2151
2517
  function contentTypeFor(filePath) {
2152
- const ext = path6.extname(filePath).replace(/^\./, "").toLowerCase();
2518
+ const ext = path7.extname(filePath).replace(/^\./, "").toLowerCase();
2153
2519
  return CONTENT_TYPES[ext] ?? DEFAULT_CONTENT_TYPE;
2154
2520
  }
2155
2521
  function sha256Hex(bytes) {
2156
2522
  return createHash("sha256").update(bytes).digest("hex");
2157
2523
  }
2158
2524
  async function readEntry(abs, rel) {
2159
- const bytes = await readFile5(abs);
2525
+ const bytes = await readFile7(abs);
2160
2526
  return {
2161
2527
  path: rel,
2162
2528
  file: abs,
@@ -2174,7 +2540,7 @@ async function collectPaths(root2) {
2174
2540
  if (entry.name.startsWith(".")) continue;
2175
2541
  if (SKIPPED_NAMES2.has(entry.name)) continue;
2176
2542
  const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
2177
- const abs = path6.join(dir, entry.name);
2543
+ const abs = path7.join(dir, entry.name);
2178
2544
  if (entry.isDirectory()) {
2179
2545
  await walk2(abs, rel);
2180
2546
  continue;
@@ -2187,8 +2553,8 @@ async function collectPaths(root2) {
2187
2553
  return found;
2188
2554
  }
2189
2555
  async function buildManifest(input, opts = {}) {
2190
- const root2 = path6.resolve(input);
2191
- const info = await stat3(root2).catch(() => void 0);
2556
+ const root2 = path7.resolve(input);
2557
+ const info = await stat4(root2).catch(() => void 0);
2192
2558
  if (info === void 0) throw new UsageError(`no such file or folder: ${input}`);
2193
2559
  const entries = [];
2194
2560
  if (info.isFile()) {
@@ -2198,7 +2564,7 @@ async function buildManifest(input, opts = {}) {
2198
2564
  if (opts.requireIndex !== false && !paths.includes("index.html")) {
2199
2565
  throw new UsageError(`${input} has no index.html`);
2200
2566
  }
2201
- for (const rel of paths) entries.push(await readEntry(path6.join(root2, rel), rel));
2567
+ for (const rel of paths) entries.push(await readEntry(path7.join(root2, rel), rel));
2202
2568
  } else {
2203
2569
  throw new UsageError(`not a file or folder: ${input}`);
2204
2570
  }
@@ -2316,7 +2682,7 @@ async function publishTarget(api, target, opts) {
2316
2682
  let uploaded = 0;
2317
2683
  await runPool(missing, UPLOAD_CONCURRENCY, async (hash) => {
2318
2684
  const entry = byHash.get(hash);
2319
- await api.putBlob(hash, await readFile6(entry.file));
2685
+ await api.putBlob(hash, await readFile8(entry.file));
2320
2686
  uploaded += 1;
2321
2687
  });
2322
2688
  const version = await api.createVersion(opts.slug, { label: opts.label, files: manifest.files });
@@ -2344,7 +2710,7 @@ function sameFiles(local, remote) {
2344
2710
  const paths = Object.keys(local);
2345
2711
  const other = remote;
2346
2712
  if (paths.length !== Object.keys(other).length) return false;
2347
- return paths.every((path25) => other[path25]?.hash === local[path25]?.hash);
2713
+ return paths.every((path26) => other[path26]?.hash === local[path26]?.hash);
2348
2714
  }
2349
2715
  function reportUnchanged(out, result) {
2350
2716
  out.data({ unchanged: true, slug: result.slug, version: result.version.n });
@@ -2372,26 +2738,19 @@ function validateSlug(slug) {
2372
2738
  return slug;
2373
2739
  }
2374
2740
 
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
2741
  // src/commands/publish.ts
2383
2742
  async function directoryOf(input) {
2384
- const dir = path7.resolve(input);
2385
- const info = await stat4(dir).catch(() => void 0);
2743
+ const dir = path8.resolve(input);
2744
+ const info = await stat5(dir).catch(() => void 0);
2386
2745
  return info !== void 0 && info.isDirectory() ? dir : void 0;
2387
2746
  }
2388
2747
  async function targetOf(input, dir, built, out) {
2389
- if (dir === void 0) return path7.resolve(input);
2390
- if (built) return path7.resolve(dir, out);
2748
+ if (dir === void 0) return path8.resolve(input);
2749
+ if (built) return path8.resolve(dir, out);
2391
2750
  const local = await readConfigFile(localConfigPath(dir));
2392
- return local.out === void 0 ? dir : path7.resolve(dir, out);
2751
+ return local.out === void 0 ? dir : path8.resolve(dir, out);
2393
2752
  }
2394
- async function run2(argv, ctx) {
2753
+ async function run(argv, ctx) {
2395
2754
  const args = parseCommandArgs(
2396
2755
  argv,
2397
2756
  {
@@ -2418,7 +2777,7 @@ async function run2(argv, ctx) {
2418
2777
  }
2419
2778
  const target = await targetOf(input, dir, build !== void 0, config.out);
2420
2779
  const slug = validateSlug(
2421
- stringValue(args, "slug") ?? config.slug ?? deriveSlug(path7.basename(path7.resolve(input)))
2780
+ stringValue(args, "slug") ?? config.slug ?? deriveSlug(path8.basename(path8.resolve(input)))
2422
2781
  );
2423
2782
  const title = stringValue(args, "title") ?? config.title;
2424
2783
  const label = stringValue(args, "label");
@@ -2429,7 +2788,7 @@ async function run2(argv, ctx) {
2429
2788
  const opts = { slug, title, label, project, tags, force: boolValue(args, "force") };
2430
2789
  if (!boolValue(args, "no-check")) {
2431
2790
  opts.beforeUpload = () => {
2432
- if (config.art === void 0) warn2("no art host configured; skipping theme checks");
2791
+ if (config.art === void 0) warn("no art host configured; skipping theme checks");
2433
2792
  return enforceCheck(ctx.out, target, { art: config.art, offline: config.art === void 0 });
2434
2793
  };
2435
2794
  }
@@ -2453,9 +2812,9 @@ async function run2(argv, ctx) {
2453
2812
  }
2454
2813
 
2455
2814
  // 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) {
2815
+ import { stat as stat6 } from "node:fs/promises";
2816
+ import path9 from "node:path";
2817
+ async function run2(argv, ctx) {
2459
2818
  const args = parseCommandArgs(
2460
2819
  argv,
2461
2820
  { offline: { type: "boolean" }, "no-style": { type: "boolean" } },
@@ -2464,13 +2823,13 @@ async function run3(argv, ctx) {
2464
2823
  CHECK_USAGE
2465
2824
  );
2466
2825
  const input = args.positionals[0];
2467
- const dir = path8.resolve(input);
2468
- const info = await stat5(dir).catch(() => void 0);
2826
+ const dir = path9.resolve(input);
2827
+ const info = await stat6(dir).catch(() => void 0);
2469
2828
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
2470
2829
  const config = await ctx.config(dir);
2471
2830
  const offline = boolValue(args, "offline");
2472
2831
  const art = config.art;
2473
- if (art === void 0 && !offline) warn2("no art host configured; skipping theme checks");
2832
+ if (art === void 0 && !offline) warn("no art host configured; skipping theme checks");
2474
2833
  const result = await checkFolder(dir, {
2475
2834
  art,
2476
2835
  offline: offline || art === void 0,
@@ -2488,21 +2847,21 @@ async function run3(argv, ctx) {
2488
2847
  }
2489
2848
 
2490
2849
  // src/commands/preview.ts
2491
- import { stat as stat8 } from "node:fs/promises";
2492
- import path11 from "node:path";
2850
+ import { stat as stat9 } from "node:fs/promises";
2851
+ import path12 from "node:path";
2493
2852
 
2494
2853
  // src/serve.ts
2495
2854
  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";
2855
+ import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm3, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
2497
2856
  import { createHash as createHash2 } from "node:crypto";
2498
2857
  import { createServer } from "node:http";
2499
2858
  import { tmpdir } from "node:os";
2500
- import path10 from "node:path";
2501
- import process6 from "node:process";
2859
+ import path11 from "node:path";
2860
+ import process11 from "node:process";
2502
2861
 
2503
2862
  // 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";
2863
+ import { cp, mkdir as mkdir3, readFile as readFile9, rm as rm2, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
2864
+ import path10 from "node:path";
2506
2865
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2507
2866
 
2508
2867
  // src/color.ts
@@ -2837,7 +3196,7 @@ function flattenDtcg(tree) {
2837
3196
  function sourceDir() {
2838
3197
  try {
2839
3198
  const url = import.meta.url;
2840
- return typeof url === "string" && url !== "" ? path9.dirname(fileURLToPath2(url)) : void 0;
3199
+ return typeof url === "string" && url !== "" ? path10.dirname(fileURLToPath2(url)) : void 0;
2841
3200
  } catch {
2842
3201
  return void 0;
2843
3202
  }
@@ -2845,7 +3204,7 @@ function sourceDir() {
2845
3204
  var HERE = sourceDir();
2846
3205
  function nearSource(...segments) {
2847
3206
  if (HERE === void 0) return [];
2848
- return [path9.resolve(HERE, "..", "..", "..", ...segments), path9.resolve(HERE, "..", ...segments)];
3207
+ return [path10.resolve(HERE, "..", "..", "..", ...segments), path10.resolve(HERE, "..", ...segments)];
2849
3208
  }
2850
3209
  var BUILTIN_BASES = nearSource("themes");
2851
3210
  var BUILTIN_SKILL_BASES = nearSource("skills");
@@ -2863,7 +3222,7 @@ var FONT_FORMATS = {
2863
3222
  ".ttf": "truetype",
2864
3223
  ".otf": "opentype"
2865
3224
  };
2866
- function isRecord2(value) {
3225
+ function isRecord3(value) {
2867
3226
  return value !== null && typeof value === "object" && !Array.isArray(value);
2868
3227
  }
2869
3228
  function stringField(value) {
@@ -2873,16 +3232,16 @@ function messageOf6(err) {
2873
3232
  return err instanceof Error ? err.message : String(err);
2874
3233
  }
2875
3234
  async function isFile(file) {
2876
- const info = await stat6(file).catch(() => void 0);
3235
+ const info = await stat7(file).catch(() => void 0);
2877
3236
  return info !== void 0 && info.isFile();
2878
3237
  }
2879
3238
  async function isDir(dir) {
2880
- const info = await stat6(dir).catch(() => void 0);
3239
+ const info = await stat7(dir).catch(() => void 0);
2881
3240
  return info !== void 0 && info.isDirectory();
2882
3241
  }
2883
3242
  function tokenMap(value) {
2884
3243
  const out = {};
2885
- if (!isRecord2(value)) return out;
3244
+ if (!isRecord3(value)) return out;
2886
3245
  for (const [name, raw] of Object.entries(value)) {
2887
3246
  if (typeof raw === "string" && raw.trim() !== "") out[name] = raw.trim();
2888
3247
  }
@@ -2892,7 +3251,7 @@ function fontList(value) {
2892
3251
  if (!Array.isArray(value)) return [];
2893
3252
  const out = [];
2894
3253
  for (const item of value) {
2895
- if (!isRecord2(item)) continue;
3254
+ if (!isRecord3(item)) continue;
2896
3255
  const family = stringField(item.family);
2897
3256
  const file = stringField(item.file);
2898
3257
  if (family === void 0 || file === void 0) continue;
@@ -2903,8 +3262,8 @@ function fontList(value) {
2903
3262
  return out;
2904
3263
  }
2905
3264
  function themeValues(json) {
2906
- const doc = isRecord2(json) ? json : {};
2907
- const modes = isRecord2(doc.modeTokens) ? doc.modeTokens : {};
3265
+ const doc = isRecord3(json) ? json : {};
3266
+ const modes = isRecord3(doc.modeTokens) ? doc.modeTokens : {};
2908
3267
  return {
2909
3268
  tokens: tokenMap(doc.tokens),
2910
3269
  light: tokenMap(modes.light),
@@ -2912,10 +3271,10 @@ function themeValues(json) {
2912
3271
  };
2913
3272
  }
2914
3273
  async function readThemeJson(dir) {
2915
- const file = path9.join(dir, "theme.json");
3274
+ const file = path10.join(dir, "theme.json");
2916
3275
  let text;
2917
3276
  try {
2918
- text = await readFile7(file, "utf8");
3277
+ text = await readFile9(file, "utf8");
2919
3278
  } catch (err) {
2920
3279
  if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
2921
3280
  throw new UsageError(`cannot read ${file}: ${messageOf6(err)}`);
@@ -2926,13 +3285,13 @@ async function readThemeJson(dir) {
2926
3285
  } catch {
2927
3286
  throw new UsageError(`invalid JSON in ${file}`);
2928
3287
  }
2929
- if (!isRecord2(parsed)) throw new UsageError(`${file} must hold a JSON object`);
3288
+ if (!isRecord3(parsed)) throw new UsageError(`${file} must hold a JSON object`);
2930
3289
  return { dir, text, json: parsed };
2931
3290
  }
2932
3291
  async function builtinThemeDir(name, bases = BUILTIN_BASES) {
2933
3292
  if (!THEME_NAME_PATTERN.test(name)) return void 0;
2934
3293
  for (const base of [...await binaryThemeBases(), ...bases]) {
2935
- const dir = path9.join(base, name);
3294
+ const dir = path10.join(base, name);
2936
3295
  if (await isDir(dir)) return dir;
2937
3296
  }
2938
3297
  return void 0;
@@ -2968,8 +3327,8 @@ function stripPrint(css) {
2968
3327
  return out;
2969
3328
  }
2970
3329
  async function themeTokenSets(dir) {
2971
- const file = path9.join(dir, "tokens.css");
2972
- const text = await readFile7(file, "utf8").catch(() => void 0);
3330
+ const file = path10.join(dir, "tokens.css");
3331
+ const text = await readFile9(file, "utf8").catch(() => void 0);
2973
3332
  return extractTokens(text === void 0 ? [] : [stripPrint(text)]);
2974
3333
  }
2975
3334
  function declMap(list3) {
@@ -3022,7 +3381,7 @@ function presetFontCss(fonts) {
3022
3381
  lines.push(` font-weight: ${font.weight ?? "400"};`);
3023
3382
  lines.push(" font-display: swap;");
3024
3383
  const url = fontUrl(font.file);
3025
- const format = FONT_FORMATS[path9.extname(url).toLowerCase()];
3384
+ const format = FONT_FORMATS[path10.extname(url).toLowerCase()];
3026
3385
  const source = format === void 0 ? `url("${url}")` : `url("${url}") format("${format}")`;
3027
3386
  lines.push(` src: ${source};`);
3028
3387
  return `@font-face {
@@ -3047,15 +3406,15 @@ function mergeTheme(base, preset, baseName) {
3047
3406
  }
3048
3407
  async function appendFile(file, addition) {
3049
3408
  if (addition === "") return;
3050
- const before = await readFile7(file, "utf8").catch(() => "");
3409
+ const before = await readFile9(file, "utf8").catch(() => "");
3051
3410
  const glue = before === "" || before.endsWith("\n") ? "" : "\n";
3052
- await writeFile2(file, `${before}${glue}${addition}`);
3411
+ await writeFile3(file, `${before}${glue}${addition}`);
3053
3412
  }
3054
3413
  async function buildPreset(presetDir, outDir) {
3055
3414
  const preset = await readThemeJson(presetDir);
3056
3415
  const baseName = stringField(preset.json.extends);
3057
3416
  await rm2(outDir, { recursive: true, force: true });
3058
- await mkdir2(outDir, { recursive: true });
3417
+ await mkdir3(outDir, { recursive: true });
3059
3418
  if (baseName === void 0) {
3060
3419
  await cp(presetDir, outDir, { recursive: true });
3061
3420
  return;
@@ -3066,15 +3425,15 @@ async function buildPreset(presetDir, outDir) {
3066
3425
  await cp(baseDir, outDir, { recursive: true });
3067
3426
  const values = themeValues(preset.json);
3068
3427
  const name = stringField(preset.json.name) ?? baseName;
3069
- await appendFile(path9.join(outDir, "tokens.css"), presetTokenCss(name, values));
3428
+ await appendFile(path10.join(outDir, "tokens.css"), presetTokenCss(name, values));
3070
3429
  const fonts = fontList(preset.json.fonts);
3071
3430
  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));
3431
+ const from = path10.join(presetDir, "fonts");
3432
+ if (await isDir(from)) await cp(from, path10.join(outDir, "fonts"), { recursive: true });
3433
+ await appendFile(path10.join(outDir, "fonts.css"), presetFontCss(fonts));
3075
3434
  }
3076
3435
  const merged = mergeTheme(base.json, preset.json, baseName);
3077
- await writeFile2(path9.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
3436
+ await writeFile3(path10.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
3078
3437
  `);
3079
3438
  }
3080
3439
  function keyLine(text, key2, mode) {
@@ -3089,8 +3448,8 @@ function keyLine(text, key2, mode) {
3089
3448
  }
3090
3449
  return 0;
3091
3450
  }
3092
- function finding(path25, line, severity, message) {
3093
- return { path: path25, line: Math.max(1, line), severity, message };
3451
+ function finding(path26, line, severity, message) {
3452
+ return { path: path26, line: Math.max(1, line), severity, message };
3094
3453
  }
3095
3454
  function resolveValue(name, map, fallback) {
3096
3455
  let current = map[name] ?? fallback[name];
@@ -3188,13 +3547,13 @@ async function checkPreset(presetDir) {
3188
3547
  }
3189
3548
  const fonts = fontList(preset.json.fonts);
3190
3549
  for (const font of fonts) {
3191
- const file = path9.join(presetDir, font.file);
3550
+ const file = path10.join(presetDir, font.file);
3192
3551
  if (!await isFile(file)) {
3193
3552
  findings.push(finding("theme.json", keyLine(text, "file"), "error", `missing font file: ${font.file}`));
3194
3553
  }
3195
3554
  }
3196
3555
  if (fonts.length > 0) {
3197
- const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path9.join(presetDir, "fonts", rel))));
3556
+ const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path10.join(presetDir, "fonts", rel))));
3198
3557
  if (!found.some(Boolean)) {
3199
3558
  findings.push(
3200
3559
  finding(
@@ -3292,7 +3651,7 @@ var SCRIPT_TYPE = "text/javascript; charset=utf-8";
3292
3651
  function messageOf7(err) {
3293
3652
  return err instanceof Error ? err.message : String(err);
3294
3653
  }
3295
- function isRecord3(value) {
3654
+ function isRecord4(value) {
3296
3655
  return value !== null && typeof value === "object" && !Array.isArray(value);
3297
3656
  }
3298
3657
  function etagOf(text) {
@@ -3327,25 +3686,26 @@ async function resolveFile(root2, pathname) {
3327
3686
  return void 0;
3328
3687
  }
3329
3688
  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);
3689
+ const target = path11.resolve(root2, decoded.replace(/^\/+/, ""));
3690
+ const rel = path11.relative(root2, target);
3691
+ if (rel.startsWith("..") || path11.isAbsolute(rel)) return void 0;
3692
+ const info = await stat8(target).catch(() => void 0);
3334
3693
  if (info === void 0) return void 0;
3335
3694
  if (info.isFile()) return target;
3336
3695
  if (!info.isDirectory()) return void 0;
3337
- const index = path10.join(target, "index.html");
3338
- const indexInfo = await stat7(index).catch(() => void 0);
3696
+ const index = path11.join(target, "index.html");
3697
+ const indexInfo = await stat8(index).catch(() => void 0);
3339
3698
  if (indexInfo === void 0 || !indexInfo.isFile()) return void 0;
3340
3699
  return index;
3341
3700
  }
3342
3701
  async function proxy(art, pathname, method, res) {
3343
3702
  let reply;
3703
+ await readyNetwork();
3344
3704
  try {
3345
3705
  reply = await fetch(art + pathname, { method: method === "HEAD" ? "HEAD" : "GET" });
3346
3706
  } catch (err) {
3347
3707
  res.writeHead(502, { "content-type": TEXT_TYPE, "x-rshow-source": "proxy" });
3348
- res.end(`proxy error: ${messageOf7(err)}
3708
+ res.end(`proxy error: ${networkText(err, art + pathname)}
3349
3709
  `);
3350
3710
  return;
3351
3711
  }
@@ -3361,10 +3721,10 @@ async function proxy(art, pathname, method, res) {
3361
3721
  async function prepareTheme(dir) {
3362
3722
  const doc = await readThemeJson(dir);
3363
3723
  const declared = typeof doc.json.name === "string" ? doc.json.name.trim() : "";
3364
- const name = declared === "" ? path10.basename(dir) : declared;
3724
+ const name = declared === "" ? path11.basename(dir) : declared;
3365
3725
  const base = typeof doc.json.extends === "string" ? doc.json.extends.trim() : "";
3366
3726
  if (base === "") return { name, source: dir, built: dir, label: "local" };
3367
- const built = await mkdtemp2(path10.join(tmpdir(), "rshow-theme-"));
3727
+ const built = await mkdtemp2(path11.join(tmpdir(), "rshow-theme-"));
3368
3728
  await buildPreset(dir, built);
3369
3729
  return { name, source: dir, built, label: "preset" };
3370
3730
  }
@@ -3435,9 +3795,9 @@ function createDataStore() {
3435
3795
  const values = /* @__PURE__ */ new Map();
3436
3796
  let dir;
3437
3797
  const persist = async () => {
3438
- if (dir === void 0) dir = await mkdtemp2(path10.join(tmpdir(), "rshow-data-"));
3798
+ if (dir === void 0) dir = await mkdtemp2(path11.join(tmpdir(), "rshow-data-"));
3439
3799
  const doc = Object.fromEntries(values);
3440
- await writeFile3(path10.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
3800
+ await writeFile4(path11.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
3441
3801
  `);
3442
3802
  };
3443
3803
  return {
@@ -3483,9 +3843,9 @@ async function readBody(req, limit) {
3483
3843
  function valueText(value) {
3484
3844
  return JSON.stringify(value === void 0 ? null : value);
3485
3845
  }
3486
- async function handleData(req, res, store, key2, method) {
3846
+ async function handleData(req, res, store2, key2, method) {
3487
3847
  if (method === "GET" || method === "HEAD") {
3488
- const entry = store.get(key2);
3848
+ const entry = store2.get(key2);
3489
3849
  if (entry === void 0) {
3490
3850
  jsonError(res, "not found", 404);
3491
3851
  return;
@@ -3526,12 +3886,12 @@ async function handleData(req, res, store, key2, method) {
3526
3886
  jsonError(res, "expected a JSON body", 400);
3527
3887
  return;
3528
3888
  }
3529
- if (!isRecord3(parsed) || !("value" in parsed)) {
3889
+ if (!isRecord4(parsed) || !("value" in parsed)) {
3530
3890
  jsonError(res, "expected {value}", 400);
3531
3891
  return;
3532
3892
  }
3533
3893
  const text = valueText(parsed.value);
3534
- const existing = store.get(key2);
3894
+ const existing = store2.get(key2);
3535
3895
  const ifMatch = req.headers["if-match"] ?? null;
3536
3896
  if (ifMatch !== null && ifMatch.trim() !== "") {
3537
3897
  if (existing === void 0 || !etagMatches(ifMatch, etagOf(existing.text))) {
@@ -3539,7 +3899,7 @@ async function handleData(req, res, store, key2, method) {
3539
3899
  return;
3540
3900
  }
3541
3901
  }
3542
- if (existing === void 0 && store.count() >= MAX_DATA_KEYS) {
3902
+ if (existing === void 0 && store2.count() >= MAX_DATA_KEYS) {
3543
3903
  jsonError(res, "too many keys for this artifact", 409);
3544
3904
  return;
3545
3905
  }
@@ -3547,7 +3907,7 @@ async function handleData(req, res, store, key2, method) {
3547
3907
  jsonError(res, "value too large", 413);
3548
3908
  return;
3549
3909
  }
3550
- const written = await store.write(key2, text);
3910
+ const written = await store2.write(key2, text);
3551
3911
  jsonReply(res, 200, { ok: true, key: key2, updated_at: written.updated_at }, { etag: written.etag });
3552
3912
  }
3553
3913
  function subscribe(res, session) {
@@ -3578,7 +3938,7 @@ function injectReload(html) {
3578
3938
  ${html.slice(at)}`;
3579
3939
  }
3580
3940
  async function sendFile(res, method, session, file, headers = {}) {
3581
- const bytes = await readFile8(file);
3941
+ const bytes = await readFile10(file);
3582
3942
  const type = contentTypeFor(file);
3583
3943
  const all = { "content-type": type, "cache-control": "no-store", ...headers };
3584
3944
  if (method === "HEAD") {
@@ -3603,7 +3963,7 @@ function startWatchers(session, targets) {
3603
3963
  const done = rebuild ? buildPreset(theme.source, theme.built) : Promise.resolve();
3604
3964
  done.then(
3605
3965
  () => broadcast(session),
3606
- (err) => warn2(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
3966
+ (err) => warn(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
3607
3967
  );
3608
3968
  }, DEBOUNCE_MS);
3609
3969
  timer.unref();
@@ -3613,7 +3973,7 @@ function startWatchers(session, targets) {
3613
3973
  watcher.on("error", () => void 0);
3614
3974
  watchers.push(watcher);
3615
3975
  } catch (err) {
3616
- warn2(`cannot watch ${target.dir}: ${messageOf7(err)}`);
3976
+ warn(`cannot watch ${target.dir}: ${messageOf7(err)}`);
3617
3977
  }
3618
3978
  }
3619
3979
  return watchers;
@@ -3656,16 +4016,16 @@ async function handle(req, res, session) {
3656
4016
  }
3657
4017
  function closeOnSignals(preview2) {
3658
4018
  const stop = () => {
3659
- preview2.close().catch(() => void 0).finally(() => process6.exit(0));
4019
+ preview2.close().catch(() => void 0).finally(() => process11.exit(0));
3660
4020
  };
3661
- process6.once("SIGINT", stop);
3662
- process6.once("SIGTERM", stop);
4021
+ process11.once("SIGINT", stop);
4022
+ process11.once("SIGTERM", stop);
3663
4023
  }
3664
4024
  async function startPreview(opts) {
3665
- const dir = path10.resolve(opts.dir);
4025
+ const dir = path11.resolve(opts.dir);
3666
4026
  const art = (opts.art ?? "").replace(/\/+$/, "");
3667
4027
  const host = opts.host ?? "127.0.0.1";
3668
- const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path10.resolve(opts.theme));
4028
+ const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path11.resolve(opts.theme));
3669
4029
  const root2 = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
3670
4030
  const session = {
3671
4031
  root: root2,
@@ -3733,12 +4093,12 @@ function portOf(value) {
3733
4093
  return port;
3734
4094
  }
3735
4095
  async function requireFolder(input, what) {
3736
- const dir = path11.resolve(input);
3737
- const info = await stat8(dir).catch(() => void 0);
4096
+ const dir = path12.resolve(input);
4097
+ const info = await stat9(dir).catch(() => void 0);
3738
4098
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such ${what}: ${input}`);
3739
4099
  return dir;
3740
4100
  }
3741
- async function run4(argv, ctx) {
4101
+ async function run3(argv, ctx) {
3742
4102
  const args = parseCommandArgs(
3743
4103
  argv,
3744
4104
  {
@@ -3757,7 +4117,7 @@ async function run4(argv, ctx) {
3757
4117
  const offline = boolValue(args, "offline");
3758
4118
  const port = portOf(stringValue(args, "port"));
3759
4119
  const config = await ctx.config(dir);
3760
- if (config.art === void 0 && !offline) warn2("no art host configured; skipping the /_themes proxy");
4120
+ if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
3761
4121
  const preview2 = await startPreview({
3762
4122
  dir,
3763
4123
  art: offline ? "" : config.art ?? "",
@@ -3781,7 +4141,14 @@ function themeRefOf(theme) {
3781
4141
  return { name: theme.slice(0, at), version: theme.slice(at + 1) };
3782
4142
  }
3783
4143
  async function fetchVersion(art, name) {
3784
- const reply = await fetch(`${art}/_themes/${name}.json`, { signal: AbortSignal.timeout(THEME_TIMEOUT_MS) });
4144
+ const url = `${art}/_themes/${name}.json`;
4145
+ await readyNetwork();
4146
+ let reply;
4147
+ try {
4148
+ reply = await fetch(url, { signal: AbortSignal.timeout(THEME_TIMEOUT_MS) });
4149
+ } catch (err) {
4150
+ throw new Error(networkDetail(err, url));
4151
+ }
3785
4152
  if (reply.status !== 200) throw new Error(`HTTP ${reply.status}`);
3786
4153
  const body = await reply.json();
3787
4154
  const version = body?.version;
@@ -3800,17 +4167,17 @@ async function resolveTheme2(config) {
3800
4167
  const ref = themeRefOf(theme);
3801
4168
  if (ref.version !== void 0 && ref.version !== "latest") return config;
3802
4169
  if (config.art === void 0) {
3803
- warn2("no art host configured; skipping theme version lookup");
4170
+ warn("no art host configured; skipping theme version lookup");
3804
4171
  return config;
3805
4172
  }
3806
4173
  try {
3807
4174
  return { ...config, theme: await resolvedTheme(config.art, theme) };
3808
4175
  } catch (err) {
3809
- warn2(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
4176
+ warn(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
3810
4177
  return config.theme === void 0 ? config : { ...config, theme: ref.name };
3811
4178
  }
3812
4179
  }
3813
- async function run5(argv, ctx) {
4180
+ async function run4(argv, ctx) {
3814
4181
  const args = parseCommandArgs(argv, { offline: { type: "boolean" } }, 0, 1, CONFIG_USAGE);
3815
4182
  const dir = args.positionals[0];
3816
4183
  const loaded = await ctx.config(dir);
@@ -3820,14 +4187,14 @@ async function run5(argv, ctx) {
3820
4187
  }
3821
4188
 
3822
4189
  // 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";
4190
+ import { mkdir as mkdir4, readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
4191
+ import path13 from "node:path";
4192
+ import process12 from "node:process";
3826
4193
  var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
3827
4194
  function messageOf9(err) {
3828
4195
  return err instanceof Error ? err.message : String(err);
3829
4196
  }
3830
- function isRecord4(value) {
4197
+ function isRecord5(value) {
3831
4198
  return value !== null && typeof value === "object" && !Array.isArray(value);
3832
4199
  }
3833
4200
  function serverUrl(value, flag) {
@@ -3849,16 +4216,16 @@ function serverUrl(value, flag) {
3849
4216
  async function rawConfig(file) {
3850
4217
  let text;
3851
4218
  try {
3852
- text = await readFile9(file, "utf8");
4219
+ text = await readFile11(file, "utf8");
3853
4220
  } catch (err) {
3854
4221
  if (err.code === "ENOENT") return {};
3855
4222
  throw new UsageError(`cannot read ${file}: ${messageOf9(err)}`);
3856
4223
  }
3857
4224
  const parsed = JSON.parse(text);
3858
- if (!isRecord4(parsed)) throw new UsageError(`${file} must hold a JSON object`);
4225
+ if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
3859
4226
  return parsed;
3860
4227
  }
3861
- async function run6(argv, ctx) {
4228
+ async function run5(argv, ctx) {
3862
4229
  const args = parseCommandArgs(
3863
4230
  argv,
3864
4231
  {
@@ -3874,7 +4241,7 @@ async function run6(argv, ctx) {
3874
4241
  const url = serverUrl(stringValue(args, "url"), "url");
3875
4242
  const art = serverUrl(stringValue(args, "art"), "art");
3876
4243
  const theme = (stringValue(args, "theme") ?? "").trim();
3877
- const file = globalConfigPath(process7.env);
4244
+ const file = globalConfigPath(process12.env);
3878
4245
  const current = await readConfigFile(file);
3879
4246
  const same = (have, want) => have === void 0 || have.replace(/\/+$/, "") === want;
3880
4247
  if (!boolValue(args, "force")) {
@@ -3889,8 +4256,8 @@ async function run6(argv, ctx) {
3889
4256
  raw.url = url;
3890
4257
  raw.art = art;
3891
4258
  if (theme !== "") raw.theme = theme;
3892
- await mkdir3(path12.dirname(file), { recursive: true });
3893
- await writeFile4(file, `${JSON.stringify(raw, null, 2)}
4259
+ await mkdir4(path13.dirname(file), { recursive: true });
4260
+ await writeFile5(file, `${JSON.stringify(raw, null, 2)}
3894
4261
  `, "utf8");
3895
4262
  ctx.out.data({ path: file, url, art });
3896
4263
  ctx.out.line(`wrote ${file}`);
@@ -3898,15 +4265,15 @@ async function run6(argv, ctx) {
3898
4265
  }
3899
4266
 
3900
4267
  // 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";
4268
+ import { readFile as readFile13, stat as stat11 } from "node:fs/promises";
4269
+ import path15 from "node:path";
4270
+ import process13 from "node:process";
3904
4271
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3905
4272
 
3906
4273
  // 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";
4274
+ 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";
4275
+ import { homedir as homedir4 } from "node:os";
4276
+ import path14 from "node:path";
3910
4277
  var SKILL_TARGETS = ["claude", "pi", "codex"];
3911
4278
  var SKILL_MARKER = ".rshow-skill";
3912
4279
  var SKILL_SOURCE = "rshow";
@@ -3916,26 +4283,26 @@ var TARGET_SEGMENTS = {
3916
4283
  codex: [".codex"]
3917
4284
  };
3918
4285
  async function isDir2(dir) {
3919
- const info = await stat9(dir).catch(() => void 0);
4286
+ const info = await stat10(dir).catch(() => void 0);
3920
4287
  return info !== void 0 && info.isDirectory();
3921
4288
  }
3922
4289
  async function isFile2(file) {
3923
- const info = await stat9(file).catch(() => void 0);
4290
+ const info = await stat10(file).catch(() => void 0);
3924
4291
  return info !== void 0 && info.isFile();
3925
4292
  }
3926
4293
  function isUnder(file, dir) {
3927
- const rel = path13.relative(dir, file);
3928
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${path13.sep}`) && !path13.isAbsolute(rel);
4294
+ const rel = path14.relative(dir, file);
4295
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
3929
4296
  }
3930
4297
  function homeDir(env) {
3931
4298
  const home = (env.HOME ?? "").trim();
3932
- return home !== "" ? home : homedir3();
4299
+ return home !== "" ? home : homedir4();
3933
4300
  }
3934
4301
  function targetParent(home, target) {
3935
- return path13.join(home, ...TARGET_SEGMENTS[target]);
4302
+ return path14.join(home, ...TARGET_SEGMENTS[target]);
3936
4303
  }
3937
4304
  function targetSkillsDir(home, target) {
3938
- return path13.join(targetParent(home, target), "skills");
4305
+ return path14.join(targetParent(home, target), "skills");
3939
4306
  }
3940
4307
  async function detectedTargets(home) {
3941
4308
  const out = [];
@@ -3959,13 +4326,13 @@ function targetHint() {
3959
4326
  return SKILL_TARGETS.join(", ");
3960
4327
  }
3961
4328
  function defaultModeOf(dir) {
3962
- return dir.split(path13.sep).join("/").includes("/_npx/") ? "copy" : "link";
4329
+ return dir.split(path14.sep).join("/").includes("/_npx/") ? "copy" : "link";
3963
4330
  }
3964
4331
  async function skillsSource(env) {
3965
4332
  const override = (env.RSHOW_SKILLS_DIR ?? "").trim();
3966
4333
  let dir;
3967
4334
  if (override !== "") {
3968
- dir = path13.resolve(override);
4335
+ dir = path14.resolve(override);
3969
4336
  if (!await isDir2(dir)) throw new UsageError(`RSHOW_SKILLS_DIR is not a folder: ${dir}`);
3970
4337
  } else {
3971
4338
  dir = await builtinSkillsDir();
@@ -3973,22 +4340,22 @@ async function skillsSource(env) {
3973
4340
  if (dir === void 0) return void 0;
3974
4341
  const skills = [];
3975
4342
  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);
4343
+ if (entry.isDirectory() && await isFile2(path14.join(dir, entry.name, "SKILL.md"))) skills.push(entry.name);
3977
4344
  }
3978
4345
  skills.sort();
3979
4346
  return { dir, mode: await isBinary() ? "copy" : defaultModeOf(dir), skills };
3980
4347
  }
3981
4348
  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("/"));
4349
+ const resolved = path14.resolve(target);
4350
+ if (isUnder(resolved, path14.resolve(source))) return true;
4351
+ return /(^|\/)rshow\/skills\//.test(resolved.split(path14.sep).join("/"));
3985
4352
  }
3986
4353
  async function linkTargetOf(dst) {
3987
4354
  const value = await readlink(dst);
3988
- return path13.isAbsolute(value) ? value : path13.resolve(path13.dirname(dst), value);
4355
+ return path14.isAbsolute(value) ? value : path14.resolve(path14.dirname(dst), value);
3989
4356
  }
3990
4357
  async function readMarker(dir) {
3991
- const text = await readFile10(path13.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
4358
+ const text = await readFile12(path14.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
3992
4359
  if (text === void 0) return void 0;
3993
4360
  let parsed;
3994
4361
  try {
@@ -4007,7 +4374,7 @@ async function inspectSkill(dst, source, skill) {
4007
4374
  if (info.isSymbolicLink()) {
4008
4375
  const link = await linkTargetOf(dst);
4009
4376
  if (!isOurLink(link, source)) return { kind: "foreign" };
4010
- return { kind: "link", path: link, current: path13.resolve(link) === path13.join(path13.resolve(source), skill) };
4377
+ return { kind: "link", path: link, current: path14.resolve(link) === path14.join(path14.resolve(source), skill) };
4011
4378
  }
4012
4379
  if (info.isDirectory()) {
4013
4380
  const marker = await readMarker(dst);
@@ -4045,17 +4412,17 @@ async function writeSkill(dst, src, mode, version) {
4045
4412
  }
4046
4413
  await cp2(src, dst, { recursive: true });
4047
4414
  const marker = { version, source: SKILL_SOURCE };
4048
- await writeFile5(path13.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
4415
+ await writeFile6(path14.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
4049
4416
  `, "utf8");
4050
4417
  }
4051
4418
  async function installSkills(options) {
4052
4419
  const entries = [];
4053
4420
  for (const target of options.targets) {
4054
4421
  const dir = targetSkillsDir(options.home, target);
4055
- if (!options.dryRun) await mkdir4(dir, { recursive: true });
4422
+ if (!options.dryRun) await mkdir5(dir, { recursive: true });
4056
4423
  for (const skill of options.source.skills) {
4057
- const dst = path13.join(dir, skill);
4058
- const src = path13.join(options.source.dir, skill);
4424
+ const dst = path14.join(dir, skill);
4425
+ const src = path14.join(options.source.dir, skill);
4059
4426
  const state = await inspectSkill(dst, options.source.dir, skill);
4060
4427
  const action = actionFor(state, options.version);
4061
4428
  if (!options.dryRun && (action === "installed" || action === "updated")) {
@@ -4072,7 +4439,7 @@ async function uninstallSkills(options) {
4072
4439
  for (const target of options.targets) {
4073
4440
  const dir = targetSkillsDir(options.home, target);
4074
4441
  for (const skill of (await readdir3(dir).catch(() => [])).sort()) {
4075
- const dst = path13.join(dir, skill);
4442
+ const dst = path14.join(dir, skill);
4076
4443
  const state = await inspectSkill(dst, options.source.dir, skill);
4077
4444
  if (state.kind !== "link" && state.kind !== "copy") continue;
4078
4445
  if (!options.dryRun) await rm4(dst, { recursive: true, force: true });
@@ -4086,7 +4453,7 @@ async function skillsStatus(options) {
4086
4453
  for (const target of options.targets) {
4087
4454
  const dir = targetSkillsDir(options.home, target);
4088
4455
  for (const skill of options.source.skills) {
4089
- const dst = path13.join(dir, skill);
4456
+ const dst = path14.join(dir, skill);
4090
4457
  out.push({ target, skill, state: await inspectSkill(dst, options.source.dir, skill), path: dst });
4091
4458
  }
4092
4459
  }
@@ -4099,13 +4466,13 @@ function messageOf10(err) {
4099
4466
  return err instanceof Error ? err.message : String(err);
4100
4467
  }
4101
4468
  async function isFile3(file) {
4102
- const info = await stat10(file).catch(() => void 0);
4469
+ const info = await stat11(file).catch(() => void 0);
4103
4470
  return info !== void 0 && info.isFile();
4104
4471
  }
4105
4472
  function sourceDir2() {
4106
4473
  try {
4107
4474
  const url = import.meta.url;
4108
- return typeof url === "string" && url !== "" ? path14.dirname(fileURLToPath3(url)) : void 0;
4475
+ return typeof url === "string" && url !== "" ? path15.dirname(fileURLToPath3(url)) : void 0;
4109
4476
  } catch {
4110
4477
  return void 0;
4111
4478
  }
@@ -4115,9 +4482,9 @@ async function packageJsonPath() {
4115
4482
  if (start === void 0) return void 0;
4116
4483
  let dir = start;
4117
4484
  for (let i = 0; i < 6; i += 1) {
4118
- const file = path14.join(dir, "package.json");
4485
+ const file = path15.join(dir, "package.json");
4119
4486
  if (await isFile3(file)) return file;
4120
- const up = path14.dirname(dir);
4487
+ const up = path15.dirname(dir);
4121
4488
  if (up === dir) break;
4122
4489
  dir = up;
4123
4490
  }
@@ -4141,7 +4508,7 @@ function minimumOf(range) {
4141
4508
  }
4142
4509
  async function nodeCheck() {
4143
4510
  const name = "node";
4144
- const current = process8.versions.node;
4511
+ const current = process13.versions.node;
4145
4512
  const file = await packageJsonPath();
4146
4513
  if (file === void 0) {
4147
4514
  if (await isBinary()) return { name, status: "ok", detail: `v${current} (inside the binary)` };
@@ -4149,7 +4516,7 @@ async function nodeCheck() {
4149
4516
  }
4150
4517
  let range;
4151
4518
  try {
4152
- const body = JSON.parse(await readFile11(file, "utf8"));
4519
+ const body = JSON.parse(await readFile13(file, "utf8"));
4153
4520
  range = typeof body.engines?.node === "string" ? body.engines.node : "";
4154
4521
  } catch (err) {
4155
4522
  return { name, status: "warn", detail: `v${current} (${messageOf10(err)})` };
@@ -4160,20 +4527,22 @@ async function nodeCheck() {
4160
4527
  return { name, status: status2, detail: `v${current} (needs ${range})` };
4161
4528
  }
4162
4529
  async function tokenOf(url) {
4163
- const env = (process8.env.RSHOW_TOKEN ?? "").trim();
4530
+ const env = (process13.env.RSHOW_TOKEN ?? "").trim();
4164
4531
  if (env !== "") return { token: env, source: "env" };
4165
- if (url === void 0) return { source: "none" };
4532
+ if (url === void 0) return {};
4166
4533
  try {
4167
- const saved = await read(url);
4168
- if (saved !== void 0) return { token: saved, source: "keychain" };
4169
- return { source: "none" };
4534
+ const store2 = await choose(process13.env);
4535
+ const saved = await store2.read(url);
4536
+ if (saved !== void 0) return { token: saved, source: store2.detail };
4537
+ return {};
4170
4538
  } catch (err) {
4171
- return { source: "none", error: messageOf10(err) };
4539
+ return { error: messageOf10(err) };
4172
4540
  }
4173
4541
  }
4174
4542
  async function getJson(url, token) {
4175
4543
  const headers = {};
4176
4544
  if (token !== void 0) headers.authorization = `Bearer ${token}`;
4545
+ await readyNetwork();
4177
4546
  const reply = await fetch(url, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
4178
4547
  if (reply.status !== 200) return { status: reply.status };
4179
4548
  const body = await reply.json().catch(() => void 0);
@@ -4199,7 +4568,7 @@ function tokenCheck(token) {
4199
4568
  const reason = token.error === void 0 ? "" : ` (${token.error})`;
4200
4569
  return { name, status: "fail", detail: `none${reason}: run rshow login, or set RSHOW_TOKEN` };
4201
4570
  }
4202
- return { name, status: "ok", detail: `source: ${token.source}` };
4571
+ return { name, status: "ok", detail: `source: ${token.source ?? "none"}` };
4203
4572
  }
4204
4573
  async function apiCheck(url, token) {
4205
4574
  const name = "api";
@@ -4213,7 +4582,7 @@ async function apiCheck(url, token) {
4213
4582
  const scope = typeof body.scope === "string" ? body.scope : "";
4214
4583
  return { name, status: "ok", detail: `${email} (scope ${scope})` };
4215
4584
  } catch (err) {
4216
- return { name, status: "fail", detail: messageOf10(err) };
4585
+ return { name, status: "fail", detail: networkDetail(err, `${url}/api/me`) };
4217
4586
  }
4218
4587
  }
4219
4588
  async function artCheck(art) {
@@ -4228,7 +4597,7 @@ async function artCheck(art) {
4228
4597
  }
4229
4598
  return { name, status: "ok", detail: `version ${version}` };
4230
4599
  } catch (err) {
4231
- return { name, status: "warn", detail: messageOf10(err) };
4600
+ return { name, status: "warn", detail: networkDetail(err, `${art}/_themes/default.json`) };
4232
4601
  }
4233
4602
  }
4234
4603
  async function themeCheck(config) {
@@ -4247,10 +4616,10 @@ async function themeCheck(config) {
4247
4616
  }
4248
4617
  async function skillsCheck() {
4249
4618
  const name = "skills";
4250
- const home = homeDir(process8.env);
4619
+ const home = homeDir(process13.env);
4251
4620
  const targets = await detectedTargets(home);
4252
4621
  if (targets.length === 0) return { name, status: "ok", detail: "no target detected" };
4253
- const source = await skillsSource(process8.env).catch(() => void 0);
4622
+ const source = await skillsSource(process13.env).catch(() => void 0);
4254
4623
  if (source === void 0) return { name, status: "warn", detail: "no skills folder next to rshow" };
4255
4624
  const list3 = await skillsStatus({ home, source, targets });
4256
4625
  const problems = [];
@@ -4261,11 +4630,11 @@ async function skillsCheck() {
4261
4630
  if (problems.length === 0) return { name, status: "ok", detail: targets.join(", ") };
4262
4631
  return { name, status: "warn", detail: `${problems.join("; ")} (run rshow skills install)` };
4263
4632
  }
4264
- async function run7(argv, ctx) {
4633
+ async function run6(argv, ctx) {
4265
4634
  const args = parseCommandArgs(argv, {}, 0, 1, DOCTOR_USAGE);
4266
4635
  const dir = args.positionals[0];
4267
4636
  const found = [];
4268
- for (const file of [globalConfigPath(process8.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
4637
+ for (const file of [globalConfigPath(process13.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
4269
4638
  if (file !== void 0 && await isFile3(file)) found.push(file);
4270
4639
  }
4271
4640
  let config;
@@ -4293,13 +4662,13 @@ async function run7(argv, ctx) {
4293
4662
  }
4294
4663
 
4295
4664
  // 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";
4665
+ import { mkdir as mkdir6, mkdtemp as mkdtemp3, rm as rm5, stat as stat13, writeFile as writeFile7 } from "node:fs/promises";
4297
4666
  import { tmpdir as tmpdir2 } from "node:os";
4298
- import path16 from "node:path";
4667
+ import path17 from "node:path";
4299
4668
 
4300
4669
  // src/theme.ts
4301
- import { readFile as readFile12, stat as stat11 } from "node:fs/promises";
4302
- import path15 from "node:path";
4670
+ import { readFile as readFile14, stat as stat12 } from "node:fs/promises";
4671
+ import path16 from "node:path";
4303
4672
  function themeSlug(name) {
4304
4673
  return `theme-${name}`;
4305
4674
  }
@@ -4307,10 +4676,10 @@ function themeUrl(name, n) {
4307
4676
  return `/_themes/${name}@${n}/`;
4308
4677
  }
4309
4678
  async function readThemeMeta(dir) {
4310
- const file = path15.join(dir, "theme.json");
4679
+ const file = path16.join(dir, "theme.json");
4311
4680
  let text;
4312
4681
  try {
4313
- text = await readFile12(file, "utf8");
4682
+ text = await readFile14(file, "utf8");
4314
4683
  } catch (err) {
4315
4684
  if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
4316
4685
  throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -4331,8 +4700,8 @@ async function readThemeMeta(dir) {
4331
4700
  return { name };
4332
4701
  }
4333
4702
  async function requireThemeDir(input) {
4334
- const dir = path15.resolve(input);
4335
- const info = await stat11(dir).catch(() => void 0);
4703
+ const dir = path16.resolve(input);
4704
+ const info = await stat12(dir).catch(() => void 0);
4336
4705
  if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
4337
4706
  return dir;
4338
4707
  }
@@ -4340,7 +4709,7 @@ async function requireThemeDir(input) {
4340
4709
  // src/commands/theme.ts
4341
4710
  async function presetCheck(dir) {
4342
4711
  const findings = await checkPreset(dir);
4343
- const info = await stat12(path16.join(dir, "theme.json")).catch(() => void 0);
4712
+ const info = await stat13(path17.join(dir, "theme.json")).catch(() => void 0);
4344
4713
  const errors = findings.filter((finding2) => finding2.severity === "error").length;
4345
4714
  return { findings, errors, warnings: findings.length - errors, files: 1, bytes: info?.size ?? 0 };
4346
4715
  }
@@ -4369,14 +4738,14 @@ async function publish(argv, ctx) {
4369
4738
  if (!boolValue(args, "no-check")) {
4370
4739
  if (preset) await enforcePresetCheck(ctx.out, dir);
4371
4740
  else {
4372
- if (config.art === void 0) warn2("no art host configured; skipping theme checks");
4741
+ if (config.art === void 0) warn("no art host configured; skipping theme checks");
4373
4742
  await enforceCheck(ctx.out, dir, { art: config.art, offline: config.art === void 0 });
4374
4743
  }
4375
4744
  }
4376
4745
  let target = dir;
4377
4746
  let temp;
4378
4747
  if (preset) {
4379
- temp = await mkdtemp3(path16.join(tmpdir2(), "rshow-theme-"));
4748
+ temp = await mkdtemp3(path17.join(tmpdir2(), "rshow-theme-"));
4380
4749
  await buildPreset(dir, temp);
4381
4750
  target = temp;
4382
4751
  }
@@ -4464,14 +4833,14 @@ async function create(argv, ctx) {
4464
4833
  const from = stringValue(args, "from") ?? "default";
4465
4834
  const baseDir = await builtinThemeDir(from);
4466
4835
  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);
4836
+ const parent = path17.resolve(stringValue(args, "dir") ?? ".");
4837
+ const target = path17.join(parent, name);
4838
+ const exists2 = await stat13(target).catch(() => void 0);
4470
4839
  if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
4471
4840
  const values = await baseRoleValues(baseDir, ROLE_TOKENS);
4472
4841
  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)}
4842
+ await mkdir6(target, { recursive: true });
4843
+ await writeFile7(path17.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
4475
4844
  `);
4476
4845
  ctx.out.data({ path: target, name, extends: from });
4477
4846
  ctx.out.line(target);
@@ -4506,7 +4875,7 @@ async function preview(argv, ctx) {
4506
4875
  const port = portOf(stringValue(args, "port"));
4507
4876
  const offline = boolValue(args, "offline");
4508
4877
  const config = await ctx.config(dir);
4509
- if (config.art === void 0 && !offline) warn2("no art host configured; skipping the /_themes proxy");
4878
+ if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
4510
4879
  const server = await startPreview({
4511
4880
  dir,
4512
4881
  theme: dir,
@@ -4520,7 +4889,7 @@ async function preview(argv, ctx) {
4520
4889
  ctx.out.raw(`${server.url}/index.html`);
4521
4890
  return EXIT.ok;
4522
4891
  }
4523
- async function run8(argv, ctx) {
4892
+ async function run7(argv, ctx) {
4524
4893
  const [sub, ...rest] = argv;
4525
4894
  if (sub === void 0) throw new UsageError(`expected a theme command (try: ${usageLine(THEME_USAGE)})`);
4526
4895
  if (sub === "new") return create(rest, ctx);
@@ -4538,11 +4907,11 @@ async function run8(argv, ctx) {
4538
4907
  }
4539
4908
 
4540
4909
  // src/commands/skills.ts
4541
- import path17 from "node:path";
4542
- import process9 from "node:process";
4910
+ import path18 from "node:path";
4911
+ import process14 from "node:process";
4543
4912
  var CHANGE_ACTIONS = /* @__PURE__ */ new Set(["installed", "updated", "removed"]);
4544
4913
  async function sourceOf() {
4545
- const source = await skillsSource(process9.env);
4914
+ const source = await skillsSource(process14.env);
4546
4915
  if (source === void 0) {
4547
4916
  throw new UsageError(`no skills folder next to rshow (set RSHOW_SKILLS_DIR to one)`);
4548
4917
  }
@@ -4555,7 +4924,7 @@ async function targetsWithDetection(names, home) {
4555
4924
  }
4556
4925
  function noTargetsError(home) {
4557
4926
  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()}`
4927
+ `no target detected (looked for ${path18.join(home, ".claude")}, ${path18.join(home, ".pi/agent")} and ${path18.join(home, ".codex")}); name one of ${targetHint()}`
4559
4928
  );
4560
4929
  }
4561
4930
  function actionLine(entry, dryRun) {
@@ -4581,7 +4950,7 @@ async function install(argv, ctx) {
4581
4950
  const link = boolValue(args, "link");
4582
4951
  const copy = boolValue(args, "copy");
4583
4952
  if (link && copy) throw new UsageError(`--link and --copy cannot be used together (try: ${usageLine(SKILLS_INSTALL_USAGE)})`);
4584
- const home = homeDir(process9.env);
4953
+ const home = homeDir(process14.env);
4585
4954
  const targets = await targetsWithDetection(args.positionals, home);
4586
4955
  if (targets.length === 0) throw noTargetsError(home);
4587
4956
  const source = await sourceOf();
@@ -4595,13 +4964,13 @@ async function install(argv, ctx) {
4595
4964
  else ctx.out.line(actionLine(entry, dryRun));
4596
4965
  }
4597
4966
  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
4967
+ for (const dst of skipped) process14.stderr.write(`skip ${dst}: exists and is not from rshow
4599
4968
  `);
4600
4969
  return skipped.length > 0 ? EXIT.check : EXIT.ok;
4601
4970
  }
4602
4971
  async function uninstall(argv, ctx) {
4603
4972
  const args = parseCommandArgs(argv, { "dry-run": { type: "boolean" } }, 0, 3, SKILLS_UNINSTALL_USAGE);
4604
- const home = homeDir(process9.env);
4973
+ const home = homeDir(process14.env);
4605
4974
  const targets = await targetsWithDetection(args.positionals, home);
4606
4975
  if (targets.length === 0) return EXIT.ok;
4607
4976
  const source = await sourceOf();
@@ -4619,7 +4988,7 @@ function actionOf(item) {
4619
4988
  }
4620
4989
  async function status(argv, ctx) {
4621
4990
  const args = parseCommandArgs(argv, {}, 0, 3, SKILLS_STATUS_USAGE);
4622
- const home = homeDir(process9.env);
4991
+ const home = homeDir(process14.env);
4623
4992
  const targets = await targetsWithDetection(args.positionals, home);
4624
4993
  if (targets.length === 0) return EXIT.ok;
4625
4994
  const source = await sourceOf();
@@ -4634,12 +5003,12 @@ async function status(argv, ctx) {
4634
5003
  const name = `${item.target.padEnd(targetWidth)} ${item.skill.padEnd(skillWidth)}`;
4635
5004
  ctx.out.line(`${name} ${skillStateLabel(item.state)}`);
4636
5005
  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`);
5006
+ warn(`${item.path} is a copy of v${item.state.version}, this rshow is ${VERSION}: run rshow skills install`);
4638
5007
  }
4639
5008
  }
4640
5009
  return EXIT.ok;
4641
5010
  }
4642
- async function run9(argv, ctx) {
5011
+ async function run8(argv, ctx) {
4643
5012
  const [sub, ...rest] = argv;
4644
5013
  switch (sub) {
4645
5014
  case "install":
@@ -4654,7 +5023,7 @@ async function run9(argv, ctx) {
4654
5023
  }
4655
5024
 
4656
5025
  // src/commands/list.ts
4657
- async function run10(argv, ctx) {
5026
+ async function run9(argv, ctx) {
4658
5027
  const args = parseCommandArgs(
4659
5028
  argv,
4660
5029
  { project: { type: "string" }, tag: { type: "string" }, q: { type: "string" } },
@@ -4687,7 +5056,7 @@ async function run10(argv, ctx) {
4687
5056
  }
4688
5057
 
4689
5058
  // src/commands/show.ts
4690
- async function run11(argv, ctx) {
5059
+ async function run10(argv, ctx) {
4691
5060
  const args = parseCommandArgs(argv, {}, 1, 1, SHOW_USAGE);
4692
5061
  const slug = validateSlug(args.positionals[0]);
4693
5062
  const api = await ctx.api();
@@ -4720,7 +5089,7 @@ async function run11(argv, ctx) {
4720
5089
 
4721
5090
  // src/commands/share.ts
4722
5091
  var MODES = /* @__PURE__ */ new Set(["private", "people", "public"]);
4723
- async function run12(argv, ctx) {
5092
+ async function run11(argv, ctx) {
4724
5093
  const args = parseCommandArgs(
4725
5094
  argv,
4726
5095
  {
@@ -4760,7 +5129,7 @@ async function run12(argv, ctx) {
4760
5129
  }
4761
5130
 
4762
5131
  // src/commands/pin.ts
4763
- async function run13(argv, ctx) {
5132
+ async function run12(argv, ctx) {
4764
5133
  const args = parseCommandArgs(argv, {}, 2, 2, PIN_USAGE);
4765
5134
  const slug = validateSlug(args.positionals[0]);
4766
5135
  const target = args.positionals[1];
@@ -4782,7 +5151,7 @@ async function run13(argv, ctx) {
4782
5151
  }
4783
5152
 
4784
5153
  // src/commands/delete.ts
4785
- async function run14(argv, ctx) {
5154
+ async function run13(argv, ctx) {
4786
5155
  const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, DELETE_USAGE);
4787
5156
  const slug = validateSlug(args.positionals[0]);
4788
5157
  if (!boolValue(args, "yes")) throw new UsageError(`delete needs --yes (try: ${usageLine(DELETE_USAGE)})`);
@@ -4896,7 +5265,7 @@ async function share(argv, ctx) {
4896
5265
  for (const member of members) ctx.out.line(`member ${member.email} ${member.role}`);
4897
5266
  for (const email of removals) ctx.out.line(`removed ${email}`);
4898
5267
  }
4899
- async function remove2(argv, ctx) {
5268
+ async function remove4(argv, ctx) {
4900
5269
  const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, PROJECT_DELETE_USAGE);
4901
5270
  const slug = validateSlug(args.positionals[0]);
4902
5271
  if (!boolValue(args, "yes")) {
@@ -4907,7 +5276,7 @@ async function remove2(argv, ctx) {
4907
5276
  ctx.out.data(null);
4908
5277
  ctx.out.line(`deleted: ${slug}`);
4909
5278
  }
4910
- async function run15(argv, ctx) {
5279
+ async function run14(argv, ctx) {
4911
5280
  const [sub, ...rest] = argv;
4912
5281
  if (sub === void 0) {
4913
5282
  throw new UsageError(`expected a project command (try: ${usageLine(PROJECT_USAGE)})`);
@@ -4916,12 +5285,12 @@ async function run15(argv, ctx) {
4916
5285
  if (sub === "list") return list2(rest, ctx);
4917
5286
  if (sub === "show") return show(rest, ctx);
4918
5287
  if (sub === "share") return share(rest, ctx);
4919
- if (sub === "delete") return remove2(rest, ctx);
5288
+ if (sub === "delete") return remove4(rest, ctx);
4920
5289
  throw new UsageError(`unknown project command: ${sub} (try: ${usageLine(PROJECT_USAGE)})`);
4921
5290
  }
4922
5291
 
4923
5292
  // src/commands/tag.ts
4924
- async function run16(argv, ctx) {
5293
+ async function run15(argv, ctx) {
4925
5294
  const args = parseCommandArgs(
4926
5295
  argv,
4927
5296
  { add: { type: "string", multiple: true }, remove: { type: "string", multiple: true } },
@@ -4931,13 +5300,13 @@ async function run16(argv, ctx) {
4931
5300
  );
4932
5301
  const slug = validateSlug(args.positionals[0]);
4933
5302
  const add2 = normalizeTags(stringList(args, "add"));
4934
- const remove3 = normalizeTags(stringList(args, "remove"));
4935
- if (add2.length === 0 && remove3.length === 0) {
5303
+ const remove5 = normalizeTags(stringList(args, "remove"));
5304
+ if (add2.length === 0 && remove5.length === 0) {
4936
5305
  throw new UsageError(`nothing to change (try: ${usageLine(TAG_USAGE)})`);
4937
5306
  }
4938
5307
  const api = await ctx.api();
4939
5308
  const current = await api.artifact(slug);
4940
- const tags = withTags(tagsOf(current?.tags), add2, remove3);
5309
+ const tags = withTags(tagsOf(current?.tags), add2, remove5);
4941
5310
  const artifact = await api.patchArtifact(slug, { tags });
4942
5311
  ctx.out.data({ artifact, tags });
4943
5312
  ctx.out.line(`slug: ${slug}`);
@@ -4945,20 +5314,20 @@ async function run16(argv, ctx) {
4945
5314
  }
4946
5315
 
4947
5316
  // src/commands/handoff.ts
4948
- import process13 from "node:process";
5317
+ import process18 from "node:process";
4949
5318
 
4950
5319
  // 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";
5320
+ import { mkdir as mkdir7, stat as stat14, writeFile as writeFile9 } from "node:fs/promises";
5321
+ import path21 from "node:path";
5322
+ import process17 from "node:process";
4954
5323
 
4955
5324
  // src/cdp.ts
4956
5325
  import { spawn as spawn3 } from "node:child_process";
4957
5326
  import { existsSync, rmSync } from "node:fs";
4958
5327
  import { mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
4959
5328
  import { tmpdir as tmpdir3 } from "node:os";
4960
- import path18 from "node:path";
4961
- import process10 from "node:process";
5329
+ import path19 from "node:path";
5330
+ import process15 from "node:process";
4962
5331
  var CHROME_CANDIDATES = [
4963
5332
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
4964
5333
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
@@ -4978,9 +5347,9 @@ var LOAD_TIMEOUT_MS = 2e4;
4978
5347
  var liveBrowsers = /* @__PURE__ */ new Map();
4979
5348
  var signalHandlersInstalled = false;
4980
5349
  function killBrowser(child, signal) {
4981
- if (process10.platform !== "win32" && child.pid !== void 0) {
5350
+ if (process15.platform !== "win32" && child.pid !== void 0) {
4982
5351
  try {
4983
- process10.kill(-child.pid, signal);
5352
+ process15.kill(-child.pid, signal);
4984
5353
  return;
4985
5354
  } catch {
4986
5355
  }
@@ -5005,24 +5374,24 @@ function reapBrowsers() {
5005
5374
  }
5006
5375
  function onInterrupt() {
5007
5376
  reapBrowsers();
5008
- process10.exit(130);
5377
+ process15.exit(130);
5009
5378
  }
5010
5379
  function onTerminate() {
5011
5380
  reapBrowsers();
5012
- process10.exit(143);
5381
+ process15.exit(143);
5013
5382
  }
5014
5383
  function dropSignalHandlers() {
5015
5384
  if (!signalHandlersInstalled) return;
5016
5385
  signalHandlersInstalled = false;
5017
- process10.off("SIGINT", onInterrupt);
5018
- process10.off("SIGTERM", onTerminate);
5386
+ process15.off("SIGINT", onInterrupt);
5387
+ process15.off("SIGTERM", onTerminate);
5019
5388
  }
5020
5389
  function trackBrowser(child, profile) {
5021
5390
  liveBrowsers.set(child, profile);
5022
5391
  if (!signalHandlersInstalled) {
5023
5392
  signalHandlersInstalled = true;
5024
- process10.on("SIGINT", onInterrupt);
5025
- process10.on("SIGTERM", onTerminate);
5393
+ process15.on("SIGINT", onInterrupt);
5394
+ process15.on("SIGTERM", onTerminate);
5026
5395
  }
5027
5396
  }
5028
5397
  function forgetBrowser(child) {
@@ -5033,20 +5402,20 @@ var exitHookInstalled = false;
5033
5402
  function installExitHook() {
5034
5403
  if (exitHookInstalled) return;
5035
5404
  exitHookInstalled = true;
5036
- process10.once("exit", reapBrowsers);
5405
+ process15.once("exit", reapBrowsers);
5037
5406
  }
5038
5407
  function messageOf11(err) {
5039
5408
  return err instanceof Error ? err.message : String(err);
5040
5409
  }
5041
5410
  function whichInPath(name, pathValue) {
5042
- for (const dir of String(pathValue ?? "").split(path18.delimiter)) {
5411
+ for (const dir of String(pathValue ?? "").split(path19.delimiter)) {
5043
5412
  if (dir === "") continue;
5044
- const file = path18.join(dir, name);
5413
+ const file = path19.join(dir, name);
5045
5414
  if (existsSync(file)) return file;
5046
5415
  }
5047
5416
  return void 0;
5048
5417
  }
5049
- function findChrome(env = process10.env) {
5418
+ function findChrome(env = process15.env) {
5050
5419
  const explicit = (env.CHROME ?? "").trim();
5051
5420
  if (explicit !== "") {
5052
5421
  if (existsSync(explicit)) return explicit;
@@ -5217,7 +5586,7 @@ var Chrome = class _Chrome {
5217
5586
  static async launch(opts = {}) {
5218
5587
  const binary = opts.binary ?? findChrome();
5219
5588
  if (binary === void 0) throw new UsageError("no Chrome or Chromium found, set CHROME");
5220
- const profile = await mkdtemp4(path18.join(tmpdir3(), "rshow-chrome-"));
5589
+ const profile = await mkdtemp4(path19.join(tmpdir3(), "rshow-chrome-"));
5221
5590
  const child = spawn3(
5222
5591
  binary,
5223
5592
  [
@@ -5237,7 +5606,7 @@ var Chrome = class _Chrome {
5237
5606
  "--window-size=1440,900",
5238
5607
  "about:blank"
5239
5608
  ],
5240
- { stdio: ["ignore", "pipe", "pipe"], detached: process10.platform !== "win32" }
5609
+ { stdio: ["ignore", "pipe", "pipe"], detached: process15.platform !== "win32" }
5241
5610
  );
5242
5611
  installExitHook();
5243
5612
  trackBrowser(child, profile);
@@ -5369,16 +5738,16 @@ var Page = class _Page {
5369
5738
  };
5370
5739
 
5371
5740
  // 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";
5741
+ import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
5742
+ import path20 from "node:path";
5743
+ import process16 from "node:process";
5375
5744
  var MAP_FILE = "rshow.map.json";
5376
- function isRecord5(value) {
5745
+ function isRecord6(value) {
5377
5746
  return value !== null && typeof value === "object" && !Array.isArray(value);
5378
5747
  }
5379
5748
  function textMap(file, key2, value) {
5380
5749
  if (value === void 0 || value === null) return {};
5381
- if (!isRecord5(value)) throw new UsageError(`${file}: ${key2} must be an object`);
5750
+ if (!isRecord6(value)) throw new UsageError(`${file}: ${key2} must be an object`);
5382
5751
  const out = {};
5383
5752
  for (const [name, target] of Object.entries(value)) {
5384
5753
  if (typeof target !== "string") throw new UsageError(`${file}: ${key2}.${name} must be a string`);
@@ -5387,12 +5756,12 @@ function textMap(file, key2, value) {
5387
5756
  return out;
5388
5757
  }
5389
5758
  function mapPath(dir) {
5390
- return path19.resolve(dir ?? process11.cwd(), MAP_FILE);
5759
+ return path20.resolve(dir ?? process16.cwd(), MAP_FILE);
5391
5760
  }
5392
5761
  async function readMapFile(file) {
5393
5762
  let text;
5394
5763
  try {
5395
- text = await readFile13(file, "utf8");
5764
+ text = await readFile15(file, "utf8");
5396
5765
  } catch (err) {
5397
5766
  if (err.code === "ENOENT") return void 0;
5398
5767
  throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -5403,7 +5772,7 @@ async function readMapFile(file) {
5403
5772
  } catch {
5404
5773
  throw new UsageError(`invalid JSON in ${file}`);
5405
5774
  }
5406
- if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
5775
+ if (!isRecord6(parsed)) throw new UsageError(`${file} must hold a JSON object`);
5407
5776
  return {
5408
5777
  components: textMap(file, "components", parsed.components),
5409
5778
  tokens: textMap(file, "tokens", parsed.tokens),
@@ -5411,7 +5780,7 @@ async function readMapFile(file) {
5411
5780
  };
5412
5781
  }
5413
5782
  async function writeMapFile(file, map) {
5414
- await writeFile7(file, `${JSON.stringify(sortMap(map), null, 2)}
5783
+ await writeFile8(file, `${JSON.stringify(sortMap(map), null, 2)}
5415
5784
  `);
5416
5785
  }
5417
5786
  function sortRecord(value) {
@@ -5855,14 +6224,14 @@ function parseTarget(input) {
5855
6224
  if (remote !== null) {
5856
6225
  return { kind: "remote", slug: remote[1], version: Number(remote[2]) };
5857
6226
  }
5858
- const dir = path20.resolve(input);
5859
- return { kind: "local", dir, slug: deriveSlug(path20.basename(dir)) || "handoff" };
6227
+ const dir = path21.resolve(input);
6228
+ return { kind: "local", dir, slug: deriveSlug(path21.basename(dir)) || "handoff" };
5860
6229
  }
5861
6230
  async function requireLocalDir(target) {
5862
6231
  if (target.kind !== "local") throw new UsageError("expected a local folder");
5863
- const info = await stat13(target.dir).catch(() => void 0);
6232
+ const info = await stat14(target.dir).catch(() => void 0);
5864
6233
  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);
6234
+ const index = await stat14(path21.join(target.dir, "index.html")).catch(() => void 0);
5866
6235
  if (index === void 0 || !index.isFile()) throw new UsageError(`${target.dir} has no index.html`);
5867
6236
  return target.dir;
5868
6237
  }
@@ -5877,10 +6246,10 @@ function safeName(text) {
5877
6246
  return cleaned === "" ? "screen" : cleaned;
5878
6247
  }
5879
6248
  function displayPath(target) {
5880
- const resolved = path20.resolve(target);
5881
- const relative = path20.relative(process12.cwd(), resolved);
6249
+ const resolved = path21.resolve(target);
6250
+ const relative = path21.relative(process17.cwd(), resolved);
5882
6251
  if (relative === "") return ".";
5883
- if (relative.startsWith("..") || path20.isAbsolute(relative)) return resolved;
6252
+ if (relative.startsWith("..") || path21.isAbsolute(relative)) return resolved;
5884
6253
  return relative;
5885
6254
  }
5886
6255
  function baselineName(screen, state, width, mode) {
@@ -5915,10 +6284,11 @@ async function collectStyleTexts(url, fetchImpl = fetch) {
5915
6284
  }
5916
6285
  async function assertPublic(url, art, fetchImpl = fetch) {
5917
6286
  let reply;
6287
+ await readyNetwork();
5918
6288
  try {
5919
6289
  reply = await fetchImpl(url, { headers: { accept: "text/html" }, redirect: "follow" });
5920
6290
  } catch (err) {
5921
- throw new UsageError(`cannot reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
6291
+ throw new UsageError(`cannot reach ${url}: ${networkDetail(err, url)}`);
5922
6292
  }
5923
6293
  const host = new URL(art).host;
5924
6294
  const wandered = new URL(reply.url === "" ? url : reply.url).host !== host;
@@ -6041,7 +6411,7 @@ async function runHandoff(target, opts) {
6041
6411
  const version = target.kind === "remote" ? target.version : null;
6042
6412
  const slug = target.slug;
6043
6413
  const url = target.kind === "remote" ? `${art}/${slug}/v${version}/` : "";
6044
- const out = path20.resolve(opts.out);
6414
+ const out = path21.resolve(opts.out);
6045
6415
  let closePreview;
6046
6416
  let pageUrl = url;
6047
6417
  if (target.kind === "remote") {
@@ -6056,8 +6426,8 @@ async function runHandoff(target, opts) {
6056
6426
  const styleTexts = await collectStyleTexts(pageUrl, fetchImpl);
6057
6427
  const tokens = extractTokens(styleTexts);
6058
6428
  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)}
6429
+ await mkdir7(path21.join(out, "baseline"), { recursive: true });
6430
+ await writeFile9(path21.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
6061
6431
  `);
6062
6432
  const screens = [];
6063
6433
  const baselines = [];
@@ -6108,7 +6478,7 @@ async function runHandoff(target, opts) {
6108
6478
  }
6109
6479
  const png = await capture(page, screen);
6110
6480
  const name = baselineName(screen.id, state, width, mode);
6111
- await writeFile8(path20.join(out, "baseline", name), png);
6481
+ await writeFile9(path21.join(out, "baseline", name), png);
6112
6482
  baselines.push({ screen: screen.id, state, width, mode, file: `baseline/${name}` });
6113
6483
  }
6114
6484
  }
@@ -6137,7 +6507,7 @@ async function runHandoff(target, opts) {
6137
6507
  screens,
6138
6508
  baselines
6139
6509
  };
6140
- await writeFile8(path20.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
6510
+ await writeFile9(path21.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
6141
6511
  `);
6142
6512
  const mapFile = MAP_FILE;
6143
6513
  let mapWritten = false;
@@ -6156,7 +6526,7 @@ async function runHandoff(target, opts) {
6156
6526
  createdAt,
6157
6527
  mapFile
6158
6528
  });
6159
- await writeFile8(path20.join(out, "HANDOFF.md"), markdown);
6529
+ await writeFile9(path21.join(out, "HANDOFF.md"), markdown);
6160
6530
  return {
6161
6531
  out,
6162
6532
  url: pageUrl,
@@ -6197,7 +6567,7 @@ function modeList(text) {
6197
6567
  }
6198
6568
  return out;
6199
6569
  }
6200
- async function run17(argv, ctx) {
6570
+ async function run16(argv, ctx) {
6201
6571
  const args = parseCommandArgs(
6202
6572
  argv,
6203
6573
  { out: { type: "string" }, widths: { type: "string" }, modes: { type: "string" } },
@@ -6213,14 +6583,14 @@ async function run17(argv, ctx) {
6213
6583
  requireUrl(config);
6214
6584
  requireArt(config);
6215
6585
  } else if (config.art === void 0) {
6216
- warn2("no art host configured; skipping the /_themes proxy");
6586
+ warn("no art host configured; skipping the /_themes proxy");
6217
6587
  }
6218
6588
  const result = await runHandoff(target, {
6219
6589
  out,
6220
6590
  widths: widthList(stringValue(args, "widths")),
6221
6591
  modes: modeList(stringValue(args, "modes")),
6222
6592
  art: config.art ?? "",
6223
- chrome: process13.env.CHROME,
6593
+ chrome: process18.env.CHROME,
6224
6594
  onProgress: (line) => ctx.out.line(line)
6225
6595
  });
6226
6596
  const display = displayPath(result.out);
@@ -6246,11 +6616,11 @@ async function run17(argv, ctx) {
6246
6616
  }
6247
6617
 
6248
6618
  // src/commands/compare.ts
6249
- import process15 from "node:process";
6619
+ import process20 from "node:process";
6250
6620
 
6251
6621
  // 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";
6622
+ import { copyFile, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile10 } from "node:fs/promises";
6623
+ import path23 from "node:path";
6254
6624
 
6255
6625
  // src/diff.ts
6256
6626
  var MAX_Y = 35215;
@@ -6412,10 +6782,10 @@ function diffImages(before, after, opts = {}) {
6412
6782
 
6413
6783
  // src/native.ts
6414
6784
  import { spawn as spawn4 } from "node:child_process";
6415
- import { readFile as readFile14, unlink } from "node:fs/promises";
6785
+ import { readFile as readFile16, unlink as unlink2 } from "node:fs/promises";
6416
6786
  import { tmpdir as tmpdir4 } from "node:os";
6417
- import path21 from "node:path";
6418
- import process14 from "node:process";
6787
+ import path22 from "node:path";
6788
+ import process19 from "node:process";
6419
6789
  var FILE_PREFIX = "file:";
6420
6790
  var WINDOW_PREFIX = "window:";
6421
6791
  var TMUX_PREFIX = "tmux:";
@@ -6444,9 +6814,9 @@ function isNative(source) {
6444
6814
  }
6445
6815
  async function captureNative(source, mapDir, deps = {}) {
6446
6816
  if (source.kind === "file") {
6447
- const file = path21.resolve(mapDir, source.file);
6817
+ const file = path22.resolve(mapDir, source.file);
6448
6818
  try {
6449
- return new Uint8Array(await readFile14(file));
6819
+ return new Uint8Array(await readFile16(file));
6450
6820
  } catch {
6451
6821
  throw new UsageError(`no screenshot at ${source.file}, which is relative to rshow.map.json`);
6452
6822
  }
@@ -6494,8 +6864,8 @@ function noWindow(title, seen) {
6494
6864
  return new UsageError(`no window matches ${JSON.stringify(title)} (${list3}); use ${WINDOW_PREFIX} with an owner name or a title, or ${NATIVE_HINT}`);
6495
6865
  }
6496
6866
  var systemRunner = {
6497
- run: (bin2, args) => new Promise((resolve2, reject) => {
6498
- const child = spawn4(bin2, [...args], { stdio: ["ignore", "pipe", "pipe"] });
6867
+ run: (bin3, args) => new Promise((resolve2, reject) => {
6868
+ const child = spawn4(bin3, [...args], { stdio: ["ignore", "pipe", "pipe"] });
6499
6869
  let stdout = "";
6500
6870
  let stderr = "";
6501
6871
  child.stdout?.setEncoding("utf8");
@@ -6506,10 +6876,10 @@ var systemRunner = {
6506
6876
  child.stderr?.on("data", (chunk2) => {
6507
6877
  stderr += chunk2;
6508
6878
  });
6509
- child.on("error", (err) => reject(new UsageError(`cannot run ${bin2}: ${err.message}`)));
6879
+ child.on("error", (err) => reject(new UsageError(`cannot run ${bin3}: ${err.message}`)));
6510
6880
  child.on("close", (code) => {
6511
6881
  if (code !== 0) {
6512
- reject(new UsageError(stderr.trim() === "" ? `${bin2} exited ${code}` : `${bin2}: ${stderr.trim()}`));
6882
+ reject(new UsageError(stderr.trim() === "" ? `${bin3} exited ${code}` : `${bin3}: ${stderr.trim()}`));
6513
6883
  return;
6514
6884
  }
6515
6885
  resolve2(stdout);
@@ -6517,9 +6887,9 @@ var systemRunner = {
6517
6887
  })
6518
6888
  };
6519
6889
  async function captureWindow(title, deps = {}) {
6520
- const platform = deps.platform ?? process14.platform;
6890
+ const platform = deps.platform ?? process19.platform;
6521
6891
  const runner = deps.runner ?? systemRunner;
6522
- const scratch = path21.join(tmpdir4(), `rshow-window-${process14.pid}-${Date.now()}.png`);
6892
+ const scratch = path22.join(tmpdir4(), `rshow-window-${process19.pid}-${Date.now()}.png`);
6523
6893
  const wrap = (err) => err instanceof UsageError ? err : new UsageError(`window: ${title}: ${err instanceof Error ? err.message : String(err)}; ${NATIVE_HINT}`);
6524
6894
  try {
6525
6895
  if (platform === "darwin") {
@@ -6535,11 +6905,11 @@ async function captureWindow(title, deps = {}) {
6535
6905
  } else {
6536
6906
  throw new UsageError(`window: capture is not supported on ${platform}; ${NATIVE_HINT}`);
6537
6907
  }
6538
- return new Uint8Array(await readFile14(scratch));
6908
+ return new Uint8Array(await readFile16(scratch));
6539
6909
  } catch (err) {
6540
6910
  throw wrap(err);
6541
6911
  } finally {
6542
- await unlink(scratch).catch(() => {
6912
+ await unlink2(scratch).catch(() => {
6543
6913
  });
6544
6914
  }
6545
6915
  }
@@ -6576,23 +6946,23 @@ function extractPalette(image, k = 8) {
6576
6946
  const g = image.data[pos + 1];
6577
6947
  const b = image.data[pos + 2];
6578
6948
  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 });
6949
+ const bin3 = bins.get(key2);
6950
+ if (bin3 === void 0) bins.set(key2, { count: 1, r, g, b });
6581
6951
  else {
6582
- bin2.count += 1;
6583
- bin2.r += r;
6584
- bin2.g += g;
6585
- bin2.b += b;
6952
+ bin3.count += 1;
6953
+ bin3.r += r;
6954
+ bin3.g += g;
6955
+ bin3.b += b;
6586
6956
  }
6587
6957
  used += 1;
6588
6958
  }
6589
6959
  }
6590
6960
  if (used === 0) return [];
6591
6961
  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
6962
+ let centers = seeds.map((bin3) => ({ r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count }));
6963
+ const points = [...bins.values()].map((bin3) => ({
6964
+ color: { r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count },
6965
+ weight: bin3.count
6596
6966
  }));
6597
6967
  for (let round2 = 0; round2 < ROUNDS && centers.length > 1; round2 += 1) {
6598
6968
  const sums = centers.map(() => ({ r: 0, g: 0, b: 0, weight: 0 }));
@@ -7307,9 +7677,9 @@ function compareGrid(expected, actual) {
7307
7677
  }
7308
7678
  return { rows, differing, pass: differing.length === 0 };
7309
7679
  }
7310
- function captureTmux(target, bin2 = "tmux") {
7680
+ function captureTmux(target, bin3 = "tmux") {
7311
7681
  return new Promise((resolve2, reject) => {
7312
- const child = spawn5(bin2, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
7682
+ const child = spawn5(bin3, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
7313
7683
  let stdout = "";
7314
7684
  let stderr = "";
7315
7685
  child.stdout?.setEncoding("utf8");
@@ -7321,11 +7691,11 @@ function captureTmux(target, bin2 = "tmux") {
7321
7691
  stderr += chunk2;
7322
7692
  });
7323
7693
  child.on("error", (err) => {
7324
- reject(new ApiError(`cannot run ${bin2}: ${err.message}`));
7694
+ reject(new ApiError(`cannot run ${bin3}: ${err.message}`));
7325
7695
  });
7326
7696
  child.on("close", (code) => {
7327
7697
  if (code !== 0) {
7328
- reject(new ApiError(stderr.trim() === "" ? `${bin2} exited ${code}` : stderr.trim()));
7698
+ reject(new ApiError(stderr.trim() === "" ? `${bin3} exited ${code}` : stderr.trim()));
7329
7699
  return;
7330
7700
  }
7331
7701
  resolve2(stdout);
@@ -7349,9 +7719,9 @@ var EMPTY_MAP = { components: {}, tokens: {}, screens: {} };
7349
7719
  async function readJson(file, what) {
7350
7720
  let text;
7351
7721
  try {
7352
- text = await readFile15(file, "utf8");
7722
+ text = await readFile17(file, "utf8");
7353
7723
  } catch {
7354
- throw new UsageError(`${what} is missing ${path22.basename(file)} (run rshow handoff first)`);
7724
+ throw new UsageError(`${what} is missing ${path23.basename(file)} (run rshow handoff first)`);
7355
7725
  }
7356
7726
  try {
7357
7727
  return JSON.parse(text);
@@ -7360,13 +7730,13 @@ async function readJson(file, what) {
7360
7730
  }
7361
7731
  }
7362
7732
  async function readHandoff(dir) {
7363
- const spec = await readJson(path22.join(dir, "spec.json"), dir);
7733
+ const spec = await readJson(path23.join(dir, "spec.json"), dir);
7364
7734
  if (spec === null || typeof spec !== "object" || !Array.isArray(spec.screens)) {
7365
7735
  throw new UsageError(`${dir}/spec.json does not hold a handoff spec`);
7366
7736
  }
7367
7737
  let tokens = { light: [], dark: [] };
7368
7738
  try {
7369
- tokens = flattenDtcg(JSON.parse(await readFile15(path22.join(dir, "tokens.json"), "utf8")));
7739
+ tokens = flattenDtcg(JSON.parse(await readFile17(path23.join(dir, "tokens.json"), "utf8")));
7370
7740
  } catch {
7371
7741
  tokens = { light: [], dark: [] };
7372
7742
  }
@@ -7389,15 +7759,15 @@ function clipOf(info, screenId) {
7389
7759
  async function runCompare(opts) {
7390
7760
  const say = opts.onProgress ?? (() => {
7391
7761
  });
7392
- const dir = path22.resolve(opts.against);
7762
+ const dir = path23.resolve(opts.against);
7393
7763
  const { spec, tokens } = await readHandoff(dir);
7394
- const mapFile = opts.map === void 0 ? mapPath() : path22.resolve(opts.map);
7764
+ const mapFile = opts.map === void 0 ? mapPath() : path23.resolve(opts.map);
7395
7765
  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 });
7766
+ const mapDir = path23.dirname(mapFile);
7767
+ const reportDir = path23.join(dir, "compare-report");
7768
+ const imagesDir = path23.join(reportDir, "images");
7769
+ await mkdir8(imagesDir, { recursive: true });
7770
+ await mkdir8(path23.join(dir, "diff"), { recursive: true });
7401
7771
  const results = [];
7402
7772
  const collected = [];
7403
7773
  let skipped = 0;
@@ -7432,16 +7802,16 @@ async function runCompare(opts) {
7432
7802
  throw new UsageError(`tmux capture failed for ${target}: ${err instanceof Error ? err.message : String(err)}`);
7433
7803
  });
7434
7804
  const text = opts.tuiColors ? captured : stripAnsi(captured);
7435
- const baselineFile = path22.join(dir, "baseline", `${safeName(screen)}.txt`);
7805
+ const baselineFile = path23.join(dir, "baseline", `${safeName(screen)}.txt`);
7436
7806
  let expected;
7437
7807
  try {
7438
- expected = await readFile15(baselineFile, "utf8");
7808
+ expected = await readFile17(baselineFile, "utf8");
7439
7809
  } catch {
7440
7810
  expected = void 0;
7441
7811
  }
7442
7812
  if (expected === void 0) {
7443
- await mkdir7(path22.dirname(baselineFile), { recursive: true });
7444
- await writeFile9(baselineFile, text);
7813
+ await mkdir8(path23.dirname(baselineFile), { recursive: true });
7814
+ await writeFile10(baselineFile, text);
7445
7815
  results.push({
7446
7816
  screen,
7447
7817
  state: "default",
@@ -7486,12 +7856,12 @@ async function runCompare(opts) {
7486
7856
  const raw = (map.screens[screen] ?? "").trim();
7487
7857
  say(`capture: ${raw}`);
7488
7858
  let capture2;
7489
- let failure2;
7859
+ let failure;
7490
7860
  try {
7491
7861
  capture2 = decodePng(await captureNative(group.source, mapDir));
7492
7862
  } catch (err) {
7493
7863
  if (err instanceof UsageError) throw err;
7494
- failure2 = err instanceof Error ? err.message : String(err);
7864
+ failure = err instanceof Error ? err.message : String(err);
7495
7865
  }
7496
7866
  const screenSpec = spec.screens.find((item) => item.id === screen);
7497
7867
  const rubricFile = nativeName(screen);
@@ -7510,13 +7880,13 @@ async function runCompare(opts) {
7510
7880
  rubric: rubricFile,
7511
7881
  mismatches: []
7512
7882
  };
7513
- if (failure2 !== void 0 || capture2 === void 0) {
7514
- results.push({ ...base, note: `capture failed: ${failure2 ?? "no pixels"}` });
7883
+ if (failure !== void 0 || capture2 === void 0) {
7884
+ results.push({ ...base, note: `capture failed: ${failure ?? "no pixels"}` });
7515
7885
  continue;
7516
7886
  }
7517
7887
  let expectedImage;
7518
7888
  try {
7519
- expectedImage = decodePng(new Uint8Array(await readFile15(path22.join(dir, baseline.file))));
7889
+ expectedImage = decodePng(new Uint8Array(await readFile17(path23.join(dir, baseline.file))));
7520
7890
  } catch {
7521
7891
  results.push({ ...base, note: `missing baseline ${baseline.file}` });
7522
7892
  continue;
@@ -7528,10 +7898,10 @@ async function runCompare(opts) {
7528
7898
  threshold: opts.threshold
7529
7899
  });
7530
7900
  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`));
7901
+ await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
7902
+ await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
7903
+ await writeFile10(path23.join(imagesDir, `${stem}-capture.png`), encodePng(scaled));
7904
+ await copyFile(path23.join(dir, baseline.file), path23.join(imagesDir, `${stem}-baseline.png`));
7535
7905
  const expected = screenSpec === void 0 ? [] : normativeColors(screenSpec, tokens, baseline.state, baseline.mode);
7536
7906
  const palette = checkPalette(extractPalette(scaled), expected);
7537
7907
  const pixelOk = diff.ratio <= opts.nativeMaxDiffRatio;
@@ -7576,7 +7946,7 @@ async function runCompare(opts) {
7576
7946
  });
7577
7947
  }
7578
7948
  if (screenSpec !== void 0) {
7579
- await writeFile9(path22.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
7949
+ await writeFile10(path23.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
7580
7950
  `);
7581
7951
  }
7582
7952
  }
@@ -7633,12 +8003,12 @@ async function runCompare(opts) {
7633
8003
  }
7634
8004
  const clip = clipOf(info, screenId) ? await screenClip(page, screenId) : null;
7635
8005
  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);
8006
+ const actualFile = path23.join(imagesDir, `${stem}-actual.png`);
8007
+ await writeFile10(actualFile, actual);
8008
+ const baselineFile = path23.join(dir, baseline.file);
7639
8009
  let expected;
7640
8010
  try {
7641
- expected = new Uint8Array(await readFile15(baselineFile));
8011
+ expected = new Uint8Array(await readFile17(baselineFile));
7642
8012
  } catch {
7643
8013
  results.push({
7644
8014
  screen: baseline.screen,
@@ -7653,14 +8023,14 @@ async function runCompare(opts) {
7653
8023
  });
7654
8024
  continue;
7655
8025
  }
7656
- const baselineCopy = path22.join(imagesDir, `${stem}-baseline.png`);
8026
+ const baselineCopy = path23.join(imagesDir, `${stem}-baseline.png`);
7657
8027
  await copyFile(baselineFile, baselineCopy);
7658
8028
  const left = decodePng(expected);
7659
8029
  const right = decodePng(actual);
7660
8030
  const diff = diffImages(left, right, { threshold: opts.threshold });
7661
8031
  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);
8032
+ await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
8033
+ await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
7664
8034
  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
8035
  results.push({
7666
8036
  screen: baseline.screen,
@@ -7729,8 +8099,8 @@ async function runCompare(opts) {
7729
8099
  const checked = results.filter((item) => item.source !== "").length;
7730
8100
  const review = results.filter((item) => item.status === "review").length;
7731
8101
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
7732
- await writeFile9(
7733
- path22.join(reportDir, "report.json"),
8102
+ await writeFile10(
8103
+ path23.join(reportDir, "report.json"),
7734
8104
  `${JSON.stringify(
7735
8105
  {
7736
8106
  schema: 1,
@@ -7766,8 +8136,8 @@ async function runCompare(opts) {
7766
8136
  )}
7767
8137
  `
7768
8138
  );
7769
- await writeFile9(
7770
- path22.join(reportDir, "index.html"),
8139
+ await writeFile10(
8140
+ path23.join(reportDir, "index.html"),
7771
8141
  reportHtml({
7772
8142
  spec,
7773
8143
  results,
@@ -7888,7 +8258,7 @@ function ratioValue(text, name, fallback) {
7888
8258
  }
7889
8259
  return value;
7890
8260
  }
7891
- async function run18(argv, ctx) {
8261
+ async function run17(argv, ctx) {
7892
8262
  const args = parseCommandArgs(
7893
8263
  argv,
7894
8264
  {
@@ -7918,7 +8288,7 @@ async function run18(argv, ctx) {
7918
8288
  DEFAULT_NATIVE_MAX_DIFF_RATIO
7919
8289
  ),
7920
8290
  tuiColors: boolValue(args, "tui-colors"),
7921
- chrome: process15.env.CHROME,
8291
+ chrome: process20.env.CHROME,
7922
8292
  onProgress: (line) => ctx.out.line(line)
7923
8293
  });
7924
8294
  const failing = result.results.filter((item) => !item.pass);
@@ -7966,8 +8336,8 @@ async function run18(argv, ctx) {
7966
8336
  }
7967
8337
 
7968
8338
  // 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";
8339
+ import { mkdir as mkdir9, readFile as readFile18, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
8340
+ import path24 from "node:path";
7971
8341
  var USAGE = {
7972
8342
  publish: PUBLISH_USAGE,
7973
8343
  check: CHECK_USAGE,
@@ -8246,7 +8616,7 @@ function heading(label) {
8246
8616
  async function themeTokensPage() {
8247
8617
  const dir = await builtinThemeDir("default");
8248
8618
  if (dir === void 0) throw new Error("no built-in theme: default");
8249
- const css = await readFile16(path23.join(dir, "tokens.css"), "utf8");
8619
+ const css = await readFile18(path24.join(dir, "tokens.css"), "utf8");
8250
8620
  const end = css.indexOf("*/");
8251
8621
  const roles = parseRoles(end < 0 ? css : css.slice(0, end));
8252
8622
  const sections = parseSections(rootBlock(css));
@@ -8294,45 +8664,45 @@ function exitCodesPage() {
8294
8664
  `;
8295
8665
  }
8296
8666
  async function generate(out) {
8297
- const dir = path23.join(out, "commands");
8667
+ const dir = path24.join(out, "commands");
8298
8668
  await rm7(dir, { recursive: true, force: true });
8299
- await mkdir8(dir, { recursive: true });
8669
+ await mkdir9(dir, { recursive: true });
8300
8670
  const rows = commandRows();
8301
8671
  const files = [];
8302
8672
  for (const row of rows) {
8303
- const file = path23.join(dir, row.file);
8304
- await writeFile10(file, commandPage(row, rows));
8673
+ const file = path24.join(dir, row.file);
8674
+ await writeFile11(file, commandPage(row, rows));
8305
8675
  files.push(file);
8306
8676
  }
8307
- const index = path23.join(dir, "index.md");
8308
- await writeFile10(index, indexPage(rows));
8677
+ const index = path24.join(dir, "index.md");
8678
+ await writeFile11(index, indexPage(rows));
8309
8679
  files.push(index);
8310
8680
  for (const [name, text] of [
8311
8681
  ["theme-tokens.md", await themeTokensPage()],
8312
8682
  ["exit-codes.md", exitCodesPage()]
8313
8683
  ]) {
8314
- const file = path23.join(out, name);
8315
- await writeFile10(file, text);
8684
+ const file = path24.join(out, name);
8685
+ await writeFile11(file, text);
8316
8686
  files.push(file);
8317
8687
  }
8318
- return { commands: rows.length, files: files.map((file) => path23.relative(out, file)).sort() };
8688
+ return { commands: rows.length, files: files.map((file) => path24.relative(out, file)).sort() };
8319
8689
  }
8320
- async function run19(argv, ctx) {
8690
+ async function run18(argv, ctx) {
8321
8691
  const args = parseCommandArgs(argv, { out: { type: "string" } }, 0, 0, DOCS_USAGE);
8322
8692
  const out = stringValue(args, "out");
8323
8693
  if (out === void 0) throw new UsageError(`docs needs --out <dir> (try: ${usageLine(DOCS_USAGE)})`);
8324
8694
  const result = await generate(out);
8325
8695
  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")}`);
8696
+ ctx.out.line(`wrote ${result.commands} command pages and the index under ${path24.join(out, "commands")}`);
8697
+ ctx.out.line(`wrote ${path24.join(out, "theme-tokens.md")}`);
8698
+ ctx.out.line(`wrote ${path24.join(out, "exit-codes.md")}`);
8329
8699
  }
8330
8700
 
8331
8701
  // src/commands/login.ts
8332
- import process16 from "node:process";
8702
+ import process21 from "node:process";
8333
8703
  async function hiddenLine() {
8334
- process16.stderr.write("token: ");
8335
- const stdin = process16.stdin;
8704
+ process21.stderr.write("token: ");
8705
+ const stdin = process21.stdin;
8336
8706
  stdin.setRawMode(true);
8337
8707
  stdin.resume();
8338
8708
  stdin.setEncoding("utf8");
@@ -8342,7 +8712,7 @@ async function hiddenLine() {
8342
8712
  stdin.off("data", onData);
8343
8713
  stdin.setRawMode(false);
8344
8714
  stdin.pause();
8345
- process16.stderr.write("\n");
8715
+ process21.stderr.write("\n");
8346
8716
  if (err === void 0) resolve2(text);
8347
8717
  else reject(err);
8348
8718
  };
@@ -8365,16 +8735,16 @@ async function hiddenLine() {
8365
8735
  }
8366
8736
  async function pipedText() {
8367
8737
  const chunks = [];
8368
- for await (const chunk2 of process16.stdin) chunks.push(chunk2);
8738
+ for await (const chunk2 of process21.stdin) chunks.push(chunk2);
8369
8739
  return Buffer.concat(chunks).toString("utf8").trim();
8370
8740
  }
8371
8741
  async function tokenFromStdin() {
8372
- const token = process16.stdin.isTTY === true ? await hiddenLine() : await pipedText();
8742
+ const token = process21.stdin.isTTY === true ? await hiddenLine() : await pipedText();
8373
8743
  if (token === "") throw new UsageError("expected a token on stdin");
8374
8744
  if (!token.startsWith(TOKEN_PREFIX)) throw new UsageError(`that is not an rshow token: it must start with ${TOKEN_PREFIX}`);
8375
8745
  return token;
8376
8746
  }
8377
- async function run20(argv, ctx) {
8747
+ async function run19(argv, ctx, platform = process21.platform) {
8378
8748
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGIN_USAGE);
8379
8749
  const settings = await ctx.config();
8380
8750
  const given = (stringValue(args, "url") ?? "").trim();
@@ -8383,48 +8753,60 @@ async function run20(argv, ctx) {
8383
8753
  const me = (await new Client(url, token).send("GET", "/api/me")).body;
8384
8754
  const email = typeof me?.email === "string" ? me.email : "";
8385
8755
  const scope = typeof me?.scope === "string" ? me.scope : "";
8386
- await save(url, token);
8756
+ const store2 = await choose(process21.env, platform);
8757
+ await store2.save(url, token);
8758
+ if (await store2.read(url) !== token) {
8759
+ throw new ApiError(`cannot save the token in ${store2.description}`);
8760
+ }
8387
8761
  ctx.out.data({ email, scope, url });
8388
- ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}`);
8762
+ ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}, saved in ${store2.description}`);
8389
8763
  }
8390
8764
 
8391
8765
  // src/commands/logout.ts
8392
- async function run21(argv, ctx) {
8766
+ import process22 from "node:process";
8767
+ async function run20(argv, ctx, platform = process22.platform) {
8393
8768
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
8394
8769
  const settings = await ctx.config();
8395
8770
  const given = (stringValue(args, "url") ?? "").trim();
8396
8771
  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}`);
8772
+ const removed = [];
8773
+ for (const store2 of await every(process22.env, platform)) {
8774
+ if (await store2.remove(url)) removed.push(store2.description);
8775
+ }
8776
+ if (removed.length === 0) {
8777
+ ctx.out.line(`no saved token for ${url}`);
8778
+ return;
8779
+ }
8780
+ ctx.out.line(`logged out of ${url}, removed from ${removed.join(" and ")}`);
8399
8781
  }
8400
8782
 
8401
8783
  // src/main.ts
8402
8784
  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 },
8785
+ publish: { usage: PUBLISH_USAGE, run },
8786
+ check: { usage: CHECK_USAGE, run: run2 },
8787
+ handoff: { usage: HANDOFF_USAGE, run: run16 },
8788
+ compare: { usage: COMPARE_USAGE, run: run17 },
8789
+ preview: { usage: PREVIEW_USAGE, run: run3 },
8790
+ config: { usage: CONFIG_USAGE, run: run4 },
8791
+ init: { usage: INIT_USAGE, run: run5 },
8792
+ doctor: { usage: DOCTOR_USAGE, run: run6 },
8793
+ theme: { usage: THEME_USAGE, run: run7 },
8794
+ skills: { usage: SKILLS_USAGE, run: run8 },
8795
+ project: { usage: PROJECT_USAGE, run: run14 },
8796
+ tag: { usage: TAG_USAGE, run: run15 },
8797
+ list: { usage: LIST_USAGE, run: run9 },
8798
+ show: { usage: SHOW_USAGE, run: run10 },
8799
+ share: { usage: SHARE_USAGE, run: run11 },
8800
+ pin: { usage: PIN_USAGE, run: run12 },
8801
+ delete: { usage: DELETE_USAGE, run: run13 },
8802
+ login: { usage: LOGIN_USAGE, run: run19 },
8803
+ logout: { usage: LOGOUT_USAGE, run: run20 },
8422
8804
  // Kept out of the general help: the docs site runs it, a person rarely does.
8423
- docs: { usage: DOCS_USAGE, run: run19 }
8805
+ docs: { usage: DOCS_USAGE, run: run18 }
8424
8806
  };
8425
8807
  function fail(err) {
8426
8808
  const message = err instanceof Error ? err.message : String(err);
8427
- process17.stderr.write(`rshow: ${message}
8809
+ process23.stderr.write(`rshow: ${message}
8428
8810
  `);
8429
8811
  if (err instanceof UsageError) return EXIT.usage;
8430
8812
  if (err instanceof CheckError) return EXIT.check;
@@ -8442,17 +8824,17 @@ async function main(argv) {
8442
8824
  const spec = name === void 0 ? void 0 : COMMANDS[name];
8443
8825
  if (wantsVersion) {
8444
8826
  const suffix = await isBinary() ? " (binary)" : "";
8445
- process17.stdout.write(`rshow ${VERSION}${suffix}
8827
+ process23.stdout.write(`rshow ${VERSION}${suffix}
8446
8828
  `);
8447
8829
  return EXIT.ok;
8448
8830
  }
8449
8831
  if (wantsHelp) {
8450
- process17.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
8832
+ process23.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
8451
8833
  `);
8452
8834
  return EXIT.ok;
8453
8835
  }
8454
8836
  if (name === void 0) {
8455
- process17.stderr.write(`${GENERAL_USAGE}
8837
+ process23.stderr.write(`${GENERAL_USAGE}
8456
8838
  `);
8457
8839
  return EXIT.usage;
8458
8840
  }
@@ -8463,12 +8845,12 @@ async function main(argv) {
8463
8845
  const rest = [...args.slice(0, index), ...args.slice(index + 1)];
8464
8846
  const configs = /* @__PURE__ */ new Map();
8465
8847
  const clients = /* @__PURE__ */ new Map();
8466
- const keyOf = (dir) => dir === void 0 ? "" : path24.resolve(dir);
8848
+ const keyOf = (dir) => dir === void 0 ? "" : path25.resolve(dir);
8467
8849
  const configFor = (dir) => {
8468
8850
  const key2 = keyOf(dir);
8469
8851
  let pending = configs.get(key2);
8470
8852
  if (pending === void 0) {
8471
- pending = loadConfig(process17.env, dir);
8853
+ pending = loadConfig(process23.env, dir);
8472
8854
  configs.set(key2, pending);
8473
8855
  }
8474
8856
  return pending;
@@ -8481,7 +8863,7 @@ async function main(argv) {
8481
8863
  if (cached !== void 0) return cached;
8482
8864
  const settings = await configFor(dir);
8483
8865
  const url = requireUrl(settings);
8484
- const client = new Client(url, await readToken(process17.env, url));
8866
+ const client = new Client(url, await readToken(process23.env, url));
8485
8867
  clients.set(key2, client);
8486
8868
  return client;
8487
8869
  },
@@ -8490,11 +8872,11 @@ async function main(argv) {
8490
8872
  const code = await spec.run(rest, ctx);
8491
8873
  return code ?? EXIT.ok;
8492
8874
  }
8493
- main(process17.argv.slice(2)).then(
8875
+ main(process23.argv.slice(2)).then(
8494
8876
  (code) => {
8495
- process17.exitCode = code;
8877
+ process23.exitCode = code;
8496
8878
  },
8497
8879
  (err) => {
8498
- process17.exitCode = fail(err);
8880
+ process23.exitCode = fail(err);
8499
8881
  }
8500
8882
  );