@kecan0406/ttheme 0.1.3 → 0.1.5

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/bin/ttheme.js CHANGED
@@ -4098,6 +4098,9 @@ var require_src = __commonJS((exports, module) => {
4098
4098
  module.exports = { cursor, scroll, erase, beep };
4099
4099
  });
4100
4100
 
4101
+ // src/bin.ts
4102
+ import { isMainThread as isMainThread2 } from "node:worker_threads";
4103
+
4101
4104
  // node_modules/commander/lib/error.js
4102
4105
  class CommanderError extends Error {
4103
4106
  constructor(exitCode, code, message) {
@@ -6144,7 +6147,7 @@ var program = new Command;
6144
6147
  // package.json
6145
6148
  var package_default = {
6146
6149
  name: "@kecan0406/ttheme",
6147
- version: "0.1.3",
6150
+ version: "0.1.5",
6148
6151
  type: "module",
6149
6152
  bin: {
6150
6153
  ttheme: "bin/ttheme.js"
@@ -6790,8 +6793,8 @@ function zshrcBlock(tthemeDir) {
6790
6793
  var CONFIG_HEADER = "# ttheme settings — exported variables win over this file";
6791
6794
  var CONFIG_SETTINGS = {
6792
6795
  TTHEME_TAB_PALETTE: {
6793
- doc: "# new tabs: seq rotates through the palettes, off keeps the configured terminal theme (default seq)",
6794
- default: "seq"
6796
+ doc: "# new tabs: off keeps the configured terminal theme, seq rotates through the palettes (default off)",
6797
+ default: "off"
6795
6798
  },
6796
6799
  TTHEME_ANNOUNCE: {
6797
6800
  doc: '# the palette notice under "Last login:": 1 shows it, 0 silences it (default 1)',
@@ -7042,12 +7045,12 @@ ${themes.length} themes (${rotation(themes).length} in the new-tab rotation) ->
7042
7045
 
7043
7046
  // src/find.ts
7044
7047
  import { spawn as spawn2 } from "node:child_process";
7045
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
7048
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync6, rmSync as rmSync5, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
7046
7049
  import { tmpdir } from "node:os";
7047
- import { dirname as dirname3, join as join7 } from "node:path";
7050
+ import { dirname as dirname4, join as join7 } from "node:path";
7048
7051
 
7049
7052
  // src/backdrop.ts
7050
- import { mkdirSync, readdirSync as readdirSync2, rmSync as rmSync2, statSync, writeFileSync } from "node:fs";
7053
+ import { mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, statSync, utimesSync, writeFileSync } from "node:fs";
7051
7054
  import { join as join3 } from "node:path";
7052
7055
 
7053
7056
  // src/png.ts
@@ -7231,7 +7234,11 @@ var FILL = { width: 2560, height: 1550 };
7231
7234
  var FIGURE = 2560;
7232
7235
  var FAINT = 0.1;
7233
7236
  var HEADROOM = 0.15;
7237
+ var KEEP = 0.55;
7238
+ var PLACE = 0.65;
7239
+ var STANDS = 3;
7234
7240
  var PEAK = luminance(mix("#19161e", "#9b86c8", 0.2));
7241
+ var SHELF = "shelf";
7235
7242
  function mix(a, b, t) {
7236
7243
  const from = rgb(a);
7237
7244
  const to = rgb(b);
@@ -7321,11 +7328,14 @@ function figureBox(image) {
7321
7328
  function headAnchor(box) {
7322
7329
  return Math.min(0.5, (HEADROOM + 0.5) * (box.w * FILL.height / FILL.width / box.h));
7323
7330
  }
7331
+ function band(box, h, anchor) {
7332
+ const top = Math.min(Math.max(0, anchor * box.h - h / 2), box.h - h);
7333
+ return { x: box.x, y: box.y + top, w: box.w, h };
7334
+ }
7324
7335
  function fillBox(box, width, height, anchor) {
7325
7336
  const h = box.w * height / width;
7326
7337
  if (h <= box.h) {
7327
- const top = Math.min(Math.max(0, anchor * box.h - h / 2), box.h - h);
7328
- return { x: box.x, y: box.y + top, w: box.w, h };
7338
+ return band(box, h, anchor);
7329
7339
  }
7330
7340
  const w = box.h * width / height;
7331
7341
  return { x: box.x + (box.w - w) / 2, y: box.y, w, h: box.h };
@@ -7342,20 +7352,66 @@ function figure(image, box) {
7342
7352
  const k = Math.min(1, FIGURE / Math.max(box.w, box.h));
7343
7353
  return resample(image, box, Math.round(box.w * k), Math.round(box.h * k));
7344
7354
  }
7345
- function fill(image, box, anchor) {
7346
- return resample(image, fillBox(box, FILL.width, FILL.height, anchor), FILL.width, FILL.height);
7355
+ function fillFrame(box, width, height, anchor, clear) {
7356
+ const keep = KEEP * box.h;
7357
+ const span = box.w * height / width;
7358
+ if (clear < STANDS || span > keep) {
7359
+ return { crop: fillBox(box, width, height, anchor), at: { x: 0, y: 0, w: width, h: height }, inset: false };
7360
+ }
7361
+ const short = 1 - span / keep;
7362
+ const h = height * (1 - HEADROOM * short);
7363
+ const w = box.w * h / keep;
7364
+ return {
7365
+ crop: band(box, keep, anchor),
7366
+ at: { x: (width - w) * PLACE, y: height - h, w, h },
7367
+ inset: true
7368
+ };
7369
+ }
7370
+ function paint(image, frame, width, height) {
7371
+ const w = Math.max(1, Math.round(frame.at.w));
7372
+ const h = Math.max(1, Math.round(frame.at.h));
7373
+ const cut = resample(image, frame.crop, w, h);
7374
+ const out = new Uint8Array(width * height * 4);
7375
+ const ox = Math.round(frame.at.x);
7376
+ const oy = Math.round(frame.at.y);
7377
+ for (let y = 0;y < h; y++) {
7378
+ const ty = oy + y;
7379
+ const from = Math.max(0, -ox);
7380
+ const to = Math.min(w, width - ox);
7381
+ if (ty < 0 || ty >= height || to <= from) {
7382
+ continue;
7383
+ }
7384
+ out.set(cut.data.subarray((y * w + from) * 4, (y * w + to) * 4), (ty * width + ox + from) * 4);
7385
+ }
7386
+ return { width, height, data: out };
7387
+ }
7388
+ function fill(image, box, anchor, clear) {
7389
+ const frame = fillFrame(box, FILL.width, FILL.height, anchor, clear);
7390
+ return frame.inset ? paint(image, frame, FILL.width, FILL.height) : resample(image, frame.crop, FILL.width, FILL.height);
7347
7391
  }
7348
- function tryOn(image, colors, tone, width, height) {
7392
+ function tryOn(image, colors, tone, width, height, clear) {
7349
7393
  const box = figureBox(image);
7350
- const view = coverBox(fillBox(box, FILL.width, FILL.height, headAnchor(box)), width, height);
7351
- return composite(tint(resample(image, view, width, height), colors.background, tone.color), colors.background, tone.opacity);
7394
+ const frame = fillFrame(box, FILL.width, FILL.height, headAnchor(box), clear);
7395
+ let shown;
7396
+ if (frame.inset) {
7397
+ const view = coverBox({ x: 0, y: 0, w: FILL.width, h: FILL.height }, width, height);
7398
+ const k = width / view.w;
7399
+ shown = paint(image, {
7400
+ ...frame,
7401
+ at: { x: (frame.at.x - view.x) * k, y: (frame.at.y - view.y) * k, w: frame.at.w * k, h: frame.at.h * k }
7402
+ }, width, height);
7403
+ } else {
7404
+ shown = resample(image, coverBox(frame.crop, width, height), width, height);
7405
+ }
7406
+ return composite(tint(shown, colors.background, tone.color), colors.background, tone.opacity);
7352
7407
  }
7353
7408
  function backgroundsDir(configHome) {
7354
7409
  return join3(configHome, "ttheme", "backgrounds");
7355
7410
  }
7356
- function backdropConf(name, fillPath, opacity, from) {
7411
+ function backdropConf(name, fillPath, opacity, from, key) {
7357
7412
  return [
7358
7413
  ...from ? [`# from ${from}`] : [],
7414
+ ...key ? [`# image ${key}`] : [],
7359
7415
  `background-image = ${fillPath}`,
7360
7416
  "background-image-fit = cover",
7361
7417
  "background-image-position = center",
@@ -7373,26 +7429,139 @@ function clearBackdrop(dir, name) {
7373
7429
  }
7374
7430
  }
7375
7431
  }
7432
+ function imageKey(origin) {
7433
+ return `${origin.site}_${origin.id}`;
7434
+ }
7435
+ function shelfDir(dir, name) {
7436
+ return join3(dir, SHELF, name);
7437
+ }
7438
+ function belongs(file, name) {
7439
+ const rest = file.slice(name.length);
7440
+ return file.startsWith(name) && (rest === ".png" || rest === ".conf" || /^@fill-\d+\.png$/.test(rest));
7441
+ }
7442
+ function activeKey(dir, name) {
7443
+ try {
7444
+ return /^# image (\S+)$/m.exec(readFileSync2(join3(dir, `${name}.conf`), "utf8"))?.[1];
7445
+ } catch {
7446
+ return;
7447
+ }
7448
+ }
7449
+ function shelved(dir, name) {
7450
+ try {
7451
+ return readdirSync2(shelfDir(dir, name));
7452
+ } catch {
7453
+ return [];
7454
+ }
7455
+ }
7456
+ function imageKeys(dir, name) {
7457
+ const active = activeKey(dir, name);
7458
+ return [...active ? [active] : [], ...shelved(dir, name)].sort();
7459
+ }
7460
+ function shelve(dir, name) {
7461
+ const key = activeKey(dir, name);
7462
+ if (!key) {
7463
+ return;
7464
+ }
7465
+ const to = join3(shelfDir(dir, name), key);
7466
+ rmSync2(to, { recursive: true, force: true });
7467
+ mkdirSync(to, { recursive: true });
7468
+ for (const file of readdirSync2(dir)) {
7469
+ if (belongs(file, name)) {
7470
+ renameSync(join3(dir, file), join3(to, file.slice(name.length)));
7471
+ }
7472
+ }
7473
+ clearBackdrop(dir, name);
7474
+ }
7475
+ function unshelve(dir, name, key) {
7476
+ const from = join3(shelfDir(dir, name), key);
7477
+ for (const file of readdirSync2(from)) {
7478
+ renameSync(join3(from, file), join3(dir, `${name}${file}`));
7479
+ }
7480
+ rmSync2(from, { recursive: true });
7481
+ }
7482
+ function originalOf(dir, name, key) {
7483
+ try {
7484
+ return readdirSync2(join3(dir, "originals")).find((file) => file.startsWith(`${name}-${key}.`));
7485
+ } catch {
7486
+ return;
7487
+ }
7488
+ }
7489
+ function newest(dir, name, key) {
7490
+ const file = originalOf(dir, name, key);
7491
+ if (file) {
7492
+ const now = new Date;
7493
+ utimesSync(join3(dir, "originals", file), now, now);
7494
+ }
7495
+ }
7496
+ function switchImage(configHome, name, step) {
7497
+ const dir = backgroundsDir(configHome);
7498
+ const keys = imageKeys(dir, name);
7499
+ const now = activeKey(dir, name);
7500
+ if (!now || keys.length < 2) {
7501
+ throw new Error(`${name} has no other image`);
7502
+ }
7503
+ const at2 = (keys.indexOf(now) + step + keys.length) % keys.length;
7504
+ const key = keys[at2];
7505
+ shelve(dir, name);
7506
+ unshelve(dir, name, key);
7507
+ newest(dir, name, key);
7508
+ return { key, at: at2 + 1, of: keys.length };
7509
+ }
7510
+ function dropImage(configHome, name) {
7511
+ const dir = backgroundsDir(configHome);
7512
+ const files = readdirSync2(dir).filter((file) => belongs(file, name));
7513
+ if (files.length === 0) {
7514
+ throw new Error(`${name} has no image`);
7515
+ }
7516
+ const keys = imageKeys(dir, name);
7517
+ const now = activeKey(dir, name);
7518
+ const original = now && originalOf(dir, name, now);
7519
+ if (original) {
7520
+ rmSync2(join3(dir, "originals", original));
7521
+ }
7522
+ for (const file of files) {
7523
+ rmSync2(join3(dir, file));
7524
+ }
7525
+ clearBackdrop(dir, name);
7526
+ const rest = keys.filter((key) => key !== now);
7527
+ const next = rest[(now ? keys.indexOf(now) : 0) % rest.length];
7528
+ if (next) {
7529
+ unshelve(dir, name, next);
7530
+ newest(dir, name, next);
7531
+ }
7532
+ return { key: now, left: rest.length };
7533
+ }
7534
+ function retire(dir, name, key) {
7535
+ if (activeKey(dir, name) !== key) {
7536
+ shelve(dir, name);
7537
+ }
7538
+ if (key) {
7539
+ rmSync2(join3(shelfDir(dir, name), key), { recursive: true, force: true });
7540
+ }
7541
+ clearBackdrop(dir, name);
7542
+ }
7376
7543
  function installBackdrop(configHome, colors, tone, image, original) {
7377
7544
  const dir = backgroundsDir(configHome);
7378
7545
  const name = colors.name;
7546
+ const key = imageKey(original);
7379
7547
  mkdirSync(join3(dir, "originals"), { recursive: true });
7380
- clearBackdrop(dir, name);
7548
+ retire(dir, name, key);
7381
7549
  const box = figureBox(image);
7382
7550
  const anchor = headAnchor(box);
7551
+ const clear = transparency(image);
7383
7552
  const whole = join3(dir, `${name}.png`);
7384
7553
  const fillPath = join3(dir, `${name}@fill-${Math.round(anchor * 100)}.png`);
7385
7554
  const conf = join3(dir, `${name}.conf`);
7386
7555
  const source = join3(dir, "originals", `${name}-${original.site}_${original.id}.${original.ext}`);
7387
7556
  writeFileSync(whole, encodePng(tint(figure(image, box), colors.background, tone.color)));
7388
- writeFileSync(fillPath, encodePng(tint(fill(image, box, anchor), colors.background, tone.color)));
7389
- writeFileSync(conf, backdropConf(name, fillPath, tone.opacity, original.from));
7557
+ writeFileSync(fillPath, encodePng(tint(fill(image, box, anchor, clear), colors.background, tone.color)));
7558
+ writeFileSync(conf, backdropConf(name, fillPath, tone.opacity, original.from, key));
7390
7559
  writeFileSync(source, original.bytes);
7391
7560
  return [whole, fillPath, conf, source];
7392
7561
  }
7393
7562
  function origins(configHome) {
7394
7563
  const dir = join3(backgroundsDir(configHome), "originals");
7395
- const newest = new Map;
7564
+ const newest2 = new Map;
7396
7565
  let files;
7397
7566
  try {
7398
7567
  files = readdirSync2(dir);
@@ -7406,21 +7575,23 @@ function origins(configHome) {
7406
7575
  continue;
7407
7576
  }
7408
7577
  const at2 = statSync(join3(dir, file)).mtimeMs;
7409
- const known = newest.get(name);
7578
+ const known = newest2.get(name);
7410
7579
  if (!known || at2 > known.at) {
7411
- newest.set(name, { site, id: Number(id), at: at2 });
7580
+ newest2.set(name, { site, id: Number(id), at: at2 });
7412
7581
  }
7413
7582
  }
7414
- return new Map([...newest].map(([name, { site, id }]) => [name, { site, id }]));
7583
+ return new Map([...newest2].map(([name, { site, id }]) => [name, { site, id }]));
7415
7584
  }
7416
7585
 
7417
7586
  // src/booru.ts
7587
+ import { readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2 } from "node:fs";
7418
7588
  import { setDefaultAutoSelectFamilyAttemptTimeout } from "node:net";
7419
7589
  import { homedir } from "node:os";
7420
7590
  import { join as join4 } from "node:path";
7421
7591
  import { setTimeout as sleep } from "node:timers/promises";
7422
7592
  var PAGE = 100;
7423
7593
  var MAX_PIXELS = 25000000;
7594
+ var CACHE_BYTES = 512 * 1024 * 1024;
7424
7595
  var RATINGS = ["safe", "questionable", "explicit"];
7425
7596
  var BLOCKS = ["nudity", "underwear"];
7426
7597
  var EXPOSED = {
@@ -7435,10 +7606,18 @@ var CONNECT = 3000;
7435
7606
  var MAX_WAIT = 60000;
7436
7607
  var TRIES = 3;
7437
7608
  var AGENT = `ttheme/${package_default.version} (+${package_default.homepage})`;
7609
+ var HELD = ["orig", "tile", "thumb", "cut"];
7438
7610
  var paused = new Map;
7439
7611
  var HOSTS = hostMap(process.env.TTHEME_FIND_HOSTS);
7440
7612
  var CUTOUTS = cutoutMap(process.env.TTHEME_FIND_CUTOUTS);
7441
7613
  setDefaultAutoSelectFamilyAttemptTimeout(CONNECT);
7614
+ function listing(dir) {
7615
+ try {
7616
+ return readdirSync3(dir);
7617
+ } catch {
7618
+ return [];
7619
+ }
7620
+ }
7442
7621
  function absolute(url) {
7443
7622
  return url.startsWith("//") ? `https:${url}` : url;
7444
7623
  }
@@ -7762,8 +7941,38 @@ function mates(owners) {
7762
7941
  }
7763
7942
  return byOwner;
7764
7943
  }
7944
+ function cacheRoot() {
7945
+ return join4(process.env.XDG_CACHE_HOME ?? join4(homedir(), ".cache"), "ttheme");
7946
+ }
7765
7947
  function cacheDir(site) {
7766
- return join4(process.env.XDG_CACHE_HOME ?? join4(homedir(), ".cache"), "ttheme", site.key);
7948
+ return join4(cacheRoot(), site.key);
7949
+ }
7950
+ function sweepCache(limit = CACHE_BYTES, root2 = cacheRoot()) {
7951
+ const files = [];
7952
+ let total = 0;
7953
+ for (const site of listing(root2)) {
7954
+ for (const kind of HELD) {
7955
+ const dir = join4(root2, site, kind);
7956
+ for (const name of listing(dir)) {
7957
+ const path2 = join4(dir, name);
7958
+ try {
7959
+ const { size, mtimeMs } = statSync2(path2);
7960
+ files.push({ path: path2, size, at: mtimeMs });
7961
+ total += size;
7962
+ } catch {}
7963
+ }
7964
+ }
7965
+ }
7966
+ files.sort((a, b) => a.at - b.at);
7967
+ for (const file of files) {
7968
+ if (total <= limit) {
7969
+ return;
7970
+ }
7971
+ try {
7972
+ rmSync3(file.path, { force: true });
7973
+ total -= file.size;
7974
+ } catch {}
7975
+ }
7767
7976
  }
7768
7977
  function retryAfter(value, now) {
7769
7978
  if (value !== null && /^\s*\d+\s*$/.test(value)) {
@@ -7846,7 +8055,7 @@ async function fetchBytes(site, url, signal, progress) {
7846
8055
  }
7847
8056
 
7848
8057
  // src/catalog.ts
7849
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
8058
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
7850
8059
  import { dirname, join as join5 } from "node:path";
7851
8060
  var REGISTRY_URL = "https://kecan0406.github.io/ttheme/manifest.json";
7852
8061
  function catalogPath(configHome) {
@@ -7863,7 +8072,7 @@ function readCatalog(configHome) {
7863
8072
  if (!existsSync(path2)) {
7864
8073
  throw new Error(`no catalog at ${path2} — run \`ttheme init\` first`);
7865
8074
  }
7866
- return parseCatalog(readFileSync2(path2, "utf8"));
8075
+ return parseCatalog(readFileSync3(path2, "utf8"));
7867
8076
  }
7868
8077
  function parseCatalog(source) {
7869
8078
  let doc;
@@ -7925,7 +8134,6 @@ function search(palettes, query) {
7925
8134
 
7926
8135
  // src/cutout.ts
7927
8136
  import { spawn } from "node:child_process";
7928
- import { readFileSync as readFileSync3 } from "node:fs";
7929
8137
  var TIMEOUT2 = 30000;
7930
8138
  var LEAST = 3;
7931
8139
  var MOST = 97;
@@ -7967,16 +8175,12 @@ function removeBackground(input, output, signal) {
7967
8175
  });
7968
8176
  child.on("error", reject);
7969
8177
  child.on("close", (code) => {
7970
- if (code !== 0) {
7971
- reject(new Error(failure.trim().split(`
7972
- `).at(-1) || `osascript exited ${code}`));
8178
+ if (code === 0) {
8179
+ resolve();
7973
8180
  return;
7974
8181
  }
7975
- try {
7976
- resolve(decodePng(new Uint8Array(readFileSync3(output))));
7977
- } catch (error) {
7978
- reject(error);
7979
- }
8182
+ reject(new Error(failure.trim().split(`
8183
+ `).at(-1) || `osascript exited ${code}`));
7980
8184
  });
7981
8185
  });
7982
8186
  }
@@ -8108,9 +8312,10 @@ function dims(tile) {
8108
8312
  }
8109
8313
  function foot(line, cols, accent, spec) {
8110
8314
  const keys = [...spec.keys ?? []];
8315
+ let lead = spec.lead;
8111
8316
  let right = spec.right;
8112
8317
  const size = () => {
8113
- const segs2 = [...spec.lead ? [width(spec.lead[0])] : [], ...keys.map(([k, l]) => width(k) + 1 + width(l))];
8318
+ const segs2 = [...lead ? [width(lead[0])] : [], ...keys.map(([k, l]) => width(k) + 1 + width(l))];
8114
8319
  const left = (spec.badge ? width(spec.badge) + 4 : 0) + segs2.reduce((n, w) => n + w, 0) + 3 * Math.max(0, segs2.length - 1);
8115
8320
  return left + (right ? 3 + width(right[0]) + 1 + width(right[1]) : 0);
8116
8321
  };
@@ -8125,12 +8330,16 @@ function foot(line, cols, accent, spec) {
8125
8330
  break;
8126
8331
  }
8127
8332
  }
8333
+ if (lead && size() > cols) {
8334
+ const keep = Math.max(0, width(lead[0]) - (size() - cols) - 1);
8335
+ lead = [`${Array.from(lead[0]).slice(0, keep).join("")}…`, lead[1]];
8336
+ }
8128
8337
  let c = 0;
8129
8338
  if (spec.badge) {
8130
8339
  c = line.put(0, ` ${spec.badge} `, `\x1B[7;1m${accent}`) + 2;
8131
8340
  }
8132
8341
  const segs = [
8133
- ...spec.lead ? [[spec.lead]] : [],
8342
+ ...lead ? [[lead]] : [],
8134
8343
  ...keys.map(([k, l]) => [
8135
8344
  [k, B],
8136
8345
  [` ${l}`, D]
@@ -8530,7 +8739,7 @@ function renderFind(view, cols, rows) {
8530
8739
  }
8531
8740
 
8532
8741
  // src/palettes.ts
8533
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
8742
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "node:fs";
8534
8743
  import { homedir as homedir2 } from "node:os";
8535
8744
  import { dirname as dirname2, join as join6 } from "node:path";
8536
8745
  var EMITTERS = { ghostty, kitty, alacritty };
@@ -8552,6 +8761,7 @@ function readInstalled(configHome2) {
8552
8761
  return {
8553
8762
  terminals: doc.terminals.filter((t) => INIT_TERMINALS.includes(t)),
8554
8763
  ...doc.startup ? { startup: doc.startup } : {},
8764
+ ...doc.keepTheme ? { keepTheme: true } : {},
8555
8765
  palettes: doc.palettes
8556
8766
  };
8557
8767
  }
@@ -8632,7 +8842,7 @@ function sync(configHome2, catalog, state) {
8632
8842
  writeFileSync3(path2, content);
8633
8843
  written.push(path2);
8634
8844
  };
8635
- const startup = startupPalette(state);
8845
+ const startup = state.keepTheme ? undefined : startupPalette(state);
8636
8846
  for (const terminal of state.terminals) {
8637
8847
  for (const entry of entries) {
8638
8848
  for (const { file: file2, content } of themeFiles(terminal, toTheme(entry, catalog))) {
@@ -8658,7 +8868,7 @@ function forget(configHome2, catalog, terminals, names) {
8658
8868
  for (const { file } of themeFiles(terminal, toTheme(entry, catalog))) {
8659
8869
  const path2 = join6(configHome2, terminal, "themes", file);
8660
8870
  if (existsSync2(path2)) {
8661
- rmSync3(path2, { force: true });
8871
+ rmSync4(path2, { force: true });
8662
8872
  removed.push(path2);
8663
8873
  }
8664
8874
  }
@@ -8667,6 +8877,117 @@ function forget(configHome2, catalog, terminals, names) {
8667
8877
  return removed;
8668
8878
  }
8669
8879
 
8880
+ // src/render.ts
8881
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
8882
+ import { dirname as dirname3 } from "node:path";
8883
+ import { isMainThread, parentPort, Worker } from "node:worker_threads";
8884
+ var LANES = { thumb: "tile", show: "view", backdrop: "view" };
8885
+ function work(task) {
8886
+ const image = decodeImage(new Uint8Array(readFileSync5(task.from)), MAX_PIXELS);
8887
+ if (task.job === "backdrop") {
8888
+ installBackdrop(task.home, task.colors, task.tone, image, {
8889
+ ...task.origin,
8890
+ bytes: new Uint8Array(readFileSync5(task.source))
8891
+ });
8892
+ return 0;
8893
+ }
8894
+ if (task.job === "thumb") {
8895
+ mkdirSync4(dirname3(task.to), { recursive: true });
8896
+ writeFileSync4(task.to, encodePng(contain(image, task.width, task.height)));
8897
+ return 0;
8898
+ }
8899
+ const clear = transparency(image);
8900
+ mkdirSync4(dirname3(task.to), { recursive: true });
8901
+ writeFileSync4(task.to, encodePng(tryOn(image, task.colors, task.tone, task.width, task.height, clear)));
8902
+ return clear;
8903
+ }
8904
+ function serveRenders(port) {
8905
+ port.on("message", (task) => {
8906
+ try {
8907
+ port.postMessage({ id: task.id, value: work(task) });
8908
+ } catch (error) {
8909
+ port.postMessage({ id: task.id, error: error instanceof Error ? error.message : String(error) });
8910
+ }
8911
+ });
8912
+ }
8913
+
8914
+ class Line2 {
8915
+ worker;
8916
+ next = 1;
8917
+ waiting = new Map;
8918
+ send(task) {
8919
+ const id = this.next++;
8920
+ return new Promise((ok, no) => {
8921
+ const worker = this.open();
8922
+ this.waiting.set(id, { ok, no });
8923
+ worker.ref();
8924
+ worker.postMessage({ ...task, id });
8925
+ });
8926
+ }
8927
+ close() {
8928
+ this.worker?.terminate();
8929
+ this.worker = undefined;
8930
+ this.drop(new Error("renderer closed"));
8931
+ }
8932
+ open() {
8933
+ if (this.worker) {
8934
+ return this.worker;
8935
+ }
8936
+ const worker = new Worker(import.meta.filename);
8937
+ worker.unref();
8938
+ worker.on("message", ({ id, value, error }) => {
8939
+ const held = this.waiting.get(id);
8940
+ this.waiting.delete(id);
8941
+ if (this.waiting.size === 0) {
8942
+ worker.unref();
8943
+ }
8944
+ if (error !== undefined) {
8945
+ held?.no(new Error(error));
8946
+ } else {
8947
+ held?.ok(value);
8948
+ }
8949
+ });
8950
+ const lost = (error) => {
8951
+ if (this.worker === worker) {
8952
+ this.worker = undefined;
8953
+ }
8954
+ this.drop(error instanceof Error ? error : new Error("renderer stopped"));
8955
+ };
8956
+ worker.on("error", lost);
8957
+ worker.on("exit", () => lost(undefined));
8958
+ this.worker = worker;
8959
+ return worker;
8960
+ }
8961
+ drop(error) {
8962
+ for (const held of this.waiting.values()) {
8963
+ held.no(error);
8964
+ }
8965
+ this.waiting.clear();
8966
+ }
8967
+ }
8968
+
8969
+ class Renderer {
8970
+ lines = new Map;
8971
+ run(task) {
8972
+ const lane = LANES[task.job];
8973
+ let line = this.lines.get(lane);
8974
+ if (!line) {
8975
+ line = new Line2;
8976
+ this.lines.set(lane, line);
8977
+ }
8978
+ return line.send(task);
8979
+ }
8980
+ close() {
8981
+ for (const line of this.lines.values()) {
8982
+ line.close();
8983
+ }
8984
+ this.lines.clear();
8985
+ }
8986
+ }
8987
+ if (!isMainThread && parentPort) {
8988
+ serveRenders(parentPort);
8989
+ }
8990
+
8670
8991
  // src/unblock.ts
8671
8992
  import { connect, createServer } from "node:net";
8672
8993
  var SPLIT = 20;
@@ -8801,12 +9122,13 @@ function initial(setting, raw) {
8801
9122
  }
8802
9123
  return setting.choices.find((choice) => choice === raw) ?? setting.choices[0];
8803
9124
  }
8804
- var WORKERS = 4;
9125
+ var PROBE = 12;
9126
+ var THUMB = 12;
8805
9127
  var PRELOAD = 2;
8806
9128
  var SETTLE = 150;
8807
9129
  var ESCAPE = 30;
8808
9130
  var TRY_WIDTH = 1280;
8809
- var LOOKAHEAD = 1;
9131
+ var LOOKAHEAD = 2;
8810
9132
  function blank(key, searching) {
8811
9133
  return {
8812
9134
  key,
@@ -8845,19 +9167,16 @@ function incomplete(input) {
8845
9167
  const at2 = input.lastIndexOf("\x1B");
8846
9168
  return at2 !== -1 && /^\[?[0-9;]*$/.test(input.slice(at2 + 1));
8847
9169
  }
8848
- function tick() {
8849
- return new Promise((resolve2) => setImmediate(resolve2));
8850
- }
8851
9170
  function readCache(site, name) {
8852
9171
  try {
8853
- return JSON.parse(readFileSync5(join7(cacheDir(site), name), "utf8"));
9172
+ return JSON.parse(readFileSync6(join7(cacheDir(site), name), "utf8"));
8854
9173
  } catch {
8855
9174
  return {};
8856
9175
  }
8857
9176
  }
8858
9177
  function writeCache(site, name, data) {
8859
- mkdirSync4(cacheDir(site), { recursive: true });
8860
- writeFileSync4(join7(cacheDir(site), name), `${JSON.stringify(data)}
9178
+ mkdirSync5(cacheDir(site), { recursive: true });
9179
+ writeFileSync5(join7(cacheDir(site), name), `${JSON.stringify(data)}
8861
9180
  `);
8862
9181
  }
8863
9182
 
@@ -8884,6 +9203,8 @@ class Finder {
8884
9203
  unsaved = new Set;
8885
9204
  thumbQueue = [];
8886
9205
  thumbing = 0;
9206
+ clarity = new Map;
9207
+ renders = new Renderer;
8887
9208
  sent = new Map;
8888
9209
  placed = new Map;
8889
9210
  current;
@@ -8955,7 +9276,7 @@ class Finder {
8955
9276
  return `${site.key}:${id}`;
8956
9277
  }
8957
9278
  origPath(site, id, ext) {
8958
- return join7(this.scratch, "orig", `${site.key}-${id}.${ext}`);
9279
+ return join7(cacheDir(site), "orig", `${id}.${ext}`);
8959
9280
  }
8960
9281
  tileOf(site, post2, variants) {
8961
9282
  const version2 = rendition(post2) ?? post2;
@@ -9024,7 +9345,10 @@ class Finder {
9024
9345
  this.write("\x1B_Ga=d,d=A,q=2\x1B\\\x1B[H\x1B[J");
9025
9346
  stdin.setRawMode(false);
9026
9347
  stdin.pause();
9027
- rmSync4(this.scratch, { recursive: true, force: true });
9348
+ this.saveProbes();
9349
+ this.renders.close();
9350
+ rmSync5(this.scratch, { recursive: true, force: true });
9351
+ sweepCache();
9028
9352
  return code;
9029
9353
  }
9030
9354
  write(text) {
@@ -9085,7 +9409,7 @@ class Finder {
9085
9409
  this.rows = process.stdout.rows || this.rows;
9086
9410
  this.scroll();
9087
9411
  if (this.view.mode === "try" && this.current) {
9088
- this.present();
9412
+ this.reveal();
9089
9413
  }
9090
9414
  this.pump();
9091
9415
  this.draw();
@@ -9260,9 +9584,9 @@ class Finder {
9260
9584
  save(changed) {
9261
9585
  const path2 = join7(this.home, "ttheme", "config.zsh");
9262
9586
  try {
9263
- const before = existsSync3(path2) ? readFileSync5(path2, "utf8") : "";
9587
+ const before = existsSync3(path2) ? readFileSync6(path2, "utf8") : "";
9264
9588
  const after = changed.reduce((text, setting) => withSetting(text, setting.name, this.setting(setting.name)), before);
9265
- writeFileSync4(path2, after);
9589
+ writeFileSync5(path2, after);
9266
9590
  } catch (error) {
9267
9591
  this.view.error = error instanceof Error ? error.message : String(error);
9268
9592
  }
@@ -9489,12 +9813,12 @@ class Finder {
9489
9813
  this.show();
9490
9814
  this.draw();
9491
9815
  const tags = this.query(site);
9816
+ this.tally(gen, site, board, tags);
9492
9817
  try {
9493
- const [total, owners] = await Promise.all([fetchCount(site, tags, this.signal), this.mateOwners(site)]);
9818
+ const owners = await this.mateOwners(site);
9494
9819
  if (gen !== this.gen) {
9495
9820
  return;
9496
9821
  }
9497
- board.total = total;
9498
9822
  const room = tagsOf(tags) + 1 <= site.tagBudget;
9499
9823
  board.sources = [
9500
9824
  ...room ? [...owners.keys()].map((owner) => ({ tags: `${tags} user:${owner}`, page: 0, done: false })) : [],
@@ -9507,6 +9831,16 @@ class Finder {
9507
9831
  this.fail(gen, error);
9508
9832
  }
9509
9833
  }
9834
+ async tally(gen, site, board, tags) {
9835
+ try {
9836
+ const total = await fetchCount(site, tags, this.signal);
9837
+ if (gen === this.gen) {
9838
+ board.total = total;
9839
+ this.show();
9840
+ this.draw();
9841
+ }
9842
+ } catch {}
9843
+ }
9510
9844
  fail(gen, error) {
9511
9845
  if (gen !== this.gen || this.signal.aborted) {
9512
9846
  return;
@@ -9521,6 +9855,24 @@ class Finder {
9521
9855
  const { perRow, rowsVis } = gridShape(this.cols, this.rows);
9522
9856
  return this.view.tiles.length < (this.board.top + rowsVis + LOOKAHEAD) * perRow;
9523
9857
  }
9858
+ async nextPage(site, board, gen) {
9859
+ const source = board.sources.find((s) => !s.done);
9860
+ if (!source) {
9861
+ return;
9862
+ }
9863
+ const posts = await fetchPosts(site, source.tags, source.page, this.signal);
9864
+ if (gen !== this.gen) {
9865
+ return;
9866
+ }
9867
+ source.page++;
9868
+ source.done = posts.length < PAGE;
9869
+ for (const post2 of posts) {
9870
+ if (!board.seen.has(post2.id)) {
9871
+ board.seen.add(post2.id);
9872
+ board.queue.push(post2);
9873
+ }
9874
+ }
9875
+ }
9524
9876
  async pump() {
9525
9877
  const gen = this.gen;
9526
9878
  const board = this.board;
@@ -9529,40 +9881,40 @@ class Finder {
9529
9881
  }
9530
9882
  this.pumping = gen;
9531
9883
  const site = this.site;
9884
+ const flight = [];
9885
+ let page;
9532
9886
  try {
9533
9887
  board.searching = true;
9534
- while (gen === this.gen && this.wants()) {
9535
- if (board.queue.length === 0) {
9536
- const source = board.sources.find((s) => !s.done);
9537
- if (!source) {
9538
- break;
9539
- }
9540
- const posts = await fetchPosts(site, source.tags, source.page, this.signal);
9541
- if (gen !== this.gen) {
9542
- return;
9543
- }
9544
- source.page++;
9545
- source.done = posts.length < PAGE;
9546
- for (const post2 of posts) {
9547
- if (!board.seen.has(post2.id)) {
9548
- board.seen.add(post2.id);
9549
- board.queue.push(post2);
9888
+ while (gen === this.gen) {
9889
+ while (flight.length < PROBE && board.queue.length > 0 && this.wants()) {
9890
+ const post2 = board.queue.shift();
9891
+ flight.push({ post: post2, passed: this.passes(site, post2) });
9892
+ }
9893
+ if (!page && board.queue.length < PROBE && board.sources.some((s) => !s.done) && this.wants()) {
9894
+ const job = this.nextPage(site, board, gen).then(() => {
9895
+ if (page === job) {
9896
+ page = undefined;
9550
9897
  }
9898
+ });
9899
+ job.catch(() => {});
9900
+ page = job;
9901
+ }
9902
+ const head = flight.shift();
9903
+ if (!head) {
9904
+ if (!page) {
9905
+ break;
9551
9906
  }
9907
+ await page;
9552
9908
  continue;
9553
9909
  }
9554
- const batch = board.queue.splice(0, WORKERS);
9555
- const passed = await Promise.all(batch.map((post2) => this.passes(site, post2)));
9556
- this.saveProbes(site);
9910
+ const passed = await head.passed;
9557
9911
  if (gen !== this.gen) {
9558
9912
  return;
9559
9913
  }
9560
- batch.forEach((post2, i) => {
9561
- board.checked++;
9562
- if (passed[i]) {
9563
- this.admit(site, post2);
9564
- }
9565
- });
9914
+ board.checked++;
9915
+ if (passed) {
9916
+ this.admit(site, head.post);
9917
+ }
9566
9918
  this.show();
9567
9919
  this.draw();
9568
9920
  }
@@ -9574,6 +9926,7 @@ class Finder {
9574
9926
  } catch (error) {
9575
9927
  this.fail(gen, error);
9576
9928
  } finally {
9929
+ this.saveProbes();
9577
9930
  if (this.pumping === gen) {
9578
9931
  this.pumping = 0;
9579
9932
  }
@@ -9615,10 +9968,14 @@ class Finder {
9615
9968
  return false;
9616
9969
  }
9617
9970
  }
9618
- saveProbes(site) {
9619
- if (this.unsaved.delete(site.key)) {
9620
- writeCache(site, "probes.json", this.probes(site));
9971
+ saveProbes() {
9972
+ for (const key of this.unsaved) {
9973
+ const site = SITES.find((s) => s.key === key);
9974
+ if (site) {
9975
+ writeCache(site, "probes.json", this.probes(site));
9976
+ }
9621
9977
  }
9978
+ this.unsaved.clear();
9622
9979
  }
9623
9980
  admit(site, post2) {
9624
9981
  this.posts.set(this.mark(site, post2.id), post2);
@@ -9644,22 +10001,27 @@ class Finder {
9644
10001
  }
9645
10002
  const found = origins(this.home);
9646
10003
  const known = readCache(site, "owners.json");
9647
- const byPalette = new Map;
9648
- for (const sibling of this.catalog.palettes) {
10004
+ const siblings = this.catalog.palettes.flatMap((sibling) => {
9649
10005
  const origin = found.get(sibling.name);
9650
- if (sibling.group !== this.entry.group || sibling.name === this.entry.name || origin?.site !== site.key) {
9651
- continue;
10006
+ return sibling.group === this.entry.group && sibling.name !== this.entry.name && origin?.site === site.key ? [{ name: sibling.name, id: origin.id }] : [];
10007
+ });
10008
+ const asked = await Promise.all(siblings.map(async (sibling) => {
10009
+ const owner = known[sibling.id];
10010
+ if (owner !== undefined) {
10011
+ return { ...sibling, owner };
9652
10012
  }
9653
- let owner = known[origin.id];
9654
- if (owner === undefined) {
9655
- try {
9656
- owner = (await fetchPost(site, origin.id, this.signal))?.owner ?? "";
9657
- known[origin.id] = owner;
9658
- } catch {
9659
- continue;
9660
- }
10013
+ try {
10014
+ return { ...sibling, owner: (await fetchPost(site, sibling.id, this.signal))?.owner ?? "" };
10015
+ } catch {
10016
+ return;
10017
+ }
10018
+ }));
10019
+ const byPalette = new Map;
10020
+ for (const sibling of asked) {
10021
+ if (sibling) {
10022
+ known[sibling.id] = sibling.owner;
10023
+ byPalette.set(sibling.name, sibling.owner);
9661
10024
  }
9662
- byPalette.set(sibling.name, owner);
9663
10025
  }
9664
10026
  writeCache(site, "owners.json", known);
9665
10027
  const owners = mates(byPalette);
@@ -9668,7 +10030,7 @@ class Finder {
9668
10030
  }
9669
10031
  async cached(site, path2, url, progress2) {
9670
10032
  if (existsSync3(path2)) {
9671
- return new Uint8Array(readFileSync5(path2));
10033
+ return statSync3(path2).size;
9672
10034
  }
9673
10035
  const running = this.inflight.get(path2);
9674
10036
  if (running) {
@@ -9676,9 +10038,9 @@ class Finder {
9676
10038
  }
9677
10039
  const job = (async () => {
9678
10040
  const bytes = await fetchBytes(site, url, this.signal, progress2);
9679
- mkdirSync4(dirname3(path2), { recursive: true });
9680
- writeFileSync4(path2, bytes);
9681
- return bytes;
10041
+ mkdirSync5(dirname4(path2), { recursive: true });
10042
+ writeFileSync5(path2, bytes);
10043
+ return bytes.length;
9682
10044
  })();
9683
10045
  this.inflight.set(path2, job);
9684
10046
  try {
@@ -9688,7 +10050,7 @@ class Finder {
9688
10050
  }
9689
10051
  }
9690
10052
  thumbs() {
9691
- while (this.thumbing < WORKERS && this.thumbQueue.length > 0) {
10053
+ while (this.thumbing < THUMB && this.thumbQueue.length > 0) {
9692
10054
  const { site, post: post2 } = this.thumbQueue.shift();
9693
10055
  this.thumbing++;
9694
10056
  this.thumb(site, post2).catch(() => {}).finally(() => {
@@ -9700,12 +10062,11 @@ class Finder {
9700
10062
  async thumb(site, post2) {
9701
10063
  const w = TILE.cols * this.cell.w;
9702
10064
  const h = TILE.rows * this.cell.h;
9703
- const path2 = join7(this.scratch, "tile", `${site.key}-${post2.id}-${w}x${h}.png`);
10065
+ const path2 = join7(cacheDir(site), "tile", `${post2.id}-${w}x${h}.png`);
9704
10066
  if (!existsSync3(path2)) {
9705
10067
  const thumb = join7(cacheDir(site), "thumb", `${post2.id}.${extension(post2.preview)}`);
9706
- const bytes = await this.cached(site, thumb, post2.preview);
9707
- mkdirSync4(dirname3(path2), { recursive: true });
9708
- writeFileSync4(path2, encodePng(contain(decodeImage(bytes, MAX_PIXELS), w, h)));
10068
+ await this.cached(site, thumb, post2.preview);
10069
+ await this.renders.run({ job: "thumb", from: thumb, to: path2, width: w, height: h });
9709
10070
  }
9710
10071
  this.thumbPath.set(this.mark(site, post2.id), path2);
9711
10072
  this.show();
@@ -9721,8 +10082,7 @@ class Finder {
9721
10082
  return;
9722
10083
  }
9723
10084
  if (this.current?.id === tile.id) {
9724
- this.present();
9725
- this.draw();
10085
+ this.reveal();
9726
10086
  return;
9727
10087
  }
9728
10088
  this.settle = setTimeout(() => void this.load(tile), SETTLE);
@@ -9744,9 +10104,9 @@ class Finder {
9744
10104
  view.fetching = { id: tile.id, got: 0, size: 0 };
9745
10105
  this.draw();
9746
10106
  }
9747
- const bytes = await this.cached(site, path2, version2.file, (got, size) => {
10107
+ const size = await this.cached(site, path2, version2.file, (got, total) => {
9748
10108
  if (view.fetching?.id === tile.id) {
9749
- view.fetching = { id: tile.id, got, size };
10109
+ view.fetching = { id: tile.id, got, size: total };
9750
10110
  this.draw();
9751
10111
  }
9752
10112
  });
@@ -9754,42 +10114,48 @@ class Finder {
9754
10114
  return;
9755
10115
  }
9756
10116
  view.fetching = undefined;
10117
+ if (site !== this.site) {
10118
+ return;
10119
+ }
10120
+ const current = { site, id: tile.id, path: path2, ext: version2.ext, size, using: "plain", failed: false };
9757
10121
  view.preparing = tile.id;
9758
10122
  this.flush();
9759
- await tick();
9760
- const image = decodeImage(bytes, MAX_PIXELS);
9761
- if (site !== this.site) {
10123
+ const plain2 = await this.render(current, "plain");
10124
+ if (control.signal.aborted || site !== this.site) {
9762
10125
  return;
9763
10126
  }
9764
- const current = {
9765
- site,
9766
- id: tile.id,
9767
- image,
9768
- plain: image,
9769
- failed: false,
9770
- bytes,
9771
- ext: version2.ext,
9772
- clear: transparency(image)
9773
- };
9774
- if (current.clear === 0 && canRemoveBackground() && this.setting("TTHEME_FIND_REMOVE_BG") !== "off") {
10127
+ if (plain2.clear === 0 && canRemoveBackground() && this.setting("TTHEME_FIND_REMOVE_BG") !== "off") {
9775
10128
  view.preparing = undefined;
9776
10129
  view.cutting = tile.id;
9777
10130
  this.flush();
9778
- const cut = await this.cutOut(site, tile.id, path2, control.signal);
10131
+ current.cut = await this.cutOut(site, tile.id, path2, control.signal);
9779
10132
  if (control.signal.aborted || site !== this.site) {
9780
10133
  return;
9781
10134
  }
9782
- if (cut) {
9783
- current.cut = cut;
9784
- current.image = cut;
9785
- current.clear = transparency(cut);
10135
+ view.cutting = undefined;
10136
+ if (current.cut) {
10137
+ view.preparing = tile.id;
10138
+ this.flush();
10139
+ const made = await this.render(current, "cut").catch(() => {
10140
+ return;
10141
+ });
10142
+ if (control.signal.aborted || site !== this.site) {
10143
+ return;
10144
+ }
10145
+ if (made && keepable(made.clear)) {
10146
+ current.using = "cut";
10147
+ } else {
10148
+ rmSync5(current.cut, { force: true });
10149
+ current.cut = undefined;
10150
+ current.failed = true;
10151
+ }
9786
10152
  } else {
9787
10153
  current.failed = true;
9788
10154
  }
9789
10155
  }
9790
10156
  this.current = current;
9791
10157
  if (view.tiles[view.focus]?.id === tile.id) {
9792
- this.present();
10158
+ await this.present();
9793
10159
  }
9794
10160
  this.preload();
9795
10161
  } catch (error) {
@@ -9810,13 +10176,16 @@ class Finder {
9810
10176
  }
9811
10177
  }
9812
10178
  async cutOut(site, id, path2, signal) {
9813
- const out = join7(this.scratch, "cut", `${site.key}-${id}.png`);
10179
+ const out = join7(cacheDir(site), "cut", `${id}.png`);
10180
+ if (existsSync3(out)) {
10181
+ return out;
10182
+ }
9814
10183
  try {
9815
- mkdirSync4(dirname3(out), { recursive: true });
9816
- const cut = existsSync3(out) ? decodeImage(new Uint8Array(readFileSync5(out)), MAX_PIXELS) : await removeBackground(path2, out, signal);
9817
- return keepable(transparency(cut)) ? cut : undefined;
10184
+ mkdirSync5(dirname4(out), { recursive: true });
10185
+ await removeBackground(path2, out, signal);
10186
+ return out;
9818
10187
  } catch {
9819
- rmSync4(out, { force: true });
10188
+ rmSync5(out, { force: true });
9820
10189
  return;
9821
10190
  }
9822
10191
  }
@@ -9825,10 +10194,8 @@ class Finder {
9825
10194
  if (!current?.cut || this.view.shown?.id !== current.id) {
9826
10195
  return;
9827
10196
  }
9828
- current.image = current.image === current.cut ? current.plain : current.cut;
9829
- current.clear = transparency(current.image);
9830
- this.present();
9831
- this.draw();
10197
+ current.using = current.using === "cut" ? "plain" : "cut";
10198
+ this.reveal();
9832
10199
  }
9833
10200
  preload() {
9834
10201
  const view = this.view;
@@ -9847,30 +10214,65 @@ class Finder {
9847
10214
  continue;
9848
10215
  }
9849
10216
  const path2 = this.origPath(site, post2.id, version2.ext);
9850
- if (existsSync3(path2)) {
9851
- continue;
9852
- }
9853
10217
  this.prefetching++;
9854
- this.cached(site, path2, version2.file).catch(() => {}).finally(() => {
10218
+ this.cached(site, path2, version2.file).then(() => this.render({ site, id: post2.id, path: path2, ext: version2.ext, size: 0, using: "plain", failed: false }, "plain")).catch(() => {}).finally(() => {
9855
10219
  this.prefetching--;
9856
10220
  });
9857
10221
  }
9858
10222
  }
9859
- present() {
9860
- const current = this.current;
9861
- if (!current) {
9862
- return;
9863
- }
10223
+ async render(current, using) {
9864
10224
  const W = this.cols * this.cell.w;
9865
10225
  const H = this.rows * this.cell.h;
9866
10226
  const width2 = Math.min(W, TRY_WIDTH);
9867
10227
  const height = Math.max(1, Math.round(H * width2 / W));
9868
- const cut = current.cut ? current.image === current.cut ? "on" : "off" : current.failed ? "failed" : "none";
9869
- const path2 = join7(this.scratch, `${current.id}${cut === "on" ? "c" : ""}-${width2}x${height}.png`);
9870
- if (!existsSync3(path2)) {
9871
- writeFileSync4(path2, encodePng(tryOn(current.image, this.entry, this.tone, width2, height)));
10228
+ const path2 = join7(this.scratch, `${current.site.key}-${current.id}${using === "cut" ? "c" : ""}-${width2}x${height}.png`);
10229
+ const key = `${current.site.key}:${current.id}:${using}`;
10230
+ const known = this.clarity.get(key);
10231
+ if (known !== undefined && existsSync3(path2)) {
10232
+ return { clear: known, path: path2 };
10233
+ }
10234
+ const clear = await this.renders.run({
10235
+ job: "show",
10236
+ from: using === "cut" ? current.cut : current.path,
10237
+ to: path2,
10238
+ width: width2,
10239
+ height,
10240
+ colors: this.entry,
10241
+ tone: this.tone
10242
+ });
10243
+ this.clarity.set(key, clear);
10244
+ return { clear, path: path2 };
10245
+ }
10246
+ async present() {
10247
+ const current = this.current;
10248
+ if (!current) {
10249
+ return;
10250
+ }
10251
+ const using = current.using;
10252
+ const { clear, path: path2 } = await this.render(current, using);
10253
+ if (this.current !== current || current.using !== using) {
10254
+ return;
9872
10255
  }
9873
- this.view.shown = { id: current.id, clear: current.clear, bytes: current.bytes.length, path: path2, cut };
10256
+ this.view.shown = {
10257
+ id: current.id,
10258
+ clear,
10259
+ bytes: current.size,
10260
+ path: path2,
10261
+ cut: current.cut ? using === "cut" ? "on" : "off" : current.failed ? "failed" : "none"
10262
+ };
10263
+ }
10264
+ reveal() {
10265
+ const view = this.view;
10266
+ view.preparing = this.current?.id;
10267
+ this.flush();
10268
+ this.present().catch((error) => {
10269
+ view.error = error instanceof Error ? error.message : String(error);
10270
+ }).finally(() => {
10271
+ if (view.preparing === this.current?.id) {
10272
+ view.preparing = undefined;
10273
+ }
10274
+ this.draw();
10275
+ });
9874
10276
  }
9875
10277
  async install() {
9876
10278
  const view = this.view;
@@ -9882,15 +10284,24 @@ class Finder {
9882
10284
  view.installing = tile.id;
9883
10285
  view.error = undefined;
9884
10286
  this.flush();
9885
- await tick();
9886
10287
  try {
9887
- installBackdrop(this.home, this.entry, this.tone, current.image, {
9888
- site: current.site.key,
9889
- id: current.id,
9890
- ext: current.ext,
9891
- bytes: current.bytes,
9892
- from: `${current.site.name} ${current.id} ${current.site.pageUrl(current.id)}`
10288
+ await this.renders.run({
10289
+ job: "backdrop",
10290
+ from: current.using === "cut" ? current.cut : current.path,
10291
+ source: current.path,
10292
+ home: this.home,
10293
+ colors: this.entry,
10294
+ tone: this.tone,
10295
+ origin: {
10296
+ site: current.site.key,
10297
+ id: current.id,
10298
+ ext: current.ext,
10299
+ from: `${current.site.name} ${current.id} ${current.site.pageUrl(current.id)}`
10300
+ }
9893
10301
  });
10302
+ const known = readCache(current.site, "owners.json");
10303
+ known[current.id] = this.posts.get(this.mark(current.site, current.id))?.owner ?? "";
10304
+ writeCache(current.site, "owners.json", known);
9894
10305
  process.stderr.write(`background · ${this.entry.name} ← ${current.site.name} ${current.id}
9895
10306
  `);
9896
10307
  this.finish(0);
@@ -9981,19 +10392,38 @@ async function runFind(name) {
9981
10392
  return new Finder(home, catalog, entry, entry.booru ?? "").run();
9982
10393
  }
9983
10394
 
10395
+ // src/images.ts
10396
+ function runImage(name, action) {
10397
+ const home = configHome();
10398
+ find2(readCatalog(home).palettes, name);
10399
+ if (action === "next" || action === "prev") {
10400
+ const { key, at: at2, of } = switchImage(home, name, action === "next" ? 1 : -1);
10401
+ process.stderr.write(`background · ${name} ${at2}/${of} ${key}
10402
+ `);
10403
+ return 0;
10404
+ }
10405
+ if (action === "drop") {
10406
+ const { key, left } = dropImage(home, name);
10407
+ process.stderr.write(`background · ${name} removed ${key ?? "the picture"} · ${left} left
10408
+ `);
10409
+ return 0;
10410
+ }
10411
+ throw new Error(`unknown image action ${action} — next, prev or drop`);
10412
+ }
10413
+
9984
10414
  // src/init.ts
9985
10415
  import {
9986
10416
  chmodSync,
9987
10417
  copyFileSync,
9988
10418
  existsSync as existsSync5,
9989
- mkdirSync as mkdirSync5,
9990
- readdirSync as readdirSync3,
9991
- readFileSync as readFileSync6,
9992
- rmSync as rmSync5,
9993
- writeFileSync as writeFileSync5
10419
+ mkdirSync as mkdirSync6,
10420
+ readdirSync as readdirSync4,
10421
+ readFileSync as readFileSync7,
10422
+ rmSync as rmSync6,
10423
+ writeFileSync as writeFileSync6
9994
10424
  } from "node:fs";
9995
10425
  import { homedir as homedir3 } from "node:os";
9996
- import { dirname as dirname4, join as join8 } from "node:path";
10426
+ import { dirname as dirname5, join as join8 } from "node:path";
9997
10427
 
9998
10428
  // node_modules/@clack/core/dist/index.mjs
9999
10429
  import { styleText } from "node:util";
@@ -11107,6 +11537,34 @@ class a extends V {
11107
11537
  });
11108
11538
  }
11109
11539
  }
11540
+ var n$1 = class n extends V {
11541
+ options;
11542
+ cursor = 0;
11543
+ get _selectedValue() {
11544
+ return this.options[this.cursor];
11545
+ }
11546
+ changeValue() {
11547
+ const e = this._selectedValue;
11548
+ this.value = e === undefined ? undefined : e.value;
11549
+ }
11550
+ constructor(e) {
11551
+ super(e, false), this.options = e.options;
11552
+ const o2 = this.options.findIndex(({ value: s }) => s === e.initialValue), t2 = o2 === -1 ? 0 : o2;
11553
+ this.cursor = this.options[t2]?.disabled ? findCursor(t2, 1, this.options) : t2, this.changeValue(), this.on("cursor", (s) => {
11554
+ switch (s) {
11555
+ case "left":
11556
+ case "up":
11557
+ this.cursor = findCursor(this.cursor, -1, this.options);
11558
+ break;
11559
+ case "down":
11560
+ case "right":
11561
+ this.cursor = findCursor(this.cursor, 1, this.options);
11562
+ break;
11563
+ }
11564
+ this.changeValue();
11565
+ });
11566
+ }
11567
+ };
11110
11568
 
11111
11569
  // node_modules/@clack/prompts/dist/index.mjs
11112
11570
  import { styleText as styleText2, stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
@@ -11209,7 +11667,7 @@ var limitOptions = ({
11209
11667
  d && g2++, c2 && g2++;
11210
11668
  const T3 = f2 + (d ? 1 : 0), y = W - (c2 ? 1 : 0);
11211
11669
  for (let t2 = T3;t2 < y; t2++) {
11212
- const n2 = e[t2], o2 = n2 ? w(n2, t2 === l) : "", h2 = wrapAnsi(o2, i, {
11670
+ const n3 = e[t2], o2 = n3 ? w(n3, t2 === l) : "", h2 = wrapAnsi(o2, i, {
11213
11671
  hard: true,
11214
11672
  trim: false
11215
11673
  }).split(`
@@ -11217,17 +11675,17 @@ var limitOptions = ({
11217
11675
  s.push(h2), g2 += h2.length;
11218
11676
  }
11219
11677
  if (g2 > v) {
11220
- let t2 = 0, n2 = 0, o2 = g2;
11678
+ let t2 = 0, n3 = 0, o2 = g2;
11221
11679
  const h2 = l - T3;
11222
11680
  let u3 = v;
11223
11681
  const L = () => I(s, o2, 0, h2, u3), E = () => I(s, o2, h2 + 1, s.length, u3, true);
11224
- d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u3 && (c2 || (u3 -= 1), { lineCount: o2, removals: n2 } = E())) : (c2 || (u3 -= 1), { lineCount: o2, removals: n2 } = E(), o2 > u3 && (u3 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n2 > 0 && (c2 = true, s.splice(s.length - n2, n2));
11682
+ d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u3 && (c2 || (u3 -= 1), { lineCount: o2, removals: n3 } = E())) : (c2 || (u3 -= 1), { lineCount: o2, removals: n3 } = E(), o2 > u3 && (u3 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n3 > 0 && (c2 = true, s.splice(s.length - n3, n3));
11225
11683
  }
11226
11684
  const x = [];
11227
11685
  d && x.push(M2);
11228
11686
  for (const t2 of s)
11229
- for (const n2 of t2)
11230
- x.push(n2);
11687
+ for (const n3 of t2)
11688
+ x.push(n3);
11231
11689
  return c2 && x.push(M2), x;
11232
11690
  };
11233
11691
  var confirm = (i) => {
@@ -11294,58 +11752,58 @@ ${styleText2("reset", styleText2("dim", `Press ${styleText2(["gray", "bgWhite",
11294
11752
  render() {
11295
11753
  const t2 = i.withGuide ?? settings.withGuide, a2 = wrapTextWithPrefix(i.output, i.message, t2 ? `${symbolBar(this.state)} ` : "", `${symbol(this.state)} `), r2 = `${t2 ? `${styleText2("gray", S_BAR)}
11296
11754
  ` : ""}${a2}
11297
- `, o2 = this.value ?? [], p2 = (n2, l) => {
11298
- if (n2.disabled)
11299
- return u3(n2, "disabled");
11300
- const s = o2.includes(n2.value);
11301
- return l && s ? u3(n2, "active-selected") : s ? u3(n2, "selected") : u3(n2, l ? "active" : "inactive");
11755
+ `, o2 = this.value ?? [], p2 = (n3, l) => {
11756
+ if (n3.disabled)
11757
+ return u3(n3, "disabled");
11758
+ const s = o2.includes(n3.value);
11759
+ return l && s ? u3(n3, "active-selected") : s ? u3(n3, "selected") : u3(n3, l ? "active" : "inactive");
11302
11760
  };
11303
11761
  switch (this.state) {
11304
11762
  case "submit": {
11305
- const n2 = this.options.filter(({ value: s }) => o2.includes(s)).map((s) => u3(s, "submitted")).join(styleText2("dim", ", ")) || styleText2("dim", "none"), l = wrapTextWithPrefix(i.output, n2, t2 ? `${styleText2("gray", S_BAR)} ` : "");
11763
+ const n3 = this.options.filter(({ value: s }) => o2.includes(s)).map((s) => u3(s, "submitted")).join(styleText2("dim", ", ")) || styleText2("dim", "none"), l = wrapTextWithPrefix(i.output, n3, t2 ? `${styleText2("gray", S_BAR)} ` : "");
11306
11764
  return `${r2}${l}`;
11307
11765
  }
11308
11766
  case "cancel": {
11309
- const n2 = this.options.filter(({ value: s }) => o2.includes(s)).map((s) => u3(s, "cancelled")).join(styleText2("dim", ", "));
11310
- if (n2.trim() === "")
11767
+ const n3 = this.options.filter(({ value: s }) => o2.includes(s)).map((s) => u3(s, "cancelled")).join(styleText2("dim", ", "));
11768
+ if (n3.trim() === "")
11311
11769
  return `${r2}${styleText2("gray", S_BAR)}`;
11312
- const l = wrapTextWithPrefix(i.output, n2, t2 ? `${styleText2("gray", S_BAR)} ` : "");
11770
+ const l = wrapTextWithPrefix(i.output, n3, t2 ? `${styleText2("gray", S_BAR)} ` : "");
11313
11771
  return `${r2}${l}${t2 ? `
11314
11772
  ${styleText2("gray", S_BAR)}` : ""}`;
11315
11773
  }
11316
11774
  case "error": {
11317
- const n2 = t2 ? `${styleText2("yellow", S_BAR)} ` : "", l = this.error.split(`
11775
+ const n3 = t2 ? `${styleText2("yellow", S_BAR)} ` : "", l = this.error.split(`
11318
11776
  `).map(($, C2) => C2 === 0 ? `${t2 ? `${styleText2("yellow", S_BAR_END)} ` : ""}${styleText2("yellow", $)}` : ` ${$}`).join(`
11319
11777
  `), s = r2.split(`
11320
11778
  `).length, h2 = l.split(`
11321
11779
  `).length + 1;
11322
- return `${r2}${n2}${limitOptions({
11780
+ return `${r2}${n3}${limitOptions({
11323
11781
  output: i.output,
11324
11782
  options: this.options,
11325
11783
  cursor: this.cursor,
11326
11784
  maxItems: i.maxItems,
11327
- columnPadding: n2.length,
11785
+ columnPadding: n3.length,
11328
11786
  rowPadding: s + h2,
11329
11787
  style: p2
11330
11788
  }).join(`
11331
- ${n2}`)}
11789
+ ${n3}`)}
11332
11790
  ${l}
11333
11791
  `;
11334
11792
  }
11335
11793
  default: {
11336
- const n2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l = r2.split(`
11794
+ const n3 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l = r2.split(`
11337
11795
  `).length, s = v ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, t2) : t2 ? [styleText2("cyan", S_BAR_END)] : [], h2 = s.join(`
11338
11796
  `), $ = s.length + 1;
11339
- return `${r2}${n2}${limitOptions({
11797
+ return `${r2}${n3}${limitOptions({
11340
11798
  output: i.output,
11341
11799
  options: this.options,
11342
11800
  cursor: this.cursor,
11343
11801
  maxItems: i.maxItems,
11344
- columnPadding: n2.length,
11802
+ columnPadding: n3.length,
11345
11803
  rowPadding: l + $,
11346
11804
  style: p2
11347
11805
  }).join(`
11348
- ${n2}`)}
11806
+ ${n3}`)}
11349
11807
  ${h2}
11350
11808
  `;
11351
11809
  }
@@ -11377,18 +11835,18 @@ var C2 = (o2, e, s) => {
11377
11835
  hard: true,
11378
11836
  trim: false
11379
11837
  }, i = wrapAnsi(o2, e, a2).split(`
11380
- `), c2 = i.reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), u3 = i.map(s).reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), g2 = e - (u3 - c2);
11838
+ `), c2 = i.reduce((n3, t2) => Math.max(dist_default2(t2), n3), 0), u3 = i.map(s).reduce((n3, t2) => Math.max(dist_default2(t2), n3), 0), g2 = e - (u3 - c2);
11381
11839
  return wrapAnsi(o2, g2, a2);
11382
11840
  };
11383
11841
  var note = (o2 = "", e = "", s) => {
11384
11842
  const a2 = s?.output ?? process$1.stdout, i = s?.withGuide ?? settings.withGuide, c2 = s?.format ?? W$1, g2 = ["", ...C2(o2, getColumns(a2) - 6, c2).split(`
11385
- `).map(c2), ""], n2 = dist_default2(e), t2 = Math.max(g2.reduce((m3, F) => {
11843
+ `).map(c2), ""], n3 = dist_default2(e), t2 = Math.max(g2.reduce((m3, F) => {
11386
11844
  const O = dist_default2(F);
11387
11845
  return O > m3 ? O : m3;
11388
- }, 0), n2) + 2, h2 = g2.map((m3) => `${styleText2("gray", S_BAR)} ${m3}${" ".repeat(t2 - dist_default2(m3))}${styleText2("gray", S_BAR)}`).join(`
11846
+ }, 0), n3) + 2, h2 = g2.map((m3) => `${styleText2("gray", S_BAR)} ${m3}${" ".repeat(t2 - dist_default2(m3))}${styleText2("gray", S_BAR)}`).join(`
11389
11847
  `), T3 = i ? `${styleText2("gray", S_BAR)}
11390
11848
  ` : "", l$1 = i ? S_CONNECT_LEFT : S_CORNER_BOTTOM_LEFT;
11391
- a2.write(`${T3}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2("gray", S_BAR_H.repeat(Math.max(t2 - n2 - 1, 1)) + S_CORNER_TOP_RIGHT)}
11849
+ a2.write(`${T3}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2("gray", S_BAR_H.repeat(Math.max(t2 - n3 - 1, 1)) + S_CORNER_TOP_RIGHT)}
11392
11850
  ${h2}
11393
11851
  ${styleText2("gray", l$1 + S_BAR_H.repeat(t2 + 2) + S_CORNER_BOTTOM_RIGHT)}
11394
11852
  `);
@@ -11402,6 +11860,69 @@ var SELECT_INSTRUCTIONS = [
11402
11860
  `${styleText2("dim", "↑/↓")} to navigate`,
11403
11861
  `${styleText2("dim", "Enter:")} confirm`
11404
11862
  ];
11863
+ var c2 = (t2, o2) => t2.includes(`
11864
+ `) ? t2.split(`
11865
+ `).map((d) => o2(d)).join(`
11866
+ `) : o2(t2);
11867
+ var select = (t2) => {
11868
+ const o2 = (n3, m3) => {
11869
+ if (n3 === undefined)
11870
+ return "";
11871
+ const s = n3.label ?? String(n3.value);
11872
+ switch (m3) {
11873
+ case "disabled":
11874
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (i) => styleText2("gray", i))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
11875
+ case "selected":
11876
+ return `${c2(s, (i) => styleText2("dim", i))}`;
11877
+ case "active":
11878
+ return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${n3.hint ? ` ${styleText2("dim", `(${n3.hint})`)}` : ""}`;
11879
+ case "cancelled":
11880
+ return `${c2(s, (i) => styleText2(["strikethrough", "dim"], i))}`;
11881
+ default:
11882
+ return `${styleText2("dim", S_RADIO_INACTIVE)} ${c2(s, (i) => styleText2("dim", i))}`;
11883
+ }
11884
+ }, d = t2.showInstructions ?? true;
11885
+ return new n$1({
11886
+ options: t2.options,
11887
+ signal: t2.signal,
11888
+ input: t2.input,
11889
+ output: t2.output,
11890
+ initialValue: t2.initialValue,
11891
+ render() {
11892
+ const n3 = t2.withGuide ?? settings.withGuide, m3 = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, i = wrapTextWithPrefix(t2.output, t2.message, s, m3), u4 = `${n3 ? `${styleText2("gray", S_BAR)}
11893
+ ` : ""}${i}
11894
+ `;
11895
+ switch (this.state) {
11896
+ case "submit": {
11897
+ const r2 = n3 ? `${styleText2("gray", S_BAR)} ` : "", a2 = wrapTextWithPrefix(t2.output, o2(this.options[this.cursor], "selected"), r2);
11898
+ return `${u4}${a2}`;
11899
+ }
11900
+ case "cancel": {
11901
+ const r2 = n3 ? `${styleText2("gray", S_BAR)} ` : "", a2 = wrapTextWithPrefix(t2.output, o2(this.options[this.cursor], "cancelled"), r2);
11902
+ return `${u4}${a2}${n3 ? `
11903
+ ${styleText2("gray", S_BAR)}` : ""}`;
11904
+ }
11905
+ default: {
11906
+ const r2 = n3 ? `${styleText2("cyan", S_BAR)} ` : "", a2 = u4.split(`
11907
+ `).length, p2 = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n3) : n3 ? [styleText2("cyan", S_BAR_END)] : [], b2 = p2.join(`
11908
+ `), f2 = p2.length + 1;
11909
+ return `${u4}${r2}${limitOptions({
11910
+ output: t2.output,
11911
+ cursor: this.cursor,
11912
+ options: this.options,
11913
+ maxItems: t2.maxItems,
11914
+ columnPadding: r2.length,
11915
+ rowPadding: a2 + f2,
11916
+ style: (g2, x) => o2(g2, g2.disabled ? "disabled" : x ? "active" : "inactive")
11917
+ }).join(`
11918
+ ${r2}`)}
11919
+ ${b2}
11920
+ `;
11921
+ }
11922
+ }
11923
+ }
11924
+ }).prompt();
11925
+ };
11405
11926
  var i = `${styleText2("gray", S_BAR)} `;
11406
11927
 
11407
11928
  // src/market.ts
@@ -11412,7 +11933,7 @@ import { openSync, writeSync } from "node:fs";
11412
11933
  import { ReadStream } from "node:tty";
11413
11934
  var QUERY_CODES = ["10", "11", "12", "17", ...Array.from({ length: 16 }, (_2, i2) => `4;${i2}`)];
11414
11935
  function paletteOsc(entry) {
11415
- const osc4 = entry.ansi.map((c2, i2) => `;${i2};${c2}`).join("");
11936
+ const osc4 = entry.ansi.map((c3, i2) => `;${i2};${c3}`).join("");
11416
11937
  return [
11417
11938
  `\x1B]11;${entry.background}\x1B\\`,
11418
11939
  `\x1B]10;${entry.foreground}\x1B\\`,
@@ -11468,7 +11989,7 @@ async function queryTerminalColors() {
11468
11989
  // src/ansi.ts
11469
11990
  function rgb2(hex2) {
11470
11991
  const h2 = hex2.replace("#", "");
11471
- return [h2.slice(0, 2), h2.slice(2, 4), h2.slice(4, 6)].map((c2) => Number.parseInt(c2, 16)).join(";");
11992
+ return [h2.slice(0, 2), h2.slice(2, 4), h2.slice(4, 6)].map((c3) => Number.parseInt(c3, 16)).join(";");
11472
11993
  }
11473
11994
  function ansiChip(text, bg2) {
11474
11995
  return `\x1B[48;2;${rgb2(bg2)}m ${text} \x1B[0m`;
@@ -11480,7 +12001,7 @@ function ansiFg(color2) {
11480
12001
  return `\x1B[38;2;${rgb2(color2)}m`;
11481
12002
  }
11482
12003
  function ansiSwatch(colors, bg2) {
11483
- return `\x1B[48;2;${rgb2(bg2)}m${colors.map((c2) => `\x1B[38;2;${rgb2(c2)}m▄`).join("")} \x1B[0m`;
12004
+ return `\x1B[48;2;${rgb2(bg2)}m${colors.map((c3) => `\x1B[38;2;${rgb2(c3)}m▄`).join("")} \x1B[0m`;
11484
12005
  }
11485
12006
 
11486
12007
  // src/palette-prompt.ts
@@ -11624,12 +12145,12 @@ class PalettePrompt extends V {
11624
12145
  if (names.length === 0) {
11625
12146
  return;
11626
12147
  }
11627
- const add = names.some((n2) => !this.picked.has(n2));
11628
- for (const n2 of names) {
12148
+ const add = names.some((n3) => !this.picked.has(n3));
12149
+ for (const n3 of names) {
11629
12150
  if (add) {
11630
- this.picked.add(n2);
12151
+ this.picked.add(n3);
11631
12152
  } else {
11632
- this.picked.delete(n2);
12153
+ this.picked.delete(n3);
11633
12154
  }
11634
12155
  }
11635
12156
  }
@@ -11816,8 +12337,8 @@ function runAdd(names) {
11816
12337
  const home = configHome();
11817
12338
  const catalog = readCatalog(home);
11818
12339
  const state = readInstalled(home);
11819
- const already = names.filter((n2) => state.palettes.includes(n2));
11820
- const fresh = names.filter((n2) => !state.palettes.includes(n2));
12340
+ const already = names.filter((n3) => state.palettes.includes(n3));
12341
+ const fresh = names.filter((n3) => !state.palettes.includes(n3));
11821
12342
  if (fresh.length === 0) {
11822
12343
  console.log(`already installed: ${already.join(", ")}`);
11823
12344
  return;
@@ -11834,12 +12355,12 @@ function runRemove(names) {
11834
12355
  const home = configHome();
11835
12356
  const catalog = readCatalog(home);
11836
12357
  const state = readInstalled(home);
11837
- const gone = names.filter((n2) => state.palettes.includes(n2));
12358
+ const gone = names.filter((n3) => state.palettes.includes(n3));
11838
12359
  if (gone.length === 0) {
11839
12360
  console.log(`not installed: ${names.join(", ")}`);
11840
12361
  return;
11841
12362
  }
11842
- const next = { ...state, palettes: state.palettes.filter((n2) => !gone.includes(n2)) };
12363
+ const next = { ...state, palettes: state.palettes.filter((n3) => !gone.includes(n3)) };
11843
12364
  sync(home, catalog, next);
11844
12365
  forget(home, catalog, state.terminals, gone);
11845
12366
  writeInstalled(home, next);
@@ -11855,7 +12376,8 @@ function runDefault(name) {
11855
12376
  if (!state.palettes.includes(name)) {
11856
12377
  throw new Error(`${name} is not installed — \`ttheme add ${name}\` first`);
11857
12378
  }
11858
- const next = { ...state, startup: name };
12379
+ const { keepTheme: _2, ...rest } = state;
12380
+ const next = { ...rest, startup: name };
11859
12381
  sync(home, catalog, next);
11860
12382
  writeInstalled(home, next);
11861
12383
  console.log(`new tabs open with ${name} — reload your terminal config to pick it up`);
@@ -11911,8 +12433,8 @@ async function runBrowse() {
11911
12433
  console.log("nothing changed");
11912
12434
  return;
11913
12435
  }
11914
- const dropped = state.palettes.filter((n2) => !wanted.includes(n2));
11915
- const added = wanted.filter((n2) => !state.palettes.includes(n2));
12436
+ const dropped = state.palettes.filter((n3) => !wanted.includes(n3));
12437
+ const added = wanted.filter((n3) => !state.palettes.includes(n3));
11916
12438
  if (added.length === 0 && dropped.length === 0) {
11917
12439
  console.log("nothing changed");
11918
12440
  return;
@@ -11932,12 +12454,12 @@ async function runBrowse() {
11932
12454
 
11933
12455
  // src/init.ts
11934
12456
  function copyDir(copies, from, to) {
11935
- for (const f2 of readdirSync3(from)) {
12457
+ for (const f2 of readdirSync4(from)) {
11936
12458
  copies.push({ from: join8(from, f2), to: join8(to, f2) });
11937
12459
  }
11938
12460
  }
11939
12461
  function loadManifest(root2) {
11940
- return JSON.parse(readFileSync6(join8(root2, "dist", "manifest.json"), "utf8"));
12462
+ return JSON.parse(readFileSync7(join8(root2, "dist", "manifest.json"), "utf8"));
11941
12463
  }
11942
12464
  function planInit(opts, paths) {
11943
12465
  const dist = join8(paths.root, "dist");
@@ -11951,9 +12473,10 @@ function planInit(opts, paths) {
11951
12473
  copyDir(copies, join8(paths.root, "shell", "adapters"), join8(home, "adapters"));
11952
12474
  const edits = [{ file: join8(paths.zdotdir, ".zshrc"), block: zshrcBlock(home) }];
11953
12475
  const configPath = join8(home, "config.zsh");
12476
+ const seeded = configFile(existsSync5(configPath) ? readFileSync7(configPath, "utf8") : "");
11954
12477
  const settings2 = {
11955
12478
  file: configPath,
11956
- content: configFile(existsSync5(configPath) ? readFileSync6(configPath, "utf8") : "")
12479
+ content: opts.wear ? withSetting(seeded, "TTHEME_TAB_PALETTE", opts.wear === "rotate" ? "seq" : "off") : seeded
11957
12480
  };
11958
12481
  const notes = [];
11959
12482
  if (opts.terminals.includes("ghostty")) {
@@ -11961,33 +12484,37 @@ function planInit(opts, paths) {
11961
12484
  }
11962
12485
  if (opts.terminals.includes("alacritty")) {
11963
12486
  const config = join8(paths.configHome, "alacritty", "alacritty.toml");
11964
- if (existsSync5(config) && !readFileSync6(config, "utf8").includes("# ttheme begin")) {
12487
+ if (existsSync5(config) && !readFileSync7(config, "utf8").includes("# ttheme begin")) {
11965
12488
  notes.push("alacritty.toml already exists — ttheme left it alone; add its themes/ import yourself");
11966
12489
  }
11967
12490
  }
11968
12491
  notes.push("wezterm and iterm2: import the palettes you install from the release archive");
11969
- const installed = { terminals: opts.terminals, palettes: opts.palettes };
12492
+ const installed = {
12493
+ terminals: opts.terminals,
12494
+ palettes: opts.palettes,
12495
+ ...opts.wear === "keep" ? { keepTheme: true } : {}
12496
+ };
11970
12497
  return { copies, edits, settings: settings2, catalog: loadManifest(paths.root), installed, notes };
11971
12498
  }
11972
12499
  function applyInit(plan) {
11973
- for (const c2 of plan.copies) {
11974
- mkdirSync5(dirname4(c2.to), { recursive: true });
11975
- rmSync5(c2.to, { force: true });
11976
- copyFileSync(c2.from, c2.to);
11977
- if (c2.executable) {
11978
- chmodSync(c2.to, 493);
12500
+ for (const c3 of plan.copies) {
12501
+ mkdirSync6(dirname5(c3.to), { recursive: true });
12502
+ rmSync6(c3.to, { force: true });
12503
+ copyFileSync(c3.from, c3.to);
12504
+ if (c3.executable) {
12505
+ chmodSync(c3.to, 493);
11979
12506
  }
11980
12507
  }
11981
- mkdirSync5(dirname4(plan.settings.file), { recursive: true });
11982
- writeFileSync5(plan.settings.file, plan.settings.content);
11983
- const configHome2 = dirname4(dirname4(plan.settings.file));
12508
+ mkdirSync6(dirname5(plan.settings.file), { recursive: true });
12509
+ writeFileSync6(plan.settings.file, plan.settings.content);
12510
+ const configHome2 = dirname5(dirname5(plan.settings.file));
11984
12511
  writeCatalog(configHome2, plan.catalog);
11985
12512
  writeInstalled(configHome2, plan.installed);
11986
12513
  sync(configHome2, plan.catalog, plan.installed);
11987
12514
  for (const e of plan.edits) {
11988
- mkdirSync5(dirname4(e.file), { recursive: true });
11989
- const current = existsSync5(e.file) ? readFileSync6(e.file, "utf8") : "";
11990
- writeFileSync5(e.file, upsertBlock(current, e.block));
12515
+ mkdirSync6(dirname5(e.file), { recursive: true });
12516
+ const current = existsSync5(e.file) ? readFileSync7(e.file, "utf8") : "";
12517
+ writeFileSync6(e.file, upsertBlock(current, e.block));
11991
12518
  }
11992
12519
  }
11993
12520
  function accepted(value) {
@@ -12005,11 +12532,28 @@ async function askTerminals(detected, preselected) {
12005
12532
  required: true
12006
12533
  }));
12007
12534
  }
12535
+ function wearSummary(wear, startup) {
12536
+ if (wear === "keep") {
12537
+ return "leave the terminal colors as they are";
12538
+ }
12539
+ return wear === "rotate" ? `paint every new tab with the next palette, this tab with ${startup} now` : `paint every tab with ${startup}, this one now`;
12540
+ }
12541
+ async function askWear(startup) {
12542
+ return accepted(await select({
12543
+ message: "how should the terminal wear them?",
12544
+ options: [
12545
+ { value: "default", label: "default", hint: `every tab wears ${startup}` },
12546
+ { value: "rotate", label: "rotate", hint: "every new tab wears the next palette" },
12547
+ { value: "keep", label: "keep", hint: "leave my terminal colors alone" }
12548
+ ],
12549
+ initialValue: "default"
12550
+ }));
12551
+ }
12008
12552
  function verify(plan) {
12009
12553
  const missing = [
12010
- ...plan.copies.filter((c2) => !existsSync5(c2.to)).map((c2) => c2.to),
12554
+ ...plan.copies.filter((c3) => !existsSync5(c3.to)).map((c3) => c3.to),
12011
12555
  ...existsSync5(plan.settings.file) ? [] : [plan.settings.file],
12012
- ...plan.edits.filter((e) => !readFileSync6(e.file, "utf8").includes("# ttheme begin")).map((e) => e.file)
12556
+ ...plan.edits.filter((e) => !readFileSync7(e.file, "utf8").includes("# ttheme begin")).map((e) => e.file)
12013
12557
  ];
12014
12558
  if (missing.length > 0) {
12015
12559
  throw new Error(`init left gaps:
@@ -12029,13 +12573,23 @@ function paintStartup(catalog, installed) {
12029
12573
  process.stdout.write(paletteOsc(startup));
12030
12574
  return true;
12031
12575
  }
12032
- function receipt(plan, opts, painted) {
12576
+ function wearLines(wear, startup, painted) {
12577
+ if (wear === "keep") {
12578
+ return [
12579
+ "terminal colors left alone — paint a tab with `ttheme <palette>`",
12580
+ " `ttheme default <palette>` sets the terminal default"
12581
+ ];
12582
+ }
12583
+ return [
12584
+ `default ${startup}${painted ? " — this tab wears it already" : ""}`,
12585
+ wear === "rotate" ? "new tabs rotate through the palettes — change with `ttheme config`" : `new tabs all wear ${startup} — change with \`ttheme config\``
12586
+ ];
12587
+ }
12588
+ function receipt(plan, opts, wear, painted) {
12033
12589
  const series = seriesOf(plan.catalog, opts.palettes);
12034
- const startup = startupPalette(plan.installed);
12035
12590
  note([
12036
12591
  `${series.join(", ")} (${opts.palettes.length})`,
12037
- `startup ${startup}${painted ? " — this tab wears it already" : ""}`,
12038
- "new tabs rotate through the palettes — change with `ttheme config`"
12592
+ ...wearLines(wear, startupPalette(plan.installed), painted)
12039
12593
  ].join(`
12040
12594
  `), `installed ${opts.palettes.length} palettes`);
12041
12595
  const next = ["exec zsh the ttheme command in this tab"];
@@ -12102,13 +12656,16 @@ async function runInit(flags = {}) {
12102
12656
  cancel("nothing changed");
12103
12657
  process.exit(1);
12104
12658
  }
12105
- const opts = { terminals, palettes };
12659
+ const startup = startupPalette({ terminals, palettes });
12660
+ const wear = await askWear(startup);
12661
+ const opts = { terminals, palettes, wear };
12106
12662
  const plan = planInit(opts, paths);
12107
12663
  note([
12108
12664
  `install ${palettes.length} palettes — ${seriesOf(plan.catalog, palettes).join(", ")}`,
12109
12665
  `copy ${plan.copies.length} files under ${configHome2}`,
12110
12666
  `write ${plan.settings.file}`,
12111
- ...plan.edits.map((e) => `edit ${e.file}`)
12667
+ ...plan.edits.map((e) => `edit ${e.file}`),
12668
+ wearSummary(wear, startup)
12112
12669
  ].join(`
12113
12670
  `), `wiring ${terminals.join(", ")}`);
12114
12671
  if (!accepted(await confirm({ message: "apply these changes?" }))) {
@@ -12117,7 +12674,7 @@ async function runInit(flags = {}) {
12117
12674
  }
12118
12675
  applyInit(plan);
12119
12676
  verify(plan);
12120
- receipt(plan, opts, paintStartup(plan.catalog, plan.installed));
12677
+ receipt(plan, opts, wear, wear !== "keep" && paintStartup(plan.catalog, plan.installed));
12121
12678
  }
12122
12679
 
12123
12680
  // src/cli.ts
@@ -12134,6 +12691,9 @@ function createProgram() {
12134
12691
  program2.command("find", { hidden: true }).argument("<palette>").description("pick a safebooru background for a palette — preview opens this on tab").action(async (name) => {
12135
12692
  process.exitCode = await runFind(name);
12136
12693
  });
12694
+ program2.command("image", { hidden: true }).argument("<palette>").argument("<action>", "next, prev or drop").description("switch a palette between its saved backgrounds, or remove the one shown — preview calls this").action((name, action) => {
12695
+ process.exitCode = runImage(name, action);
12696
+ });
12137
12697
  program2.command("update").description("refresh the catalog from the registry").action(() => runUpdate());
12138
12698
  return program2;
12139
12699
  }
@@ -12152,4 +12712,6 @@ ${error instanceof Error ? error.message : String(error)}`);
12152
12712
  }
12153
12713
 
12154
12714
  // src/bin.ts
12155
- process.exitCode = await runCli(process.argv);
12715
+ if (isMainThread2) {
12716
+ process.exitCode = await runCli(process.argv);
12717
+ }