@kaynooo/cli-docker 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sf-cli.js CHANGED
@@ -1,14 +1,14 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env -S bun --no-env-file
2
2
  // @bun
3
3
 
4
4
  // src/sf-cli.ts
5
- import process11 from "process";
5
+ import process16 from "process";
6
6
  // ../kit/src/args.ts
7
- import process4 from "process";
7
+ import process7 from "process";
8
8
  import { parseArgs } from "util";
9
9
 
10
10
  // ../kit/src/style.ts
11
- import process2 from "process";
11
+ import process5 from "process";
12
12
 
13
13
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
14
14
  function x(s) {
@@ -271,21 +271,209 @@ function le(i = {}) {
271
271
  }
272
272
  var t = le();
273
273
 
274
+ // ../kit/src/theme.ts
275
+ import { existsSync as existsSync2, readFileSync, statSync } from "fs";
276
+ import { homedir as homedir2 } from "os";
277
+ import { join as join3, resolve as resolve2 } from "path";
278
+ import process4 from "process";
279
+
280
+ // ../kit/src/config.ts
281
+ import { existsSync } from "fs";
282
+ import { dirname, join, resolve } from "path";
283
+ import process2 from "process";
284
+ function findUp(names, from = process2.cwd(), stop = () => false) {
285
+ const wanted = typeof names === "string" ? [names] : names;
286
+ for (let dir = resolve(from);; dir = dirname(dir)) {
287
+ const found = wanted.map((name) => join(dir, name)).find((path) => existsSync(path));
288
+ if (found !== undefined)
289
+ return found;
290
+ if (stop(dir) || dirname(dir) === dir)
291
+ return;
292
+ }
293
+ }
294
+
295
+ // ../kit/src/user-config.ts
296
+ import { homedir } from "os";
297
+ import { join as join2 } from "path";
298
+ import process3 from "process";
299
+ function configBase(where = {}) {
300
+ const xdg = where.xdg ?? (where.home === undefined ? process3.env.XDG_CONFIG_HOME : undefined);
301
+ return xdg !== undefined && xdg !== "" ? xdg : join2(where.home ?? homedir(), ".config");
302
+ }
303
+ function userConfigPath(bin, where = {}) {
304
+ return join2(configBase(where), "kaynooo-cli", `${bin}.json`);
305
+ }
306
+
307
+ // ../kit/src/theme.ts
308
+ var DEFAULT = {
309
+ colors: { accent: "cyan", id: "yellow", path: "blue", label: "dim", value: "bold", ok: "green", warn: "yellow", problem: "red", note: "dim", passed: "green", failed: "red", running: "cyan", queued: "yellow", skipped: "dim" },
310
+ glyphs: { ok: "\u2713", warn: "\u25B2", problem: "\u2717", note: "\u203A", passed: "\u2713", failed: "\u2717", running: "\u25CF", queued: "\u25CB", skipped: "-", bar: "\u2501" },
311
+ accents: ["cyan", "magenta", "green", "blue", "yellow", "redBright"],
312
+ bins: {}
313
+ };
314
+ var BRIGHT = { accent: "cyanBright", id: "yellowBright", path: "blueBright", label: "white", value: "bold whiteBright", ok: "greenBright", warn: "yellowBright", problem: "redBright", note: "white", passed: "greenBright", failed: "redBright", running: "cyanBright", queued: "yellowBright", skipped: "white" };
315
+ var MONO = { accent: "bold", id: "bold", path: "underline", label: "dim", value: "bold", ok: "bold", warn: "bold", problem: "bold", note: "dim", passed: "bold", failed: "bold", running: "bold", queued: "dim", skipped: "dim" };
316
+ var ASCII = { ok: "+", warn: "!", problem: "x", note: ">", passed: "+", failed: "x", running: "*", queued: "o", skipped: "-", bar: "#" };
317
+ var PRESETS = {
318
+ default: {},
319
+ "high-contrast": { colors: BRIGHT, accents: ["cyanBright", "magentaBright", "greenBright", "blueBright", "yellowBright", "redBright"] },
320
+ mono: { colors: MONO, accents: ["bold"] },
321
+ ascii: { glyphs: ASCII }
322
+ };
323
+ var STYLES = new Set(["bold", "dim", "italic", "underline", "inverse", "strikethrough", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray", "grey", "blackBright", "redBright", "greenBright", "yellowBright", "blueBright", "magentaBright", "cyanBright", "whiteBright"]);
324
+ var HEX = /^#[0-9a-f]{6}$/i;
325
+ var CONTROL = /\p{Cc}/u;
326
+ var MAX_FILE_BYTES = 64 * 1024;
327
+ var SPACES = /\s+/;
328
+ function isColorSpec(spec) {
329
+ if (typeof spec === "number")
330
+ return Number.isInteger(spec) && spec >= 0 && spec <= 255;
331
+ return typeof spec === "string" && spec.trim() !== "" && spec.trim().split(SPACES).every((token) => STYLES.has(token) || HEX.test(token));
332
+ }
333
+ function tinted(colors, spec) {
334
+ if (typeof spec === "number")
335
+ return colors.ansi256(spec);
336
+ return spec.trim().split(SPACES).reduce((chain, token) => HEX.test(token) ? chain.hex(token) : chain[token] ?? chain, colors);
337
+ }
338
+ function isRecord(value) {
339
+ return typeof value === "object" && value !== null && !Array.isArray(value);
340
+ }
341
+ function readThemeFile(path, warn) {
342
+ try {
343
+ const stat = statSync(path, { throwIfNoEntry: false });
344
+ if (stat === undefined)
345
+ return;
346
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES) {
347
+ warn(`${path} is not a regular file under ${MAX_FILE_BYTES / 1024} KiB, theme ignored`);
348
+ return;
349
+ }
350
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
351
+ if (isRecord(parsed))
352
+ return parsed;
353
+ warn(`${path} is not a JSON object, theme ignored`);
354
+ } catch (error) {
355
+ warn(`${path} does not parse (${error instanceof Error ? error.message : String(error)}), theme ignored`);
356
+ }
357
+ return;
358
+ }
359
+ function applyPreset(theme, name, warn, where) {
360
+ if (name === undefined)
361
+ return;
362
+ if (typeof name !== "string" || !Object.hasOwn(PRESETS, name)) {
363
+ warn(`${where}: no preset ${String(name)}, one of ${Object.keys(PRESETS).join(", ")}`);
364
+ return;
365
+ }
366
+ const preset = PRESETS[name];
367
+ Object.assign(theme.colors, preset.colors);
368
+ Object.assign(theme.glyphs, preset.glyphs);
369
+ if (preset.accents !== undefined)
370
+ theme.accents = [...preset.accents];
371
+ return name;
372
+ }
373
+ function applyFile(theme, file, where, warn) {
374
+ if (isRecord(file.colors)) {
375
+ for (const [key, value] of Object.entries(file.colors)) {
376
+ if (!Object.hasOwn(DEFAULT.colors, key))
377
+ warn(`${where}: no colour ${key}, one of ${Object.keys(DEFAULT.colors).join(", ")}`);
378
+ else if (!isColorSpec(value))
379
+ warn(`${where}: colour ${key} is not an ANSI name, \`#rrggbb\` or a number up to 255`);
380
+ else
381
+ theme.colors[key] = value;
382
+ }
383
+ }
384
+ if (isRecord(file.glyphs)) {
385
+ for (const [key, value] of Object.entries(file.glyphs)) {
386
+ if (!Object.hasOwn(DEFAULT.glyphs, key))
387
+ warn(`${where}: no glyph ${key}, one of ${Object.keys(DEFAULT.glyphs).join(", ")}`);
388
+ else if (typeof value !== "string" || CONTROL.test(value) || Bun.stringWidth(value) !== 1)
389
+ warn(`${where}: glyph ${key} must be one visible character wide, with no control character`);
390
+ else
391
+ theme.glyphs[key] = value;
392
+ }
393
+ }
394
+ if (Array.isArray(file.accents) && file.accents.length > 0 && file.accents.every(isColorSpec))
395
+ theme.accents = [...file.accents];
396
+ else if (file.accents !== undefined)
397
+ warn(`${where}: accents must be a non-empty list of colours`);
398
+ if (isRecord(file.bins)) {
399
+ for (const [bin, value] of Object.entries(file.bins)) {
400
+ if (isColorSpec(value))
401
+ theme.bins[bin] = value;
402
+ else
403
+ warn(`${where}: bin ${bin} has no valid colour`);
404
+ }
405
+ }
406
+ }
407
+ function endsWalk(home) {
408
+ return (dir) => dir === resolve2(home) || existsSync2(join3(dir, ".git"));
409
+ }
410
+ var warned = new Set;
411
+ function warnOnce(message) {
412
+ if (warned.has(message))
413
+ return;
414
+ warned.add(message);
415
+ console.error(`warning: theme ${message}`);
416
+ }
417
+ function isPath(chosen) {
418
+ return chosen.includes("/") || chosen.endsWith(".json");
419
+ }
420
+ function loadTheme({ env = process4.env, from = process4.cwd(), home = homedir2(), warn = warnOnce } = {}) {
421
+ const theme = { colors: { ...DEFAULT.colors }, glyphs: { ...DEFAULT.glyphs }, accents: [...DEFAULT.accents], bins: Object.create(null) };
422
+ const sources = [];
423
+ const chosen = env.KIT_THEME ?? "";
424
+ const wanted = chosen === "" ? [userConfigPath("theme", { home, xdg: env.XDG_CONFIG_HOME }), findUp(join3(".kaynooo", "theme.json"), from, endsWalk(home))] : [];
425
+ if (isPath(chosen))
426
+ wanted.push(resolve2(from, chosen));
427
+ const files = [];
428
+ for (const path of wanted) {
429
+ const file = path === undefined ? undefined : readThemeFile(path, warn);
430
+ if (path !== undefined && file !== undefined)
431
+ files.push({ path, file });
432
+ }
433
+ let preset = "default";
434
+ if (chosen !== "" && !isPath(chosen)) {
435
+ preset = applyPreset(theme, chosen, warn, "KIT_THEME") ?? preset;
436
+ } else {
437
+ const named = files.findLast(({ file }) => file.preset !== undefined);
438
+ if (named !== undefined)
439
+ preset = applyPreset(theme, named.file.preset, warn, named.path) ?? preset;
440
+ }
441
+ for (const { path, file } of files) {
442
+ applyFile(theme, file, path, warn);
443
+ sources.push(path);
444
+ }
445
+ return { theme, sources, preset };
446
+ }
447
+ var current;
448
+ var currentKey = "";
449
+ function activeTheme() {
450
+ const key = `${process4.cwd()}\x00${process4.env.KIT_THEME ?? ""}\x00${process4.env.XDG_CONFIG_HOME ?? ""}`;
451
+ if (current === undefined || key !== currentKey) {
452
+ current = loadTheme();
453
+ currentKey = key;
454
+ }
455
+ return current;
456
+ }
457
+
274
458
  // ../kit/src/style.ts
275
- function takesControlCodes(stream, env = process2.env) {
459
+ function takesControlCodes(stream, env = process5.env) {
276
460
  return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
277
461
  }
278
- function paint(stream, env = process2.env) {
462
+ function paint(stream, env = process5.env) {
279
463
  return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
280
464
  }
281
- var STATUSES = {
282
- passed: { symbol: "\u2713", words: ["passed", "success", "succeeded", "ok"], paint: (colors) => colors.green },
283
- failed: { symbol: "\u2717", words: ["failed", "failure", "error", "errored"], paint: (colors) => colors.red },
284
- running: { symbol: "\u25CF", words: ["running", "in_progress", "started"], paint: (colors) => colors.cyan },
285
- queued: { symbol: "\u25CB", words: ["queued", "waiting", "pending", "created", "blocked", "scheduled"], paint: (colors) => colors.yellow },
286
- skipped: { symbol: "-", words: ["skipped", "cancelled", "canceled", "manual"], paint: (colors) => colors.dim }
465
+ function role(name, stream, env = process5.env) {
466
+ const colors = paint(stream, env);
467
+ return takesControlCodes(stream, env) ? tinted(colors, activeTheme().theme.colors[name]) : colors;
468
+ }
469
+ var STATUS_WORDS = {
470
+ passed: ["passed", "success", "succeeded", "ok"],
471
+ failed: ["failed", "failure", "error", "errored"],
472
+ running: ["running", "in_progress", "started"],
473
+ queued: ["queued", "waiting", "pending", "created", "blocked", "scheduled"],
474
+ skipped: ["skipped", "cancelled", "canceled", "manual"]
287
475
  };
288
- var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
476
+ var STATUS_OF = new Map(Object.entries(STATUS_WORDS).flatMap(([status, words]) => words.map((word) => [word, status])));
289
477
 
290
478
  // ../kit/src/format.ts
291
479
  function upTo(text, separator) {
@@ -298,27 +486,27 @@ function printJson(value) {
298
486
  }
299
487
 
300
488
  // ../kit/src/help.ts
301
- import process3 from "process";
489
+ import process6 from "process";
302
490
  var OPTIONAL = /\[[^\]]+\]/g;
303
491
  var USAGE_PREFIX = /^usage:\s*/i;
304
492
  var FULL_STOP = /\.$/;
305
- function usageLine(usage, colors = paint(process3.stdout)) {
493
+ function usageLine(usage, colors = paint(process6.stdout)) {
306
494
  return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
307
495
  }
308
- function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces, exits = [] } = {}) {
496
+ function helpText(usage, options, colors = paint(process6.stdout), { examples = [], replaces, exits = [] } = {}) {
309
497
  return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
310
498
  `);
311
499
  }
312
- function exitRows(exits, colors = paint(process3.stdout)) {
500
+ function exitRows(exits, colors = paint(process6.stdout)) {
313
501
  if (exits.length === 0)
314
502
  return [];
315
503
  const width = Math.max(...exits.map(([code]) => String(code).length));
316
504
  return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
317
505
  }
318
- function replacesRows(replaces, colors = paint(process3.stdout)) {
506
+ function replacesRows(replaces, colors = paint(process6.stdout)) {
319
507
  return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
320
508
  }
321
- function wantsHelpJson(env = process3.env) {
509
+ function wantsHelpJson(env = process6.env) {
322
510
  return env.KIT_HELP === "json";
323
511
  }
324
512
  function description(usage, { examples = [], replaces, exits } = {}, options) {
@@ -337,12 +525,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
337
525
  ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
338
526
  };
339
527
  }
340
- function exampleRows(examples, colors = paint(process3.stdout)) {
528
+ function exampleRows(examples, colors = paint(process6.stdout)) {
341
529
  if (examples.length === 0)
342
530
  return [];
343
531
  return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
344
532
  }
345
- function optionRows(options, colors = paint(process3.stdout)) {
533
+ function optionRows(options, colors = paint(process6.stdout)) {
346
534
  const rows = Object.entries(options).map(([name, option]) => {
347
535
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
348
536
  const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
@@ -436,7 +624,7 @@ var OUTPUT_OPTIONS = {
436
624
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
437
625
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
438
626
  };
439
- function applyOutput(color, plain, env = process4.env) {
627
+ function applyOutput(color, plain, env = process7.env) {
440
628
  const when = plain === true ? "never" : color ?? "auto";
441
629
  if (when === "auto")
442
630
  return;
@@ -611,15 +799,15 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
611
799
  var RESET_STYLE = `${ESC}[0m`;
612
800
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
613
801
  // ../kit/src/cli.ts
614
- import { readFileSync, realpathSync } from "fs";
615
- import { dirname as dirname2, join as join2 } from "path";
616
- import process6 from "process";
802
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
803
+ import { dirname as dirname3, join as join5 } from "path";
804
+ import process9 from "process";
617
805
 
618
806
  // ../kit/src/usage.ts
619
807
  import { appendFileSync, mkdirSync } from "fs";
620
- import { homedir } from "os";
621
- import { dirname, isAbsolute, join } from "path";
622
- import process5 from "process";
808
+ import { homedir as homedir3 } from "os";
809
+ import { dirname as dirname2, isAbsolute, join as join4 } from "path";
810
+ import process8 from "process";
623
811
 
624
812
  // ../kit/src/logs.ts
625
813
  var DAY_MS = 24 * 60 * 60 * 1000;
@@ -633,14 +821,14 @@ var usageError;
633
821
  function noteUsageError(message) {
634
822
  usageError = message;
635
823
  }
636
- function stateHome(env = process5.env) {
824
+ function stateHome(env = process8.env) {
637
825
  const dir = env.XDG_STATE_HOME;
638
- return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
826
+ return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
639
827
  }
640
- function usagePath(env = process5.env) {
641
- return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
828
+ function usagePath(env = process8.env) {
829
+ return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
642
830
  }
643
- function usageRecorded(env = process5.env) {
831
+ function usageRecorded(env = process8.env) {
644
832
  const off = env.KAYNOOO_CLI_NO_USAGE;
645
833
  return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
646
834
  }
@@ -654,12 +842,12 @@ function usageEntry(bin, command, argv, code, now = new Date) {
654
842
  usageError = undefined;
655
843
  return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
656
844
  }
657
- function recordUsage(entry, env = process5.env) {
845
+ function recordUsage(entry, env = process8.env) {
658
846
  if (!usageRecorded(env))
659
847
  return;
660
848
  try {
661
849
  const path = usagePath(env);
662
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
850
+ mkdirSync(dirname2(path), { recursive: true, mode: 448 });
663
851
  appendFileSync(path, `${JSON.stringify(entry)}
664
852
  `, { mode: 384 });
665
853
  } catch {}
@@ -672,8 +860,8 @@ var LEVEL_OPTIONS = {
672
860
  };
673
861
  var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
674
862
  function printError(prefix, message, hint, cause) {
675
- const colors = paint(process6.stderr);
676
- const styled = takesControlCodes(process6.stderr);
863
+ const colors = paint(process9.stderr);
864
+ const styled = takesControlCodes(process9.stderr);
677
865
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
678
866
  const under = (label, text, tint) => text === undefined ? "" : `
679
867
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -692,7 +880,7 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
692
880
  const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
693
881
  const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
694
882
  const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
695
- if (error instanceof UsageError)
883
+ if (error instanceof Exit && error.code === 2)
696
884
  noteUsageError(error.message);
697
885
  const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
698
886
  report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
@@ -756,17 +944,17 @@ async function printCandidates(found) {
756
944
  return 0;
757
945
  }
758
946
  function packageVersion(entry = Bun.main) {
759
- for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
947
+ for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
760
948
  try {
761
- const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
949
+ const { version } = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf8"));
762
950
  if (typeof version === "string")
763
951
  return version;
764
952
  } catch {}
765
- if (dirname2(dir) === dir)
953
+ if (dirname3(dir) === dir)
766
954
  return "0.0.0";
767
955
  }
768
956
  }
769
- async function runMain({ name, version, run, redact, usage }, argv = process6.argv.slice(2)) {
957
+ async function runMain({ name, version, run, redact, usage }, argv = process9.argv.slice(2)) {
770
958
  if (argv[0] === COMPLETE)
771
959
  return printCandidates(leafCandidates(run, argv.slice(1)));
772
960
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
@@ -809,16 +997,635 @@ var {Glob } = globalThis.Bun;
809
997
  var SESSION_FILES = new Glob("*.json");
810
998
  var ACCOUNT_DIRS = new Glob("*/sessions");
811
999
  // src/sf.ts
812
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
813
- import { homedir as homedir3 } from "os";
814
- import { dirname as dirname6, join as join6, resolve as resolve2 } from "path";
815
- import process10 from "process";
1000
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1001
+ import { homedir as homedir5 } from "os";
1002
+ import { dirname as dirname7, join as join9, resolve as resolve4 } from "path";
1003
+ import process15 from "process";
816
1004
 
817
- // ../kit/src/fzf.ts
818
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
819
- import { homedir as homedir2 } from "os";
820
- import { dirname as dirname3, join as join3 } from "path";
821
- import process8 from "process";
1005
+ // ../kit/src/picker.ts
1006
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
1007
+ import { homedir as homedir4 } from "os";
1008
+ import { dirname as dirname4, join as join6 } from "path";
1009
+ import process13 from "process";
1010
+
1011
+ // ../kit/src/picker/score.ts
1012
+ var MATCH = 16;
1013
+ var CONSECUTIVE = 12;
1014
+ var BOUNDARY = 10;
1015
+ var PREFIX = 8;
1016
+ var GAP = 1;
1017
+ var BOUNDARY_AFTER = new Set([" ", "/", "-", "_", ".", ":", "\\", "(", "[", ",", "\t"]);
1018
+ var UPPER = /\p{Lu}/u;
1019
+ var LOWER = /\p{Ll}/u;
1020
+ var NONE = Number.NEGATIVE_INFINITY;
1021
+ function startsWord(text, index) {
1022
+ if (index === 0)
1023
+ return true;
1024
+ const before = text[index - 1];
1025
+ return BOUNDARY_AFTER.has(before) || LOWER.test(before) && UPPER.test(text[index]);
1026
+ }
1027
+ function fuzzy(query, text) {
1028
+ if (query === "")
1029
+ return { score: 0, positions: [] };
1030
+ const exact = query !== query.toLowerCase();
1031
+ const needle = exact ? query : query.toLowerCase();
1032
+ const haystack = exact ? text : text.toLowerCase();
1033
+ if (haystack.length !== text.length || needle.length > haystack.length)
1034
+ return;
1035
+ const width = haystack.length;
1036
+ const best = Array.from({ length: needle.length }, () => new Array(width).fill(NONE));
1037
+ const from = Array.from({ length: needle.length }, () => new Array(width).fill(-1));
1038
+ for (let j = 0;j < needle.length; j++) {
1039
+ let run = NONE;
1040
+ let runAt = -1;
1041
+ for (let i = j;i < width; i++) {
1042
+ if (j > 0 && i - 2 >= j - 1) {
1043
+ const candidate = best[j - 1][i - 2] + (i - 2) * GAP;
1044
+ if (candidate > run) {
1045
+ run = candidate;
1046
+ runAt = i - 2;
1047
+ }
1048
+ }
1049
+ if (haystack[i] !== needle[j])
1050
+ continue;
1051
+ const here = MATCH + (startsWord(text, i) ? BOUNDARY : 0) + (i === 0 ? PREFIX : 0);
1052
+ if (j === 0) {
1053
+ best[0][i] = here - i * GAP;
1054
+ continue;
1055
+ }
1056
+ const joined = best[j - 1][i - 1] + CONSECUTIVE;
1057
+ const skipped = run - (i - 1) * GAP;
1058
+ if (joined > NONE && joined >= skipped) {
1059
+ best[j][i] = joined + here;
1060
+ from[j][i] = i - 1;
1061
+ } else if (skipped > NONE) {
1062
+ best[j][i] = skipped + here;
1063
+ from[j][i] = runAt;
1064
+ }
1065
+ }
1066
+ }
1067
+ const last = best[needle.length - 1];
1068
+ let end = -1;
1069
+ for (let i = 0;i < width; i++) {
1070
+ if (last[i] > NONE && (end === -1 || last[i] > last[end]))
1071
+ end = i;
1072
+ }
1073
+ if (end === -1)
1074
+ return;
1075
+ const positions = [];
1076
+ for (let j = needle.length - 1, i = end;j >= 0; j--) {
1077
+ positions.unshift(i);
1078
+ i = from[j][i];
1079
+ }
1080
+ return { score: last[end], positions };
1081
+ }
1082
+ function rank(query, lines) {
1083
+ if (query === "")
1084
+ return lines.map((_, index) => ({ index, positions: [] }));
1085
+ const found = [];
1086
+ for (const [index, line] of lines.entries()) {
1087
+ const match = fuzzy(query, line);
1088
+ if (match !== undefined)
1089
+ found.push({ index, positions: match.positions, score: match.score });
1090
+ }
1091
+ return found.sort((a, b) => b.score - a.score || lines[a.index].length - lines[b.index].length || a.index - b.index);
1092
+ }
1093
+
1094
+ // ../kit/src/picker/state.ts
1095
+ var ANSI2 = /\x1B(?:\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x40-\x5A\x5C-\x5F])/g;
1096
+ var CONTROL2 = /[\x00-\x1F\x7F-\x9F]/g;
1097
+ var IS_CONTROL = /[\x00-\x1F\x7F-\x9F]/;
1098
+ var SGR = /^\x1B\[[0-9;]*m$/;
1099
+ var RESET = /^\x1B\[0?m$/;
1100
+ var COLUMN_GAP = 2;
1101
+ var PASTE_START = "\x1B[200~";
1102
+ var PASTE_END = "\x1B[201~";
1103
+ var LAST_WORD = /\S*\s*$/;
1104
+ var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|\x1B[\s\S]|[\s\S]/gu;
1105
+ var KEYS = {
1106
+ "\x1B[A": "up",
1107
+ "\x1BOA": "up",
1108
+ "\x10": "up",
1109
+ "\x1B[B": "down",
1110
+ "\x1BOB": "down",
1111
+ "\x0E": "down",
1112
+ "\r": "enter",
1113
+ "\n": "enter",
1114
+ "\x03": "cancel",
1115
+ "\x1B": "cancel",
1116
+ "\x7F": "erase",
1117
+ "\b": "erase",
1118
+ "\x15": "clear",
1119
+ "\x17": "word",
1120
+ "\t": "tab"
1121
+ };
1122
+ var CTRL_KEY = /^ctrl-[abd-gkln-z]$/;
1123
+ function repeat(value, count) {
1124
+ return Array.from({ length: count }).fill(value);
1125
+ }
1126
+ function styled(line) {
1127
+ let text = "";
1128
+ const styles = [];
1129
+ let active = "";
1130
+ let colored = false;
1131
+ let last = 0;
1132
+ function add(chunk) {
1133
+ const flat = chunk.replace(CONTROL2, " ");
1134
+ text += flat;
1135
+ colored ||= active !== "" && flat !== "";
1136
+ styles.push(...repeat(active, flat.length));
1137
+ }
1138
+ for (const found of line.matchAll(ANSI2)) {
1139
+ add(line.slice(last, found.index));
1140
+ last = found.index + found[0].length;
1141
+ if (SGR.test(found[0]))
1142
+ active = RESET.test(found[0]) ? "" : active + found[0];
1143
+ }
1144
+ add(line.slice(last));
1145
+ return { text, styles: colored ? styles : undefined };
1146
+ }
1147
+ function lay(rows, searched) {
1148
+ const parsed = rows.map((row) => row.cells.map(styled));
1149
+ const widths = (parsed[0] ?? []).map((_, column) => Math.max(...parsed.map((cells) => Bun.stringWidth(cells[column]?.text ?? ""))));
1150
+ return parsed.map((cells) => {
1151
+ let text = "";
1152
+ const styles = [];
1153
+ let colored = false;
1154
+ let searchEnd = 0;
1155
+ for (const [column, cell] of cells.entries()) {
1156
+ const last = column === cells.length - 1;
1157
+ const shown = cells.length > 1 && last ? cell.text.trimEnd() : cell.text;
1158
+ text += shown;
1159
+ colored ||= cell.styles !== undefined;
1160
+ styles.push(...cell.styles?.slice(0, shown.length) ?? repeat("", shown.length));
1161
+ if (column < searched)
1162
+ searchEnd = cells.length > 1 ? text.trimEnd().length : text.length;
1163
+ if (!last) {
1164
+ const pad = widths[column] - Bun.stringWidth(cell.text) + COLUMN_GAP;
1165
+ text += " ".repeat(pad);
1166
+ styles.push(...repeat("", pad));
1167
+ }
1168
+ }
1169
+ return { text, styles: colored ? styles : undefined, searchable: text.slice(0, searchEnd) };
1170
+ });
1171
+ }
1172
+ function parseKeys(input, paste = { active: false }, ctrls = []) {
1173
+ const codes = new Map(ctrls.map((name) => [String.fromCharCode(name.codePointAt(name.length - 1) - 96), name]));
1174
+ return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => {
1175
+ const named = codes.get(sequence);
1176
+ const key = named === undefined ? KEYS[sequence] : { ctrl: named };
1177
+ if (sequence === PASTE_START || sequence === PASTE_END) {
1178
+ paste.active = sequence === PASTE_START;
1179
+ return [];
1180
+ }
1181
+ if (paste.active && (sequence === "\r" || sequence === `
1182
+ `))
1183
+ return [];
1184
+ if (key !== undefined)
1185
+ return [key];
1186
+ return IS_CONTROL.test(sequence) || sequence.startsWith("\x1B") ? [] : [{ text: sequence }];
1187
+ });
1188
+ }
1189
+ function createState(rows, { multi = false, prompt = "> ", query = "", header = "", expect = [], searched = Number.POSITIVE_INFINITY } = {}) {
1190
+ const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, marked: new Set, multi, prompt, done: undefined };
1191
+ return withRows(state, rows);
1192
+ }
1193
+ function withRows(state, rows, header = state.header) {
1194
+ const cells = rows.map((row) => typeof row === "string" ? { hidden: [], cells: [row] } : row);
1195
+ const laid = lay(cells, state.searched);
1196
+ const searchable = laid.map((row) => row.searchable);
1197
+ const matches = rank(state.query, searchable);
1198
+ return { ...state, rows: cells, items: laid.map((row) => row.text), styles: laid.map((row) => row.styles), searchable, header, matches, cursor: Math.max(0, Math.min(matches.length - 1, state.cursor)), marked: new Set };
1199
+ }
1200
+ function withQuery(state, query) {
1201
+ return { ...state, query, matches: rank(query, state.searchable), cursor: 0 };
1202
+ }
1203
+ function move(state, by) {
1204
+ return { ...state, cursor: Math.max(0, Math.min(state.matches.length - 1, state.cursor + by)) };
1205
+ }
1206
+ function highlighted(state) {
1207
+ return state.matches[state.cursor]?.index;
1208
+ }
1209
+ function press(state, key) {
1210
+ if (state.done !== undefined)
1211
+ return state;
1212
+ if (typeof key === "object") {
1213
+ if ("text" in key)
1214
+ return withQuery(state, state.query + key.text);
1215
+ return state.expect.includes(key.ctrl) ? { ...state, done: "accept", key: key.ctrl } : state;
1216
+ }
1217
+ switch (key) {
1218
+ case "up":
1219
+ return move(state, -1);
1220
+ case "down":
1221
+ return move(state, 1);
1222
+ case "cancel":
1223
+ return { ...state, done: "cancel" };
1224
+ case "erase":
1225
+ return withQuery(state, [...state.query].slice(0, -1).join(""));
1226
+ case "clear":
1227
+ return withQuery(state, "");
1228
+ case "word":
1229
+ return withQuery(state, state.query.replace(LAST_WORD, ""));
1230
+ case "enter":
1231
+ return state.matches.length === 0 && state.marked.size === 0 ? state : { ...state, done: "accept" };
1232
+ case "tab": {
1233
+ const at = highlighted(state);
1234
+ if (!state.multi || at === undefined)
1235
+ return state;
1236
+ const marked = new Set(state.marked);
1237
+ if (!marked.delete(at))
1238
+ marked.add(at);
1239
+ return move({ ...state, marked }, 1);
1240
+ }
1241
+ }
1242
+ }
1243
+ function chosen(state) {
1244
+ if (state.marked.size > 0)
1245
+ return state.rows.filter((_, index) => state.marked.has(index));
1246
+ const at = highlighted(state);
1247
+ return at === undefined ? [] : [state.rows[at]];
1248
+ }
1249
+
1250
+ // ../kit/src/picker/terminal.ts
1251
+ import { openSync } from "fs";
1252
+ import process12 from "process";
1253
+ import tty from "tty";
1254
+
1255
+ // ../kit/src/picker/preview.ts
1256
+ import process11 from "process";
1257
+
1258
+ // ../kit/src/shell.ts
1259
+ var SEPARATORS = new Set([`
1260
+ `, ";", "|"]);
1261
+ var PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
1262
+ var GLOB_CHARS = new Set(["*", "?", "["]);
1263
+ var COMMENT_PREFIXES = new Set([";", "&", "|", "(", ")"]);
1264
+ var SAFE_WORD = /^[\w@%+=:,./-]+$/;
1265
+ var DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
1266
+ var KEYWORDS = new Set(["then", "do", "else", "if", "elif", "while", "until", "!", "{", "}"]);
1267
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ash", "ksh", "fish", "csh", "tcsh", "nu", "xonsh", "elvish", "pwsh", "powershell"]);
1268
+ var SCANNED_PROGRAMS = new Set(["su", "runuser", "flock"]);
1269
+ var COMMAND_STRING_PROGRAMS = new Set([...SHELLS, ...SCANNED_PROGRAMS]);
1270
+ var POWERSHELL = new Set(["pwsh", "powershell"]);
1271
+ var SHELL_VALUE_OPTIONS = new Set(["-o", "-O", "--rcfile", "--init-file"]);
1272
+ var FISH_VALUE_OPTIONS = new Set(["-C", "--init-command"]);
1273
+ var PRINTERS = new Set(["echo", "printf"]);
1274
+ var FIND_EXEC = new Set(["-exec", "-execdir", "-ok", "-okdir"]);
1275
+ function wrapper(short = "", long = [], operands = 0, runsString = false) {
1276
+ return { short, long, operands, runsString };
1277
+ }
1278
+ var WRAPPERS = new Map([
1279
+ ["command", wrapper()],
1280
+ ["builtin", wrapper()],
1281
+ ["nohup", wrapper()],
1282
+ ["setsid", wrapper()],
1283
+ ["busybox", wrapper()],
1284
+ ["exec", wrapper("a")],
1285
+ ["time", wrapper("fo", ["--format", "--output"])],
1286
+ ["env", wrapper("uCS", ["--unset", "--chdir", "--split-string", "--argv0"])],
1287
+ ["sudo", wrapper("ughpCTrtUDR", ["--user", "--group", "--host", "--prompt", "--close-from", "--command-timeout", "--role", "--type", "--other-user", "--chdir", "--chroot"])],
1288
+ ["doas", wrapper("uC")],
1289
+ ["timeout", wrapper("sk", ["--signal", "--kill-after"], 1)],
1290
+ ["nice", wrapper("n", ["--adjustment"])],
1291
+ ["ionice", wrapper("cnpPu", ["--class", "--classdata", "--pid", "--pgid", "--uid"])],
1292
+ ["stdbuf", wrapper("ioe", ["--input", "--output", "--error"])],
1293
+ ["chroot", wrapper("", ["--userspec", "--groups"], 1)],
1294
+ ["watch", wrapper("n", ["--interval"], 0, true)],
1295
+ ["flock", wrapper("wE", ["--timeout", "--conflict-exit-code"], 1)],
1296
+ ["parallel", wrapper("jnNSaIPlLdTsC", ["--jobs", "--max-args", "--max-replace-args", "--sshlogin", "--arg-file", "--delimiter", "--colsep"], 0, true)],
1297
+ ["unbuffer", wrapper()],
1298
+ ["systemd-run", wrapper("puEMH", ["--property", "--unit", "--description", "--slice", "--setenv", "--machine", "--host", "--uid", "--gid", "--working-directory", "--nice", "--service-type"])],
1299
+ ["xargs", wrapper("IndsEaPL", ["--max-args", "--max-procs", "--max-chars", "--delimiter", "--arg-file", "--eof", "--max-lines", "--process-slot-var"])]
1300
+ ]);
1301
+ function shellQuote(word) {
1302
+ return SAFE_WORD.test(word) && !word.startsWith("=") ? word : `'${word.replaceAll("'", "'\\''")}'`;
1303
+ }
1304
+
1305
+ // ../kit/src/picker/preview.ts
1306
+ var PREVIEW_BYTES = 64 * 1024;
1307
+ var PREVIEW_LINES = 200;
1308
+ var PREVIEW_TIMEOUT_MS = 3000;
1309
+ var PLACEHOLDER = /\{([1-9]\d*)?\}/g;
1310
+ function previewCommand(template, line, hidden = []) {
1311
+ return template.replace(PLACEHOLDER, (_, field) => shellQuote(field === undefined ? line : hidden[Number(field) - 1] ?? ""));
1312
+ }
1313
+ function previewLines(output) {
1314
+ const lines = output.split(`
1315
+ `);
1316
+ if (lines.at(-1) === "")
1317
+ lines.pop();
1318
+ return lines.slice(0, PREVIEW_LINES);
1319
+ }
1320
+ async function runPreview(template, line, signal, hidden = []) {
1321
+ const proc = Bun.spawn(["sh", "-c", `exec 2>&1
1322
+ ${previewCommand(template, line, hidden)}`], { stdin: "ignore", stdout: "pipe", stderr: "ignore", detached: true });
1323
+ const stop = () => {
1324
+ try {
1325
+ process11.kill(-proc.pid, "SIGKILL");
1326
+ } catch {
1327
+ proc.kill();
1328
+ }
1329
+ };
1330
+ const timer = setTimeout(stop, PREVIEW_TIMEOUT_MS);
1331
+ const chunks = [];
1332
+ let size = 0;
1333
+ signal.addEventListener("abort", stop);
1334
+ try {
1335
+ for await (const chunk of proc.stdout) {
1336
+ chunks.push(chunk);
1337
+ size += chunk.length;
1338
+ if (size >= PREVIEW_BYTES) {
1339
+ stop();
1340
+ break;
1341
+ }
1342
+ }
1343
+ } finally {
1344
+ clearTimeout(timer);
1345
+ signal.removeEventListener("abort", stop);
1346
+ }
1347
+ return signal.aborted ? [] : previewLines(Buffer.concat(chunks).subarray(0, PREVIEW_BYTES).toString());
1348
+ }
1349
+
1350
+ // ../kit/src/picker/view.ts
1351
+ var MIN_HEIGHT = 4;
1352
+ var PREVIEW_MIN_COLUMNS = 80;
1353
+ var PREVIEW_MIN_ROWS = 10;
1354
+ var PREVIEW_SHARE = 0.4;
1355
+ var RESET2 = "\x1B[0m";
1356
+ var SEPARATOR = " \u2502 ";
1357
+ var HEIGHT = /^(\d+)(%?)$/;
1358
+ function parseHeight(text) {
1359
+ const found = HEIGHT.exec(text);
1360
+ if (found === null || Number(found[1]) < 1)
1361
+ return;
1362
+ return found[2] === "%" ? { percent: Math.min(100, Number(found[1])) } : { lines: Number(found[1]) };
1363
+ }
1364
+ function layoutFor(size, height, previewing, position = "right") {
1365
+ const wanted = "lines" in height ? height.lines : Math.round(size.rows * height.percent / 100);
1366
+ const rows = Math.min(size.rows, Math.max(MIN_HEIGHT, wanted));
1367
+ if (position === "down") {
1368
+ const previewHeight = previewing && rows >= PREVIEW_MIN_ROWS ? Math.round(rows * PREVIEW_SHARE) : 0;
1369
+ return { height: rows, listHeight: rows - previewHeight, listWidth: size.columns, previewWidth: 0, previewHeight };
1370
+ }
1371
+ const split = previewing && size.columns >= PREVIEW_MIN_COLUMNS;
1372
+ const listWidth = split ? Math.floor(size.columns / 2) : size.columns;
1373
+ return { height: rows, listHeight: rows, listWidth, previewWidth: split ? size.columns - listWidth - SEPARATOR.length : 0, previewHeight: 0 };
1374
+ }
1375
+ function fit(text, width, plain, mark = plain, marked = new Set, styles) {
1376
+ const room = Bun.stringWidth(text) > width ? width - 1 : width;
1377
+ let out = "";
1378
+ let used = 0;
1379
+ let at = 0;
1380
+ for (const char of text) {
1381
+ const size = Bun.stringWidth(char);
1382
+ if (used + size > room)
1383
+ break;
1384
+ const style = marked.has(at) ? "" : styles?.[at] ?? "";
1385
+ out += style === "" ? (marked.has(at) ? mark : plain)(char) : `${style}${char}${RESET2}`;
1386
+ used += size;
1387
+ at += char.length;
1388
+ }
1389
+ if (room < width) {
1390
+ out += plain("\u2026");
1391
+ used += 1;
1392
+ }
1393
+ return { text: out, width: used };
1394
+ }
1395
+ var identity = (text) => text;
1396
+ function fitStyled(line, width, plain = identity) {
1397
+ const { text, styles } = styled(line);
1398
+ return fit(text, width, plain, plain, new Set, styles);
1399
+ }
1400
+ function caretColumn(state, layout) {
1401
+ return Math.min(layout.listWidth, Bun.stringWidth(state.prompt) + Bun.stringWidth(state.query) + 1);
1402
+ }
1403
+ function listLines(state, layout, theme) {
1404
+ const header = state.header === "" ? [] : state.header.split(`
1405
+ `).map((line) => fitStyled(line, layout.listWidth, theme.label));
1406
+ const rows = Math.max(1, layout.listHeight - 2 - header.length);
1407
+ const prefixWidth = state.multi ? 4 : 2;
1408
+ const top = Math.max(0, Math.min(state.cursor - Math.floor(rows / 2), state.matches.length - rows));
1409
+ const marks = (state.marked.size > 0 ? ` (${state.marked.size} marked)` : "") + (state.status === "" ? "" : ` ${state.status}`);
1410
+ const promptWidth = Bun.stringWidth(state.prompt);
1411
+ const query = fit(state.query, Math.max(1, layout.listWidth - promptWidth), identity);
1412
+ const count = fit(` ${state.matches.length}/${state.items.length}${marks}`, layout.listWidth, theme.label);
1413
+ const lines = [{ text: theme.accent(state.prompt) + query.text, width: promptWidth + query.width }, count, ...header];
1414
+ if (state.matches.length === 0)
1415
+ lines.push(fit(" no match", layout.listWidth, theme.label));
1416
+ for (const [offset, match] of state.matches.slice(top, top + rows).entries()) {
1417
+ const current = top + offset === state.cursor;
1418
+ const pointer = current ? theme.accent("\u276F") : " ";
1419
+ const mark = state.multi ? `${state.marked.has(match.index) ? theme.id("\u25CF") : " "} ` : "";
1420
+ const text = fit(state.items[match.index], layout.listWidth - prefixWidth, current ? theme.value : identity, theme.accent, new Set(match.positions), state.styles[match.index]);
1421
+ lines.push({ text: `${pointer} ${mark}${text.text}`, width: prefixWidth + text.width });
1422
+ }
1423
+ return lines;
1424
+ }
1425
+ function render(state, layout, theme, preview = []) {
1426
+ const left = listLines(state, layout, theme);
1427
+ return Array.from({ length: layout.height }, (_, row) => {
1428
+ if (row >= layout.listHeight)
1429
+ return row === layout.listHeight ? theme.label("\u2500".repeat(layout.listWidth)) : fitStyled(preview[row - layout.listHeight - 1] ?? "", layout.listWidth).text;
1430
+ const list = left[row] ?? { text: "", width: 0 };
1431
+ if (layout.previewWidth === 0)
1432
+ return list.text;
1433
+ return `${list.text}${" ".repeat(Math.max(0, layout.listWidth - list.width))}${theme.label(SEPARATOR)}${fitStyled(preview[row] ?? "", layout.previewWidth).text}`;
1434
+ });
1435
+ }
1436
+
1437
+ // ../kit/src/picker/terminal.ts
1438
+ var PREVIEW_DELAY_MS = 120;
1439
+ var OPEN = { ctrl: "open" };
1440
+ var HIDE_CURSOR = "\x1B[?25l";
1441
+ var SHOW_CURSOR = "\x1B[?25h";
1442
+ var PASTE_ON = "\x1B[?2004h";
1443
+ var PASTE_OFF = "\x1B[?2004l";
1444
+ var CLEAR_BELOW = "\r\x1B[J";
1445
+ var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
1446
+ function up(lines) {
1447
+ return lines > 0 ? `\x1B[${lines}A` : "";
1448
+ }
1449
+ function openTerminal() {
1450
+ let input;
1451
+ try {
1452
+ input = new tty.ReadStream(openSync("/dev/tty", "r"));
1453
+ } catch {
1454
+ return fail("no terminal to read keys from (/dev/tty)");
1455
+ }
1456
+ const out = process12.stderr;
1457
+ let resize;
1458
+ return {
1459
+ get columns() {
1460
+ return out.columns || 80;
1461
+ },
1462
+ get rows() {
1463
+ return out.rows || 24;
1464
+ },
1465
+ theme: { accent: role("accent", out), label: role("label", out), value: role("value", out), id: role("id", out) },
1466
+ write: (text) => void out.write(text),
1467
+ listen(onChunk, onResize) {
1468
+ resize = onResize;
1469
+ input.setRawMode(true);
1470
+ out.on("resize", onResize);
1471
+ input.on("data", (chunk) => onChunk(chunk.toString()));
1472
+ },
1473
+ close() {
1474
+ if (resize !== undefined)
1475
+ out.off("resize", resize);
1476
+ try {
1477
+ input.setRawMode(false);
1478
+ } finally {
1479
+ input.destroy();
1480
+ }
1481
+ }
1482
+ };
1483
+ }
1484
+ async function select(rows, settings, terminal = openTerminal()) {
1485
+ const { theme } = terminal;
1486
+ let state = createState(rows, settings);
1487
+ let preview = [];
1488
+ let reserved = 0;
1489
+ let previewTimer;
1490
+ let previewing = new AbortController;
1491
+ let shownLine;
1492
+ let finish;
1493
+ let restored = false;
1494
+ function draw(clear = false) {
1495
+ if (restored)
1496
+ return;
1497
+ const layout = layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition);
1498
+ let frame = HIDE_CURSOR;
1499
+ if (reserved !== layout.height || clear) {
1500
+ frame += CLEAR_BELOW + `
1501
+ `.repeat(layout.height - 1) + up(layout.height - 1);
1502
+ reserved = layout.height;
1503
+ }
1504
+ frame += `\r${render(state, layout, theme, preview).map((line) => `\x1B[2K${line}`).join(`
1505
+ `)}${up(layout.height - 1)}\r\x1B[${caretColumn(state, layout)}G${SHOW_CURSOR}`;
1506
+ terminal.write(frame);
1507
+ }
1508
+ function refreshPreview() {
1509
+ const at = highlighted(state);
1510
+ if (settings.preview === undefined || at === shownLine)
1511
+ return;
1512
+ shownLine = at;
1513
+ clearTimeout(previewTimer);
1514
+ previewing.abort();
1515
+ previewing = new AbortController;
1516
+ if (at === undefined) {
1517
+ preview = [];
1518
+ return;
1519
+ }
1520
+ const { signal } = previewing;
1521
+ const template = settings.preview;
1522
+ const row = state.rows[at];
1523
+ async function show() {
1524
+ const shown = typeof template === "string" ? await runPreview(template, row.cells.join(" "), signal, row.hidden) : await previewOf(template, row.hidden, signal);
1525
+ if (!signal.aborted) {
1526
+ preview = shown;
1527
+ draw();
1528
+ }
1529
+ }
1530
+ previewTimer = setTimeout(() => void show(), PREVIEW_DELAY_MS);
1531
+ }
1532
+ function restore() {
1533
+ if (restored)
1534
+ return;
1535
+ restored = true;
1536
+ clearTimeout(previewTimer);
1537
+ previewing.abort();
1538
+ terminal.write(`${CLEAR_BELOW}${PASTE_OFF}${SHOW_CURSOR}`);
1539
+ terminal.close();
1540
+ }
1541
+ const stop = () => {
1542
+ state = { ...state, done: "cancel" };
1543
+ finish?.();
1544
+ };
1545
+ const paste = { active: false };
1546
+ const { reload } = settings;
1547
+ const pending = [];
1548
+ let busy = false;
1549
+ let draining = false;
1550
+ async function reloadRows() {
1551
+ if (reload === undefined)
1552
+ return;
1553
+ busy = true;
1554
+ state = { ...state, status: "reloading" };
1555
+ draw();
1556
+ try {
1557
+ const next = await reload.run();
1558
+ state = { ...withRows(state, next.rows, next.header), status: "" };
1559
+ shownLine = undefined;
1560
+ } catch (error) {
1561
+ state = { ...state, status: `reload failed: ${error instanceof Error ? error.message : String(error)}` };
1562
+ } finally {
1563
+ busy = false;
1564
+ }
1565
+ }
1566
+ async function drain() {
1567
+ if (draining)
1568
+ return;
1569
+ draining = true;
1570
+ try {
1571
+ for (let key = pending.shift();key !== undefined && state.done === undefined; key = pending.shift()) {
1572
+ if (key === OPEN || typeof key === "object" && "ctrl" in key && key.ctrl === reload?.key)
1573
+ await reloadRows();
1574
+ else
1575
+ state = press(state, key);
1576
+ }
1577
+ } finally {
1578
+ draining = false;
1579
+ }
1580
+ if (state.done !== undefined) {
1581
+ finish?.();
1582
+ return;
1583
+ }
1584
+ refreshPreview();
1585
+ draw();
1586
+ }
1587
+ function onChunk(chunk) {
1588
+ const keys = parseKeys(chunk, paste, [...settings.expect ?? [], ...reload?.key === undefined ? [] : [reload.key]]);
1589
+ if (busy && keys.includes("cancel")) {
1590
+ stop();
1591
+ return;
1592
+ }
1593
+ pending.push(...keys);
1594
+ drain();
1595
+ }
1596
+ const ended = new Promise((resolve) => {
1597
+ finish = resolve;
1598
+ });
1599
+ process12.once("exit", restore);
1600
+ for (const signal of SIGNALS)
1601
+ process12.once(signal, stop);
1602
+ try {
1603
+ terminal.write(PASTE_ON);
1604
+ terminal.listen(onChunk, () => draw(true));
1605
+ refreshPreview();
1606
+ draw(true);
1607
+ if (reload?.onOpen === true) {
1608
+ pending.push(OPEN);
1609
+ drain();
1610
+ }
1611
+ await ended;
1612
+ } finally {
1613
+ for (const signal of SIGNALS)
1614
+ process12.off(signal, stop);
1615
+ process12.off("exit", restore);
1616
+ restore();
1617
+ }
1618
+ return state.done === "accept" ? { rows: chosen(state), key: state.key, query: state.query } : undefined;
1619
+ }
1620
+ async function previewOf(preview, hidden, signal) {
1621
+ try {
1622
+ return previewLines(await preview(hidden, signal));
1623
+ } catch (error) {
1624
+ return [error instanceof Error ? error.message : String(error)];
1625
+ }
1626
+ }
1627
+
1628
+ // ../kit/src/picker.ts
822
1629
  var REMEMBERED = 20;
823
1630
  function table(rows) {
824
1631
  const widths = rows[0]?.cells.map((_, index) => Math.max(...rows.map((row) => Bun.stringWidth(row.cells[index])))) ?? [];
@@ -827,70 +1634,40 @@ function table(rows) {
827
1634
  ...row.cells.map((cell, index) => index === row.cells.length - 1 ? cell.trimEnd() : `${cell}${" ".repeat(widths[index] + 1 - Bun.stringWidth(cell))}`)
828
1635
  ].join("\t"));
829
1636
  }
830
- function fzf(rows, options) {
831
- const hidden = options.hidden ?? rows[0]?.hidden.length ?? 0;
832
- const proc = Bun.spawnSync([
833
- "fzf",
834
- "--prompt",
835
- options.prompt,
836
- ...options.header === undefined ? [] : ["--header", options.header],
837
- ...(options.expect ?? []).length > 0 ? ["--expect", (options.expect ?? []).join(",")] : [],
838
- "--ansi",
839
- "--reverse",
840
- "--height",
841
- options.height ?? "100%",
842
- "--delimiter",
843
- "\t",
844
- "--with-nth",
845
- `${hidden + 1}..`,
846
- "--nth",
847
- `1..${options.searched}`,
848
- "--tabstop",
849
- "1",
850
- "--tiebreak",
851
- "chunk,index",
852
- ...options.preview === undefined ? [] : ["--preview", options.preview, "--preview-window", "down,40%"],
853
- ...(options.bind ?? []).flatMap((bind) => ["--bind", bind]),
854
- ...options.query === true ? ["--print-query"] : []
855
- ], {
856
- stdin: new TextEncoder().encode(table(rows).join(`
857
- `)),
858
- stdout: "pipe",
859
- stderr: "inherit"
860
- });
861
- return picked(proc.stdout.toString(), hidden, options);
862
- }
863
- function picked(output, hidden, options) {
864
- const lines = output.split(`
865
- `);
866
- const query = options.query === true ? lines.shift() : "";
867
- const [key, line] = (options.expect ?? []).length > 0 ? [lines[0], lines[1]] : ["", lines[0]];
868
- if (line !== undefined && line !== "")
869
- return { key, hidden: line.split("\t").slice(0, hidden), query };
870
- return options.query === true && key !== "" ? { key, hidden: [], query } : undefined;
1637
+ async function pick(rows, options) {
1638
+ const height = parseHeight(options.height ?? "100%");
1639
+ const keys = [...options.expect ?? [], ...options.reload?.key === undefined ? [] : [options.reload.key]];
1640
+ if (height === undefined)
1641
+ throw new Error(`pick: the height is lines or a percentage, not ${options.height}`);
1642
+ for (const key of keys) {
1643
+ if (!CTRL_KEY.test(key))
1644
+ throw new Error(`pick: ${key} is not a key it can report, one of ctrl-a to ctrl-z but ctrl-c, h, i, j and m`);
1645
+ }
1646
+ const found = await select(rows, { multi: false, prompt: options.prompt, query: "", height, preview: options.preview, previewPosition: "down", header: options.header, expect: options.expect, searched: options.searched, reload: options.reload });
1647
+ return found === undefined ? undefined : { key: found.key, hidden: found.rows[0]?.hidden ?? [], query: found.query };
871
1648
  }
872
1649
  function heightFor(count) {
873
1650
  return String(Math.max(15, Math.ceil((count + 4) / 0.6)));
874
1651
  }
875
1652
  function historyFile(name) {
876
- return join3(process8.env.XDG_CACHE_HOME ?? join3(homedir2(), ".cache"), name, "history.json");
1653
+ return join6(process13.env.XDG_CACHE_HOME ?? join6(homedir4(), ".cache"), name, "history.json");
877
1654
  }
878
1655
  function history(name) {
879
1656
  try {
880
1657
  const file = historyFile(name);
881
- return existsSync(file) ? JSON.parse(readFileSync2(file, "utf-8")) : {};
1658
+ return existsSync3(file) ? JSON.parse(readFileSync3(file, "utf-8")) : {};
882
1659
  } catch {
883
1660
  return {};
884
1661
  }
885
1662
  }
886
1663
  function runs(name) {
887
- return history(name)[process8.cwd()] ?? [];
1664
+ return history(name)[process13.cwd()] ?? [];
888
1665
  }
889
1666
  function record(name, run) {
890
1667
  const all = history(name);
891
1668
  const file = historyFile(name);
892
- all[process8.cwd()] = [run, ...(all[process8.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
893
- mkdirSync2(dirname3(file), { recursive: true });
1669
+ all[process13.cwd()] = [run, ...(all[process13.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
1670
+ mkdirSync2(dirname4(file), { recursive: true });
894
1671
  writeFileSync(file, JSON.stringify(all));
895
1672
  }
896
1673
  function byRecency(items, previous, idOf) {
@@ -913,12 +1690,12 @@ function list(rows) {
913
1690
  }
914
1691
 
915
1692
  // ../kit/src/prompt.ts
916
- import process9 from "process";
917
- var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
918
- var CONTROL = /^[\x00-\x1F\x7F]/;
919
- var KEYS = { "\x1B[A": "up", "\x1BOA": "up", "\x1B[B": "down", "\x1BOB": "down", "\r": "enter", "\n": "enter", "\x03": "cancel", "\x1B": "cancel", "\x7F": "erase", "\b": "erase" };
1693
+ import process14 from "process";
1694
+ var KEY_SEQUENCES2 = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
1695
+ var CONTROL3 = /^[\x00-\x1F\x7F]/;
1696
+ var KEYS2 = { "\x1B[A": "up", "\x1BOA": "up", "\x1B[B": "down", "\x1BOB": "down", "\r": "enter", "\n": "enter", "\x03": "cancel", "\x1B": "cancel", "\x7F": "erase", "\b": "erase" };
920
1697
  function readKeys(input) {
921
- return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => KEYS[sequence] ?? (CONTROL.test(sequence) ? [] : [{ text: sequence }]));
1698
+ return (input.match(KEY_SEQUENCES2) ?? []).flatMap((sequence) => KEYS2[sequence] ?? (CONTROL3.test(sequence) ? [] : [{ text: sequence }]));
922
1699
  }
923
1700
  async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
924
1701
  let value = "";
@@ -952,7 +1729,7 @@ async function input2(question, { default: fallback, mask = false, validate, ...
952
1729
  function answer(colors, question, state, picked) {
953
1730
  return state === "enter" ? `${colors.green("\u2713")} ${question} ${colors.cyan(picked)}` : `${colors.red("\u2717")} ${question} ${colors.dim("cancelled")}`;
954
1731
  }
955
- async function ask(question, { hint, terminal = process9 }, render, press) {
1732
+ async function ask(question, { hint, terminal = process14 }, render, press) {
956
1733
  const { stdin, stderr } = terminal;
957
1734
  if (stdin.isTTY !== true || stderr.isTTY !== true)
958
1735
  fail(`cannot ask "${question}" off a terminal`, 2, hint);
@@ -1003,12 +1780,12 @@ async function ask(question, { hint, terminal = process9 }, render, press) {
1003
1780
  }
1004
1781
 
1005
1782
  // src/stack.ts
1006
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "fs";
1007
- import { dirname as dirname5, join as join5, relative, resolve } from "path";
1783
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4 } from "fs";
1784
+ import { dirname as dirname6, join as join8, relative, resolve as resolve3 } from "path";
1008
1785
 
1009
1786
  // ../env/src/files.ts
1010
1787
  import { chmod, chown, readdir, realpath, rename, rm, stat, writeFile } from "fs/promises";
1011
- import { basename, dirname as dirname4, join as join4 } from "path";
1788
+ import { basename, dirname as dirname5, join as join7 } from "path";
1012
1789
 
1013
1790
  // ../env/src/dotenv.ts
1014
1791
  var ASSIGNMENT = /^\s*(?:export\s+)?([A-Z_][\w.-]*)\s*=(.*)$/i;
@@ -1088,12 +1865,12 @@ function entries(file) {
1088
1865
  var MIN_KNOWN_LENGTH = 4;
1089
1866
  var KEY_MASK = "*** private key redacted ***";
1090
1867
  var PLAIN_VALUE = /^(?:true|false|null|none|nil|yes|no|on|off|undefined)$/i;
1091
- var PLACEHOLDER = /^[$%{<(*/]/;
1868
+ var PLACEHOLDER2 = /^[$%{<(*/]/;
1092
1869
  var URL_PASSWORD = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/[^\s:/@]*:([^\s'"]{1,1024})@/dgi;
1093
1870
  var PORT = /^\d{1,5}$/;
1094
1871
  var ESCAPE2 = /[.*+?^${}()|[\]\\]/g;
1095
1872
  function isPlaceholder(value) {
1096
- return PLACEHOLDER.test(value) || PLAIN_VALUE.test(value);
1873
+ return PLACEHOLDER2.test(value) || PLAIN_VALUE.test(value);
1097
1874
  }
1098
1875
  var TOKEN_FORMATS = [
1099
1876
  { kind: "GitHub token", pattern: /\b(?:gh[pousr]_|github_pat_)\w{20,}/dg },
@@ -1279,7 +2056,7 @@ async function envFilesBelow(folder, depth = 0) {
1279
2056
  return found;
1280
2057
  }
1281
2058
  for (const entry of children) {
1282
- const path = join4(folder, entry.name);
2059
+ const path = join7(folder, entry.name);
1283
2060
  if (entry.isDirectory() && depth < KNOWN_DEPTH && !SKIPPED.has(entry.name))
1284
2061
  found.push(...await envFilesBelow(path, depth + 1));
1285
2062
  else if (ENV_FILE.test(entry.name) && await isFileEntry(entry, path))
@@ -1368,29 +2145,29 @@ function from(dir, command) {
1368
2145
  function children(dir = ".") {
1369
2146
  return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
1370
2147
  }
1371
- function up(dir) {
2148
+ function up2(dir) {
1372
2149
  const dirs = [];
1373
- for (let current = dir;; current = join5(current, "..")) {
2150
+ for (let current = dir;; current = join8(current, "..")) {
1374
2151
  dirs.push(current);
1375
- const absolute = resolve(current);
1376
- if (existsSync2(join5(current, ".git")) || dirname5(absolute) === absolute)
2152
+ const absolute = resolve3(current);
2153
+ if (existsSync4(join8(current, ".git")) || dirname6(absolute) === absolute)
1377
2154
  return dirs;
1378
2155
  }
1379
2156
  }
1380
2157
  function stackAt(dir) {
1381
- return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve(dir), env: process.env } };
2158
+ return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve3(dir), env: process.env } };
1382
2159
  }
1383
2160
  function holdsStack(dir) {
1384
- if (COMPOSE_FILES.some((name) => existsSync2(join5(dir, name))))
2161
+ if (COMPOSE_FILES.some((name) => existsSync4(join8(dir, name))))
1385
2162
  return true;
1386
2163
  try {
1387
- return COMPOSE_FILE_VAR.test(readFileSync3(join5(dir, ".env"), "utf-8"));
2164
+ return COMPOSE_FILE_VAR.test(readFileSync4(join8(dir, ".env"), "utf-8"));
1388
2165
  } catch {
1389
2166
  return false;
1390
2167
  }
1391
2168
  }
1392
2169
  function nearestStack(dir) {
1393
- const found = up(dir).find(holdsStack);
2170
+ const found = up2(dir).find(holdsStack);
1394
2171
  return found === undefined ? undefined : stackAt(found);
1395
2172
  }
1396
2173
  function stacks() {
@@ -1413,7 +2190,7 @@ async function labels(project) {
1413
2190
  const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
1414
2191
  if (await proc.exited !== 0)
1415
2192
  return [];
1416
- return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync2(file)));
2193
+ return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync4(file)));
1417
2194
  }
1418
2195
  function labelledStack(dir, { project, files, envFiles, workdir }) {
1419
2196
  const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
@@ -1428,7 +2205,7 @@ function stackOf(recorded) {
1428
2205
  return labelledStack(relative(".", recorded.workdir) || ".", recorded);
1429
2206
  }
1430
2207
  async function recordedNearby() {
1431
- const dirs = new Set([...up("."), ...children()].map((dir) => resolve(dir)));
2208
+ const dirs = new Set([...up2("."), ...children()].map((dir) => resolve3(dir)));
1432
2209
  return (await labels()).filter((stack) => dirs.has(stack.workdir));
1433
2210
  }
1434
2211
  async function attempt(stack, args) {
@@ -1522,7 +2299,7 @@ async function logsOf(stack, name) {
1522
2299
  var usage2 = `sf [command] [args...]
1523
2300
  sf -
1524
2301
 
1525
- Without a terminal or without fzf (a script, a pipe, an agent) \`sf\` lists the commands, one per line with its description: that listing is the form to use there, and \`sf --list --json\` gives each with its arguments and options. On a terminal with fzf it picks a Symfony console command instead and runs it, asking for each required argument it needs. The commands last run come first, per folder, and the picker opens on the list cached from the last run while the console answers.
2302
+ Without a terminal (a script, a pipe, an agent) \`sf\` lists the commands, one per line with its description: that listing is the form to use there, and \`sf --list --json\` gives each with its arguments and options. On a terminal it picks a Symfony console command instead and runs it, asking for each required argument it needs. The commands last run come first, per folder, and the picker opens on the list cached from the last run while the console answers.
1526
2303
  The console is the nearest bin/console up to the repository root, then those of the child folders (keyed \`api/cache:clear\`). It runs in the php service of the nearest compose stack when there is one, else with the local php; the container's working directory is taken to be the app.
1527
2304
  \`sf -\` runs the last command again with the arguments it ran with, \`sf <command> [args...]\` runs without the picker: a name the list does not hold goes to the console as is, which resolves \`c:c\` to \`cache:clear\` itself.
1528
2305
  \`sf --list --json\` (or \`sf --json\`) prints every command as JSON, each with its console folder, description, signature, arguments and options.
@@ -1549,19 +2326,19 @@ async function consoleAt(dir) {
1549
2326
  const php = stack === undefined ? undefined : phpService((await services(stack)).map((service) => service.name));
1550
2327
  if (stack !== undefined && php !== undefined)
1551
2328
  return { dir, run: [...stack.run, "exec", php, "bin/console"], list: { ...stack.list, argv: [...stack.list.argv, "exec", "-T", php, "bin/console"] } };
1552
- return { dir, run: from(dir, ["php", "bin/console"]), list: { argv: ["php", "bin/console"], cwd: resolve2(dir), env: process10.env } };
2329
+ return { dir, run: from(dir, ["php", "bin/console"]), list: { argv: ["php", "bin/console"], cwd: resolve4(dir), env: process15.env } };
1553
2330
  }
1554
2331
  async function consoles() {
1555
- const here = up(".").find((dir) => existsSync3(join6(dir, "bin/console")));
1556
- const dirs = [...here === undefined ? [] : [here], ...children().filter((dir) => existsSync3(join6(dir, "bin/console")))];
2332
+ const here = up2(".").find((dir) => existsSync5(join9(dir, "bin/console")));
2333
+ const dirs = [...here === undefined ? [] : [here], ...children().filter((dir) => existsSync5(join9(dir, "bin/console")))];
1557
2334
  return Promise.all([...new Set(dirs)].map(consoleAt));
1558
2335
  }
1559
2336
  function cached(dir) {
1560
- return join6(process10.env.XDG_CACHE_HOME ?? join6(homedir3(), ".cache"), "sf", `${resolve2(dir).replaceAll("/", "%")}.json`);
2337
+ return join9(process15.env.XDG_CACHE_HOME ?? join9(homedir5(), ".cache"), "sf", `${resolve4(dir).replaceAll("/", "%")}.json`);
1561
2338
  }
1562
2339
  function fromCache(dir) {
1563
2340
  try {
1564
- return existsSync3(cached(dir)) ? JSON.parse(readFileSync4(cached(dir), "utf-8")) : undefined;
2341
+ return existsSync5(cached(dir)) ? JSON.parse(readFileSync5(cached(dir), "utf-8")) : undefined;
1565
2342
  } catch {
1566
2343
  return;
1567
2344
  }
@@ -1577,7 +2354,7 @@ async function live(app) {
1577
2354
  } catch {
1578
2355
  return fromCache(app.dir) ?? [];
1579
2356
  }
1580
- mkdirSync3(dirname6(cached(app.dir)), { recursive: true });
2357
+ mkdirSync3(dirname7(cached(app.dir)), { recursive: true });
1581
2358
  writeFileSync2(cached(app.dir), JSON.stringify(commands));
1582
2359
  return commands;
1583
2360
  }
@@ -1651,7 +2428,7 @@ async function answered(command, ask) {
1651
2428
  return { argv, given };
1652
2429
  }
1653
2430
  function interactive() {
1654
- return process10.stdin.isTTY && process10.stderr.isTTY;
2431
+ return process15.stdin.isTTY && process15.stderr.isTTY;
1655
2432
  }
1656
2433
  async function execute(emitting, typed, recorded, command) {
1657
2434
  if (emitting) {
@@ -1698,7 +2475,7 @@ async function hidden(flag, rest) {
1698
2475
  return 0;
1699
2476
  }
1700
2477
  if (refreshed)
1701
- Bun.spawn([process10.execPath, process10.argv[1], "--rows"], { stdio: ["ignore", "ignore", "ignore"] }).unref();
2478
+ Bun.spawn([process15.execPath, process15.argv[1], "--rows"], { stdio: ["ignore", "ignore", "ignore"] }).unref();
1702
2479
  list(byRecency(found, runs("sf"), key2).map((entry) => ({
1703
2480
  label: [key2(entry), signature(entry.command)].filter(Boolean).join(" "),
1704
2481
  hint: entry.command.description,
@@ -1749,24 +2526,21 @@ async function sf(args) {
1749
2526
  }
1750
2527
  if (found.length === 0)
1751
2528
  fail("no bin/console here, or it listed no command", 1);
1752
- if (!interactive() || Bun.which("fzf") === null) {
2529
+ if (!interactive()) {
1753
2530
  if (emitting)
1754
- fail("the picker needs a terminal and fzf", 1);
1755
- if (interactive())
1756
- console.error("fzf is not installed, listing the commands instead");
2531
+ fail("the picker needs a terminal", 1);
1757
2532
  console.log(listing(found));
1758
2533
  return 0;
1759
2534
  }
1760
- const self = `${process10.execPath} ${process10.argv[1]}`;
1761
- const picked = fzf(rows(found), {
2535
+ const picked = await pick(rows(found), {
1762
2536
  prompt: "sf> ",
1763
2537
  searched: 1,
1764
- preview: `${self} --show {1} {2}`,
1765
- bind: refreshed ? [`load:unbind(load)+reload-sync(${self} --rows)`] : []
2538
+ preview: `${process15.execPath} ${process15.argv[1]} --show {1} {2}`,
2539
+ reload: refreshed ? { onOpen: true, run: async () => ({ rows: rows(await entries2(true)) }) } : undefined
1766
2540
  });
1767
2541
  const entry = picked && (await entries2(false)).find((candidate) => candidate.app.dir === picked.hidden[0] && candidate.command.name === picked.hidden[1]);
1768
2542
  return entry === undefined ? 1 : execute(emitting, key2(entry), key2(entry), commandFor2(entry, []));
1769
2543
  }
1770
2544
 
1771
2545
  // src/sf-cli.ts
1772
- process11.exitCode = await runMain({ name: "sf", run: sf });
2546
+ process16.exitCode = await runMain({ name: "sf", run: sf });