@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/README.md +2 -2
- package/dist/dc-cli.js +297 -62
- package/dist/dlog-cli.js +297 -62
- package/dist/dx-cli.js +954 -132
- package/dist/sf-cli.js +962 -141
- package/package.json +3 -3
- package/src/dc.ts +2 -2
- package/src/dx.ts +7 -10
- package/src/picker.ts +5 -5
- package/src/sf.ts +15 -19
package/dist/sf-cli.js
CHANGED
|
@@ -2,13 +2,37 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/sf-cli.ts
|
|
5
|
-
import
|
|
5
|
+
import process18 from "process";
|
|
6
6
|
// ../kit/src/args.ts
|
|
7
|
-
import
|
|
7
|
+
import process9 from "process";
|
|
8
8
|
import { parseArgs } from "util";
|
|
9
9
|
|
|
10
|
-
// ../kit/src/
|
|
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 =
|
|
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 =
|
|
506
|
+
function paint(stream, env = process6.env) {
|
|
279
507
|
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
280
508
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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(
|
|
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(
|
|
532
|
+
console.log(jsonText(value));
|
|
298
533
|
}
|
|
299
534
|
|
|
300
535
|
// ../kit/src/help.ts
|
|
301
|
-
import
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
616
|
-
import
|
|
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
|
|
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 =
|
|
871
|
+
function stateHome(env = process10.env) {
|
|
637
872
|
const dir = env.XDG_STATE_HOME;
|
|
638
|
-
return dir !== undefined && isAbsolute(dir) ? dir :
|
|
873
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
|
|
639
874
|
}
|
|
640
|
-
function usagePath(env =
|
|
641
|
-
return
|
|
875
|
+
function usagePath(env = process10.env) {
|
|
876
|
+
return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
642
877
|
}
|
|
643
|
-
function usageRecorded(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 =
|
|
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(
|
|
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(
|
|
676
|
-
const styled = takesControlCodes(
|
|
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
|
|
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 =
|
|
994
|
+
for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
|
|
760
995
|
try {
|
|
761
|
-
const { version } = JSON.parse(
|
|
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 (
|
|
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 =
|
|
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,16 +1044,635 @@ var {Glob } = globalThis.Bun;
|
|
|
809
1044
|
var SESSION_FILES = new Glob("*.json");
|
|
810
1045
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
811
1046
|
// src/sf.ts
|
|
812
|
-
import { existsSync as
|
|
813
|
-
import { homedir as
|
|
814
|
-
import { dirname as
|
|
815
|
-
import
|
|
1047
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1048
|
+
import { homedir as homedir5 } from "os";
|
|
1049
|
+
import { dirname as dirname7, join as join9, resolve as resolve4 } from "path";
|
|
1050
|
+
import process17 from "process";
|
|
816
1051
|
|
|
817
|
-
// ../kit/src/
|
|
818
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as
|
|
819
|
-
import { homedir as
|
|
820
|
-
import { dirname as
|
|
821
|
-
import
|
|
1052
|
+
// ../kit/src/picker.ts
|
|
1053
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1054
|
+
import { homedir as homedir4 } from "os";
|
|
1055
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1056
|
+
import process15 from "process";
|
|
1057
|
+
|
|
1058
|
+
// ../kit/src/picker/score.ts
|
|
1059
|
+
var MATCH = 16;
|
|
1060
|
+
var CONSECUTIVE = 12;
|
|
1061
|
+
var BOUNDARY = 10;
|
|
1062
|
+
var PREFIX = 8;
|
|
1063
|
+
var GAP = 1;
|
|
1064
|
+
var BOUNDARY_AFTER = new Set([" ", "/", "-", "_", ".", ":", "\\", "(", "[", ",", "\t"]);
|
|
1065
|
+
var UPPER = /\p{Lu}/u;
|
|
1066
|
+
var LOWER = /\p{Ll}/u;
|
|
1067
|
+
var NONE = Number.NEGATIVE_INFINITY;
|
|
1068
|
+
function startsWord(text, index) {
|
|
1069
|
+
if (index === 0)
|
|
1070
|
+
return true;
|
|
1071
|
+
const before = text[index - 1];
|
|
1072
|
+
return BOUNDARY_AFTER.has(before) || LOWER.test(before) && UPPER.test(text[index]);
|
|
1073
|
+
}
|
|
1074
|
+
function fuzzy(query, text) {
|
|
1075
|
+
if (query === "")
|
|
1076
|
+
return { score: 0, positions: [] };
|
|
1077
|
+
const exact = query !== query.toLowerCase();
|
|
1078
|
+
const needle = exact ? query : query.toLowerCase();
|
|
1079
|
+
const haystack = exact ? text : text.toLowerCase();
|
|
1080
|
+
if (haystack.length !== text.length || needle.length > haystack.length)
|
|
1081
|
+
return;
|
|
1082
|
+
const width = haystack.length;
|
|
1083
|
+
const best = Array.from({ length: needle.length }, () => new Array(width).fill(NONE));
|
|
1084
|
+
const from = Array.from({ length: needle.length }, () => new Array(width).fill(-1));
|
|
1085
|
+
for (let j = 0;j < needle.length; j++) {
|
|
1086
|
+
let run = NONE;
|
|
1087
|
+
let runAt = -1;
|
|
1088
|
+
for (let i = j;i < width; i++) {
|
|
1089
|
+
if (j > 0 && i - 2 >= j - 1) {
|
|
1090
|
+
const candidate = best[j - 1][i - 2] + (i - 2) * GAP;
|
|
1091
|
+
if (candidate > run) {
|
|
1092
|
+
run = candidate;
|
|
1093
|
+
runAt = i - 2;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (haystack[i] !== needle[j])
|
|
1097
|
+
continue;
|
|
1098
|
+
const here = MATCH + (startsWord(text, i) ? BOUNDARY : 0) + (i === 0 ? PREFIX : 0);
|
|
1099
|
+
if (j === 0) {
|
|
1100
|
+
best[0][i] = here - i * GAP;
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const joined = best[j - 1][i - 1] + CONSECUTIVE;
|
|
1104
|
+
const skipped = run - (i - 1) * GAP;
|
|
1105
|
+
if (joined > NONE && joined >= skipped) {
|
|
1106
|
+
best[j][i] = joined + here;
|
|
1107
|
+
from[j][i] = i - 1;
|
|
1108
|
+
} else if (skipped > NONE) {
|
|
1109
|
+
best[j][i] = skipped + here;
|
|
1110
|
+
from[j][i] = runAt;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
const last = best[needle.length - 1];
|
|
1115
|
+
let end = -1;
|
|
1116
|
+
for (let i = 0;i < width; i++) {
|
|
1117
|
+
if (last[i] > NONE && (end === -1 || last[i] > last[end]))
|
|
1118
|
+
end = i;
|
|
1119
|
+
}
|
|
1120
|
+
if (end === -1)
|
|
1121
|
+
return;
|
|
1122
|
+
const positions = [];
|
|
1123
|
+
for (let j = needle.length - 1, i = end;j >= 0; j--) {
|
|
1124
|
+
positions.unshift(i);
|
|
1125
|
+
i = from[j][i];
|
|
1126
|
+
}
|
|
1127
|
+
return { score: last[end], positions };
|
|
1128
|
+
}
|
|
1129
|
+
function rank(query, lines) {
|
|
1130
|
+
if (query === "")
|
|
1131
|
+
return lines.map((_, index) => ({ index, positions: [] }));
|
|
1132
|
+
const found = [];
|
|
1133
|
+
for (const [index, line] of lines.entries()) {
|
|
1134
|
+
const match = fuzzy(query, line);
|
|
1135
|
+
if (match !== undefined)
|
|
1136
|
+
found.push({ index, positions: match.positions, score: match.score });
|
|
1137
|
+
}
|
|
1138
|
+
return found.sort((a, b) => b.score - a.score || lines[a.index].length - lines[b.index].length || a.index - b.index);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// ../kit/src/picker/state.ts
|
|
1142
|
+
var ANSI2 = /\x1B(?:\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x40-\x5A\x5C-\x5F])/g;
|
|
1143
|
+
var CONTROL2 = /[\x00-\x1F\x7F-\x9F]/g;
|
|
1144
|
+
var IS_CONTROL = /[\x00-\x1F\x7F-\x9F]/;
|
|
1145
|
+
var SGR = /^\x1B\[[0-9;]*m$/;
|
|
1146
|
+
var RESET = /^\x1B\[0?m$/;
|
|
1147
|
+
var COLUMN_GAP = 2;
|
|
1148
|
+
var PASTE_START = "\x1B[200~";
|
|
1149
|
+
var PASTE_END = "\x1B[201~";
|
|
1150
|
+
var LAST_WORD = /\S*\s*$/;
|
|
1151
|
+
var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|\x1B[\s\S]|[\s\S]/gu;
|
|
1152
|
+
var KEYS = {
|
|
1153
|
+
"\x1B[A": "up",
|
|
1154
|
+
"\x1BOA": "up",
|
|
1155
|
+
"\x10": "up",
|
|
1156
|
+
"\x1B[B": "down",
|
|
1157
|
+
"\x1BOB": "down",
|
|
1158
|
+
"\x0E": "down",
|
|
1159
|
+
"\r": "enter",
|
|
1160
|
+
"\n": "enter",
|
|
1161
|
+
"\x03": "cancel",
|
|
1162
|
+
"\x1B": "cancel",
|
|
1163
|
+
"\x7F": "erase",
|
|
1164
|
+
"\b": "erase",
|
|
1165
|
+
"\x15": "clear",
|
|
1166
|
+
"\x17": "word",
|
|
1167
|
+
"\t": "tab"
|
|
1168
|
+
};
|
|
1169
|
+
var CTRL_KEY = /^ctrl-[abd-gkln-z]$/;
|
|
1170
|
+
function repeat(value, count) {
|
|
1171
|
+
return Array.from({ length: count }).fill(value);
|
|
1172
|
+
}
|
|
1173
|
+
function styled(line) {
|
|
1174
|
+
let text = "";
|
|
1175
|
+
const styles = [];
|
|
1176
|
+
let active = "";
|
|
1177
|
+
let colored = false;
|
|
1178
|
+
let last = 0;
|
|
1179
|
+
function add(chunk) {
|
|
1180
|
+
const flat = chunk.replace(CONTROL2, " ");
|
|
1181
|
+
text += flat;
|
|
1182
|
+
colored ||= active !== "" && flat !== "";
|
|
1183
|
+
styles.push(...repeat(active, flat.length));
|
|
1184
|
+
}
|
|
1185
|
+
for (const found of line.matchAll(ANSI2)) {
|
|
1186
|
+
add(line.slice(last, found.index));
|
|
1187
|
+
last = found.index + found[0].length;
|
|
1188
|
+
if (SGR.test(found[0]))
|
|
1189
|
+
active = RESET.test(found[0]) ? "" : active + found[0];
|
|
1190
|
+
}
|
|
1191
|
+
add(line.slice(last));
|
|
1192
|
+
return { text, styles: colored ? styles : undefined };
|
|
1193
|
+
}
|
|
1194
|
+
function lay(rows, searched) {
|
|
1195
|
+
const parsed = rows.map((row) => row.cells.map(styled));
|
|
1196
|
+
const widths = (parsed[0] ?? []).map((_, column) => Math.max(...parsed.map((cells) => Bun.stringWidth(cells[column]?.text ?? ""))));
|
|
1197
|
+
return parsed.map((cells) => {
|
|
1198
|
+
let text = "";
|
|
1199
|
+
const styles = [];
|
|
1200
|
+
let colored = false;
|
|
1201
|
+
let searchEnd = 0;
|
|
1202
|
+
for (const [column, cell] of cells.entries()) {
|
|
1203
|
+
const last = column === cells.length - 1;
|
|
1204
|
+
const shown = cells.length > 1 && last ? cell.text.trimEnd() : cell.text;
|
|
1205
|
+
text += shown;
|
|
1206
|
+
colored ||= cell.styles !== undefined;
|
|
1207
|
+
styles.push(...cell.styles?.slice(0, shown.length) ?? repeat("", shown.length));
|
|
1208
|
+
if (column < searched)
|
|
1209
|
+
searchEnd = cells.length > 1 ? text.trimEnd().length : text.length;
|
|
1210
|
+
if (!last) {
|
|
1211
|
+
const pad = widths[column] - Bun.stringWidth(cell.text) + COLUMN_GAP;
|
|
1212
|
+
text += " ".repeat(pad);
|
|
1213
|
+
styles.push(...repeat("", pad));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return { text, styles: colored ? styles : undefined, searchable: text.slice(0, searchEnd) };
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
function parseKeys(input, paste = { active: false }, ctrls = []) {
|
|
1220
|
+
const codes = new Map(ctrls.map((name) => [String.fromCharCode(name.codePointAt(name.length - 1) - 96), name]));
|
|
1221
|
+
return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => {
|
|
1222
|
+
const named = codes.get(sequence);
|
|
1223
|
+
const key = named === undefined ? KEYS[sequence] : { ctrl: named };
|
|
1224
|
+
if (sequence === PASTE_START || sequence === PASTE_END) {
|
|
1225
|
+
paste.active = sequence === PASTE_START;
|
|
1226
|
+
return [];
|
|
1227
|
+
}
|
|
1228
|
+
if (paste.active && (sequence === "\r" || sequence === `
|
|
1229
|
+
`))
|
|
1230
|
+
return [];
|
|
1231
|
+
if (key !== undefined)
|
|
1232
|
+
return [key];
|
|
1233
|
+
return IS_CONTROL.test(sequence) || sequence.startsWith("\x1B") ? [] : [{ text: sequence }];
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
function createState(rows, { multi = false, prompt = "> ", query = "", header = "", expect = [], searched = Number.POSITIVE_INFINITY } = {}) {
|
|
1237
|
+
const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, marked: new Set, multi, prompt, done: undefined };
|
|
1238
|
+
return withRows(state, rows);
|
|
1239
|
+
}
|
|
1240
|
+
function withRows(state, rows, header = state.header) {
|
|
1241
|
+
const cells = rows.map((row) => typeof row === "string" ? { hidden: [], cells: [row] } : row);
|
|
1242
|
+
const laid = lay(cells, state.searched);
|
|
1243
|
+
const searchable = laid.map((row) => row.searchable);
|
|
1244
|
+
const matches = rank(state.query, searchable);
|
|
1245
|
+
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 };
|
|
1246
|
+
}
|
|
1247
|
+
function withQuery(state, query) {
|
|
1248
|
+
return { ...state, query, matches: rank(query, state.searchable), cursor: 0 };
|
|
1249
|
+
}
|
|
1250
|
+
function move(state, by) {
|
|
1251
|
+
return { ...state, cursor: Math.max(0, Math.min(state.matches.length - 1, state.cursor + by)) };
|
|
1252
|
+
}
|
|
1253
|
+
function highlighted(state) {
|
|
1254
|
+
return state.matches[state.cursor]?.index;
|
|
1255
|
+
}
|
|
1256
|
+
function press(state, key) {
|
|
1257
|
+
if (state.done !== undefined)
|
|
1258
|
+
return state;
|
|
1259
|
+
if (typeof key === "object") {
|
|
1260
|
+
if ("text" in key)
|
|
1261
|
+
return withQuery(state, state.query + key.text);
|
|
1262
|
+
return state.expect.includes(key.ctrl) ? { ...state, done: "accept", key: key.ctrl } : state;
|
|
1263
|
+
}
|
|
1264
|
+
switch (key) {
|
|
1265
|
+
case "up":
|
|
1266
|
+
return move(state, -1);
|
|
1267
|
+
case "down":
|
|
1268
|
+
return move(state, 1);
|
|
1269
|
+
case "cancel":
|
|
1270
|
+
return { ...state, done: "cancel" };
|
|
1271
|
+
case "erase":
|
|
1272
|
+
return withQuery(state, [...state.query].slice(0, -1).join(""));
|
|
1273
|
+
case "clear":
|
|
1274
|
+
return withQuery(state, "");
|
|
1275
|
+
case "word":
|
|
1276
|
+
return withQuery(state, state.query.replace(LAST_WORD, ""));
|
|
1277
|
+
case "enter":
|
|
1278
|
+
return state.matches.length === 0 && state.marked.size === 0 ? state : { ...state, done: "accept" };
|
|
1279
|
+
case "tab": {
|
|
1280
|
+
const at = highlighted(state);
|
|
1281
|
+
if (!state.multi || at === undefined)
|
|
1282
|
+
return state;
|
|
1283
|
+
const marked = new Set(state.marked);
|
|
1284
|
+
if (!marked.delete(at))
|
|
1285
|
+
marked.add(at);
|
|
1286
|
+
return move({ ...state, marked }, 1);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
function chosen(state) {
|
|
1291
|
+
if (state.marked.size > 0)
|
|
1292
|
+
return state.rows.filter((_, index) => state.marked.has(index));
|
|
1293
|
+
const at = highlighted(state);
|
|
1294
|
+
return at === undefined ? [] : [state.rows[at]];
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// ../kit/src/picker/terminal.ts
|
|
1298
|
+
import { openSync } from "fs";
|
|
1299
|
+
import process14 from "process";
|
|
1300
|
+
import tty from "tty";
|
|
1301
|
+
|
|
1302
|
+
// ../kit/src/picker/preview.ts
|
|
1303
|
+
import process13 from "process";
|
|
1304
|
+
|
|
1305
|
+
// ../kit/src/shell.ts
|
|
1306
|
+
var SEPARATORS = new Set([`
|
|
1307
|
+
`, ";", "|"]);
|
|
1308
|
+
var PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
|
|
1309
|
+
var GLOB_CHARS = new Set(["*", "?", "["]);
|
|
1310
|
+
var COMMENT_PREFIXES = new Set([";", "&", "|", "(", ")"]);
|
|
1311
|
+
var SAFE_WORD = /^[\w@%+=:,./-]+$/;
|
|
1312
|
+
var DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
1313
|
+
var KEYWORDS = new Set(["then", "do", "else", "if", "elif", "while", "until", "!", "{", "}"]);
|
|
1314
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ash", "ksh", "fish", "csh", "tcsh", "nu", "xonsh", "elvish", "pwsh", "powershell"]);
|
|
1315
|
+
var SCANNED_PROGRAMS = new Set(["su", "runuser", "flock"]);
|
|
1316
|
+
var COMMAND_STRING_PROGRAMS = new Set([...SHELLS, ...SCANNED_PROGRAMS]);
|
|
1317
|
+
var POWERSHELL = new Set(["pwsh", "powershell"]);
|
|
1318
|
+
var SHELL_VALUE_OPTIONS = new Set(["-o", "-O", "--rcfile", "--init-file"]);
|
|
1319
|
+
var FISH_VALUE_OPTIONS = new Set(["-C", "--init-command"]);
|
|
1320
|
+
var PRINTERS = new Set(["echo", "printf"]);
|
|
1321
|
+
var FIND_EXEC = new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
1322
|
+
function wrapper(short = "", long = [], operands = 0, runsString = false) {
|
|
1323
|
+
return { short, long, operands, runsString };
|
|
1324
|
+
}
|
|
1325
|
+
var WRAPPERS = new Map([
|
|
1326
|
+
["command", wrapper()],
|
|
1327
|
+
["builtin", wrapper()],
|
|
1328
|
+
["nohup", wrapper()],
|
|
1329
|
+
["setsid", wrapper()],
|
|
1330
|
+
["busybox", wrapper()],
|
|
1331
|
+
["exec", wrapper("a")],
|
|
1332
|
+
["time", wrapper("fo", ["--format", "--output"])],
|
|
1333
|
+
["env", wrapper("uCS", ["--unset", "--chdir", "--split-string", "--argv0"])],
|
|
1334
|
+
["sudo", wrapper("ughpCTrtUDR", ["--user", "--group", "--host", "--prompt", "--close-from", "--command-timeout", "--role", "--type", "--other-user", "--chdir", "--chroot"])],
|
|
1335
|
+
["doas", wrapper("uC")],
|
|
1336
|
+
["timeout", wrapper("sk", ["--signal", "--kill-after"], 1)],
|
|
1337
|
+
["nice", wrapper("n", ["--adjustment"])],
|
|
1338
|
+
["ionice", wrapper("cnpPu", ["--class", "--classdata", "--pid", "--pgid", "--uid"])],
|
|
1339
|
+
["stdbuf", wrapper("ioe", ["--input", "--output", "--error"])],
|
|
1340
|
+
["chroot", wrapper("", ["--userspec", "--groups"], 1)],
|
|
1341
|
+
["watch", wrapper("n", ["--interval"], 0, true)],
|
|
1342
|
+
["flock", wrapper("wE", ["--timeout", "--conflict-exit-code"], 1)],
|
|
1343
|
+
["parallel", wrapper("jnNSaIPlLdTsC", ["--jobs", "--max-args", "--max-replace-args", "--sshlogin", "--arg-file", "--delimiter", "--colsep"], 0, true)],
|
|
1344
|
+
["unbuffer", wrapper()],
|
|
1345
|
+
["systemd-run", wrapper("puEMH", ["--property", "--unit", "--description", "--slice", "--setenv", "--machine", "--host", "--uid", "--gid", "--working-directory", "--nice", "--service-type"])],
|
|
1346
|
+
["xargs", wrapper("IndsEaPL", ["--max-args", "--max-procs", "--max-chars", "--delimiter", "--arg-file", "--eof", "--max-lines", "--process-slot-var"])]
|
|
1347
|
+
]);
|
|
1348
|
+
function shellQuote(word) {
|
|
1349
|
+
return SAFE_WORD.test(word) && !word.startsWith("=") ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// ../kit/src/picker/preview.ts
|
|
1353
|
+
var PREVIEW_BYTES = 64 * 1024;
|
|
1354
|
+
var PREVIEW_LINES = 200;
|
|
1355
|
+
var PREVIEW_TIMEOUT_MS = 3000;
|
|
1356
|
+
var PLACEHOLDER = /\{([1-9]\d*)?\}/g;
|
|
1357
|
+
function previewCommand(template, line, hidden = []) {
|
|
1358
|
+
return template.replace(PLACEHOLDER, (_, field) => shellQuote(field === undefined ? line : hidden[Number(field) - 1] ?? ""));
|
|
1359
|
+
}
|
|
1360
|
+
function previewLines(output) {
|
|
1361
|
+
const lines = output.split(`
|
|
1362
|
+
`);
|
|
1363
|
+
if (lines.at(-1) === "")
|
|
1364
|
+
lines.pop();
|
|
1365
|
+
return lines.slice(0, PREVIEW_LINES);
|
|
1366
|
+
}
|
|
1367
|
+
async function runPreview(template, line, signal, hidden = []) {
|
|
1368
|
+
const proc = Bun.spawn(["sh", "-c", `exec 2>&1
|
|
1369
|
+
${previewCommand(template, line, hidden)}`], { stdin: "ignore", stdout: "pipe", stderr: "ignore", detached: true });
|
|
1370
|
+
const stop = () => {
|
|
1371
|
+
try {
|
|
1372
|
+
process13.kill(-proc.pid, "SIGKILL");
|
|
1373
|
+
} catch {
|
|
1374
|
+
proc.kill();
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
const timer = setTimeout(stop, PREVIEW_TIMEOUT_MS);
|
|
1378
|
+
const chunks = [];
|
|
1379
|
+
let size = 0;
|
|
1380
|
+
signal.addEventListener("abort", stop);
|
|
1381
|
+
try {
|
|
1382
|
+
for await (const chunk of proc.stdout) {
|
|
1383
|
+
chunks.push(chunk);
|
|
1384
|
+
size += chunk.length;
|
|
1385
|
+
if (size >= PREVIEW_BYTES) {
|
|
1386
|
+
stop();
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
} finally {
|
|
1391
|
+
clearTimeout(timer);
|
|
1392
|
+
signal.removeEventListener("abort", stop);
|
|
1393
|
+
}
|
|
1394
|
+
return signal.aborted ? [] : previewLines(Buffer.concat(chunks).subarray(0, PREVIEW_BYTES).toString());
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// ../kit/src/picker/view.ts
|
|
1398
|
+
var MIN_HEIGHT = 4;
|
|
1399
|
+
var PREVIEW_MIN_COLUMNS = 80;
|
|
1400
|
+
var PREVIEW_MIN_ROWS = 10;
|
|
1401
|
+
var PREVIEW_SHARE = 0.4;
|
|
1402
|
+
var RESET2 = "\x1B[0m";
|
|
1403
|
+
var SEPARATOR = " \u2502 ";
|
|
1404
|
+
var HEIGHT = /^(\d+)(%?)$/;
|
|
1405
|
+
function parseHeight(text) {
|
|
1406
|
+
const found = HEIGHT.exec(text);
|
|
1407
|
+
if (found === null || Number(found[1]) < 1)
|
|
1408
|
+
return;
|
|
1409
|
+
return found[2] === "%" ? { percent: Math.min(100, Number(found[1])) } : { lines: Number(found[1]) };
|
|
1410
|
+
}
|
|
1411
|
+
function layoutFor(size, height, previewing, position = "right") {
|
|
1412
|
+
const wanted = "lines" in height ? height.lines : Math.round(size.rows * height.percent / 100);
|
|
1413
|
+
const rows = Math.min(size.rows, Math.max(MIN_HEIGHT, wanted));
|
|
1414
|
+
if (position === "down") {
|
|
1415
|
+
const previewHeight = previewing && rows >= PREVIEW_MIN_ROWS ? Math.round(rows * PREVIEW_SHARE) : 0;
|
|
1416
|
+
return { height: rows, listHeight: rows - previewHeight, listWidth: size.columns, previewWidth: 0, previewHeight };
|
|
1417
|
+
}
|
|
1418
|
+
const split = previewing && size.columns >= PREVIEW_MIN_COLUMNS;
|
|
1419
|
+
const listWidth = split ? Math.floor(size.columns / 2) : size.columns;
|
|
1420
|
+
return { height: rows, listHeight: rows, listWidth, previewWidth: split ? size.columns - listWidth - SEPARATOR.length : 0, previewHeight: 0 };
|
|
1421
|
+
}
|
|
1422
|
+
function fit(text, width, plain, mark = plain, marked = new Set, styles) {
|
|
1423
|
+
const room = Bun.stringWidth(text) > width ? width - 1 : width;
|
|
1424
|
+
let out = "";
|
|
1425
|
+
let used = 0;
|
|
1426
|
+
let at = 0;
|
|
1427
|
+
for (const char of text) {
|
|
1428
|
+
const size = Bun.stringWidth(char);
|
|
1429
|
+
if (used + size > room)
|
|
1430
|
+
break;
|
|
1431
|
+
const style = marked.has(at) ? "" : styles?.[at] ?? "";
|
|
1432
|
+
out += style === "" ? (marked.has(at) ? mark : plain)(char) : `${style}${char}${RESET2}`;
|
|
1433
|
+
used += size;
|
|
1434
|
+
at += char.length;
|
|
1435
|
+
}
|
|
1436
|
+
if (room < width) {
|
|
1437
|
+
out += plain("\u2026");
|
|
1438
|
+
used += 1;
|
|
1439
|
+
}
|
|
1440
|
+
return { text: out, width: used };
|
|
1441
|
+
}
|
|
1442
|
+
var identity = (text) => text;
|
|
1443
|
+
function fitStyled(line, width, plain = identity) {
|
|
1444
|
+
const { text, styles } = styled(line);
|
|
1445
|
+
return fit(text, width, plain, plain, new Set, styles);
|
|
1446
|
+
}
|
|
1447
|
+
function caretColumn(state, layout) {
|
|
1448
|
+
return Math.min(layout.listWidth, Bun.stringWidth(state.prompt) + Bun.stringWidth(state.query) + 1);
|
|
1449
|
+
}
|
|
1450
|
+
function listLines(state, layout, theme) {
|
|
1451
|
+
const header = state.header === "" ? [] : state.header.split(`
|
|
1452
|
+
`).map((line) => fitStyled(line, layout.listWidth, theme.label));
|
|
1453
|
+
const rows = Math.max(1, layout.listHeight - 2 - header.length);
|
|
1454
|
+
const prefixWidth = state.multi ? 4 : 2;
|
|
1455
|
+
const top = Math.max(0, Math.min(state.cursor - Math.floor(rows / 2), state.matches.length - rows));
|
|
1456
|
+
const marks = (state.marked.size > 0 ? ` (${state.marked.size} marked)` : "") + (state.status === "" ? "" : ` ${state.status}`);
|
|
1457
|
+
const promptWidth = Bun.stringWidth(state.prompt);
|
|
1458
|
+
const query = fit(state.query, Math.max(1, layout.listWidth - promptWidth), identity);
|
|
1459
|
+
const count = fit(` ${state.matches.length}/${state.items.length}${marks}`, layout.listWidth, theme.label);
|
|
1460
|
+
const lines = [{ text: theme.accent(state.prompt) + query.text, width: promptWidth + query.width }, count, ...header];
|
|
1461
|
+
if (state.matches.length === 0)
|
|
1462
|
+
lines.push(fit(" no match", layout.listWidth, theme.label));
|
|
1463
|
+
for (const [offset, match] of state.matches.slice(top, top + rows).entries()) {
|
|
1464
|
+
const current = top + offset === state.cursor;
|
|
1465
|
+
const pointer = current ? theme.accent("\u276F") : " ";
|
|
1466
|
+
const mark = state.multi ? `${state.marked.has(match.index) ? theme.id("\u25CF") : " "} ` : "";
|
|
1467
|
+
const text = fit(state.items[match.index], layout.listWidth - prefixWidth, current ? theme.value : identity, theme.accent, new Set(match.positions), state.styles[match.index]);
|
|
1468
|
+
lines.push({ text: `${pointer} ${mark}${text.text}`, width: prefixWidth + text.width });
|
|
1469
|
+
}
|
|
1470
|
+
return lines;
|
|
1471
|
+
}
|
|
1472
|
+
function render(state, layout, theme, preview = []) {
|
|
1473
|
+
const left = listLines(state, layout, theme);
|
|
1474
|
+
return Array.from({ length: layout.height }, (_, row) => {
|
|
1475
|
+
if (row >= layout.listHeight)
|
|
1476
|
+
return row === layout.listHeight ? theme.label("\u2500".repeat(layout.listWidth)) : fitStyled(preview[row - layout.listHeight - 1] ?? "", layout.listWidth).text;
|
|
1477
|
+
const list = left[row] ?? { text: "", width: 0 };
|
|
1478
|
+
if (layout.previewWidth === 0)
|
|
1479
|
+
return list.text;
|
|
1480
|
+
return `${list.text}${" ".repeat(Math.max(0, layout.listWidth - list.width))}${theme.label(SEPARATOR)}${fitStyled(preview[row] ?? "", layout.previewWidth).text}`;
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// ../kit/src/picker/terminal.ts
|
|
1485
|
+
var PREVIEW_DELAY_MS = 120;
|
|
1486
|
+
var OPEN = { ctrl: "open" };
|
|
1487
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
1488
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
1489
|
+
var PASTE_ON = "\x1B[?2004h";
|
|
1490
|
+
var PASTE_OFF = "\x1B[?2004l";
|
|
1491
|
+
var CLEAR_BELOW = "\r\x1B[J";
|
|
1492
|
+
var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
1493
|
+
function up(lines) {
|
|
1494
|
+
return lines > 0 ? `\x1B[${lines}A` : "";
|
|
1495
|
+
}
|
|
1496
|
+
function openTerminal() {
|
|
1497
|
+
let input;
|
|
1498
|
+
try {
|
|
1499
|
+
input = new tty.ReadStream(openSync("/dev/tty", "r"));
|
|
1500
|
+
} catch {
|
|
1501
|
+
return fail("no terminal to read keys from (/dev/tty)");
|
|
1502
|
+
}
|
|
1503
|
+
const out = process14.stderr;
|
|
1504
|
+
let resize;
|
|
1505
|
+
return {
|
|
1506
|
+
get columns() {
|
|
1507
|
+
return out.columns || 80;
|
|
1508
|
+
},
|
|
1509
|
+
get rows() {
|
|
1510
|
+
return out.rows || 24;
|
|
1511
|
+
},
|
|
1512
|
+
theme: { accent: role("accent", out), label: role("label", out), value: role("value", out), id: role("id", out) },
|
|
1513
|
+
write: (text) => void out.write(text),
|
|
1514
|
+
listen(onChunk, onResize) {
|
|
1515
|
+
resize = onResize;
|
|
1516
|
+
input.setRawMode(true);
|
|
1517
|
+
out.on("resize", onResize);
|
|
1518
|
+
input.on("data", (chunk) => onChunk(chunk.toString()));
|
|
1519
|
+
},
|
|
1520
|
+
close() {
|
|
1521
|
+
if (resize !== undefined)
|
|
1522
|
+
out.off("resize", resize);
|
|
1523
|
+
try {
|
|
1524
|
+
input.setRawMode(false);
|
|
1525
|
+
} finally {
|
|
1526
|
+
input.destroy();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
async function select(rows, settings, terminal = openTerminal()) {
|
|
1532
|
+
const { theme } = terminal;
|
|
1533
|
+
let state = createState(rows, settings);
|
|
1534
|
+
let preview = [];
|
|
1535
|
+
let reserved = 0;
|
|
1536
|
+
let previewTimer;
|
|
1537
|
+
let previewing = new AbortController;
|
|
1538
|
+
let shownLine;
|
|
1539
|
+
let finish;
|
|
1540
|
+
let restored = false;
|
|
1541
|
+
function draw(clear = false) {
|
|
1542
|
+
if (restored)
|
|
1543
|
+
return;
|
|
1544
|
+
const layout = layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition);
|
|
1545
|
+
let frame = HIDE_CURSOR;
|
|
1546
|
+
if (reserved !== layout.height || clear) {
|
|
1547
|
+
frame += CLEAR_BELOW + `
|
|
1548
|
+
`.repeat(layout.height - 1) + up(layout.height - 1);
|
|
1549
|
+
reserved = layout.height;
|
|
1550
|
+
}
|
|
1551
|
+
frame += `\r${render(state, layout, theme, preview).map((line) => `\x1B[2K${line}`).join(`
|
|
1552
|
+
`)}${up(layout.height - 1)}\r\x1B[${caretColumn(state, layout)}G${SHOW_CURSOR}`;
|
|
1553
|
+
terminal.write(frame);
|
|
1554
|
+
}
|
|
1555
|
+
function refreshPreview() {
|
|
1556
|
+
const at = highlighted(state);
|
|
1557
|
+
if (settings.preview === undefined || at === shownLine)
|
|
1558
|
+
return;
|
|
1559
|
+
shownLine = at;
|
|
1560
|
+
clearTimeout(previewTimer);
|
|
1561
|
+
previewing.abort();
|
|
1562
|
+
previewing = new AbortController;
|
|
1563
|
+
if (at === undefined) {
|
|
1564
|
+
preview = [];
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
const { signal } = previewing;
|
|
1568
|
+
const template = settings.preview;
|
|
1569
|
+
const row = state.rows[at];
|
|
1570
|
+
async function show() {
|
|
1571
|
+
const shown = typeof template === "string" ? await runPreview(template, row.cells.join(" "), signal, row.hidden) : await previewOf(template, row.hidden, signal);
|
|
1572
|
+
if (!signal.aborted) {
|
|
1573
|
+
preview = shown;
|
|
1574
|
+
draw();
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
previewTimer = setTimeout(() => void show(), PREVIEW_DELAY_MS);
|
|
1578
|
+
}
|
|
1579
|
+
function restore() {
|
|
1580
|
+
if (restored)
|
|
1581
|
+
return;
|
|
1582
|
+
restored = true;
|
|
1583
|
+
clearTimeout(previewTimer);
|
|
1584
|
+
previewing.abort();
|
|
1585
|
+
terminal.write(`${CLEAR_BELOW}${PASTE_OFF}${SHOW_CURSOR}`);
|
|
1586
|
+
terminal.close();
|
|
1587
|
+
}
|
|
1588
|
+
const stop = () => {
|
|
1589
|
+
state = { ...state, done: "cancel" };
|
|
1590
|
+
finish?.();
|
|
1591
|
+
};
|
|
1592
|
+
const paste = { active: false };
|
|
1593
|
+
const { reload } = settings;
|
|
1594
|
+
const pending = [];
|
|
1595
|
+
let busy = false;
|
|
1596
|
+
let draining = false;
|
|
1597
|
+
async function reloadRows() {
|
|
1598
|
+
if (reload === undefined)
|
|
1599
|
+
return;
|
|
1600
|
+
busy = true;
|
|
1601
|
+
state = { ...state, status: "reloading" };
|
|
1602
|
+
draw();
|
|
1603
|
+
try {
|
|
1604
|
+
const next = await reload.run();
|
|
1605
|
+
state = { ...withRows(state, next.rows, next.header), status: "" };
|
|
1606
|
+
shownLine = undefined;
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
state = { ...state, status: `reload failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
1609
|
+
} finally {
|
|
1610
|
+
busy = false;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
async function drain() {
|
|
1614
|
+
if (draining)
|
|
1615
|
+
return;
|
|
1616
|
+
draining = true;
|
|
1617
|
+
try {
|
|
1618
|
+
for (let key = pending.shift();key !== undefined && state.done === undefined; key = pending.shift()) {
|
|
1619
|
+
if (key === OPEN || typeof key === "object" && "ctrl" in key && key.ctrl === reload?.key)
|
|
1620
|
+
await reloadRows();
|
|
1621
|
+
else
|
|
1622
|
+
state = press(state, key);
|
|
1623
|
+
}
|
|
1624
|
+
} finally {
|
|
1625
|
+
draining = false;
|
|
1626
|
+
}
|
|
1627
|
+
if (state.done !== undefined) {
|
|
1628
|
+
finish?.();
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
refreshPreview();
|
|
1632
|
+
draw();
|
|
1633
|
+
}
|
|
1634
|
+
function onChunk(chunk) {
|
|
1635
|
+
const keys = parseKeys(chunk, paste, [...settings.expect ?? [], ...reload?.key === undefined ? [] : [reload.key]]);
|
|
1636
|
+
if (busy && keys.includes("cancel")) {
|
|
1637
|
+
stop();
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
pending.push(...keys);
|
|
1641
|
+
drain();
|
|
1642
|
+
}
|
|
1643
|
+
const ended = new Promise((resolve) => {
|
|
1644
|
+
finish = resolve;
|
|
1645
|
+
});
|
|
1646
|
+
process14.once("exit", restore);
|
|
1647
|
+
for (const signal of SIGNALS)
|
|
1648
|
+
process14.once(signal, stop);
|
|
1649
|
+
try {
|
|
1650
|
+
terminal.write(PASTE_ON);
|
|
1651
|
+
terminal.listen(onChunk, () => draw(true));
|
|
1652
|
+
refreshPreview();
|
|
1653
|
+
draw(true);
|
|
1654
|
+
if (reload?.onOpen === true) {
|
|
1655
|
+
pending.push(OPEN);
|
|
1656
|
+
drain();
|
|
1657
|
+
}
|
|
1658
|
+
await ended;
|
|
1659
|
+
} finally {
|
|
1660
|
+
for (const signal of SIGNALS)
|
|
1661
|
+
process14.off(signal, stop);
|
|
1662
|
+
process14.off("exit", restore);
|
|
1663
|
+
restore();
|
|
1664
|
+
}
|
|
1665
|
+
return state.done === "accept" ? { rows: chosen(state), key: state.key, query: state.query } : undefined;
|
|
1666
|
+
}
|
|
1667
|
+
async function previewOf(preview, hidden, signal) {
|
|
1668
|
+
try {
|
|
1669
|
+
return previewLines(await preview(hidden, signal));
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// ../kit/src/picker.ts
|
|
822
1676
|
var REMEMBERED = 20;
|
|
823
1677
|
function table(rows) {
|
|
824
1678
|
const widths = rows[0]?.cells.map((_, index) => Math.max(...rows.map((row) => Bun.stringWidth(row.cells[index])))) ?? [];
|
|
@@ -827,70 +1681,40 @@ function table(rows) {
|
|
|
827
1681
|
...row.cells.map((cell, index) => index === row.cells.length - 1 ? cell.trimEnd() : `${cell}${" ".repeat(widths[index] + 1 - Bun.stringWidth(cell))}`)
|
|
828
1682
|
].join("\t"));
|
|
829
1683
|
}
|
|
830
|
-
function
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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;
|
|
1684
|
+
async function pick(rows, options) {
|
|
1685
|
+
const height = parseHeight(options.height ?? "100%");
|
|
1686
|
+
const keys = [...options.expect ?? [], ...options.reload?.key === undefined ? [] : [options.reload.key]];
|
|
1687
|
+
if (height === undefined)
|
|
1688
|
+
throw new Error(`pick: the height is lines or a percentage, not ${options.height}`);
|
|
1689
|
+
for (const key of keys) {
|
|
1690
|
+
if (!CTRL_KEY.test(key))
|
|
1691
|
+
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`);
|
|
1692
|
+
}
|
|
1693
|
+
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 });
|
|
1694
|
+
return found === undefined ? undefined : { key: found.key, hidden: found.rows[0]?.hidden ?? [], query: found.query };
|
|
871
1695
|
}
|
|
872
1696
|
function heightFor(count) {
|
|
873
1697
|
return String(Math.max(15, Math.ceil((count + 4) / 0.6)));
|
|
874
1698
|
}
|
|
875
1699
|
function historyFile(name) {
|
|
876
|
-
return
|
|
1700
|
+
return join6(process15.env.XDG_CACHE_HOME ?? join6(homedir4(), ".cache"), name, "history.json");
|
|
877
1701
|
}
|
|
878
1702
|
function history(name) {
|
|
879
1703
|
try {
|
|
880
1704
|
const file = historyFile(name);
|
|
881
|
-
return
|
|
1705
|
+
return existsSync3(file) ? JSON.parse(readFileSync3(file, "utf-8")) : {};
|
|
882
1706
|
} catch {
|
|
883
1707
|
return {};
|
|
884
1708
|
}
|
|
885
1709
|
}
|
|
886
1710
|
function runs(name) {
|
|
887
|
-
return history(name)[
|
|
1711
|
+
return history(name)[process15.cwd()] ?? [];
|
|
888
1712
|
}
|
|
889
1713
|
function record(name, run) {
|
|
890
1714
|
const all = history(name);
|
|
891
1715
|
const file = historyFile(name);
|
|
892
|
-
all[
|
|
893
|
-
mkdirSync2(
|
|
1716
|
+
all[process15.cwd()] = [run, ...(all[process15.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
|
|
1717
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
894
1718
|
writeFileSync(file, JSON.stringify(all));
|
|
895
1719
|
}
|
|
896
1720
|
function byRecency(items, previous, idOf) {
|
|
@@ -913,12 +1737,12 @@ function list(rows) {
|
|
|
913
1737
|
}
|
|
914
1738
|
|
|
915
1739
|
// ../kit/src/prompt.ts
|
|
916
|
-
import
|
|
917
|
-
var
|
|
918
|
-
var
|
|
919
|
-
var
|
|
1740
|
+
import process16 from "process";
|
|
1741
|
+
var KEY_SEQUENCES2 = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
|
|
1742
|
+
var CONTROL3 = /^[\x00-\x1F\x7F]/;
|
|
1743
|
+
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
1744
|
function readKeys(input) {
|
|
921
|
-
return (input.match(
|
|
1745
|
+
return (input.match(KEY_SEQUENCES2) ?? []).flatMap((sequence) => KEYS2[sequence] ?? (CONTROL3.test(sequence) ? [] : [{ text: sequence }]));
|
|
922
1746
|
}
|
|
923
1747
|
async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
|
|
924
1748
|
let value = "";
|
|
@@ -952,9 +1776,9 @@ async function input2(question, { default: fallback, mask = false, validate, ...
|
|
|
952
1776
|
function answer(colors, question, state, picked) {
|
|
953
1777
|
return state === "enter" ? `${colors.green("\u2713")} ${question} ${colors.cyan(picked)}` : `${colors.red("\u2717")} ${question} ${colors.dim("cancelled")}`;
|
|
954
1778
|
}
|
|
955
|
-
async function ask(question, { hint, terminal =
|
|
1779
|
+
async function ask(question, { hint, terminal = process16 }, render, press) {
|
|
956
1780
|
const { stdin, stderr } = terminal;
|
|
957
|
-
if (
|
|
1781
|
+
if (!isInteractive(terminal))
|
|
958
1782
|
fail(`cannot ask "${question}" off a terminal`, 2, hint);
|
|
959
1783
|
const colors = paint(stderr);
|
|
960
1784
|
let drawn = 0;
|
|
@@ -1003,12 +1827,12 @@ async function ask(question, { hint, terminal = process9 }, render, press) {
|
|
|
1003
1827
|
}
|
|
1004
1828
|
|
|
1005
1829
|
// src/stack.ts
|
|
1006
|
-
import { existsSync as
|
|
1007
|
-
import { dirname as
|
|
1830
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4 } from "fs";
|
|
1831
|
+
import { dirname as dirname6, join as join8, relative, resolve as resolve3 } from "path";
|
|
1008
1832
|
|
|
1009
1833
|
// ../env/src/files.ts
|
|
1010
1834
|
import { chmod, chown, readdir, realpath, rename, rm, stat, writeFile } from "fs/promises";
|
|
1011
|
-
import { basename, dirname as
|
|
1835
|
+
import { basename, dirname as dirname5, join as join7 } from "path";
|
|
1012
1836
|
|
|
1013
1837
|
// ../env/src/dotenv.ts
|
|
1014
1838
|
var ASSIGNMENT = /^\s*(?:export\s+)?([A-Z_][\w.-]*)\s*=(.*)$/i;
|
|
@@ -1088,12 +1912,12 @@ function entries(file) {
|
|
|
1088
1912
|
var MIN_KNOWN_LENGTH = 4;
|
|
1089
1913
|
var KEY_MASK = "*** private key redacted ***";
|
|
1090
1914
|
var PLAIN_VALUE = /^(?:true|false|null|none|nil|yes|no|on|off|undefined)$/i;
|
|
1091
|
-
var
|
|
1915
|
+
var PLACEHOLDER2 = /^[$%{<(*/]/;
|
|
1092
1916
|
var URL_PASSWORD = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/[^\s:/@]*:([^\s'"]{1,1024})@/dgi;
|
|
1093
1917
|
var PORT = /^\d{1,5}$/;
|
|
1094
1918
|
var ESCAPE2 = /[.*+?^${}()|[\]\\]/g;
|
|
1095
1919
|
function isPlaceholder(value) {
|
|
1096
|
-
return
|
|
1920
|
+
return PLACEHOLDER2.test(value) || PLAIN_VALUE.test(value);
|
|
1097
1921
|
}
|
|
1098
1922
|
var TOKEN_FORMATS = [
|
|
1099
1923
|
{ kind: "GitHub token", pattern: /\b(?:gh[pousr]_|github_pat_)\w{20,}/dg },
|
|
@@ -1279,7 +2103,7 @@ async function envFilesBelow(folder, depth = 0) {
|
|
|
1279
2103
|
return found;
|
|
1280
2104
|
}
|
|
1281
2105
|
for (const entry of children) {
|
|
1282
|
-
const path =
|
|
2106
|
+
const path = join7(folder, entry.name);
|
|
1283
2107
|
if (entry.isDirectory() && depth < KNOWN_DEPTH && !SKIPPED.has(entry.name))
|
|
1284
2108
|
found.push(...await envFilesBelow(path, depth + 1));
|
|
1285
2109
|
else if (ENV_FILE.test(entry.name) && await isFileEntry(entry, path))
|
|
@@ -1368,29 +2192,29 @@ function from(dir, command) {
|
|
|
1368
2192
|
function children(dir = ".") {
|
|
1369
2193
|
return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
|
|
1370
2194
|
}
|
|
1371
|
-
function
|
|
2195
|
+
function up2(dir) {
|
|
1372
2196
|
const dirs = [];
|
|
1373
|
-
for (let current = dir;; current =
|
|
2197
|
+
for (let current = dir;; current = join8(current, "..")) {
|
|
1374
2198
|
dirs.push(current);
|
|
1375
|
-
const absolute =
|
|
1376
|
-
if (
|
|
2199
|
+
const absolute = resolve3(current);
|
|
2200
|
+
if (existsSync4(join8(current, ".git")) || dirname6(absolute) === absolute)
|
|
1377
2201
|
return dirs;
|
|
1378
2202
|
}
|
|
1379
2203
|
}
|
|
1380
2204
|
function stackAt(dir) {
|
|
1381
|
-
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd:
|
|
2205
|
+
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve3(dir), env: process.env } };
|
|
1382
2206
|
}
|
|
1383
2207
|
function holdsStack(dir) {
|
|
1384
|
-
if (COMPOSE_FILES.some((name) =>
|
|
2208
|
+
if (COMPOSE_FILES.some((name) => existsSync4(join8(dir, name))))
|
|
1385
2209
|
return true;
|
|
1386
2210
|
try {
|
|
1387
|
-
return COMPOSE_FILE_VAR.test(
|
|
2211
|
+
return COMPOSE_FILE_VAR.test(readFileSync4(join8(dir, ".env"), "utf-8"));
|
|
1388
2212
|
} catch {
|
|
1389
2213
|
return false;
|
|
1390
2214
|
}
|
|
1391
2215
|
}
|
|
1392
2216
|
function nearestStack(dir) {
|
|
1393
|
-
const found =
|
|
2217
|
+
const found = up2(dir).find(holdsStack);
|
|
1394
2218
|
return found === undefined ? undefined : stackAt(found);
|
|
1395
2219
|
}
|
|
1396
2220
|
function stacks() {
|
|
@@ -1413,7 +2237,7 @@ async function labels(project) {
|
|
|
1413
2237
|
const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
|
|
1414
2238
|
if (await proc.exited !== 0)
|
|
1415
2239
|
return [];
|
|
1416
|
-
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) =>
|
|
2240
|
+
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync4(file)));
|
|
1417
2241
|
}
|
|
1418
2242
|
function labelledStack(dir, { project, files, envFiles, workdir }) {
|
|
1419
2243
|
const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
|
|
@@ -1428,7 +2252,7 @@ function stackOf(recorded) {
|
|
|
1428
2252
|
return labelledStack(relative(".", recorded.workdir) || ".", recorded);
|
|
1429
2253
|
}
|
|
1430
2254
|
async function recordedNearby() {
|
|
1431
|
-
const dirs = new Set([...
|
|
2255
|
+
const dirs = new Set([...up2("."), ...children()].map((dir) => resolve3(dir)));
|
|
1432
2256
|
return (await labels()).filter((stack) => dirs.has(stack.workdir));
|
|
1433
2257
|
}
|
|
1434
2258
|
async function attempt(stack, args) {
|
|
@@ -1522,7 +2346,7 @@ async function logsOf(stack, name) {
|
|
|
1522
2346
|
var usage2 = `sf [command] [args...]
|
|
1523
2347
|
sf -
|
|
1524
2348
|
|
|
1525
|
-
Without a terminal
|
|
2349
|
+
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
2350
|
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
2351
|
\`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
2352
|
\`sf --list --json\` (or \`sf --json\`) prints every command as JSON, each with its console folder, description, signature, arguments and options.
|
|
@@ -1549,19 +2373,19 @@ async function consoleAt(dir) {
|
|
|
1549
2373
|
const php = stack === undefined ? undefined : phpService((await services(stack)).map((service) => service.name));
|
|
1550
2374
|
if (stack !== undefined && php !== undefined)
|
|
1551
2375
|
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:
|
|
2376
|
+
return { dir, run: from(dir, ["php", "bin/console"]), list: { argv: ["php", "bin/console"], cwd: resolve4(dir), env: process17.env } };
|
|
1553
2377
|
}
|
|
1554
2378
|
async function consoles() {
|
|
1555
|
-
const here =
|
|
1556
|
-
const dirs = [...here === undefined ? [] : [here], ...children().filter((dir) =>
|
|
2379
|
+
const here = up2(".").find((dir) => existsSync5(join9(dir, "bin/console")));
|
|
2380
|
+
const dirs = [...here === undefined ? [] : [here], ...children().filter((dir) => existsSync5(join9(dir, "bin/console")))];
|
|
1557
2381
|
return Promise.all([...new Set(dirs)].map(consoleAt));
|
|
1558
2382
|
}
|
|
1559
2383
|
function cached(dir) {
|
|
1560
|
-
return
|
|
2384
|
+
return join9(process17.env.XDG_CACHE_HOME ?? join9(homedir5(), ".cache"), "sf", `${resolve4(dir).replaceAll("/", "%")}.json`);
|
|
1561
2385
|
}
|
|
1562
2386
|
function fromCache(dir) {
|
|
1563
2387
|
try {
|
|
1564
|
-
return
|
|
2388
|
+
return existsSync5(cached(dir)) ? JSON.parse(readFileSync5(cached(dir), "utf-8")) : undefined;
|
|
1565
2389
|
} catch {
|
|
1566
2390
|
return;
|
|
1567
2391
|
}
|
|
@@ -1577,7 +2401,7 @@ async function live(app) {
|
|
|
1577
2401
|
} catch {
|
|
1578
2402
|
return fromCache(app.dir) ?? [];
|
|
1579
2403
|
}
|
|
1580
|
-
mkdirSync3(
|
|
2404
|
+
mkdirSync3(dirname7(cached(app.dir)), { recursive: true });
|
|
1581
2405
|
writeFileSync2(cached(app.dir), JSON.stringify(commands));
|
|
1582
2406
|
return commands;
|
|
1583
2407
|
}
|
|
@@ -1651,7 +2475,7 @@ async function answered(command, ask) {
|
|
|
1651
2475
|
return { argv, given };
|
|
1652
2476
|
}
|
|
1653
2477
|
function interactive() {
|
|
1654
|
-
return
|
|
2478
|
+
return isInteractive();
|
|
1655
2479
|
}
|
|
1656
2480
|
async function execute(emitting, typed, recorded, command) {
|
|
1657
2481
|
if (emitting) {
|
|
@@ -1698,7 +2522,7 @@ async function hidden(flag, rest) {
|
|
|
1698
2522
|
return 0;
|
|
1699
2523
|
}
|
|
1700
2524
|
if (refreshed)
|
|
1701
|
-
Bun.spawn([
|
|
2525
|
+
Bun.spawn([process17.execPath, process17.argv[1], "--rows"], { stdio: ["ignore", "ignore", "ignore"] }).unref();
|
|
1702
2526
|
list(byRecency(found, runs("sf"), key2).map((entry) => ({
|
|
1703
2527
|
label: [key2(entry), signature(entry.command)].filter(Boolean).join(" "),
|
|
1704
2528
|
hint: entry.command.description,
|
|
@@ -1749,24 +2573,21 @@ async function sf(args) {
|
|
|
1749
2573
|
}
|
|
1750
2574
|
if (found.length === 0)
|
|
1751
2575
|
fail("no bin/console here, or it listed no command", 1);
|
|
1752
|
-
if (!interactive()
|
|
2576
|
+
if (!interactive()) {
|
|
1753
2577
|
if (emitting)
|
|
1754
|
-
fail("the picker needs a terminal
|
|
1755
|
-
if (interactive())
|
|
1756
|
-
console.error("fzf is not installed, listing the commands instead");
|
|
2578
|
+
fail("the picker needs a terminal", 1);
|
|
1757
2579
|
console.log(listing(found));
|
|
1758
2580
|
return 0;
|
|
1759
2581
|
}
|
|
1760
|
-
const
|
|
1761
|
-
const picked = fzf(rows(found), {
|
|
2582
|
+
const picked = await pick(rows(found), {
|
|
1762
2583
|
prompt: "sf> ",
|
|
1763
2584
|
searched: 1,
|
|
1764
|
-
preview: `${
|
|
1765
|
-
|
|
2585
|
+
preview: `${process17.execPath} ${process17.argv[1]} --show {1} {2}`,
|
|
2586
|
+
reload: refreshed ? { onOpen: true, run: async () => ({ rows: rows(await entries2(true)) }) } : undefined
|
|
1766
2587
|
});
|
|
1767
2588
|
const entry = picked && (await entries2(false)).find((candidate) => candidate.app.dir === picked.hidden[0] && candidate.command.name === picked.hidden[1]);
|
|
1768
2589
|
return entry === undefined ? 1 : execute(emitting, key2(entry), key2(entry), commandFor2(entry, []));
|
|
1769
2590
|
}
|
|
1770
2591
|
|
|
1771
2592
|
// src/sf-cli.ts
|
|
1772
|
-
|
|
2593
|
+
process18.exitCode = await runMain({ name: "sf", run: sf });
|