@kaynooo/cli-out 0.6.0 → 0.7.1

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +368 -176
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -11,13 +11,13 @@ out --stats --since 7d
11
11
 
12
12
  ## Behaviour
13
13
 
14
- - Output of at most 150 lines (`-n <lines>` changes it) passes through byte for byte, stdout and stderr to their own streams.
14
+ - Output of at most 150 lines (`-n <lines>` changes it) and about 8 KB (55 bytes per allowed line) passes through byte for byte, stdout and stderr to their own streams.
15
15
  - Longer output is captured (both streams, in arrival order) and printed folded, after a header with the line count, how many lines were left out and the path of the full log.
16
16
  - Lines alike in shape (ANSI codes stripped; numbers, hashes, durations, percentages, paths and file names normalized) fold into one line with a count and the last line: `[1/300] compiling a.ts (x300, last: [300/300] compiling z.ts)`.
17
17
  - A block of 2 to 20 lines repeated back to back is shown once, followed by `(block repeated N times)`.
18
18
  - A line or block seen earlier is replaced by `(same as line N)`, N being a line of the full log, and the header lists the most repeated messages with their counts.
19
19
  - Carriage-return progress (spinners, bars) keeps only the final state of each line.
20
- - The head and tail are kept, and lines with `error`, `fail`, `warn`, `panic`, `exception`, `fatal` or a stack frame stay even in the folded middle.
20
+ - The head and tail are kept, and lines with `error`, `fail`, `warn`, `panic`, `exception`, `fatal` or a stack frame stay even in the folded middle, the first 40 of them. Lines wider than 300 characters are cut in the folded output (the full log keeps them), and a few-lines output that is still heavy keeps a shorter head and tail.
21
21
  - The exit code is always the wrapped command's. Piped into with no command, `out` folds what it reads and exits 0, the writer's code left to `set -o pipefail`; only stdout is piped, so `2>&1` brings stderr along. A terminal or `/dev/null` on stdin with no command is a usage error.
22
22
  - The output is streamed to a full log as it arrives once it outgrows the 4 MiB held in memory (the first and last 2 MiB), otherwise the log is written when folding. Logs go to `$XDG_RUNTIME_DIR/kaynooo-cli/out` (`~/.cache/kaynooo-cli/out` without it), a folder only the user can enter, files at mode 0600, and logs older than a day are removed on the next run.
23
23
  - SIGINT, SIGTERM and SIGHUP go to the command's whole process group, and `out` exits with its code. A watch command (`--watch`, `tsc -w`, `just dev`) is never wrapped by the hook, as its output would sit buffered.
@@ -26,7 +26,7 @@ out --stats --since 7d
26
26
 
27
27
  - Each run appends a line to `$XDG_STATE_HOME/kaynooo-cli/out/stats.jsonl` (`~/.local/state/...` without it, folder 0700, file 0600): the time, the folder, the words naming the command, the bytes it printed, the bytes the session would have read without `out` and the bytes `out` showed. Only lowercase words without digits after the program are kept (`bun run build`, `docker compose exec`), so no argument, assignment or token reaches the file; a `sh -c` chain is named by its last command that is not a `cd`.
28
28
  - `--was '<stage>'` (repeatable, in pipeline order) names a `tail`, `head` or `grep` stage `out` stands in for, as the hook passes them when it drops one: the session would have read what that stage printed, so the saving counts against it. Output that folding would not make shorter is printed untouched, so `out` never shows more than the command printed. A stage it cannot read (`tail -f`, a long grep option it does not know) counts the whole output.
29
- - `out --stats [--since 7d] [--json]` prints the runs, how many were folded, the bytes printed and shown, and about how many tokens were saved (bytes / 4, an estimate), then the ten commands that saved the most.
29
+ - `out --stats [--since 7d] [--json]` prints the runs, how many were folded, the bytes printed and shown, and about how many tokens were saved (bytes / 4, an estimate), then the ten commands that saved the most, with the share of runs folded and, per command, the median share of the output a folded run cut.
30
30
 
31
31
  ## Wrapping from a hook
32
32
 
package/dist/cli.js CHANGED
@@ -2,11 +2,10 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli.ts
5
- import process11 from "process";
5
+ import process13 from "process";
6
6
 
7
7
  // ../kit/src/args.ts
8
- import { fstatSync } from "fs";
9
- import process3 from "process";
8
+ import process4 from "process";
10
9
  import { parseArgs } from "util";
11
10
 
12
11
  // ../kit/src/format.ts
@@ -15,7 +14,7 @@ import process2 from "process";
15
14
  // ../kit/src/style.ts
16
15
  import process from "process";
17
16
 
18
- // ../../node_modules/.bun/@kaynooo+utils@2.31.0+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
17
+ // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
19
18
  function x(s) {
20
19
  let r = s.startsWith("#") ? s.slice(1).toLowerCase() : s.toLowerCase();
21
20
  if (!/^[a-f0-9]+$/.test(r))
@@ -235,7 +234,7 @@ class e {
235
234
  }
236
235
  }
237
236
 
238
- // ../../node_modules/.bun/@kaynooo+utils@2.31.0+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
237
+ // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
239
238
  var r = 39;
240
239
  var o = 49;
241
240
  var f2 = "\x1B[";
@@ -344,6 +343,55 @@ function printJson(value) {
344
343
  console.log(JSON.stringify(value, undefined, 2));
345
344
  }
