@kaynooo/cli-docker 0.5.1 → 0.7.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/dx-cli.js CHANGED
@@ -2,13 +2,37 @@
2
2
  // @bun
3
3
 
4
4
  // src/dx-cli.ts
5
- import process11 from "process";
5
+ import process18 from "process";
6
6
  // ../kit/src/args.ts
7
- import process4 from "process";
7
+ import process9 from "process";
8
8
  import { parseArgs } from "util";
9
9
 
10
- // ../kit/src/style.ts
10
+ // ../kit/src/format.ts
11
+ import process7 from "process";
12
+
13
+ // ../kit/src/mode.ts
11
14
  import process2 from "process";
15
+ var MODES = new Set(["terminal", "ci", "agent", "pipe"]);
16
+ var AGENT_VARIABLES = ["CLAUDECODE", "AI_AGENT"];
17
+ function isSet(value) {
18
+ return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
19
+ }
20
+ function outputMode(stream = process2.stdout, env = process2.env) {
21
+ const chosen = env.KIT_MODE;
22
+ if (chosen !== undefined && MODES.has(chosen))
23
+ return chosen;
24
+ if (AGENT_VARIABLES.some((name) => isSet(env[name])))
25
+ return "agent";
26
+ if (stream.isTTY === true)
27
+ return "terminal";
28
+ return isSet(env.CI) ? "ci" : "pipe";
29
+ }
30
+ function isInteractive(streams = process2) {
31
+ return streams.stdin?.isTTY === true && streams.stderr?.isTTY === true;
32
+ }
33
+
34
+ // ../kit/src/style.ts
35
+ import process6 from "process";
12
36
 
13
37
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
14
38
  function x(s) {
@@ -271,21 +295,229 @@ function le(i = {}) {
271
295
  }
272
296
  var t = le();
273
297
 
298
+ // ../kit/src/theme.ts
299
+ import { existsSync as existsSync2, readFileSync, statSync } from "fs";
300
+ import { homedir as homedir2 } from "os";
301
+ import { join as join3, resolve as resolve2 } from "path";
302
+ import process5 from "process";
303
+
304
+ // ../kit/src/config.ts
305
+ import { existsSync } from "fs";
306
+ import { dirname, join, resolve } from "path";
307
+ import process3 from "process";
308
+ function findUp(names, from = process3.cwd(), stop = () => false) {
309
+ const wanted = typeof names === "string" ? [names] : names;
310
+ for (let dir = resolve(from);; dir = dirname(dir)) {
311
+ const found = wanted.map((name) => join(dir, name)).find((path) => existsSync(path));
312
+ if (found !== undefined)
313
+ return found;
314
+ if (stop(dir) || dirname(dir) === dir)
315
+ return;
316
+ }
317
+ }
318
+
319
+ // ../kit/src/user-config.ts
320
+ import { homedir } from "os";
321
+ import { join as join2 } from "path";
322
+ import process4 from "process";
323
+ function configBase(where = {}) {
324
+ const xdg = where.xdg ?? (where.home === undefined ? process4.env.XDG_CONFIG_HOME : undefined);
325
+ return xdg !== undefined && xdg !== "" ? xdg : join2(where.home ?? homedir(), ".config");
326
+ }
327
+ function userConfigPath(bin, where = {}) {
328
+ return join2(configBase(where), "kaynooo-cli", `${bin}.json`);
329
+ }
330
+
331
+ // ../kit/src/theme.ts
332
+ var DEFAULT = {
333
+ 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" },
334
+ glyphs: { ok: "\u2713", warn: "\u25B2", problem: "\u2717", note: "\u203A", passed: "\u2713", failed: "\u2717", running: "\u25CF", queued: "\u25CB", skipped: "-", bar: "\u2501" },
335
+ accents: ["cyan", "magenta", "green", "blue", "yellow", "redBright"],
336
+ bins: {}
337
+ };
338
+ 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" };
339
+ 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" };
340
+ var ASCII = { ok: "+", warn: "!", problem: "x", note: ">", passed: "+", failed: "x", running: "*", queued: "o", skipped: "-", bar: "#" };
341
+ var PRESETS = {
342
+ default: {},
343
+ "high-contrast": { colors: BRIGHT, accents: ["cyanBright", "magentaBright", "greenBright", "blueBright", "yellowBright", "redBright"] },
344
+ mono: { colors: MONO, accents: ["bold"] },
345
+ ascii: { glyphs: ASCII }
346
+ };
347
+ 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"]);
348
+ var HEX = /^#[0-9a-f]{6}$/i;
349
+ var CONTROL = /\p{Cc}/u;
350
+ var MAX_FILE_BYTES = 64 * 1024;
351
+ var SPACES = /\s+/;
352
+ function isColorSpec(spec) {
353
+ if (typeof spec === "number")
354
+ return Number.isInteger(spec) && spec >= 0 && spec <= 255;
355
+ return typeof spec === "string" && spec.trim() !== "" && spec.trim().split(SPACES).every((token) => STYLES.has(token) || HEX.test(token));
356
+ }
357
+ var CUBE = [0, 95, 135, 175, 215, 255];
358
+ function cubeLevel(channel) {
359
+ return CUBE.reduce((best, value, level) => Math.abs(value - channel) < Math.abs(CUBE[best] - channel) ? level : best, 0);
360
+ }
361
+ function nearest256(hex) {
362
+ const rgb = [1, 3, 5].map((at) => Number.parseInt(hex.slice(at, at + 2), 16));
363
+ const levels = rgb.map(cubeLevel);
364
+ const cube = levels.map((level) => CUBE[level]);
365
+ const grey = Math.min(23, Math.max(0, Math.round((rgb.reduce((sum, channel) => sum + channel, 0) / 3 - 8) / 10)));
366
+ const greyValue = 8 + grey * 10;
367
+ const distance = (colour) => colour.reduce((sum, channel, at) => sum + (channel - rgb[at]) ** 2, 0);
368
+ return distance([greyValue, greyValue, greyValue]) < distance(cube) ? 232 + grey : 16 + 36 * levels[0] + 6 * levels[1] + levels[2];
369
+ }
370
+ function hasTrueColor(env) {
371
+ return env.COLORTERM === "truecolor" || env.COLORTERM === "24bit";
372
+ }
373
+ function tinted(colors, spec, env = process5.env) {
374
+ if (typeof spec === "number")
375
+ return colors.ansi256(spec);
376
+ return spec.trim().split(SPACES).reduce((chain, token) => {
377
+ if (HEX.test(token))
378
+ return hasTrueColor(env) ? chain.hex(token) : chain.ansi256(nearest256(token));
379
+ return chain[token] ?? chain;
380
+ }, colors);
381
+ }
382
+ function isRecord(value) {
383
+ return typeof value === "object" && value !== null && !Array.isArray(value);
384
+ }
385
+ function readThemeFile(path, warn) {
386
+ try {
387
+ const stat = statSync(path, { throwIfNoEntry: false });
388
+ if (stat === undefined)
389
+ return;
390
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES) {
391
+ warn(`${path} is not a regular file under ${MAX_FILE_BYTES / 1024} KiB, theme ignored`);
392
+ return;
393
+ }
394
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
395
+ if (isRecord(parsed))
396
+ return parsed;
397
+ warn(`${path} is not a JSON object, theme ignored`);
398
+ } catch (error) {
399
+ warn(`${path} does not parse (${error instanceof Error ? error.message : String(error)}), theme ignored`);
400
+ }
401
+ return;
402
+ }
403
+ function applyPreset(theme, name, warn, where) {
404
+ if (name === undefined)
405
+ return;
406
+ if (typeof name !== "string" || !Object.hasOwn(PRESETS, name)) {
407
+ warn(`${where}: no preset ${String(name)}, one of ${Object.keys(PRESETS).join(", ")}`);
408
+ return;
409
+ }
410
+ const preset = PRESETS[name];
411
+ Object.assign(theme.colors, preset.colors);
412
+ Object.assign(theme.glyphs, preset.glyphs);
413
+ if (preset.accents !== undefined)
414
+ theme.accents = [...preset.accents];
415
+ return name;
416
+ }
417
+ function applyFile(theme, file, where, warn) {
418
+ if (isRecord(file.colors)) {
419
+ for (const [key, value] of Object.entries(file.colors)) {
420
+ if (!Object.hasOwn(DEFAULT.colors, key))
421
+ warn(`${where}: no colour ${key}, one of ${Object.keys(DEFAULT.colors).join(", ")}`);
422
+ else if (!isColorSpec(value))
423
+ warn(`${where}: colour ${key} is not an ANSI name, \`#rrggbb\` or a number up to 255`);
424
+ else
425
+ theme.colors[key] = value;
426
+ }
427
+ }
428
+ if (isRecord(file.glyphs)) {
429
+ for (const [key, value] of Object.entries(file.glyphs)) {
430
+ if (!Object.hasOwn(DEFAULT.glyphs, key))
431
+ warn(`${where}: no glyph ${key}, one of ${Object.keys(DEFAULT.glyphs).join(", ")}`);
432
+ else if (typeof value !== "string" || CONTROL.test(value) || Bun.stringWidth(value) !== 1)
433
+ warn(`${where}: glyph ${key} must be one visible character wide, with no control character`);
434
+ else
435
+ theme.glyphs[key] = value;
436
+ }
437
+ }
438
+ if (Array.isArray(file.accents) && file.accents.length > 0 && file.accents.every(isColorSpec))
439
+ theme.accents = [...file.accents];
440
+ else if (file.accents !== undefined)
441
+ warn(`${where}: accents must be a non-empty list of colours`);
442
+ if (isRecord(file.bins)) {
443
+ for (const [bin, value] of Object.entries(file.bins)) {
444
+ if (isColorSpec(value))
445
+ theme.bins[bin] = value;
446
+ else
447
+ warn(`${where}: bin ${bin} has no valid colour`);
448
+ }
449
+ }
450
+ }
451
+ function endsWalk(home) {
452
+ return (dir) => dir === resolve2(home) || existsSync2(join3(dir, ".git"));
453
+ }
454
+ var warned = new Set;
455
+ function warnOnce(message) {
456
+ if (warned.has(message))
457
+ return;
458
+ warned.add(message);
459
+ console.error(`warning: theme ${message}`);
460
+ }
461
+ function isPath(chosen) {
462
+ return chosen.includes("/") || chosen.endsWith(".json");
463
+ }
464
+ function loadTheme({ env = process5.env, from = process5.cwd(), home = homedir2(), warn = warnOnce } = {}) {
465
+ const theme = { colors: { ...DEFAULT.colors }, glyphs: { ...DEFAULT.glyphs }, accents: [...DEFAULT.accents], bins: Object.create(null) };
466
+ const sources = [];
467
+ const chosen = env.KIT_THEME ?? "";
468
+ const wanted = chosen === "" ? [userConfigPath("theme", { home, xdg: env.XDG_CONFIG_HOME }), findUp(join3(".kaynooo", "theme.json"), from, endsWalk(home))] : [];
469
+ if (isPath(chosen))
470
+ wanted.push(resolve2(from, chosen));
471
+ const files = [];
472
+ for (const path of wanted) {
473
+ const file = path === undefined ? undefined : readThemeFile(path, warn);
474
+ if (path !== undefined && file !== undefined)
475
+ files.push({ path, file });
476
+ }
477
+ let preset = "default";
478
+ if (chosen !== "" && !isPath(chosen)) {
479
+ preset = applyPreset(theme, chosen, warn, "KIT_THEME") ?? preset;
480
+ } else {
481
+ const named = files.findLast(({ file }) => file.preset !== undefined);
482
+ if (named !== undefined)
483
+ preset = applyPreset(theme, named.file.preset, warn, named.path) ?? preset;
484
+ }
485
+ for (const { path, file } of files) {
486
+ applyFile(theme, file, path, warn);
487
+ sources.push(path);
488
+ }
489
+ return { theme, sources, preset };
490
+ }
491
+ var current;
492
+ var currentKey = "";
493
+ function activeTheme() {
494
+ const key = `${process5.cwd()}\x00${process5.env.KIT_THEME ?? ""}\x00${process5.env.XDG_CONFIG_HOME ?? ""}`;
495
+ if (current === undefined || key !== currentKey) {
496
+ current = loadTheme();
497
+ currentKey = key;
498
+ }
499
+ return current;
500
+ }
501
+
274
502
  // ../kit/src/style.ts
