@amdahl/cli 0.3.1 → 0.3.3

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 +11 -4
  2. package/dist/amdahl.js +243 -71
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -68,6 +68,7 @@ Set up in:
68
68
  [ ] Codex
69
69
  (Use the arrow keys, Space to tick or untick, then Enter)
70
70
  Opening your browser…
71
+ Browser didn't open? Press Enter for the link.
71
72
  ✓ Signed in · Acme
72
73
  ✓ Added to Claude Code
73
74
  Try it: in Claude Code, run /mcp once to connect, then say "make this email sound less like AI"
@@ -76,10 +77,16 @@ Try it: in Claude Code, run /mcp once to connect, then say "make this email soun
76
77
  It finds Claude Code, Cursor and Codex by their command on your PATH
77
78
  (`claude`, `cursor`, `codex`) or their folder in your home folder (`.claude`,
78
79
  `.cursor`, `.codex`), and ticks the ones it finds. Enter accepts. If you are
79
- not signed in, it signs you in through the browser first. It prints the
80
- sign-in link only if the browser could not be opened, or nothing came back
81
- within 5 seconds, and it waits up to 15 minutes, so someone new can create
82
- their workspace on the way and land back in the same setup. If the server
80
+ not signed in, it signs you in through the browser first. Under "Opening
81
+ your browser…" it prints one short line. In a terminal that shows links
82
+ (iTerm2, VS Code, Warp, Terminal, WezTerm, Ghostty, Windows Terminal, VTE 0.50
83
+ or later) it reads "Browser didn't open? Open the sign-in page" and the last
84
+ words are a link; elsewhere it says to press Enter for the link.
85
+ `FORCE_HYPERLINK=1` or `0` overrides the guess, and `NO_COLOR` turns the link
86
+ off. Either way, Enter prints the full sign-in link once. If the browser
87
+ could not be opened, or with `--no-browser`, the full link prints at once.
88
+ It waits up to 15 minutes, so someone new can create their workspace on the
89
+ way and land back in the same setup. If the server
83
90
  rejects a key set in
84
91
  `AMDAHL_KEY` (or `AMDAHL_API_KEY`, `AMDAHL_ACCESS_TOKEN`, `--api-key`), it says
85
92
  so and skips the browser sign-in, since that key would still win. Then it