346
345
 
346
+ // ../kit/src/help.ts
347
+ import process3 from "process";
348
+ var OPTIONAL = /\[[^\]]+\]/g;
349
+ var USAGE_PREFIX = /^usage:\s*/i;
350
+ var FULL_STOP = /\.$/;
351
+ function usageLine(usage, colors = paint(process3.stdout)) {
352
+ return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
353
+ }
354
+ function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
355
+ return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
356
+ `);
357
+ }
358
+ function replacesRows(replaces, colors = paint(process3.stdout)) {
359
+ return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
360
+ }
361
+ function wantsHelpJson(env = process3.env) {
362
+ return env.KIT_HELP === "json";
363
+ }
364
+ function description(usage, { examples = [], replaces } = {}) {
365
+ const lines = usage.replace(USAGE_PREFIX, "").split(`
366
+ `);
367
+ const first = lines[0];
368
+ const colon = first.indexOf(": ");
369
+ const prose = lines[lines.indexOf("") + 1] ?? "";
370
+ const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
371
+ return {
372
+ synopsis: colon === -1 ? first : first.slice(0, colon),
373
+ summary: colon === -1 ? fallback : first.slice(colon + 2),
374
+ ...replaces === undefined ? {} : { replaces },
375
+ examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
376
+ };
377
+ }
378
+ function exampleRows(examples, colors = paint(process3.stdout)) {
379
+ if (examples.length === 0)
380
+ return [];
381
+ return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
382
+ }
383
+ function optionRows(options, colors = paint(process3.stdout)) {
384
+ const rows = Object.entries(options).map(([name, option]) => {
385
+ const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
386
+ const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
387
+ const placeholder = option.optional === undefined ? named : `[${named}]`;
388
+ const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
389
+ return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
390
+ });
391
+ const width = Math.max(0, ...rows.map((row) => row.width));
392
+ return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
393
+ }
394
+
347
395
  // ../kit/src/text.ts
348
396
  var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
349
397
  function stripAnsi(text) {
@@ -366,8 +414,35 @@ function closest(name, names) {
366
414
  const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
367
415
  return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
368
416
  }
417
+ // ../kit/src/input.ts
418
+ import { fstatSync } from "fs";
419
+ var WHOLE = /^\d+$/;
420
+ function wholeNumber(value, name, fallback, min = 1) {
421
+ if (value === undefined)
422
+ return fallback;
423
+ const number = Number(value);
424
+ if (!WHOLE.test(value) || !Number.isSafeInteger(number) || number < min)
425
+ badUsage(`${name.startsWith("<") ? name : `--${name}`} takes a whole number from ${min}, not ${value}`);
426
+ return number;
427
+ }
428
+ var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
429
+ var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
430
+ function duration(value, name, fallback, unit = "s") {
431
+ if (value === undefined)
432
+ return fallback;
433
+ const match = DURATION.exec(value.trim());
434
+ const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
435
+ if (ms < 1)
436
+ badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
437
+ return ms;
438
+ }
439
+ function isStdinPiped() {
440
+ const stat = fstatSync(0);
441
+ return stat.isFIFO() || stat.isFile();
442
+ }
369
443
 
370
444
  // ../kit/src/args.ts
445
+ var DASHES = /^-+/;
371
446
  function jsonOption(holds) {
372
447
  return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
373
448
  }
@@ -402,61 +477,20 @@ class HelpShown extends Exit {
402
477
 
403
478
  class Completing {
404
479
  options;
405
- constructor(options) {
480
+ positionals;
481
+ candidates;
482
+ constructor(options, positionals = [], candidates = {}) {
406
483
  this.options = options;
484
+ this.positionals = positionals;
485
+ this.candidates = candidates;
407
486
  }
408
487
  }
409
488
  var completion = { active: false };
410
- var OPTIONAL = /\[[^\]]+\]/g;
411
- var USAGE_PREFIX = /^usage:\s*/i;
412
- var FULL_STOP = /\.$/;
413
- var DASHES = /^-+/;
414
- function usageLine(usage, colors = paint(process3.stdout)) {
415
- return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
416
- }
417
- function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
418
- return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
419
- `);
420
- }
421
- function replacesRows(replaces, colors = paint(process3.stdout)) {
422
- return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
423
- }
424
- function wantsHelpJson(env = process3.env) {
425
- return env.KIT_HELP === "json";
426
- }
427
- function description(usage, { examples = [], replaces } = {}) {
428
- const lines = usage.replace(USAGE_PREFIX, "").split(`
429
- `);
430
- const first = lines[0];
431
- const colon = first.indexOf(": ");
432
- const prose = lines[lines.indexOf("") + 1] ?? "";
433
- const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
434
- return {
435
- synopsis: colon === -1 ? first : first.slice(0, colon),
436
- summary: colon === -1 ? fallback : first.slice(colon + 2),
437
- ...replaces === undefined ? {} : { replaces },
438
- examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
439
- };
440
- }
441
- function exampleRows(examples, colors = paint(process3.stdout)) {
442
- if (examples.length === 0)
443
- return [];
444
- return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
445
- }
446
- function optionRows(options, colors = paint(process3.stdout)) {
447
- const rows = Object.entries(options).map(([name, option]) => {
448
- const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
449
- const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
450
- return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description ?? "" };
451
- });
452
- const width = Math.max(0, ...rows.map((row) => row.width));
453
- return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
454
- }
455
489
  var OUTPUT_OPTIONS = {
456
490
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
457
491
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
458
492
  };