275
- function takesControlCodes(stream, env = process2.env) {
276
- return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
503
+ function takesControlCodes(stream, env = process6.env) {
504
+ return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb" && outputMode(stream, env) === "terminal";
277
505
  }
278
- function paint(stream, env = process2.env) {
506
+ function paint(stream, env = process6.env) {
279
507
  return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
280
508
  }
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 }
509
+ function role(name, stream, env = process6.env) {
510
+ const colors = paint(stream, env);
511
+ return takesControlCodes(stream, env) ? tinted(colors, activeTheme().theme.colors[name], env) : colors;
512
+ }
513
+ var STATUS_WORDS = {
514
+ passed: ["passed", "success", "succeeded", "ok"],
515
+ failed: ["failed", "failure", "error", "errored"],
516
+ running: ["running", "in_progress", "started", "holding"],
517
+ queued: ["queued", "waiting", "pending", "created", "blocked", "scheduled"],
518
+ skipped: ["skipped", "cancelled", "canceled", "manual"]
287
519
  };
288
- var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
520
+ var STATUS_OF = new Map(Object.entries(STATUS_WORDS).flatMap(([status, words]) => words.map((word) => [word, status])));
289
521
 
290
522
  // ../kit/src/format.ts
291
523
  function upTo(text, separator) {
@@ -293,32 +525,35 @@ function upTo(text, separator) {
293
525
  return end === -1 ? text : text.slice(0, end);
294
526
  }
295
527
  var ESCAPE = String.fromCharCode(27);
528
+ function jsonText(value, replacer, { stream = process7.stdout, env = process7.env } = {}) {
529
+ return JSON.stringify(value, replacer, outputMode(stream, env) === "agent" ? undefined : 2);
530
+ }
296
531
  function printJson(value) {
297
- console.log(JSON.stringify(value, undefined, 2));
532
+ console.log(jsonText(value));
298
533
  }
299
534
 
300
535
  // ../kit/src/help.ts
301
- import process3 from "process";
536
+ import process8 from "process";
302
537
  var OPTIONAL = /\[[^\]]+\]/g;
303
538
  var USAGE_PREFIX = /^usage:\s*/i;
304
539
  var FULL_STOP = /\.$/;
305
- function usageLine(usage, colors = paint(process3.stdout)) {
540
+ function usageLine(usage, colors = paint(process8.stdout)) {
306
541
  return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
307
542
  }
308
- function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces, exits = [] } = {}) {
543
+ function helpText(usage, options, colors = paint(process8.stdout), { examples = [], replaces, exits = [] } = {}) {
309
544
  return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
310
545
  `);
311
546
  }
312
- function exitRows(exits, colors = paint(process3.stdout)) {
547
+ function exitRows(exits, colors = paint(process8.stdout)) {
313
548
  if (exits.length === 0)
314
549
  return [];
315
550
  const width = Math.max(...exits.map(([code]) => String(code).length));
316
551
  return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
317
552
  }
318
- function replacesRows(replaces, colors = paint(process3.stdout)) {
319
- return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
553
+ function replacesRows(replaces, colors = paint(process8.stdout)) {
554
+ return replaces === undefined || outputMode() === "agent" ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
320
555
  }
321
- function wantsHelpJson(env = process3.env) {
556
+ function wantsHelpJson(env = process8.env) {
322
557
  return env.KIT_HELP === "json";
323
558
  }
324
559
  function description(usage, { examples = [], replaces, exits } = {}, options) {
@@ -337,12 +572,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
337
572
  ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
338
573
  };
339
574
  }
340
- function exampleRows(examples, colors = paint(process3.stdout)) {
341
- if (examples.length === 0)
575
+ function exampleRows(examples, colors = paint(process8.stdout)) {
576
+ if (examples.length === 0 || outputMode() === "agent")
342
577
  return [];
343
578
  return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
344
579
  }
345
- function optionRows(options, colors = paint(process3.stdout)) {
580
+ function optionRows(options, colors = paint(process8.stdout)) {
346
581
  const rows = Object.entries(options).map(([name, option]) => {
347
582
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
348
583
  const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
@@ -436,7 +671,7 @@ var OUTPUT_OPTIONS = {
436
671
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
437
672
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
438
673
  };
439
- function applyOutput(color, plain, env = process4.env) {
674
+ function applyOutput(color, plain, env = process9.env) {
440
675
  const when = plain === true ? "never" : color ?? "auto";
441
676
  if (when === "auto")
442
677
  return;
@@ -611,15 +846,15 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
611
846
  var RESET_STYLE = `${ESC}[0m`;
612
847
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
613
848
  // ../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";
849
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
850
+ import { dirname as dirname3, join as join5 } from "path";
851
+ import process11 from "process";
617
852
 
618
853
  // ../kit/src/usage.ts
619
854
  import { appendFileSync, mkdirSync } from "fs";
620
- import { homedir } from "os";
621
- import { dirname, isAbsolute, join } from "path";
622
- import process5 from "process";
855
+ import { homedir as homedir3 } from "os";
856
+ import { dirname as dirname2, isAbsolute, join as join4 } from "path";
857
+ import process10 from "process";
623
858
 
624
859
  // ../kit/src/logs.ts
625
860
  var DAY_MS = 24 * 60 * 60 * 1000;
@@ -633,14 +868,14 @@ var usageError;
633
868
  function noteUsageError(message) {
634
869
  usageError = message;
635
870
  }
636
- function stateHome(env = process5.env) {
871
+ function stateHome(env = process10.env) {
637
872
  const dir = env.XDG_STATE_HOME;
638
- return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
873
+ return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
639
874
  }
640
- function usagePath(env = process5.env) {
641
- return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
875
+ function usagePath(env = process10.env) {
876
+ return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
642
877
  }
643
- function usageRecorded(env = process5.env) {
878
+ function usageRecorded(env = process10.env) {
644
879
  const off = env.KAYNOOO_CLI_NO_USAGE;
645
880
  return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
646
881
  }
@@ -654,12 +889,12 @@ function usageEntry(bin, command, argv, code, now = new Date) {
654
889
  usageError = undefined;
655
890
  return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
656
891
  }
657
- function recordUsage(entry, env = process5.env) {
892
+ function recordUsage(entry, env = process10.env) {
658
893
  if (!usageRecorded(env))
659
894
  return;
660
895
  try {
661
896
  const path = usagePath(env);
662
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
897
+ mkdirSync(dirname2(path), { recursive: true, mode: 448 });
663
898
  appendFileSync(path, `${JSON.stringify(entry)}
664
899
  `, { mode: 384 });
665
900
  } catch {}
@@ -672,8 +907,8 @@ var LEVEL_OPTIONS = {
672
907
  };
673
908
  var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
674
909
  function printError(prefix, message, hint, cause) {
675
- const colors = paint(process6.stderr);
676
- const styled = takesControlCodes(process6.stderr);
910
+ const colors = paint(process11.stderr);
911
+ const styled = takesControlCodes(process11.stderr);
677
912
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
678
913
  const under = (label, text, tint) => text === undefined ? "" : `
679
914
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -692,7 +927,7 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
692
927
  const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
693
928
  const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
694
929
  const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
695
- if (error instanceof UsageError)
930
+ if (error instanceof Exit && error.code === 2)
696
931
  noteUsageError(error.message);
697
932
  const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
698
933
  report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
@@ -756,17 +991,17 @@ async function printCandidates(found) {
756
991
  return 0;
757
992
  }
758
993
  function packageVersion(entry = Bun.main) {
759
- for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
994
+ for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
760
995
  try {
761
- const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
996
+ const { version } = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf8"));
762
997
  if (typeof version === "string")
763
998
  return version;
764
999
  } catch {}
765
- if (dirname2(dir) === dir)
1000
+ if (dirname3(dir) === dir)
766
1001
  return "0.0.0";
767
1002
  }
768
1003
  }
769
- async function runMain({ name, version, run, redact, usage }, argv = process6.argv.slice(2)) {
1004
+ async function runMain({ name, version, run, redact, usage }, argv = process11.argv.slice(2)) {
770
1005
  if (argv[0] === COMPLETE)
771
1006
  return printCandidates(leafCandidates(run, argv.slice(1)));
772
1007
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
@@ -809,11 +1044,11 @@ var {Glob } = globalThis.Bun;
809
1044
  var SESSION_FILES = new Glob("*.json");
810
1045
  var ACCOUNT_DIRS = new Glob("*/sessions");
811
1046
  // src/dx.ts
812
- import process10 from "process";
1047
+ import process17 from "process";
813
1048
 
814
1049
  // ../env/src/files.ts
815
1050
  import { chmod, chown, readdir, realpath, rename, rm, stat, writeFile } from "fs/promises";
816
- import { basename, dirname as dirname3, join as join3 } from "path";
1051
+ import { basename, dirname as dirname4, join as join6 } from "path";
817
1052
 
818
1053
  // ../env/src/dotenv.ts
819
1054
  var ASSIGNMENT = /^\s*(?:export\s+)?([A-Z_][\w.-]*)\s*=(.*)$/i;
@@ -1084,7 +1319,7 @@ async function envFilesBelow(folder, depth = 0) {
1084
1319
  return found;
1085
1320
  }
1086
1321
  for (const entry of children) {
1087
- const path = join3(folder, entry.name);
1322
+ const path = join6(folder, entry.name);
1088
1323
  if (entry.isDirectory() && depth < KNOWN_DEPTH && !SKIPPED.has(entry.name))
1089
1324
  found.push(...await envFilesBelow(path, depth + 1));
1090
1325
  else if (ENV_FILE.test(entry.name) && await isFileEntry(entry, path))
@@ -1161,11 +1396,630 @@ class Redactor {
1161
1396
  }
1162
1397
  }
1163
1398
 
1164
- // ../kit/src/fzf.ts
1165
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1166
- import { homedir as homedir2 } from "os";
1167
- import { dirname as dirname4, join as join4 } from "path";
1168
- import process8 from "process";
1399
+ // ../kit/src/picker.ts
1400
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
1401
+ import { homedir as homedir4 } from "os";
1402
+ import { dirname as dirname5, join as join7 } from "path";
1403
+ import process15 from "process";
1404
+
1405
+ // ../kit/src/picker/score.ts
1406
+ var MATCH = 16;
1407
+ var CONSECUTIVE = 12;
1408
+ var BOUNDARY = 10;
1409
+ var PREFIX = 8;
1410
+ var GAP = 1;
1411
+ var BOUNDARY_AFTER = new Set([" ", "/", "-", "_", ".", ":", "\\", "(", "[", ",", "\t"]);
1412
+ var UPPER = /\p{Lu}/u;
1413
+ var LOWER = /\p{Ll}/u;
1414
+ var NONE = Number.NEGATIVE_INFINITY;
1415
+ function startsWord(text, index) {
1416
+ if (index === 0)
1417
+ return true;
1418
+ const before = text[index - 1];
1419
+ return BOUNDARY_AFTER.has(before) || LOWER.test(before) && UPPER.test(text[index]);
1420
+ }
1421
+ function fuzzy(query, text) {
1422
+ if (query === "")
1423
+ return { score: 0, positions: [] };
1424
+ const exact = query !== query.toLowerCase();
1425
+ const needle = exact ? query : query.toLowerCase();
1426
+ const haystack = exact ? text : text.toLowerCase();
1427
+ if (haystack.length !== text.length || needle.length > haystack.length)
1428
+ return;
1429
+ const width = haystack.length;
1430
+ const best = Array.from({ length: needle.length }, () => new Array(width).fill(NONE));
1431
+ const from = Array.from({ length: needle.length }, () => new Array(width).fill(-1));
1432
+ for (let j = 0;j < needle.length; j++) {
1433
+ let run = NONE;
1434
+ let runAt = -1;
1435
+ for (let i = j;i < width; i++) {
1436
+ if (j > 0 && i - 2 >= j - 1) {
1437
+ const candidate = best[j - 1][i - 2] + (i - 2) * GAP;
1438
+ if (candidate > run) {
1439
+ run = candidate;
1440
+ runAt = i - 2;
1441
+ }
1442
+ }
1443
+ if (haystack[i] !== needle[j])
1444
+ continue;
1445
+ const here = MATCH + (startsWord(text, i) ? BOUNDARY : 0) + (i === 0 ? PREFIX : 0);
1446
+ if (j === 0) {
1447
+ best[0][i] = here - i * GAP;
1448
+ continue;
1449
+ }
1450
+ const joined = best[j - 1][i - 1] + CONSECUTIVE;
1451
+ const skipped = run - (i - 1) * GAP;
1452
+ if (joined > NONE && joined >= skipped) {
1453
+ best[j][i] = joined + here;
1454
+ from[j][i] = i - 1;
1455
+ } else if (skipped > NONE) {
1456
+ best[j][i] = skipped + here;
1457
+ from[j][i] = runAt;
1458
+ }
1459
+ }
1460
+ }
1461
+ const last = best[needle.length - 1];
1462
+ let end = -1;
1463
+ for (let i = 0;i < width; i++) {
1464
+ if (last[i] > NONE && (end === -1 || last[i] > last[end]))
1465
+ end = i;
1466
+ }
1467
+ if (end === -1)
1468
+ return;
1469
+ const positions = [];
1470
+ for (let j = needle.length - 1, i = end;j >= 0; j--) {
1471
+ positions.unshift(i);
1472
+ i = from[j][i];
1473
+ }
1474
+ return { score: last[end], positions };
1475
+ }
1476
+ function rank(query, lines) {
1477
+ if (query === "")
1478
+ return lines.map((_, index) => ({ index, positions: [] }));
1479
+ const found = [];
1480
+ for (const [index, line] of lines.entries()) {
1481
+ const match = fuzzy(query, line);
1482
+ if (match !== undefined)
1483
+ found.push({ index, positions: match.positions, score: match.score });
1484
+ }
1485
+ return found.sort((a, b) => b.score - a.score || lines[a.index].length - lines[b.index].length || a.index - b.index);
1486
+ }
1487
+
1488
+ // ../kit/src/picker/state.ts
1489
+ var ANSI2 = /\x1B(?:\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x40-\x5A\x5C-\x5F])/g;
1490
+ var CONTROL2 = /[\x00-\x1F\x7F-\x9F]/g;
1491
+ var IS_CONTROL = /[\x00-\x1F\x7F-\x9F]/;
1492
+ var SGR = /^\x1B\[[0-9;]*m$/;
1493
+ var RESET = /^\x1B\[0?m$/;
1494
+ var COLUMN_GAP = 2;
1495
+ var PASTE_START = "\x1B[200~";
1496
+ var PASTE_END = "\x1B[201~";
1497
+ var LAST_WORD = /\S*\s*$/;
1498
+ var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|\x1B[\s\S]|[\s\S]/gu;
1499
+ var KEYS = {
1500
+ "\x1B[A": "up",
1501
+ "\x1BOA": "up",
1502
+ "\x10": "up",
1503
+ "\x1B[B": "down",
1504
+ "\x1BOB": "down",
1505
+ "\x0E": "down",
1506
+ "\r": "enter",
1507
+ "\n": "enter",
1508
+ "\x03": "cancel",
1509
+ "\x1B": "cancel",
1510
+ "\x7F": "erase",
1511
+ "\b": "erase",
1512
+ "\x15": "clear",
1513
+ "\x17": "word",
1514
+ "\t": "tab"
1515
+ };
1516
+ var CTRL_KEY = /^ctrl-[abd-gkln-z]$/;
1517
+ function repeat(value, count) {
1518
+ return Array.from({ length: count }).fill(value);
1519
+ }
1520
+ function styled(line) {
1521
+ let text = "";
1522
+ const styles = [];
1523
+ let active = "";
1524
+ let colored = false;
1525
+ let last = 0;
1526
+ function add(chunk) {
1527
+ const flat = chunk.replace(CONTROL2, " ");
1528
+ text += flat;
1529
+ colored ||= active !== "" && flat !== "";
1530
+ styles.push(...repeat(active, flat.length));
1531
+ }
1532
+ for (const found of line.matchAll(ANSI2)) {
1533
+ add(line.slice(last, found.index));
1534
+ last = found.index + found[0].length;
1535
+ if (SGR.test(found[0]))
1536
+ active = RESET.test(found[0]) ? "" : active + found[0];
1537
+ }
1538
+ add(line.slice(last));
1539
+ return { text, styles: colored ? styles : undefined };
1540
+ }
1541
+ function lay(rows, searched) {
1542
+ const parsed = rows.map((row) => row.cells.map(styled));
1543
+ const widths = (parsed[0] ?? []).map((_, column) => Math.max(...parsed.map((cells) => Bun.stringWidth(cells[column]?.text ?? ""))));
1544
+ return parsed.map((cells) => {
1545
+ let text = "";
1546
+ const styles = [];
1547
+ let colored = false;
1548
+ let searchEnd = 0;
1549
+ for (const [column, cell] of cells.entries()) {
1550
+ const last = column === cells.length - 1;
1551
+ const shown = cells.length > 1 && last ? cell.text.trimEnd() : cell.text;
1552
+ text += shown;
1553
+ colored ||= cell.styles !== undefined;
1554
+ styles.push(...cell.styles?.slice(0, shown.length) ?? repeat("", shown.length));
1555
+ if (column < searched)
1556
+ searchEnd = cells.length > 1 ? text.trimEnd().length : text.length;
1557
+ if (!last) {
1558
+ const pad = widths[column] - Bun.stringWidth(cell.text) + COLUMN_GAP;
1559
+ text += " ".repeat(pad);
1560
+ styles.push(...repeat("", pad));
1561
+ }
1562
+ }
1563
+ return { text, styles: colored ? styles : undefined, searchable: text.slice(0, searchEnd) };
1564
+ });
1565
+ }
1566
+ function parseKeys(input, paste = { active: false }, ctrls = []) {
1567
+ const codes = new Map(ctrls.map((name) => [String.fromCharCode(name.codePointAt(name.length - 1) - 96), name]));
1568
+ return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => {
1569
+ const named = codes.get(sequence);
1570
+ const key = named === undefined ? KEYS[sequence] : { ctrl: named };
1571
+ if (sequence === PASTE_START || sequence === PASTE_END) {
1572
+ paste.active = sequence === PASTE_START;
1573
+ return [];
1574
+ }
1575
+ if (paste.active && (sequence === "\r" || sequence === `
1576
+ `))
1577
+ return [];
1578
+ if (key !== undefined)
1579
+ return [key];
1580
+ return IS_CONTROL.test(sequence) || sequence.startsWith("\x1B") ? [] : [{ text: sequence }];
1581
+ });
1582
+ }
1583
+ function createState(rows, { multi = false, prompt = "> ", query = "", header = "", expect = [], searched = Number.POSITIVE_INFINITY } = {}) {
1584
+ const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, marked: new Set, multi, prompt, done: undefined };
1585
+ return withRows(state, rows);
1586
+ }
1587
+ function withRows(state, rows, header = state.header) {
1588
+ const cells = rows.map((row) => typeof row === "string" ? { hidden: [], cells: [row] } : row);
1589
+ const laid = lay(cells, state.searched);
1590
+ const searchable = laid.map((row) => row.searchable);
1591
+ const matches = rank(state.query, searchable);
1592
+ 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 };
1593
+ }
1594
+ function withQuery(state, query) {
1595
+ return { ...state, query, matches: rank(query, state.searchable), cursor: 0 };
1596
+ }
1597
+ function move(state, by) {
1598
+ return { ...state, cursor: Math.max(0, Math.min(state.matches.length - 1, state.cursor + by)) };
1599
+ }
1600
+ function highlighted(state) {
1601
+ return state.matches[state.cursor]?.index;
1602
+ }
1603
+ function press(state, key) {
1604
+ if (state.done !== undefined)
1605
+ return state;
1606
+ if (typeof key === "object") {
1607
+ if ("text" in key)
1608
+ return withQuery(state, state.query + key.text);
1609
+ return state.expect.includes(key.ctrl) ? { ...state, done: "accept", key: key.ctrl } : state;
1610
+ }
1611
+ switch (key) {
1612
+ case "up":
1613
+ return move(state, -1);
1614
+ case "down":
1615
+ return move(state, 1);
1616
+ case "cancel":
1617
+ return { ...state, done: "cancel" };
1618
+ case "erase":
1619
+ return withQuery(state, [...state.query].slice(0, -1).join(""));
1620
+ case "clear":
1621
+ return withQuery(state, "");
1622
+ case "word":
1623
+ return withQuery(state, state.query.replace(LAST_WORD, ""));
1624
+ case "enter":
1625
+ return state.matches.length === 0 && state.marked.size === 0 ? state : { ...state, done: "accept" };
1626
+ case "tab": {
1627
+ const at = highlighted(state);
1628
+ if (!state.multi || at === undefined)
1629
+ return state;
1630
+ const marked = new Set(state.marked);
1631
+ if (!marked.delete(at))
1632
+ marked.add(at);
1633
+ return move({ ...state, marked }, 1);
1634
+ }
1635
+ }
1636
+ }
1637
+ function chosen(state) {
1638
+ if (state.marked.size > 0)
1639
+ return state.rows.filter((_, index) => state.marked.has(index));
1640
+ const at = highlighted(state);
1641
+ return at === undefined ? [] : [state.rows[at]];
1642
+ }
1643
+
1644
+ // ../kit/src/picker/terminal.ts
1645
+ import { openSync } from "fs";
1646
+ import process14 from "process";
1647
+ import tty from "tty";
1648
+
1649
+ // ../kit/src/picker/preview.ts
1650
+ import process13 from "process";
1651
+
1652
+ // ../kit/src/shell.ts
1653
+ var SEPARATORS = new Set([`
1654
+ `, ";", "|"]);
1655
+ var PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
1656
+ var GLOB_CHARS = new Set(["*", "?", "["]);
1657
+ var COMMENT_PREFIXES = new Set([";", "&", "|", "(", ")"]);
1658
+ var SAFE_WORD = /^[\w@%+=:,./-]+$/;
1659
+ var DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
1660
+ var KEYWORDS = new Set(["then", "do", "else", "if", "elif", "while", "until", "!", "{", "}"]);
1661
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ash", "ksh", "fish", "csh", "tcsh", "nu", "xonsh", "elvish", "pwsh", "powershell"]);
1662
+ var SCANNED_PROGRAMS = new Set(["su", "runuser", "flock"]);
1663
+ var COMMAND_STRING_PROGRAMS = new Set([...SHELLS, ...SCANNED_PROGRAMS]);
1664
+ var POWERSHELL = new Set(["pwsh", "powershell"]);
1665
+ var SHELL_VALUE_OPTIONS = new Set(["-o", "-O", "--rcfile", "--init-file"]);
1666
+ var FISH_VALUE_OPTIONS = new Set(["-C", "--init-command"]);
1667
+ var PRINTERS = new Set(["echo", "printf"]);
1668
+ var FIND_EXEC = new Set(["-exec", "-execdir", "-ok", "-okdir"]);
1669
+ function wrapper(short = "", long = [], operands = 0, runsString = false) {
1670
+ return { short, long, operands, runsString };
1671
+ }
1672
+ var WRAPPERS = new Map([
1673
+ ["command", wrapper()],
1674
+ ["builtin", wrapper()],
1675
+ ["nohup", wrapper()],
1676
+ ["setsid", wrapper()],
1677
+ ["busybox", wrapper()],
1678
+ ["exec", wrapper("a")],
1679
+ ["time", wrapper("fo", ["--format", "--output"])],
1680
+ ["env", wrapper("uCS", ["--unset", "--chdir", "--split-string", "--argv0"])],
1681
+ ["sudo", wrapper("ughpCTrtUDR", ["--user", "--group", "--host", "--prompt", "--close-from", "--command-timeout", "--role", "--type", "--other-user", "--chdir", "--chroot"])],
1682
+ ["doas", wrapper("uC")],
1683
+ ["timeout", wrapper("sk", ["--signal", "--kill-after"], 1)],
1684
+ ["nice", wrapper("n", ["--adjustment"])],
1685
+ ["ionice", wrapper("cnpPu", ["--class", "--classdata", "--pid", "--pgid", "--uid"])],
1686
+ ["stdbuf", wrapper("ioe", ["--input", "--output", "--error"])],
1687
+ ["chroot", wrapper("", ["--userspec", "--groups"], 1)],
1688
+ ["watch", wrapper("n", ["--interval"], 0, true)],
1689
+ ["flock", wrapper("wE", ["--timeout", "--conflict-exit-code"], 1)],
1690
+ ["parallel", wrapper("jnNSaIPlLdTsC", ["--jobs", "--max-args", "--max-replace-args", "--sshlogin", "--arg-file", "--delimiter", "--colsep"], 0, true)],
1691
+ ["unbuffer", wrapper()],
1692
+ ["systemd-run", wrapper("puEMH", ["--property", "--unit", "--description", "--slice", "--setenv", "--machine", "--host", "--uid", "--gid", "--working-directory", "--nice", "--service-type"])],
1693
+ ["xargs", wrapper("IndsEaPL", ["--max-args", "--max-procs", "--max-chars", "--delimiter", "--arg-file", "--eof", "--max-lines", "--process-slot-var"])]
1694
+ ]);
1695
+ function shellQuote(word) {
1696
+ return SAFE_WORD.test(word) && !word.startsWith("=") ? word : `'${word.replaceAll("'", "'\\''")}'`;
1697
+ }
1698
+
1699
+ // ../kit/src/picker/preview.ts
1700
+ var PREVIEW_BYTES = 64 * 1024;
1701
+ var PREVIEW_LINES = 200;
1702
+ var PREVIEW_TIMEOUT_MS = 3000;
1703
+ var PLACEHOLDER2 = /\{([1-9]\d*)?\}/g;
1704
+ function previewCommand(template, line, hidden = []) {
1705
+ return template.replace(PLACEHOLDER2, (_, field) => shellQuote(field === undefined ? line : hidden[Number(field) - 1] ?? ""));
1706
+ }
1707
+ function previewLines(output) {
1708
+ const lines = output.split(`
1709
+ `);
1710
+ if (lines.at(-1) === "")
1711
+ lines.pop();
1712
+ return lines.slice(0, PREVIEW_LINES);
1713
+ }
1714
+ async function runPreview(template, line, signal, hidden = []) {
1715
+ const proc = Bun.spawn(["sh", "-c", `exec 2>&1
1716
+ ${previewCommand(template, line, hidden)}`], { stdin: "ignore", stdout: "pipe", stderr: "ignore", detached: true });
1717
+ const stop = () => {
1718
+ try {
1719
+ process13.kill(-proc.pid, "SIGKILL");
1720
+ } catch {
1721
+ proc.kill();
1722
+ }
1723
+ };
1724
+ const timer = setTimeout(stop, PREVIEW_TIMEOUT_MS);
1725
+ const chunks = [];
1726
+ let size = 0;
1727
+ signal.addEventListener("abort", stop);
1728
+ try {
1729
+ for await (const chunk of proc.stdout) {
1730
+ chunks.push(chunk);
1731
+ size += chunk.length;
1732
+ if (size >= PREVIEW_BYTES) {
1733
+ stop();
1734
+ break;
1735
+ }
1736
+ }
1737
+ } finally {
1738
+ clearTimeout(timer);
1739
+ signal.removeEventListener("abort", stop);
1740
+ }
1741
+ return signal.aborted ? [] : previewLines(Buffer.concat(chunks).subarray(0, PREVIEW_BYTES).toString());
1742
+ }
1743
+
1744
+ // ../kit/src/picker/view.ts
1745
+ var MIN_HEIGHT = 4;
1746
+ var PREVIEW_MIN_COLUMNS = 80;
1747
+ var PREVIEW_MIN_ROWS = 10;
1748
+ var PREVIEW_SHARE = 0.4;
1749
+ var RESET2 = "\x1B[0m";
1750
+ var SEPARATOR = " \u2502 ";
1751
+ var HEIGHT = /^(\d+)(%?)$/;
1752
+ function parseHeight(text) {
1753
+ const found = HEIGHT.exec(text);
1754
+ if (found === null || Number(found[1]) < 1)
1755
+ return;
1756
+ return found[2] === "%" ? { percent: Math.min(100, Number(found[1])) } : { lines: Number(found[1]) };
1757
+ }
1758
+ function layoutFor(size, height, previewing, position = "right") {
1759
+ const wanted = "lines" in height ? height.lines : Math.round(size.rows * height.percent / 100);
1760
+ const rows = Math.min(size.rows, Math.max(MIN_HEIGHT, wanted));
1761
+ if (position === "down") {
1762
+ const previewHeight = previewing && rows >= PREVIEW_MIN_ROWS ? Math.round(rows * PREVIEW_SHARE) : 0;
1763
+ return { height: rows, listHeight: rows - previewHeight, listWidth: size.columns, previewWidth: 0, previewHeight };
1764
+ }
1765
+ const split = previewing && size.columns >= PREVIEW_MIN_COLUMNS;
1766
+ const listWidth = split ? Math.floor(size.columns / 2) : size.columns;
1767
+ return { height: rows, listHeight: rows, listWidth, previewWidth: split ? size.columns - listWidth - SEPARATOR.length : 0, previewHeight: 0 };
1768
+ }
1769
+ function fit(text, width, plain, mark = plain, marked = new Set, styles) {
1770
+ const room = Bun.stringWidth(text) > width ? width - 1 : width;
1771
+ let out = "";
1772
+ let used = 0;
1773
+ let at = 0;
1774
+ for (const char of text) {
1775
+ const size = Bun.stringWidth(char);
1776
+ if (used + size > room)
1777
+ break;
1778
+ const style = marked.has(at) ? "" : styles?.[at] ?? "";
1779
+ out += style === "" ? (marked.has(at) ? mark : plain)(char) : `${style}${char}${RESET2}`;
1780
+ used += size;
1781
+ at += char.length;
1782
+ }
1783
+ if (room < width) {
1784
+ out += plain("\u2026");
1785
+ used += 1;
1786
+ }
1787
+ return { text: out, width: used };
1788
+ }
1789
+ var identity = (text) => text;
1790
+ function fitStyled(line, width, plain = identity) {
1791
+ const { text, styles } = styled(line);
1792
+ return fit(text, width, plain, plain, new Set, styles);
1793
+ }
1794
+ function caretColumn(state, layout) {
1795
+ return Math.min(layout.listWidth, Bun.stringWidth(state.prompt) + Bun.stringWidth(state.query) + 1);
1796
+ }
1797
+ function listLines(state, layout, theme) {
1798
+ const header = state.header === "" ? [] : state.header.split(`
1799
+ `).map((line) => fitStyled(line, layout.listWidth, theme.label));
1800
+ const rows = Math.max(1, layout.listHeight - 2 - header.length);
1801
+ const prefixWidth = state.multi ? 4 : 2;
1802
+ const top = Math.max(0, Math.min(state.cursor - Math.floor(rows / 2), state.matches.length - rows));
1803
+ const marks = (state.marked.size > 0 ? ` (${state.marked.size} marked)` : "") + (state.status === "" ? "" : ` ${state.status}`);
1804
+ const promptWidth = Bun.stringWidth(state.prompt);
1805
+ const query = fit(state.query, Math.max(1, layout.listWidth - promptWidth), identity);
1806
+ const count = fit(` ${state.matches.length}/${state.items.length}${marks}`, layout.listWidth, theme.label);
1807
+ const lines = [{ text: theme.accent(state.prompt) + query.text, width: promptWidth + query.width }, count, ...header];
1808
+ if (state.matches.length === 0)
1809
+ lines.push(fit(" no match", layout.listWidth, theme.label));
1810
+ for (const [offset, match] of state.matches.slice(top, top + rows).entries()) {
1811
+ const current = top + offset === state.cursor;
1812
+ const pointer = current ? theme.accent("\u276F") : " ";
1813
+ const mark = state.multi ? `${state.marked.has(match.index) ? theme.id("\u25CF") : " "} ` : "";
1814
+ const text = fit(state.items[match.index], layout.listWidth - prefixWidth, current ? theme.value : identity, theme.accent, new Set(match.positions), state.styles[match.index]);
1815
+ lines.push({ text: `${pointer} ${mark}${text.text}`, width: prefixWidth + text.width });
1816
+ }
1817
+ return lines;
1818
+ }
1819
+ function render(state, layout, theme, preview = []) {
1820
+ const left = listLines(state, layout, theme);
1821
+ return Array.from({ length: layout.height }, (_, row) => {
1822
+ if (row >= layout.listHeight)
1823
+ return row === layout.listHeight ? theme.label("\u2500".repeat(layout.listWidth)) : fitStyled(preview[row - layout.listHeight - 1] ?? "", layout.listWidth).text;
1824
+ const list = left[row] ?? { text: "", width: 0 };
1825
+ if (layout.previewWidth === 0)
1826
+ return list.text;
1827
+ return `${list.text}${" ".repeat(Math.max(0, layout.listWidth - list.width))}${theme.label(SEPARATOR)}${fitStyled(preview[row] ?? "", layout.previewWidth).text}`;
1828
+ });
1829
+ }
1830
+
1831
+ // ../kit/src/picker/terminal.ts
1832
+ var PREVIEW_DELAY_MS = 120;
1833
+ var OPEN = { ctrl: "open" };
1834
+ var HIDE_CURSOR = "\x1B[?25l";
1835
+ var SHOW_CURSOR = "\x1B[?25h";
1836
+ var PASTE_ON = "\x1B[?2004h";
1837
+ var PASTE_OFF = "\x1B[?2004l";
1838
+ var CLEAR_BELOW = "\r\x1B[J";
1839
+ var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
1840
+ function up(lines) {
1841
+ return lines > 0 ? `\x1B[${lines}A` : "";
1842
+ }
1843
+ function openTerminal() {
1844
+ let input;
1845
+ try {
1846
+ input = new tty.ReadStream(openSync("/dev/tty", "r"));
1847
+ } catch {
1848
+ return fail("no terminal to read keys from (/dev/tty)");
1849
+ }
1850
+ const out = process14.stderr;
1851
+ let resize;
1852
+ return {
1853
+ get columns() {
1854
+ return out.columns || 80;
1855
+ },
1856
+ get rows() {
1857
+ return out.rows || 24;
1858
+ },
1859
+ theme: { accent: role("accent", out), label: role("label", out), value: role("value", out), id: role("id", out) },
1860
+ write: (text) => void out.write(text),
1861
+ listen(onChunk, onResize) {
1862
+ resize = onResize;
1863
+ input.setRawMode(true);
1864
+ out.on("resize", onResize);
1865
+ input.on("data", (chunk) => onChunk(chunk.toString()));
1866
+ },
1867
+ close() {
1868
+ if (resize !== undefined)
1869
+ out.off("resize", resize);
1870
+ try {
1871
+ input.setRawMode(false);
1872
+ } finally {
1873
+ input.destroy();
1874
+ }
1875
+ }
1876
+ };
1877
+ }
1878
+ async function select(rows, settings, terminal = openTerminal()) {
1879
+ const { theme } = terminal;
1880
+ let state = createState(rows, settings);
1881
+ let preview = [];
1882
+ let reserved = 0;
1883
+ let previewTimer;
1884
+ let previewing = new AbortController;
1885
+ let shownLine;
1886
+ let finish;
1887
+ let restored = false;
1888
+ function draw(clear = false) {
1889
+ if (restored)
1890
+ return;
1891
+ const layout = layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition);
1892
+ let frame = HIDE_CURSOR;
1893
+ if (reserved !== layout.height || clear) {
1894
+ frame += CLEAR_BELOW + `
1895
+ `.repeat(layout.height - 1) + up(layout.height - 1);
1896
+ reserved = layout.height;
1897
+ }
1898
+ frame += `\r${render(state, layout, theme, preview).map((line) => `\x1B[2K${line}`).join(`
1899
+ `)}${up(layout.height - 1)}\r\x1B[${caretColumn(state, layout)}G${SHOW_CURSOR}`;
1900
+ terminal.write(frame);
1901
+ }
1902
+ function refreshPreview() {
1903
+ const at = highlighted(state);
1904
+ if (settings.preview === undefined || at === shownLine)
1905
+ return;
1906
+ shownLine = at;
1907
+ clearTimeout(previewTimer);
1908
+ previewing.abort();
1909
+ previewing = new AbortController;
1910
+ if (at === undefined) {
1911
+ preview = [];
1912
+ return;
1913
+ }
1914
+ const { signal } = previewing;
1915
+ const template = settings.preview;
1916
+ const row = state.rows[at];
1917
+ async function show() {
1918
+ const shown = typeof template === "string" ? await runPreview(template, row.cells.join(" "), signal, row.hidden) : await previewOf(template, row.hidden, signal);
1919
+ if (!signal.aborted) {
1920
+ preview = shown;
1921
+ draw();
1922
+ }
1923
+ }
1924
+ previewTimer = setTimeout(() => void show(), PREVIEW_DELAY_MS);
1925
+ }
1926
+ function restore() {
1927
+ if (restored)
1928
+ return;
1929
+ restored = true;
1930
+ clearTimeout(previewTimer);
1931
+ previewing.abort();
1932
+ terminal.write(`${CLEAR_BELOW}${PASTE_OFF}${SHOW_CURSOR}`);
1933
+ terminal.close();
1934
+ }
1935
+ const stop = () => {
1936
+ state = { ...state, done: "cancel" };
1937
+ finish?.();
1938
+ };
1939
+ const paste = { active: false };
1940
+ const { reload } = settings;
1941
+ const pending = [];
1942
+ let busy = false;
1943
+ let draining = false;
1944
+ async function reloadRows() {
1945
+ if (reload === undefined)
1946
+ return;
1947
+ busy = true;
1948
+ state = { ...state, status: "reloading" };
1949
+ draw();
1950
+ try {
1951
+ const next = await reload.run();
1952
+ state = { ...withRows(state, next.rows, next.header), status: "" };
1953
+ shownLine = undefined;
1954
+ } catch (error) {
1955
+ state = { ...state, status: `reload failed: ${error instanceof Error ? error.message : String(error)}` };
1956
+ } finally {
1957
+ busy = false;
1958
+ }
1959
+ }
1960
+ async function drain() {
1961
+ if (draining)
1962
+ return;
1963
+ draining = true;
1964
+ try {
1965
+ for (let key = pending.shift();key !== undefined && state.done === undefined; key = pending.shift()) {
1966
+ if (key === OPEN || typeof key === "object" && "ctrl" in key && key.ctrl === reload?.key)
1967
+ await reloadRows();
1968
+ else
1969
+ state = press(state, key);
1970
+ }
1971
+ } finally {
1972
+ draining = false;
1973
+ }
1974
+ if (state.done !== undefined) {
1975
+ finish?.();
1976
+ return;
1977
+ }
1978
+ refreshPreview();
1979
+ draw();
1980
+ }
1981
+ function onChunk(chunk) {
1982
+ const keys = parseKeys(chunk, paste, [...settings.expect ?? [], ...reload?.key === undefined ? [] : [reload.key]]);
1983
+ if (busy && keys.includes("cancel")) {
1984
+ stop();
1985
+ return;
1986
+ }
1987
+ pending.push(...keys);
1988
+ drain();
1989
+ }
1990
+ const ended = new Promise((resolve) => {
1991
+ finish = resolve;
1992
+ });
1993
+ process14.once("exit", restore);
1994
+ for (const signal of SIGNALS)
1995
+ process14.once(signal, stop);
1996
+ try {
1997
+ terminal.write(PASTE_ON);
1998
+ terminal.listen(onChunk, () => draw(true));
1999
+ refreshPreview();
2000
+ draw(true);
2001
+ if (reload?.onOpen === true) {
2002
+ pending.push(OPEN);
2003
+ drain();
2004
+ }
2005
+ await ended;
2006
+ } finally {
2007
+ for (const signal of SIGNALS)
2008
+ process14.off(signal, stop);
2009
+ process14.off("exit", restore);
2010
+ restore();
2011
+ }
2012
+ return state.done === "accept" ? { rows: chosen(state), key: state.key, query: state.query } : undefined;
2013
+ }
2014
+ async function previewOf(preview, hidden, signal) {
2015
+ try {
2016
+ return previewLines(await preview(hidden, signal));
2017
+ } catch (error) {
2018
+ return [error instanceof Error ? error.message : String(error)];
2019
+ }
2020
+ }
2021
+
2022
+ // ../kit/src/picker.ts
1169
2023
  var REMEMBERED = 20;
1170
2024
  function table(rows) {
1171
2025
  const widths = rows[0]?.cells.map((_, index) => Math.max(...rows.map((row) => Bun.stringWidth(row.cells[index])))) ?? [];
@@ -1174,70 +2028,40 @@ function table(rows) {
1174
2028
  ...row.cells.map((cell, index) => index === row.cells.length - 1 ? cell.trimEnd() : `${cell}${" ".repeat(widths[index] + 1 - Bun.stringWidth(cell))}`)
1175
2029
  ].join("\t"));
1176
2030
  }
1177
- function fzf(rows, options) {
1178
- const hidden = options.hidden ?? rows[0]?.hidden.length ?? 0;
1179
- const proc = Bun.spawnSync([
1180
- "fzf",
1181
- "--prompt",
1182
- options.prompt,
1183
- ...options.header === undefined ? [] : ["--header", options.header],
1184
- ...(options.expect ?? []).length > 0 ? ["--expect", (options.expect ?? []).join(",")] : [],
1185
- "--ansi",
1186
- "--reverse",
1187
- "--height",
1188
- options.height ?? "100%",
1189
- "--delimiter",
1190
- "\t",
1191
- "--with-nth",
1192
- `${hidden + 1}..`,
1193
- "--nth",
1194
- `1..${options.searched}`,
1195
- "--tabstop",
1196
- "1",
1197
- "--tiebreak",
1198
- "chunk,index",
1199
- ...options.preview === undefined ? [] : ["--preview", options.preview, "--preview-window", "down,40%"],
1200
- ...(options.bind ?? []).flatMap((bind) => ["--bind", bind]),
1201
- ...options.query === true ? ["--print-query"] : []
1202
- ], {
1203
- stdin: new TextEncoder().encode(table(rows).join(`
1204
- `)),
1205
- stdout: "pipe",
1206
- stderr: "inherit"
1207
- });
1208
- return picked(proc.stdout.toString(), hidden, options);
1209
- }
1210
- function picked(output, hidden, options) {
1211
- const lines = output.split(`
1212
- `);
1213
- const query = options.query === true ? lines.shift() : "";
1214
- const [key, line] = (options.expect ?? []).length > 0 ? [lines[0], lines[1]] : ["", lines[0]];
1215
- if (line !== undefined && line !== "")
1216
- return { key, hidden: line.split("\t").slice(0, hidden), query };
1217
- return options.query === true && key !== "" ? { key, hidden: [], query } : undefined;
2031
+ async function pick(rows, options) {
2032
+ const height = parseHeight(options.height ?? "100%");
2033
+ const keys = [...options.expect ?? [], ...options.reload?.key === undefined ? [] : [options.reload.key]];
2034
+ if (height === undefined)
2035
+ throw new Error(`pick: the height is lines or a percentage, not ${options.height}`);
2036
+ for (const key of keys) {
2037
+ if (!CTRL_KEY.test(key))
2038
+ 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`);
2039
+ }
2040
+ 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 });
2041
+ return found === undefined ? undefined : { key: found.key, hidden: found.rows[0]?.hidden ?? [], query: found.query };
1218
2042
  }
1219
2043
  function heightFor(count) {
1220
2044
  return String(Math.max(15, Math.ceil((count + 4) / 0.6)));
1221
2045
  }
1222
2046
  function historyFile(name) {
1223
- return join4(process8.env.XDG_CACHE_HOME ?? join4(homedir2(), ".cache"), name, "history.json");
2047
+ return join7(process15.env.XDG_CACHE_HOME ?? join7(homedir4(), ".cache"), name, "history.json");
1224
2048
  }
1225
2049
  function history(name) {
1226
2050
  try {
1227
2051
  const file = historyFile(name);
1228
- return existsSync(file) ? JSON.parse(readFileSync2(file, "utf-8")) : {};
2052
+ return existsSync3(file) ? JSON.parse(readFileSync3(file, "utf-8")) : {};
1229
2053
  } catch {
1230
2054
  return {};
1231
2055
  }
1232
2056
  }
1233
2057
  function runs(name) {
1234
- return history(name)[process8.cwd()] ?? [];
2058
+ return history(name)[process15.cwd()] ?? [];
1235
2059
  }
1236
2060
  function record(name, run) {
1237
2061
  const all = history(name);
1238
2062
  const file = historyFile(name);
1239
- all[process8.cwd()] = [run, ...(all[process8.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
1240
- mkdirSync2(dirname4(file), { recursive: true });
2063
+ all[process15.cwd()] = [run, ...(all[process15.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
2064
+ mkdirSync2(dirname5(file), { recursive: true });
1241
2065
  writeFileSync(file, JSON.stringify(all));
1242
2066
  }
1243
2067
  function byRecency(items, previous, idOf) {
@@ -1260,11 +2084,11 @@ function list(rows) {
1260
2084
  }
1261
2085
 
1262
2086
  // src/picker.ts
1263
- import process9 from "process";
2087
+ import process16 from "process";
1264
2088
 
1265
2089
  // src/stack.ts
1266
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "fs";
1267
- import { dirname as dirname5, join as join5, relative, resolve } from "path";
2090
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4 } from "fs";
2091
+ import { dirname as dirname6, join as join8, relative, resolve as resolve3 } from "path";
1268
2092
  var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
1269
2093
  var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
1270
2094
  var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
@@ -1276,29 +2100,29 @@ function from(dir, command) {
1276
2100
  function children(dir = ".") {
1277
2101
  return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
1278
2102
  }
1279
- function up(dir) {
2103
+ function up2(dir) {
1280
2104
  const dirs = [];
1281
- for (let current = dir;; current = join5(current, "..")) {
2105
+ for (let current = dir;; current = join8(current, "..")) {
1282
2106
  dirs.push(current);
1283
- const absolute = resolve(current);
1284
- if (existsSync2(join5(current, ".git")) || dirname5(absolute) === absolute)
2107
+ const absolute = resolve3(current);
2108
+ if (existsSync4(join8(current, ".git")) || dirname6(absolute) === absolute)
1285
2109
  return dirs;
1286
2110
  }
1287
2111
  }
1288
2112
  function stackAt(dir) {
1289
- return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve(dir), env: process.env } };
2113
+ return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve3(dir), env: process.env } };
1290
2114
  }
1291
2115
  function holdsStack(dir) {
1292
- if (COMPOSE_FILES.some((name) => existsSync2(join5(dir, name))))
2116
+ if (COMPOSE_FILES.some((name) => existsSync4(join8(dir, name))))
1293
2117
  return true;
1294
2118
  try {
1295
- return COMPOSE_FILE_VAR.test(readFileSync3(join5(dir, ".env"), "utf-8"));
2119
+ return COMPOSE_FILE_VAR.test(readFileSync4(join8(dir, ".env"), "utf-8"));
1296
2120
  } catch {
1297
2121
  return false;
1298
2122
  }
1299
2123
  }
1300
2124
  function nearestStack(dir) {
1301
- const found = up(dir).find(holdsStack);
2125
+ const found = up2(dir).find(holdsStack);
1302
2126
  return found === undefined ? undefined : stackAt(found);
1303
2127
  }
1304
2128
  function stacks() {
@@ -1321,7 +2145,7 @@ async function labels(project) {
1321
2145
  const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
1322
2146
  if (await proc.exited !== 0)
1323
2147
  return [];
1324
- return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync2(file)));
2148
+ return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync4(file)));
1325
2149
  }
1326
2150
  function labelledStack(dir, { project, files, envFiles, workdir }) {
1327
2151
  const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
@@ -1336,7 +2160,7 @@ function stackOf(recorded) {
1336
2160
  return labelledStack(relative(".", recorded.workdir) || ".", recorded);
1337
2161
  }
1338
2162
  async function recordedNearby() {
1339
- const dirs = new Set([...up("."), ...children()].map((dir) => resolve(dir)));
2163
+ const dirs = new Set([...up2("."), ...children()].map((dir) => resolve3(dir)));
1340
2164
  return (await labels()).filter((stack) => dirs.has(stack.workdir));
1341
2165
  }
1342
2166
  async function attempt(stack, args) {
@@ -1427,10 +2251,10 @@ async function logsOf(stack, name) {
1427
2251
  }
1428
2252
 
1429
2253
  // src/picker.ts
1430
- var KEYS = { "ctrl-l": "logs", "ctrl-r": "restart", "ctrl-x": "stop", "ctrl-b": "build" };
2254
+ var KEYS2 = { "ctrl-l": "logs", "ctrl-r": "restart", "ctrl-x": "stop", "ctrl-b": "build" };
1431
2255
  var KEY_LABELS = { "ctrl-l": "Logs", "ctrl-r": "Restart", "ctrl-x": "Stop", "ctrl-b": "Rebuild" };
1432
2256
  function row(service) {
1433
- const colors = paint(process9.stderr);
2257
+ const colors = paint(process16.stderr);
1434
2258
  const dir = service.stack.dir;
1435
2259
  const project = service.stack.project;
1436
2260
  const name = project === undefined ? service.name : `${colors.dim(`${project}:`)}${service.name}`;
@@ -1439,14 +2263,14 @@ function row(service) {
1439
2263
  cells: [dir === "." ? name : `${colors.dim(`${dir}/`)}${name}`, colors[stateColor(service.state)](service.state), colors.cyan(service.ports), colors.dim(service.image)]
1440
2264
  };
1441
2265
  }
1442
- function pickService(services) {
1443
- const picked = fzf(services.map(row), {
2266
+ async function pickService(services) {
2267
+ const picked = await pick(services.map(row), {
1444
2268
  prompt: "dx> ",
1445
2269
  header: "enter shell or up, ctrl-l logs, ctrl-r restart, ctrl-x stop, ctrl-b rebuild",
1446
- expect: Object.keys(KEYS),
2270
+ expect: Object.keys(KEYS2),
1447
2271
  searched: 1,
1448
2272
  height: heightFor(services.length),
1449
- preview: `${process9.execPath} ${process9.argv[1]} --show {1} {2} {4}`
2273
+ preview: `${process16.execPath} ${process16.argv[1]} --show {1} {2} {4}`
1450
2274
  });
1451
2275
  const service = services.find((candidate) => key(candidate) === picked?.hidden[2]);
1452
2276
  return picked === undefined || service === undefined ? undefined : { service, key: picked.key };
@@ -1456,7 +2280,7 @@ function pickService(services) {
1456
2280
  var usage2 = `dx [service] [action]
1457
2281
  dx -
1458
2282
 
1459
- Without a terminal or without fzf (a script, a pipe, an agent) \`dx\` lists the services with their state, ports and image: that listing is the form to use there, \`--json\` giving the full record. On a terminal with fzf it picks one instead: enter opens its shell (or brings a stopped service up), ctrl-l follows its logs, ctrl-r restarts, ctrl-x stops and ctrl-b rebuilds it. The services last acted on come first, per folder.
2283
+ Without a terminal (a script, a pipe, an agent) \`dx\` lists the services with their state, ports and image: that listing is the form to use there, \`--json\` giving the full record. On a terminal it picks one instead: enter opens its shell (or brings a stopped service up), ctrl-l follows its logs, ctrl-r restarts, ctrl-x stops and ctrl-b rebuilds it. The services last acted on come first, per folder.
1460
2284
  \`dx -\` does the last action again, \`dx <service> [action]\` acts without the picker: \`shell\` (bash when the container has it), \`logs\` (follows the last ${LOGS_TAIL} lines; \`--tail N\` changes the count and \`--no-follow\` prints them once and exits, for a script), \`restart\`, \`up\`, \`stop\` or \`build\` (up with a rebuild).
1461
2285
  The services are the stack of this folder or its nearest parent, the ones of the child folders (keyed \`api/php\`), and stacks found only through their containers (keyed \`<project>:php\`).`;
1462
2286
  var EXITS = [["the code of the command it ran", "a listing, a shell, logs, restart, up, stop or build"], [0, "it listed the services"], [1, "no compose service here or none by that name"], [2, "a usage error"]];
@@ -1466,7 +2290,7 @@ var options = {
1466
2290
  follow: { type: "boolean", negative: "print the logs once and exit instead of following them" }
1467
2291
  };
1468
2292
  function table2(services) {
1469
- const colors = paint(process10.stdout);
2293
+ const colors = paint(process17.stdout);
1470
2294
  const keys = services.map(key);
1471
2295
  const width = Math.max(...keys.map((name) => name.length));
1472
2296
  const states = Math.max(...services.map((service) => service.state.length));
@@ -1491,7 +2315,7 @@ function listed2(services) {
1491
2315
  hint: [service.state, service.ports, service.image].filter(Boolean).join(" \xB7 "),
1492
2316
  typed: key(service),
1493
2317
  preview: ["--show", service.stack.dir, service.name, service.stack.project ?? ""],
1494
- actions: Object.entries(KEYS).map(([pressed, action]) => ({ label: KEY_LABELS[pressed] ?? action, key: pressed, typed: `${key(service)} ${action}` }))
2318
+ actions: Object.entries(KEYS2).map(([pressed, action]) => ({ label: KEY_LABELS[pressed] ?? action, key: pressed, typed: `${key(service)} ${action}` }))
1495
2319
  }));
1496
2320
  }
1497
2321
  async function hidden(flag, rest, services) {
@@ -1509,7 +2333,7 @@ async function hidden(flag, rest, services) {
1509
2333
  const service = await serviceNamed(dir, name, project);
1510
2334
  if (service === undefined)
1511
2335
  return 0;
1512
- const colors = paint(process10.stdout);
2336
+ const colors = paint(process17.stdout);
1513
2337
  console.log(`${colors[stateColor(service.state)](service.status || service.state)} ${colors.dim(service.image)}${service.ports ? ` ${colors.cyan(service.ports)}` : ""}
1514
2338
  `);
1515
2339
  console.log(await logsOf(service.stack, name));
@@ -1527,8 +2351,8 @@ async function followMasked(command) {
1527
2351
  const decoder = new TextDecoder;
1528
2352
  const proc = Bun.spawn(command, { stdio: ["inherit", "pipe", "inherit"] });
1529
2353
  for await (const chunk of proc.stdout)
1530
- process10.stdout.write(redactor.push(decoder.decode(chunk, { stream: true })));
1531
- process10.stdout.write(redactor.end());
2354
+ process17.stdout.write(redactor.push(decoder.decode(chunk, { stream: true })));
2355
+ process17.stdout.write(redactor.end());
1532
2356
  return proc.exited;
1533
2357
  }
1534
2358
  async function dx(args) {
@@ -1548,23 +2372,21 @@ async function dx(args) {
1548
2372
  let service;
1549
2373
  let action;
1550
2374
  if (named === undefined) {
1551
- const interactive = process10.stdin.isTTY && process10.stderr.isTTY && parsed.values.json !== true;
1552
- if (!interactive || Bun.which("fzf") === null) {
2375
+ const interactive = isInteractive() && parsed.values.json !== true;
2376
+ if (!interactive) {
1553
2377
  if (emit)
1554
- fail("the picker needs a terminal and fzf", 1);
1555
- if (interactive)
1556
- console.error("fzf is not installed, listing the services instead");
2378
+ fail("the picker needs a terminal", 1);
1557
2379
  if (parsed.values.json === true)
1558
2380
  printJson(services.map((each) => ({ key: key(each), project: each.project, name: each.name, state: each.state, status: each.status, ports: each.ports, image: each.image })));
1559
2381
  else
1560
2382
  console.log(table2(services));
1561
2383
  return 0;
1562
2384
  }
1563
- const picked = pickService(byRecency(services, runs("dx"), key));
2385
+ const picked = await pickService(byRecency(services, runs("dx"), key));
1564
2386
  if (picked === undefined)
1565
2387
  return 1;
1566
2388
  service = picked.service;
1567
- action = KEYS[picked.key];
2389
+ action = KEYS2[picked.key];
1568
2390
  } else if (named === "-") {
1569
2391
  const last = runs("dx")[0];
1570
2392
  service = last && services.find((candidate) => key(candidate) === last.id);
@@ -1592,4 +2414,4 @@ async function dx(args) {
1592
2414
  }
1593
2415
 
1594
2416
  // src/dx-cli.ts
1595
- process11.exitCode = await runMain({ name: "dx", run: dx });
2417
+ process18.exitCode = await runMain({ name: "dx", run: dx });