package/dist/amdahl.js CHANGED
@@ -1000,14 +1000,14 @@ var require_foldFlowLines = __commonJS({
1000
1000
  var FOLD_FLOW = "flow";
1001
1001
  var FOLD_BLOCK = "block";
1002
1002
  var FOLD_QUOTED = "quoted";
1003
- function foldFlowLines(text, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
1003
+ function foldFlowLines(text2, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
1004
1004
  if (!lineWidth || lineWidth < 0)
1005
- return text;
1005
+ return text2;
1006
1006
  if (lineWidth < minContentWidth)
1007
1007
  minContentWidth = 0;
1008
1008
  const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
1009
- if (text.length <= endStep)
1010
- return text;
1009
+ if (text2.length <= endStep)
1010
+ return text2;
1011
1011
  const folds = [];
1012
1012
  const escapedFolds = {};
1013
1013
  let end = lineWidth - indent.length;
@@ -1024,14 +1024,14 @@ var require_foldFlowLines = __commonJS({
1024
1024
  let escStart = -1;
1025
1025
  let escEnd = -1;
1026
1026
  if (mode === FOLD_BLOCK) {
1027
- i = consumeMoreIndentedLines(text, i, indent.length);
1027
+ i = consumeMoreIndentedLines(text2, i, indent.length);
1028
1028
  if (i !== -1)
1029
1029
  end = i + endStep;
1030
1030
  }
1031
- for (let ch; ch = text[i += 1]; ) {
1031
+ for (let ch; ch = text2[i += 1]; ) {
1032
1032
  if (mode === FOLD_QUOTED && ch === "\\") {
1033
1033
  escStart = i;
1034
- switch (text[i + 1]) {
1034
+ switch (text2[i + 1]) {
1035
1035
  case "x":
1036
1036
  i += 3;
1037
1037
  break;
@@ -1048,12 +1048,12 @@ var require_foldFlowLines = __commonJS({
1048
1048
  }
1049
1049
  if (ch === "\n") {
1050
1050
  if (mode === FOLD_BLOCK)
1051
- i = consumeMoreIndentedLines(text, i, indent.length);
1051
+ i = consumeMoreIndentedLines(text2, i, indent.length);
1052
1052
  end = i + indent.length + endStep;
1053
1053
  split = void 0;
1054
1054
  } else {
1055
1055
  if (ch === " " && prev && prev !== " " && prev !== "\n" && prev !== " ") {
1056
- const next = text[i + 1];
1056
+ const next = text2[i + 1];
1057
1057
  if (next && next !== " " && next !== "\n" && next !== " ")
1058
1058
  split = i;
1059
1059
  }
@@ -1065,12 +1065,12 @@ var require_foldFlowLines = __commonJS({
1065
1065
  } else if (mode === FOLD_QUOTED) {
1066
1066
  while (prev === " " || prev === " ") {
1067
1067
  prev = ch;
1068
- ch = text[i += 1];
1068
+ ch = text2[i += 1];
1069
1069
  overflow = true;
1070
1070
  }
1071
1071
  const j = i > escEnd + 1 ? i - 2 : escStart - 1;
1072
1072
  if (escapedFolds[j])
1073
- return text;
1073
+ return text2;
1074
1074
  folds.push(j);
1075
1075
  escapedFolds[j] = true;
1076
1076
  end = j + endStep;
@@ -1085,39 +1085,39 @@ var require_foldFlowLines = __commonJS({
1085
1085
  if (overflow && onOverflow)
1086
1086
  onOverflow();
1087
1087
  if (folds.length === 0)
1088
- return text;
1088
+ return text2;
1089
1089
  if (onFold)
1090
1090
  onFold();
1091
- let res = text.slice(0, folds[0]);
1091
+ let res = text2.slice(0, folds[0]);
1092
1092
  for (let i2 = 0; i2 < folds.length; ++i2) {
1093
1093
  const fold = folds[i2];
1094
- const end2 = folds[i2 + 1] || text.length;
1094
+ const end2 = folds[i2 + 1] || text2.length;
1095
1095
  if (fold === 0)
1096
1096
  res = `
1097
- ${indent}${text.slice(0, end2)}`;
1097
+ ${indent}${text2.slice(0, end2)}`;
1098
1098
  else {
1099
1099
  if (mode === FOLD_QUOTED && escapedFolds[fold])
1100
- res += `${text[fold]}\\`;
1100
+ res += `${text2[fold]}\\`;
1101
1101
  res += `
1102
- ${indent}${text.slice(fold + 1, end2)}`;
1102
+ ${indent}${text2.slice(fold + 1, end2)}`;
1103
1103
  }
1104
1104
  }
1105
1105
  return res;
1106
1106
  }
1107
- function consumeMoreIndentedLines(text, i, indent) {
1107
+ function consumeMoreIndentedLines(text2, i, indent) {
1108
1108
  let end = i;
1109
1109
  let start = i + 1;
1110
- let ch = text[start];
1110
+ let ch = text2[start];
1111
1111
  while (ch === " " || ch === " ") {
1112
1112
  if (i < start + indent) {
1113
- ch = text[++i];
1113
+ ch = text2[++i];
1114
1114
  } else {
1115
1115
  do {
1116
- ch = text[++i];
1116
+ ch = text2[++i];
1117
1117
  } while (ch && ch !== "\n");
1118
1118
  end = i;
1119
1119
  start = i + 1;
1120
- ch = text[start];
1120
+ ch = text2[start];
1121
1121
  }
1122
1122
  }
1123
1123
  return end;
@@ -6347,15 +6347,15 @@ var require_parser = __commonJS({
6347
6347
  var node_process = __require("process");
6348
6348
  var cst = require_cst();
6349
6349
  var lexer = require_lexer();
6350
- function includesToken(list, type) {
6351
- for (let i = 0; i < list.length; ++i)
6352
- if (list[i].type === type)
6350
+ function includesToken(list2, type) {
6351
+ for (let i = 0; i < list2.length; ++i)
6352
+ if (list2[i].type === type)
6353
6353
  return true;
6354
6354
  return false;
6355
6355
  }
6356
- function findNonEmptyIndex(list) {
6357
- for (let i = 0; i < list.length; ++i) {
6358
- switch (list[i].type) {
6356
+ function findNonEmptyIndex(list2) {
6357
+ for (let i = 0; i < list2.length; ++i) {
6358
+ switch (list2[i].type) {
6359
6359
  case "space":
6360
6360
  case "comment":
6361
6361
  case "newline":
@@ -7406,10 +7406,10 @@ ${SHOW_CURSOR}`);
7406
7406
  reject(outcome.error);
7407
7407
  }
7408
7408
  };
7409
- const onKeypress = (text, key) => {
7410
- const name = key?.name ?? text;
7409
+ const onKeypress = (text2, key) => {
7410
+ const name = key?.name ?? text2;
7411
7411
  if (key?.ctrl && (name === "c" || name === "d")) return finish({ error: new PromptCancelled() });
7412
- const result = onKey(text, name);
7412
+ const result = onKey(text2, name);
7413
7413
  if (result === "redraw") output.write(`${up(height + 1)}${draw()}`);
7414
7414
  else if (result) finish(result);
7415
7415
  };
@@ -7432,19 +7432,19 @@ ${draw()}`);
7432
7432
  function marker(options) {
7433
7433
  return options.color ? options.trueColor ? "\x1B[1;38;2;253;75;3m" : "\x1B[1;38;5;202m" : "\x1B[1m";
7434
7434
  }
7435
- function move(current, count, text, name) {
7435
+ function move(current, count, text2, name) {
7436
7436
  if (name === "up" || name === "k") return (current - 1 + count) % count;
7437
7437
  if (name === "down" || name === "j") return (current + 1) % count;
7438
- if (text && /^[1-9]$/.test(text) && Number(text) <= count) return Number(text) - 1;
7438
+ if (text2 && /^[1-9]$/.test(text2) && Number(text2) <= count) return Number(text2) - 1;
7439
7439
  return null;
7440
7440
  }
7441
7441
  function selectMenu(input, output, question, choices, options = {}) {
7442
7442
  let current = Math.min(Math.max(options.initial ?? 0, 0), choices.length - 1);
7443
7443
  const on = marker(options);
7444
7444
  const rows = () => choices.map((c, i) => i === current ? `${on}> ${c}${OFF}` : ` ${c}`);
7445
- return runMenu(input, output, question, HINT, rows, (text, name) => {
7445
+ return runMenu(input, output, question, HINT, rows, (text2, name) => {
7446
7446
  if (name === "return" || name === "enter") return { pick: current, summary: choices[current] ?? "" };
7447
- const next = move(current, choices.length, text, name);
7447
+ const next = move(current, choices.length, text2, name);
7448
7448
  if (next === null || next === current) return null;
7449
7449
  current = next;
7450
7450
  return "redraw";
@@ -7458,7 +7458,7 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
7458
7458
  const row = `[${ticked[i] ? "x" : " "}] ${c}`;
7459
7459
  return i === current ? `${on}> ${row}${OFF}` : ` ${row}`;
7460
7460
  });
7461
- return runMenu(input, output, question, MULTI_HINT, rows, (text, name) => {
7461
+ return runMenu(input, output, question, MULTI_HINT, rows, (text2, name) => {
7462
7462
  if (name === "return" || name === "enter") {
7463
7463
  const picks = choices.flatMap((_, i) => ticked[i] ? [i] : []);
7464
7464
  return { pick: picks, summary: picks.map((i) => choices[i]).join(", ") || "none" };
@@ -7467,17 +7467,53 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
7467
7467
  ticked[current] = !ticked[current];
7468
7468
  return "redraw";
7469
7469
  }
7470
- if (text && /^[1-9]$/.test(text) && Number(text) <= choices.length) {
7471
- current = Number(text) - 1;
7470
+ if (text2 && /^[1-9]$/.test(text2) && Number(text2) <= choices.length) {
7471
+ current = Number(text2) - 1;
7472
7472
  ticked[current] = !ticked[current];
7473
7473
  return "redraw";
7474
7474
  }
7475
- const next = move(current, choices.length, text, name);
7475
+ const next = move(current, choices.length, text2, name);
7476
7476
  if (next === null || next === current) return null;
7477
7477
  current = next;
7478
7478
  return "redraw";
7479
7479
  });
7480
7480
  }
7481
+ function listenForEnter(input, onEnter, onCancel) {
7482
+ const wasRaw = input.isRaw === true;
7483
+ let done = false;
7484
+ const stop = () => {
7485
+ if (done) return;
7486
+ done = true;
7487
+ input.removeListener("keypress", onKeypress);
7488
+ input.removeListener("error", stop);
7489
+ process.removeListener("SIGINT", cancel);
7490
+ try {
7491
+ input.setRawMode?.(wasRaw);
7492
+ } catch {
7493
+ }
7494
+ input.pause();
7495
+ };
7496
+ const cancel = () => {
7497
+ if (done) return;
7498
+ stop();
7499
+ onCancel();
7500
+ };
7501
+ const onKeypress = (_text, key) => {
7502
+ if (key?.ctrl && key.name === "c") cancel();
7503
+ else if (key?.name === "return" || key?.name === "enter") onEnter();
7504
+ };
7505
+ try {
7506
+ emitKeypressEvents(input);
7507
+ input.setRawMode?.(true);
7508
+ input.on("keypress", onKeypress);
7509
+ input.on("error", stop);
7510
+ process.on("SIGINT", cancel);
7511
+ input.resume();
7512
+ } catch {
7513
+ stop();
7514
+ }
7515
+ return stop;
7516
+ }
7481
7517
 
7482
7518
  // src/io.ts
7483
7519
  var PromptCancelled = class extends Error {
@@ -7548,8 +7584,8 @@ function openerFor(platform, url) {
7548
7584
  function realIo() {
7549
7585
  return {
7550
7586
  env: process.env,
7551
- stdout: (text) => process.stdout.write(text),
7552
- stderr: (text) => process.stderr.write(text),
7587
+ stdout: (text2) => process.stdout.write(text2),
7588
+ stderr: (text2) => process.stderr.write(text2),
7553
7589
  stdinIsTTY: Boolean(process.stdin.isTTY),
7554
7590
  stdoutIsTTY: Boolean(process.stdout.isTTY),
7555
7591
  stderrIsTTY: Boolean(process.stderr.isTTY),
@@ -7563,6 +7599,7 @@ function realIo() {
7563
7599
  ...options,
7564
7600
  trueColor: /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? "")
7565
7601
  }),
7602
+ listenForEnter: (onEnter, onCancel) => listenForEnter(process.stdin, onEnter, onCancel),
7566
7603
  uninterruptible,
7567
7604
  run: runProcess,
7568
7605
  openUrl: async (url) => {
@@ -7576,7 +7613,6 @@ function realIo() {
7576
7613
  now: () => Date.now(),
7577
7614
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
7578
7615
  loginTimeoutMs: 9e5,
7579
- browserGraceMs: 5e3,
7580
7616
  fetchTimeoutMs: 15e3
7581
7617
  };
7582
7618
  }
@@ -8131,8 +8167,8 @@ function signInAgainUrl(authorize) {
8131
8167
  const url = new URL(authorize);
8132
8168
  return `${url.origin}/login?redirect=${encodeURIComponent(`${url.pathname}${url.search}`)}`;
8133
8169
  }
8134
- function html(text) {
8135
- return text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
8170
+ function html(text2) {
8171
+ return text2.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
8136
8172
  }
8137
8173
  function page(body) {
8138
8174
  return `<!doctype html><html><head><meta charset="utf-8"><title>Amdahl CLI</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;line-height:1.5">${body}</body></html>`;
@@ -8542,8 +8578,20 @@ async function call(fn) {
8542
8578
 
8543
8579
  // src/output.ts
8544
8580
  var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/g;
8545
- function printable(text) {
8546
- return text.replace(CONTROL_RE, "");
8581
+ function printable(text2) {
8582
+ return text2.replace(CONTROL_RE, "");
8583
+ }
8584
+ var HYPERLINK_TERMINALS = /* @__PURE__ */ new Set(["iTerm.app", "vscode", "WarpTerminal", "Apple_Terminal", "WezTerm", "ghostty"]);
8585
+ function supportsHyperlinks(env, color) {
8586
+ if (env.FORCE_HYPERLINK === "0") return false;
8587
+ if (env.FORCE_HYPERLINK === "1") return true;
8588
+ if (!color) return false;
8589
+ if (HYPERLINK_TERMINALS.has(env.TERM_PROGRAM ?? "")) return true;
8590
+ if (Number(env.VTE_VERSION) >= 5e3) return true;
8591
+ return Boolean(env.WT_SESSION);
8592
+ }
8593
+ function hyperlink(text2, url) {
8594
+ return `\x1B]8;;${printable(url)}\x1B\\${text2}\x1B]8;;\x1B\\`;
8547
8595
  }
8548
8596
  function createOutput(io, json, noColor) {
8549
8597
  const color = !noColor && !io.env.NO_COLOR && io.env.TERM !== "dumb" && io.stderrIsTTY && !json;
@@ -8559,16 +8607,16 @@ function createOutput(io, json, noColor) {
8559
8607
  `);
8560
8608
  }
8561
8609
  },
8562
- info(text) {
8563
- io.stderr(text.endsWith("\n") ? text : `${text}
8610
+ info(text2) {
8611
+ io.stderr(text2.endsWith("\n") ? text2 : `${text2}
8564
8612
  `);
8565
8613
  },
8566
8614
  event(data) {
8567
8615
  if (json) io.stderr(`${JSON.stringify(data)}
8568
8616
  `);
8569
8617
  },
8570
- bold(text) {
8571
- return color ? `\x1B[1m${text}\x1B[22m` : text;
8618
+ bold(text2) {
8619
+ return color ? `\x1B[1m${text2}\x1B[22m` : text2;
8572
8620
  }
8573
8621
  };
8574
8622
  }
@@ -8593,7 +8641,8 @@ async function browserSignIn(ctx) {
8593
8641
  const endpoints = await discover(ctx.apiUrl);
8594
8642
  const pkce = createPkce();
8595
8643
  const consoleUrl = consoleUrlFor(io, ctx.apiUrl);
8596
- let timer;
8644
+ let stopKeys = () => {
8645
+ };
8597
8646
  let linkShown = false;
8598
8647
  const loopback = await startLoopback(io, {
8599
8648
  port,
@@ -8602,7 +8651,6 @@ async function browserSignIn(ctx) {
8602
8651
  timeoutMs: io.loginTimeoutMs,
8603
8652
  // The page carries the way on; the authorize link would only sign in as the same address again.
8604
8653
  onSignInAgain: (reason, retry) => {
8605
- clearTimeout(timer);
8606
8654
  linkShown = true;
8607
8655
  out.info(signInAgainLine(reason, retry));
8608
8656
  }
@@ -8619,18 +8667,27 @@ async function browserSignIn(ctx) {
8619
8667
  ${url}
8620
8668
  `);
8621
8669
  };
8670
+ let cancelled;
8622
8671
  if (flags["no-browser"]) {
8623
8672
  showLink("Open this URL in a browser to sign in:");
8624
8673
  } else {
8625
8674
  out.info("Opening your browser\u2026");
8675
+ if (!ctx.nonInteractive && io.stderrIsTTY) {
8676
+ const fallback = supportsHyperlinks(io.env, out.color) ? hyperlink("Open the sign-in page", url) : "Press Enter for the link.";
8677
+ out.info(`Browser didn't open? ${fallback}`);
8678
+ cancelled = new Promise((_, reject) => {
8679
+ stopKeys = io.listenForEnter(
8680
+ () => showLink("Open this link to sign in:"),
8681
+ () => reject(new PromptCancelled())
8682
+ );
8683
+ });
8684
+ }
8626
8685
  void io.openUrl(url).then((opened) => {
8627
8686
  if (!opened) showLink("Your browser did not open. Open this link to sign in:");
8628
8687
  });
8629
- timer = setTimeout(() => showLink("Finish in your browser, or open this link:"), io.browserGraceMs);
8630
- timer.unref?.();
8631
8688
  }
8632
- const callback = await loopback.result;
8633
- clearTimeout(timer);
8689
+ const callback = await (cancelled ? Promise.race([loopback.result, cancelled]) : loopback.result);
8690
+ stopKeys();
8634
8691
  linkShown = true;
8635
8692
  if (callback.kind === "no_business") {
8636
8693
  throw new CliError("no_business", noBusinessMessage(consoleUrl, callback.mayCreate, callback.reason), EXIT.noWorkspace);
@@ -8664,7 +8721,7 @@ async function browserSignIn(ctx) {
8664
8721
  ctx.saveConfig();
8665
8722
  return { profile: name, me, workspace: ws };
8666
8723
  } finally {
8667
- clearTimeout(timer);
8724
+ stopKeys();
8668
8725
  loopback.close();
8669
8726
  }
8670
8727
  }
@@ -8790,8 +8847,8 @@ var shipped_default = [
8790
8847
  var SKILL_URL = "https://docs.amdahl.ai/skills/amdahl/SKILL.md";
8791
8848
  var SKILL_TEXT = SKILL_default;
8792
8849
  var SHIPPED_SKILL_SHA256 = shipped_default;
8793
- function isShippedSkill(text) {
8794
- return SHIPPED_SKILL_SHA256.includes(createHash2("sha256").update(text).digest("hex"));
8850
+ function isShippedSkill(text2) {
8851
+ return SHIPPED_SKILL_SHA256.includes(createHash2("sha256").update(text2).digest("hex"));
8795
8852
  }
8796
8853
  var SKILL_DIRS = {
8797
8854
  "claude-code": [".claude", "skills"],
@@ -9178,6 +9235,119 @@ function withTimeout(promise, ms, file) {
9178
9235
  });
9179
9236
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
9180
9237
  }
9238
+ var WEAKEST = {
9239
+ rules: "how it follows your rules",
9240
+ ask: "the ask",
9241
+ opener: "the opener",
9242
+ fit: "how it fits the recipient",
9243
+ proof: "its proof points",
9244
+ length: "the length",
9245
+ style: "the tone",
9246
+ preference: "how it matches your preferences",
9247
+ voice: "how much it sounds like you"
9248
+ };
9249
+ var CONCERNS = {
9250
+ conflicts_with_callers_ask: "it asks for a different length or time than your ask",
9251
+ drops_ask: "it leaves out your ask",
9252
+ adds_claim: "it states something neither your draft nor your facts support",
9253
+ drops_entity: "it drops a name, link or placeholder you had",
9254
+ inflates: "it is longer than you asked for",
9255
+ no_gain: "it changed little beyond the wording",
9256
+ breaks_rules: "it breaks one of your rules"
9257
+ };
9258
+ var EVIDENCE_MISSED = {
9259
+ corpus_empty: "the workspace has none yet",
9260
+ evidence_unavailable: "this environment does not read them",
9261
+ evidence_read_failed: "the read failed, so a retry may work"
9262
+ };
9263
+ var MAX_LINES = 3;
9264
+ function list(value) {
9265
+ return Array.isArray(value) ? value.filter((v) => !!v && typeof v === "object") : null;
9266
+ }
9267
+ function record(value) {
9268
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
9269
+ }
9270
+ function text(value) {
9271
+ return typeof value === "string" ? printable(value).trim() : "";
9272
+ }
9273
+ function sentence(value) {
9274
+ return /[.!?]$/.test(value) ? value : `${value}.`;
9275
+ }
9276
+ function plural(n, one, many = `${one}s`) {
9277
+ return `${n} ${n === 1 ? one : many}`;
9278
+ }
9279
+ function readSteps(steps) {
9280
+ const reasonOf = (node) => text(steps.find((s) => s.node === node)?.reason);
9281
+ const changed = (node) => steps.some((s) => s.node === node && s.changed === true);
9282
+ const counts = new Map(
9283
+ reasonOf("w3").split(",").map((part) => part.split(":")).filter(([, n]) => /^\d+$/.test(n ?? "")).map(([code, n]) => [code, Number(n)])
9284
+ );
9285
+ const did = [];
9286
+ const repaired = counts.get("repaired") ?? 0;
9287
+ const deleted = counts.get("deleted") ?? 0;
9288
+ if (repaired && deleted) did.push(`Rewrote ${plural(repaired, "sentence")} and cut ${deleted} that nothing backed.`);
9289
+ else if (repaired) did.push(`Rewrote ${plural(repaired, "sentence")} that did not hold up.`);
9290
+ else if (deleted) did.push(`Cut ${plural(deleted, "sentence")} that nothing backed.`);
9291
+ const repair = reasonOf("w5");
9292
+ if (changed("w5") && repair.startsWith("repaired:")) {
9293
+ const weakest = WEAKEST[repair.slice("repaired:".length)];
9294
+ did.push(weakest ? `Reworked ${weakest}.` : "Reworked its weakest part.");
9295
+ }
9296
+ const flagged = [];
9297
+ const open = counts.get("open");
9298
+ if (open) flagged.push(`${plural(open, "sentence problem")} it could not fix`);
9299
+ const second = reasonOf("w6");
9300
+ if (changed("w6") && !second.startsWith("kept_before_repair")) {
9301
+ const concern = CONCERNS[second];
9302
+ flagged.push(concern ? `a second-check concern (${concern})` : "a second-check concern");
9303
+ }
9304
+ return { did: did.join(" "), flagged };
9305
+ }
9306
+ function and(parts) {
9307
+ return parts.length < 2 ? parts[0] ?? "" : `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
9308
+ }
9309
+ function whatAmdahlDid(result) {
9310
+ const steps = result.unchanged === true ? { did: "", flagged: [] } : readSteps(list(result.steps) ?? []);
9311
+ const flagged = [...steps.flagged];
9312
+ let checked = "";
9313
+ let removed = "";
9314
+ const evidence = record(result.evidence);
9315
+ const claims = list(result.unsupported_claims);
9316
+ if (evidence?.used === true) {
9317
+ const quotes = record(result.lift)?.quotes;
9318
+ const read = typeof quotes === "number" && quotes > 0 ? ` (${plural(quotes, "quote")})` : "";
9319
+ if (claims) {
9320
+ const still = claims.filter((c) => c.status !== "removed").length;
9321
+ const cut = claims.length - still;
9322
+ if (cut) removed = `Removed ${plural(cut, "claim")} your calls don't back.`;
9323
+ if (still) flagged.unshift(`${plural(still, "claim")} your calls don't back`);
9324
+ checked = `Checked every claim against your calls${read}${claims.length ? "" : " and found none they don't back"}.`;
9325
+ } else checked = `Checked the message against your calls${read}.`;
9326
+ } else if (evidence?.requested === "workspace" && EVIDENCE_MISSED[text(evidence.reason)]) {
9327
+ checked = `Skipped checking claims against your calls: ${EVIDENCE_MISSED[text(evidence.reason)]}.`;
9328
+ }
9329
+ const used = (list(record(result.context_use)?.rows) ?? []).filter((r) => r.verdict === "pass");
9330
+ const sources = [
9331
+ used.some((r) => r.bucket === "internal") ? "Amdahl's own records" : "",
9332
+ used.some((r) => r.bucket === "external") ? "what their company or market published" : ""
9333
+ ].filter(Boolean);
9334
+ const usedLine = sources.length ? `Used ${plural(sources.length, "source")}: ${and(sources)}.` : "";
9335
+ const omitted = list(result.omitted) ?? [];
9336
+ const whys = [...new Set(omitted.map((o) => text(o.why)).filter(Boolean))];
9337
+ const more = whys.length > 1 ? ` (${plural(whys.length - 1, "more reason")} in --json)` : "";
9338
+ const leftOut = omitted.length ? `Left out ${plural(omitted.length, "thing")}${whys[0] ? `: ${sentence(whys[0])}` : "."}${more}` : "";
9339
+ const lines = [
9340
+ { rank: 2, line: sentence(text(result.what_changed)) },
9341
+ { rank: 5, line: checked },
9342
+ { rank: 3, line: removed },
9343
+ { rank: 6, line: usedLine },
9344
+ { rank: 7, line: steps.did },
9345
+ { rank: 4, line: leftOut },
9346
+ { rank: 1, line: flagged.length ? `Flagged ${and(flagged)}; check before sending.` : "" }
9347
+ ].filter((l) => l.line && l.line !== ".");
9348
+ const kept = new Set([...lines].sort((a, b) => a.rank - b.rank).slice(0, MAX_LINES));
9349
+ return lines.filter((l) => kept.has(l)).map((l) => l.line);
9350
+ }
9181
9351
  async function optimize(ctx, args) {
9182
9352
  const { io, out, flags } = ctx;
9183
9353
  let files = args;
@@ -9242,6 +9412,8 @@ async function optimize(ctx, args) {
9242
9412
  if (result.unchanged === true) out.info("No rewrite beat your draft; it is returned unchanged.");
9243
9413
  else if (typeof result.summary === "string") out.info(result.summary);
9244
9414
  out.result({}, String(result.message ?? ""));
9415
+ const did = whatAmdahlDid(result);
9416
+ if (did.length) out.info(did.join("\n"));
9245
9417
  }
9246
9418
  }
9247
9419
  if (out.json) out.result(results.length === 1 ? results[0] : results);
@@ -9564,8 +9736,8 @@ async function setup(ctx) {
9564
9736
  const verbose = ctx.flags.verbose === true;
9565
9737
  const say = (line) => io.stdout(`${line}
9566
9738
  `);
9567
- const detail = (text) => {
9568
- if (verbose && text) say(text.replace(/^/gm, " "));
9739
+ const detail = (text2) => {
9740
+ if (verbose && text2) say(text2.replace(/^/gm, " "));
9569
9741
  };
9570
9742
  say(`${out.bold("Amdahl")} \xB7 predictive ML for GTM`);
9571
9743
  const found = await detectClients(ctx);
@@ -9716,8 +9888,8 @@ function field(header, start, length) {
9716
9888
  }
9717
9889
  }
9718
9890
  function octal(header, start, length) {
9719
- const text = header.toString("latin1", start, start + length).replace(/[\0 ]+$/, "").replace(/^ +/, "");
9720
- return /^[0-7]{1,12}$/.test(text) ? parseInt(text, 8) : null;
9891
+ const text2 = header.toString("latin1", start, start + length).replace(/[\0 ]+$/, "").replace(/^ +/, "");
9892
+ return /^[0-7]{1,12}$/.test(text2) ? parseInt(text2, 8) : null;
9721
9893
  }
9722
9894
  function checkPath(path) {
9723
9895
  const shown = JSON.stringify(path);
@@ -9843,15 +10015,15 @@ var UNREADABLE_WARNING = Object.freeze({
9843
10015
  function readWarning(raw) {
9844
10016
  if (raw === void 0 || raw === null) return void 0;
9845
10017
  if (typeof raw !== "object" || Array.isArray(raw)) return { ...UNREADABLE_WARNING };
9846
- const { result, flagged, line, text } = raw;
10018
+ const { result, flagged, line, text: text2 } = raw;
9847
10019
  if (result !== "warn" && result !== "fail") return { ...UNREADABLE_WARNING };
9848
- const sentence = typeof text === "string" ? warningText(text) : "";
9849
- if (!sentence) return { ...UNREADABLE_WARNING };
10020
+ const sentence2 = typeof text2 === "string" ? warningText(text2) : "";
10021
+ if (!sentence2) return { ...UNREADABLE_WARNING };
9850
10022
  return {
9851
10023
  result,
9852
10024
  flagged: typeof flagged === "string" && CHECK_ID_RE.test(flagged) ? flagged : null,
9853
10025
  line: typeof line === "number" && Number.isSafeInteger(line) && line > 0 ? line : null,
9854
- text: sentence
10026
+ text: sentence2
9855
10027
  };
9856
10028
  }
9857
10029
  function skillPage(site, ref) {
@@ -10047,13 +10219,13 @@ function checkDigest(body, fileUrl, sha256) {
10047
10219
  }
10048
10220
  function checkSkillMd(body, what, ref) {
10049
10221
  const bad = (why) => new CliError("invalid_skill", `${what} is not a valid skill: ${why}.`, EXIT.general);
10050
- let text;
10222
+ let text2;
10051
10223
  try {
10052
- text = new TextDecoder("utf-8", { fatal: true }).decode(body);
10224
+ text2 = new TextDecoder("utf-8", { fatal: true }).decode(body);
10053
10225
  } catch {
10054
10226
  throw bad("it is not UTF-8 text");
10055
10227
  }
10056
- const fm = FRONT_MATTER_RE.exec(text);
10228
+ const fm = FRONT_MATTER_RE.exec(text2);
10057
10229
  if (!fm) throw bad("it has no front matter");
10058
10230
  const doc = (0, import_yaml.parseDocument)(fm[1] ?? "", { uniqueKeys: true });
10059
10231
  const yamlError = doc.errors[0];
@@ -10248,7 +10420,7 @@ async function workspaceUse(ctx, args) {
10248
10420
  }
10249
10421
 
10250
10422
  // src/version.ts
10251
- var VERSION = "0.3.1";
10423
+ var VERSION = "0.3.3";
10252
10424
 
10253
10425
  // src/main.ts
10254
10426
  var COMMANDS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amdahl/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "The amdahl command line: sign in, check setup, optimize messages, manage API keys, connect MCP clients.",
6
6
  "license": "Apache-2.0",