459
- function applyOutput(color, plain, env = process3.env) {
493
+ function applyOutput(color, plain, env = process4.env) {
460
494
  const when = plain === true ? "never" : color ?? "auto";
461
495
  if (when === "auto")
462
496
  return;
@@ -473,14 +507,13 @@ function applyOutput(color, plain, env = process3.env) {
473
507
  function parse(args, usage, options, settings = {}) {
474
508
  const output = Object.fromEntries(Object.entries(OUTPUT_OPTIONS).filter(([name]) => !(name in options)));
475
509
  const all = { ...options, ...output, help: { type: "boolean", short: "h", description: "show this help" } };
510
+ const listing = listed(all, settings.allowNegative === true);
476
511
  if (completion.active)
477
- throw new Completing(all);
478
- const separator = settings.rest === true ? args.indexOf("--") : -1;
479
- const own = separator === -1 ? args : args.slice(0, separator);
480
- const rest = separator === -1 ? undefined : args.slice(separator + 1);
512
+ throw new Completing(listing, settings.positionals, settings.candidates);
513
+ const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
481
514
  let parsed;
482
515
  try {
483
- parsed = parseArgs({ args: joinDashValues(own, all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
516
+ parsed = parseArgs({ args: joinDashValues(withBare(own, all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
484
517
  } catch (error) {
485
518
  const message = parseError(error);
486
519
  throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
@@ -488,12 +521,84 @@ function parse(args, usage, options, settings = {}) {
488
521
  const values = parsed.values;
489
522
  applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
490
523
  if (values.help === true) {
491
- console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, all, undefined, settings));
524
+ console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, listing, undefined, settings));
492
525
  throw new HelpShown;
493
526
  }
527
+ checkChoices(parsed.values, all);
528
+ checkConflicts(parsed.values, all);
494
529
  if (settings.positionals !== undefined)
495
530
  checkPositionals(parsed.positionals, settings.positionals);
496
- return { ...parsed, positionals: parsed.positionals, rest };
531
+ return { ...parsed, values: parsed.values, positionals: parsed.positionals, rest };
532
+ }
533
+ function splitRest(args, passes) {
534
+ const separator = passes ? args.indexOf("--") : -1;
535
+ return { own: separator === -1 ? args : args.slice(0, separator), rest: separator === -1 ? undefined : args.slice(separator + 1) };
536
+ }
537
+ function splitTrailing(args, options, count) {
538
+ const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
539
+ let seen = 0;
540
+ if (count === 0)
541
+ return { own: args, rest: undefined };
542
+ for (let at = 0;at < args.length; at++) {
543
+ const word = args[at];
544
+ if (word === "--")
545
+ break;
546
+ if (word.startsWith("-")) {
547
+ if (takesValue(word))
548
+ at++;
549
+ continue;
550
+ }
551
+ seen++;
552
+ if (seen === count)
553
+ return { own: args.slice(0, at + 1), rest: args.slice(at + 1) };
554
+ }
555
+ return { own: args, rest: undefined };
556
+ }
557
+ function listed(options, allowNegative) {
558
+ if (!allowNegative)
559
+ return options;
560
+ return Object.fromEntries(Object.entries(options).flatMap(([name, option]) => {
561
+ if (option.type !== "boolean" || option.negative === undefined)
562
+ return [[name, option]];
563
+ return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
564
+ }));
565
+ }
566
+ function withBare(args, options) {
567
+ const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
568
+ if (bare.size === 0)
569
+ return args;
570
+ const separator = args.indexOf("--");
571
+ const own = separator === -1 ? args : args.slice(0, separator);
572
+ return [...own.flatMap((arg, index) => {
573
+ const optional = bare.get(arg);
574
+ const next = own[index + 1];
575
+ const value = next !== undefined && (optional?.accepts === undefined ? !next.startsWith("-") : optional.accepts.test(next));
576
+ return optional === undefined || value ? [arg] : [arg, optional.bare];
577
+ }), ...separator === -1 ? [] : args.slice(separator)];
578
+ }
579
+ function checkConflicts(values, options) {
580
+ const given = (name) => values[name] !== undefined && values[name] !== false;
581
+ for (const [name, { conflicts = [] }] of Object.entries(options)) {
582
+ const other = given(name) ? conflicts.find(given) : undefined;
583
+ if (other !== undefined)
584
+ badUsage(`--${name} cannot be used with --${other}`);
585
+ }
586
+ }
587
+ function checkChoices(values, options) {
588
+ for (const [name, option] of Object.entries(options)) {
589
+ if (option.choices === undefined)
590
+ continue;
591
+ const given = values[name];
592
+ for (const value of Array.isArray(given) ? given : [given]) {
593
+ if (typeof value === "string")
594
+ oneOf(value, name, option.choices);
595
+ }
596
+ }
597
+ }
598
+ function oneOf(value, name, choices) {
599
+ if (!choices.includes(value))
600
+ badUsage(`${name.startsWith("<") ? name : `--${name}`} takes one of ${choices.join(", ")}, not ${value}`);
601
+ return value;
497
602
  }
498
603
  function checkPositionals(given, names) {
499
604
  const variadic = names.at(-1)?.replace("]", "").endsWith("...") === true;
@@ -542,33 +647,9 @@ function nearestOption(message, options, allowNegative) {
542
647
  const meant = closest(word, known.map((flag) => flag.replace(DASHES, "")));
543
648
  return meant === undefined ? undefined : `did you mean ${known.find((flag) => flag.replace(DASHES, "") === meant)}?`;
544
649
  }
545
- var WHOLE = /^\d+$/;
546
- function wholeNumber(value, name, fallback, min = 1) {
547
- if (value === undefined)
548
- return fallback;
549
- const number = Number(value);
550
- if (!WHOLE.test(value) || !Number.isSafeInteger(number) || number < min)
551
- badUsage(`${name.startsWith("<") ? name : `--${name}`} takes a whole number from ${min}, not ${value}`);
552
- return number;
553
- }
554
- var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
555
- var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
556
- function duration(value, name, fallback, unit = "s") {
557
- if (value === undefined)
558
- return fallback;
559
- const match = DURATION.exec(value.trim());
560
- const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
561
- if (ms < 1)
562
- badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
563
- return ms;
564
- }
565
- function isStdinPiped() {
566
- const stat = fstatSync(0);
567
- return stat.isFIFO() || stat.isFile();
568
- }
569
650
  // ../kit/src/process.ts
570
651
  import { constants } from "os";
571
- import process4 from "process";
652
+ import process5 from "process";
572
653
  var KILL_GRACE_MS = 2000;
573
654
  var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
574
655
  function exitCodeOf(code, signal) {
@@ -590,7 +671,7 @@ function holdSignals(cleanup) {
590
671
  },
591
672
  release: () => {
592
673
  for (const signal of SIGNALS)
593
- process4.off(signal, onSignal);
674
+ process5.off(signal, onSignal);
594
675
  }
595
676
  };
596
677
  function onSignal(signal) {
@@ -603,10 +684,10 @@ function holdSignals(cleanup) {
603
684
  return;
604
685
  held.release();
605
686
  cleanup(signal);
606
- process4.kill(process4.pid, signal);
687
+ process5.kill(process5.pid, signal);
607
688
  }
608
689
  for (const signal of SIGNALS)
609
- process4.on(signal, onSignal);
690
+ process5.on(signal, onSignal);
610
691
  return held;
611
692
  }
612
693
  async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal, maxBytes = Infinity, onChunk, onStart } = {}) {
@@ -620,7 +701,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
620
701
  return;
621
702
  try {
622
703
  if (grouped)
623
- process4.kill(-proc.pid, killing);
704
+ process5.kill(-proc.pid, killing);
624
705
  else
625
706
  proc.kill(killing);
626
707
  } catch {}
@@ -694,15 +775,114 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
694
775
  var RESET_STYLE = `${ESC}[0m`;
695
776
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
696
777
  // ../kit/src/cli.ts
697
- import process5 from "process";
778
+ import process8 from "process";
779
+
780
+ // ../kit/src/usage.ts
781
+ import { appendFileSync, mkdirSync } from "fs";
782
+ import { homedir as homedir2 } from "os";
783
+ import { dirname, isAbsolute, join as join2 } from "path";
784
+ import process7 from "process";
785
+
786
+ // ../kit/src/logs.ts
787
+ import { lstatSync, openSync } from "fs";
788
+ import { mkdir, mkdtemp, readdir, rm, stat } from "fs/promises";
789
+ import { homedir, tmpdir } from "os";
790
+ import { join } from "path";
791
+ import process6 from "process";
792
+ var DAY_MS = 24 * 60 * 60 * 1000;
793
+ var CLI_FOLDER = "kaynooo-cli";
794
+ function isPrivateDirectory(path) {
795
+ try {
796
+ const info = lstatSync(path, { throwIfNoEntry: false });
797
+ return info !== undefined && info.isDirectory() && info.uid === (process6.getuid?.() ?? info.uid) && (info.mode & 63) === 0;
798
+ } catch {
799
+ return false;
800
+ }
801
+ }
802
+ async function privateLogDirectory(name, env = process6.env) {
803
+ const runtime = env.XDG_RUNTIME_DIR;
804
+ const base = runtime === undefined || runtime === "" ? join(homedir(), ".cache", CLI_FOLDER) : join(runtime, CLI_FOLDER);
805
+ const path = join(base, name);
806
+ try {
807
+ await mkdir(path, { recursive: true, mode: 448 });
808
+ if (isPrivateDirectory(path))
809
+ return path;
810
+ } catch {}
811
+ return mkdtemp(join(tmpdir(), `${name.replaceAll("/", "-")}-`));
812
+ }
813
+ async function sweepLogs(dir, maxAgeMs = DAY_MS, now = Date.now()) {
814
+ let names;
815
+ try {
816
+ names = await readdir(dir);
817
+ } catch {
818
+ return;
819
+ }
820
+ for (const name of names) {
821
+ const path = join(dir, name);
822
+ try {
823
+ const info = await stat(path);
824
+ if (info.isDirectory())
825
+ await sweepLogs(path, maxAgeMs, now);
826
+ if (now - info.mtimeMs > maxAgeMs)
827
+ await rm(path, { force: true, recursive: info.isDirectory() });
828
+ } catch {
829
+ continue;
830
+ }
831
+ }
832
+ }
833
+ function createLog(path) {
834
+ return openSync(path, "wx", 384);
835
+ }
836
+
837
+ // ../kit/src/usage.ts
838
+ var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
839
+ var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
840
+ var LONGEST_NAME = 40;
841
+ var usageError;
842
+ function noteUsageError(message) {
843
+ usageError = message;
844
+ }
845
+ function stateHome(env = process7.env) {
846
+ const dir = env.XDG_STATE_HOME;
847
+ return dir !== undefined && isAbsolute(dir) ? dir : join2(homedir2(), ".local", "state");
848
+ }
849
+ function usagePath(env = process7.env) {
850
+ return join2(stateHome(env), CLI_FOLDER, "usage.jsonl");
851
+ }
852
+ function usageRecorded(env = process7.env) {
853
+ const off = env.KAYNOOO_CLI_NO_USAGE;
854
+ return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
855
+ }
856
+ function flagNames(argv) {
857
+ const end = argv.indexOf("--");
858
+ const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
859
+ return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
860
+ }
861
+ function usageEntry(bin, command, argv, code, now = new Date) {
862
+ const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
863
+ usageError = undefined;
864
+ return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
865
+ }
866
+ function recordUsage(entry, env = process7.env) {
867
+ if (!usageRecorded(env))
868
+ return;
869
+ try {
870
+ const path = usagePath(env);
871
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
872
+ appendFileSync(path, `${JSON.stringify(entry)}
873
+ `, { mode: 384 });
874
+ } catch {}
875
+ }
876
+
877
+ // ../kit/src/cli.ts
698
878
  var LEVEL_OPTIONS = {
699
879
  ...OUTPUT_OPTIONS,
700
880
  help: { type: "boolean", short: "h", description: "show this help" }
701
881
  };
702
882
  var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
703
883
  function printError(prefix, message, hint, cause) {
704
- const colors = paint(process5.stderr);
705
- const styled = takesControlCodes(process5.stderr);
884
+ const colors = paint(process8.stderr);
885
+ const styled = takesControlCodes(process8.stderr);
706
886
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
707
887
  const under = (label, text, tint) => text === undefined ? "" : `
708
888
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -721,96 +901,85 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
721
901
  const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
722
902
  const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
723
903
  const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
904
+ if (error instanceof UsageError)
905
+ noteUsageError(error.message);
724
906
  const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
725
907
  report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
726
908
  return error instanceof Exit ? error.code : 1;
727
909
  }
728
910
  var COMPLETE = "__complete";
729
911
  var FILES = "__files__";
730
- async function optionsOf(run) {
912
+ async function completingOf(run) {
731
913
  completion.active = true;
732
914
  try {
733
915
  await run([]);
734
916
  } catch (error) {
735
917
  if (error instanceof Completing)
736
- return error.options;
918
+ return error;
737
919
  } finally {
738
920
  completion.active = false;
739
921
  }
740
- return {};
922
+ return new Completing({});
923
+ }
924
+ function candidateLine(candidate) {
925
+ return typeof candidate === "string" ? candidate : candidate.join("\t");
926
+ }
927
+ function positionalBeingTyped(before, { options, positionals }) {
928
+ const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
929
+ let index = 0;
930
+ let at = 0;
931
+ while (at < before.length) {
932
+ const word = before[at];
933
+ if (word === "--") {
934
+ index += before.length - at - 1;
935
+ break;
936
+ }
937
+ if (!word.startsWith("-"))
938
+ index++;
939
+ else if (takesValue(word))
940
+ at++;
941
+ at++;
942
+ }
943
+ const last = positionals.at(-1);
944
+ if (index < positionals.length)
945
+ return positionals[index];
946
+ return last?.replace("]", "").endsWith("...") === true ? last : undefined;
741
947
  }
742
948
  async function leafCandidates(run, words) {
743
- if (!(words.at(-1) ?? "").startsWith("-"))
744
- return [FILES];
745
- return Object.entries(await optionsOf(run)).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
949
+ const completing = await completingOf(run);
950
+ if ((words.at(-1) ?? "").startsWith("-"))
951
+ return Object.entries(completing.options).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
952
+ const flag = words.at(-2);
953
+ const option = flag?.startsWith("-") === true ? Object.entries(completing.options).find(([name, { short }]) => flag === `--${name}` || flag === `-${short}`)?.[1] : undefined;
954
+ if (option?.type === "string") {
955
+ const values = option.choices === undefined ? await option.candidates?.() : option.choices;
956
+ return values === undefined ? [FILES] : values.map(candidateLine);
957
+ }
958
+ const name = positionalBeingTyped(words.slice(0, -1), completing);
959
+ const values = name === undefined ? undefined : await completing.candidates[name]?.();
960
+ return values === undefined ? [FILES] : values.map(candidateLine);
746
961
  }
747
962
  async function printCandidates(found) {
748
963
  for (const line of await found)
749
964
  console.log(line);
750
965
  return 0;
751
966
  }
752
- async function runMain({ name, version, run, redact }, argv = process5.argv.slice(2)) {
967
+ async function runMain({ name, version, run, redact, usage }, argv = process8.argv.slice(2)) {
753
968
  if (argv[0] === COMPLETE)
754
969
  return printCandidates(leafCandidates(run, argv.slice(1)));
755
970
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
756
971
  console.log(version);
757
972
  return 0;
758
973
  }
974
+ let code;
759
975
  try {
760
- return await run(argv);
976
+ code = await run(argv);
761
977
  } catch (error) {
762
- return reportThrown(name, error, redact, wantsJson(argv));
978
+ code = reportThrown(name, error, redact, wantsJson(argv));
763
979
  }
764
- }
765
- // ../kit/src/logs.ts
766
- import { lstatSync, openSync } from "fs";
767
- import { mkdir, mkdtemp, readdir, rm, stat } from "fs/promises";
768
- import { homedir, tmpdir } from "os";
769
- import { join } from "path";
770
- import process6 from "process";
771
- var DAY_MS = 24 * 60 * 60 * 1000;
772
- var CLI_FOLDER = "kaynooo-cli";
773
- function isPrivateDirectory(path) {
774
- try {
775
- const info = lstatSync(path, { throwIfNoEntry: false });
776
- return info !== undefined && info.isDirectory() && info.uid === (process6.getuid?.() ?? info.uid) && (info.mode & 63) === 0;
777
- } catch {
778
- return false;
779
- }
780
- }
781
- async function privateLogDirectory(name, env = process6.env) {
782
- const runtime = env.XDG_RUNTIME_DIR;
783
- const base = runtime === undefined || runtime === "" ? join(homedir(), ".cache", CLI_FOLDER) : join(runtime, CLI_FOLDER);
784
- const path = join(base, name);
785
- try {
786
- await mkdir(path, { recursive: true, mode: 448 });
787
- if (isPrivateDirectory(path))
788
- return path;
789
- } catch {}
790
- return mkdtemp(join(tmpdir(), `${name.replaceAll("/", "-")}-`));
791
- }
792
- async function sweepLogs(dir, maxAgeMs = DAY_MS, now = Date.now()) {
793
- let names;
794
- try {
795
- names = await readdir(dir);
796
- } catch {
797
- return;
798
- }
799
- for (const name of names) {
800
- const path = join(dir, name);
801
- try {
802
- const info = await stat(path);
803
- if (info.isDirectory())
804
- await sweepLogs(path, maxAgeMs, now);
805
- if (now - info.mtimeMs > maxAgeMs)
806
- await rm(path, { force: true, recursive: info.isDirectory() });
807
- } catch {
808
- continue;
809
- }
810
- }
811
- }
812
- function createLog(path) {
813
- return openSync(path, "wx", 384);
980
+ if (usage !== false)
981
+ recordUsage(usageEntry(name, undefined, argv, code));
982
+ return code;
814
983
  }
815
984
  // ../kit/src/secrets.ts
816
985
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
@@ -819,7 +988,7 @@ var {Glob } = globalThis.Bun;
819
988
  var SESSION_FILES = new Glob("*.json");
820
989
  var ACCOUNT_DIRS = new Glob("*/sessions");
821
990
  // package.json
822
- var version = "0.6.0";
991
+ var version = "0.7.1";
823
992
 
824
993
  // src/shape.ts
825
994
  var HEX_LITERAL = /\b0x[0-9a-f]+\b/gi;
@@ -844,6 +1013,9 @@ var HEAD = 40;
844
1013
  var TAIL = 60;
845
1014
  var MAX_BLOCK = 20;
846
1015
  var MIN_REFERENCED = 20;
1016
+ var BYTES_PER_LINE = 55;
1017
+ var MAX_WIDTH = 300;
1018
+ var MAX_KEPT_IMPORTANT = 40;
847
1019
  var TOP_REPEATED = 3;
848
1020
  var LINE_BREAK = /\r\n|\r|\n/;
849
1021
  var LINE_END = /(?:\r\n|\r|\n)$/;
@@ -854,7 +1026,10 @@ function lineCount(text) {
854
1026
  return LINE_END.test(text) ? count - 1 : count;
855
1027
  }
856
1028
  function shouldFold(text, threshold) {
857
- return lineCount(text) > threshold;
1029
+ return lineCount(text) > threshold || Buffer.byteLength(text) > threshold * BYTES_PER_LINE;
1030
+ }
1031
+ function cutWide(line) {
1032
+ return line.length > MAX_WIDTH ? `${line.slice(0, MAX_WIDTH)}... [+${line.length - MAX_WIDTH} chars]` : line;
858
1033
  }
859
1034
  function finalState(line) {
860
1035
  const segments = line.split("\r").filter((segment) => segment !== "");
@@ -948,11 +1123,16 @@ function toEntries(units) {
948
1123
  function fold(text, { threshold, logPath }) {
949
1124
  const lines = splitLines(text);
950
1125
  const { entries, references, repeated } = toEntries(toUnits(lines, lines.map((line) => shape(line))));
951
- const head = Math.min(HEAD, Math.floor(threshold / 3));
952
- const tail = Math.min(TAIL, Math.floor(threshold / 2));
1126
+ for (const entry of entries)
1127
+ entry.text = cutWide(entry.text);
1128
+ const heavy = entries.reduce((sum, { text }) => sum + text.length + 1, 0) > threshold * BYTES_PER_LINE;
1129
+ const shrink = heavy && entries.length <= threshold ? 2 : 1;
1130
+ const head = Math.floor(Math.min(HEAD, Math.floor(threshold / 3)) / shrink);
1131
+ const tail = Math.floor(Math.min(TAIL, Math.floor(threshold / 2)) / shrink);
953
1132
  const out = [];
954
1133
  let dropped = 0;
955
- if (entries.length <= threshold) {
1134
+ let kept = 0;
1135
+ if (entries.length <= threshold && !heavy) {
956
1136
  out.push(...entries.map(({ text }) => text));
957
1137
  } else {
958
1138
  const middleEnd = entries.length - tail;
@@ -969,7 +1149,8 @@ function fold(text, { threshold, logPath }) {
969
1149
  out.push(entry.text);
970
1150
  continue;
971
1151
  }
972
- if (entry.important) {
1152
+ if (entry.important && kept < MAX_KEPT_IMPORTANT) {
1153
+ kept++;
973
1154
  closeGap();
974
1155
  out.push(entry.text);
975
1156
  continue;
@@ -989,11 +1170,11 @@ function fold(text, { threshold, logPath }) {
989
1170
 
990
1171
  // src/run.ts
991
1172
  import { closeSync, rmSync, writeSync } from "fs";
992
- import process10 from "process";
1173
+ import process12 from "process";
993
1174
 
994
1175
  // src/logs.ts
995
- import { join as join2 } from "path";
996
- import process8 from "process";
1176
+ import { join as join3 } from "path";
1177
+ import process10 from "process";
997
1178
  var MAX_AGE_MS = 24 * 60 * 60 * 1000;
998
1179
  async function logDir() {
999
1180
  return privateLogDirectory("out");
@@ -1002,7 +1183,7 @@ async function cleanLogs(dir, now = Date.now()) {
1002
1183
  await sweepLogs(dir, MAX_AGE_MS, now);
1003
1184
  }
1004
1185
  function newLog(dir) {
1005
- const path = join2(dir, `${new Date().toISOString().replaceAll(/[:.]/g, "-")}-${process8.pid}.log`);
1186
+ const path = join3(dir, `${new Date().toISOString().replaceAll(/[:.]/g, "-")}-${process10.pid}.log`);
1006
1187
  return { path, fd: createLog(path) };
1007
1188
  }
1008
1189
 
@@ -1257,10 +1438,9 @@ function filtered(text, stages) {
1257
1438
  }
1258
1439
 
1259
1440
  // src/stats.ts
1260
- import { appendFileSync, mkdirSync } from "fs";
1261
- import { homedir as homedir2 } from "os";
1262
- import { basename, dirname, join as join3 } from "path";
1263
- import process9 from "process";
1441
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
1442
+ import { basename, dirname as dirname2, join as join4 } from "path";
1443
+ import process11 from "process";
1264
1444
  var BYTES_PER_TOKEN = 4;
1265
1445
  var KEY_WORD = /^[a-z][a-z:_-]*$/;
1266
1446
  var KEY_WORDS = 3;
@@ -1269,8 +1449,8 @@ var ASSIGNMENT = /^\w+=/;
1269
1449
  var CHAIN = /&&|\|\||;/;
1270
1450
  var SHELLS2 = new Set(["sh", "bash", "zsh"]);
1271
1451
  var TOP = 10;
1272
- function statsPath(env = process9.env) {
1273
- return join3(env.XDG_STATE_HOME ?? join3(homedir2(), ".local", "state"), CLI_FOLDER, "out", "stats.jsonl");
1452
+ function statsPath(env = process11.env) {
1453
+ return join4(stateHome(env), CLI_FOLDER, "out", "stats.jsonl");
1274
1454
  }
1275
1455
  function commandKey(command) {
1276
1456
  const [program = "", ...rest] = command.slice(Math.max(0, command.findIndex((word) => !ASSIGNMENT.test(word))));
@@ -1288,8 +1468,8 @@ function commandKey(command) {
1288
1468
  }
1289
1469
  function record(entry, path = statsPath()) {
1290
1470
  try {
1291
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
1292
- appendFileSync(path, `${JSON.stringify(entry)}
1471
+ mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
1472
+ appendFileSync2(path, `${JSON.stringify(entry)}
1293
1473
  `, { mode: 384 });
1294
1474
  } catch {}
1295
1475
  }
@@ -1307,29 +1487,41 @@ async function readEntries(path = statsPath(), since = 0) {
1307
1487
  }
1308
1488
  });
1309
1489
  }
1490
+ function median(values) {
1491
+ const sorted = values.toSorted((a, b) => a - b);
1492
+ const middle = Math.floor(sorted.length / 2);
1493
+ if (sorted.length === 0)
1494
+ return;
1495
+ return sorted.length % 2 === 1 ? sorted[middle] : ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2;
1496
+ }
1310
1497
  function tokens(bytes) {
1311
1498
  return Math.round(bytes / BYTES_PER_TOKEN);
1312
1499
  }
1313
1500
  function formatNumber(value) {
1314
1501
  return value.toLocaleString("en-US");
1315
1502
  }
1503
+ function cut2(runs) {
1504
+ const value = median(runs.filter((run) => run.folded && run.baseline > 0).map((run) => (1 - run.shown / run.baseline) * 100));
1505
+ return value === undefined ? undefined : Math.round(value);
1506
+ }
1316
1507
  function summarize(entries) {
1317
1508
  const commands = Map.groupBy(entries, (entry) => entry.command).entries().map(([command, runs]) => ({
1318
1509
  command,
1319
1510
  runs: runs.length,
1320
1511
  folded: runs.filter((run) => run.folded).length,
1321
- tokensSaved: tokens(runs.reduce((sum, run) => sum + run.baseline - run.shown, 0))
1512
+ tokensSaved: tokens(runs.reduce((sum, run) => sum + run.baseline - run.shown, 0)),
1513
+ medianCut: cut2(runs)
1322
1514
  }));
1323
1515
  const sum = (field) => entries.reduce((total, entry) => total + entry[field], 0);
1324
- return { runs: entries.length, folded: entries.filter((entry) => entry.folded).length, bytes: sum("bytes"), shown: sum("shown"), tokensSaved: tokens(sum("baseline") - sum("shown")), commands: [...commands].sort((a, b) => b.tokensSaved - a.tokensSaved) };
1516
+ return { runs: entries.length, folded: entries.filter((entry) => entry.folded).length, bytes: sum("bytes"), shown: sum("shown"), tokensSaved: tokens(sum("baseline") - sum("shown")), foldedShare: entries.length === 0 ? 0 : Math.round(entries.filter((entry) => entry.folded).length / entries.length * 100), commands: [...commands].sort((a, b) => b.tokensSaved - a.tokensSaved) };
1325
1517
  }
1326
1518
  function renderSummary(summary, period) {
1327
1519
  if (summary.runs === 0)
1328
1520
  return `no run of out recorded ${period}`;
1329
1521
  const top = summary.commands.slice(0, TOP).filter((each) => each.tokensSaved !== 0);
1330
1522
  return [
1331
- `${formatNumber(summary.runs)} runs ${period}, ${formatNumber(summary.folded)} folded: ${formatNumber(summary.bytes)} bytes printed, ${formatNumber(summary.shown)} shown, about ${formatNumber(summary.tokensSaved)} tokens saved (bytes / ${BYTES_PER_TOKEN})`,
1332
- ...top.length === 0 ? [] : ["", table([["command", "runs", "folded", "tokens saved"], ...top.map((each) => [each.command, formatNumber(each.runs), formatNumber(each.folded), formatNumber(each.tokensSaved)])])]
1523
+ `${formatNumber(summary.runs)} runs ${period}, ${formatNumber(summary.folded)} folded (${summary.foldedShare}%): ${formatNumber(summary.bytes)} bytes printed, ${formatNumber(summary.shown)} shown, about ${formatNumber(summary.tokensSaved)} tokens saved (bytes / ${BYTES_PER_TOKEN})`,
1524
+ ...top.length === 0 ? [] : ["", table([["command", "runs", "folded", "median cut", "tokens saved"], ...top.map((each) => [each.command, formatNumber(each.runs), formatNumber(each.folded), each.medianCut === undefined ? "-" : `${each.medianCut}%`, formatNumber(each.tokensSaved)])])]
1333
1525
  ].join(`
1334
1526
  `);
1335
1527
  }
@@ -1403,11 +1595,11 @@ ${decode(this.tail)}`;
1403
1595
  }
1404
1596
  function replay(chunks) {
1405
1597
  for (const { stream, data } of chunks)
1406
- process10[stream].write(data);
1598
+ process12[stream].write(data);
1407
1599
  }
1408
1600
  function count(recorder, shown, recorded) {
1409
1601
  const baseline = recorded.was.length === 0 || recorder.overflowing ? undefined : filtered(recorder.text, recorded.was);
1410
- record({ at: new Date().toISOString(), dir: process10.cwd(), command: recorded.command, bytes: recorder.bytes, baseline: baseline === undefined ? recorder.bytes : Buffer.byteLength(baseline), shown, folded: shown !== recorder.bytes });
1602
+ record({ at: new Date().toISOString(), dir: process12.cwd(), command: recorded.command, bytes: recorder.bytes, baseline: baseline === undefined ? recorder.bytes : Buffer.byteLength(baseline), shown, folded: shown !== recorder.bytes });
1411
1603
  }
1412
1604
  async function run(command, threshold, was = []) {
1413
1605
  const dir = await logDir();
@@ -1471,7 +1663,7 @@ function present(recorder, threshold) {
1471
1663
  replay(recorder.chunks);
1472
1664
  return recorder.bytes;
1473
1665
  }
1474
- process10.stdout.write(printed);
1666
+ process12.stdout.write(printed);
1475
1667
  return Buffer.byteLength(printed);
1476
1668
  }
1477
1669
 
@@ -1520,4 +1712,4 @@ async function main(argv) {
1520
1712
  ${USAGE}`);
1521
1713
  return run(command, lines, was);
1522
1714
  }
1523
- process11.exitCode = await runMain({ name: "out", version, run: main });
1715
+ process13.exitCode = await runMain({ name: "out", version, run: main });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaynooo/cli-out",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.7.1",
5
5
  "description": "out: shortens noisy command output for Claude Code, folding long logs and keeping errors, with the full log saved to a file.",
6
6
  "author": "Kaynooo <contact@kaynooo.fr>",
7
7
  "license": "MIT",
@@ -28,6 +28,6 @@
28
28
  "out": "$npm_execpath src/cli.ts"
29
29
  },
30
30
  "devDependencies": {
31
- "@kaynooo/cli-kit": "0.15.0"
31
+ "@kaynooo/cli-kit": "0.20.0"
32
32
  }
33
33
  }