@amdahl/cli 0.3.2 → 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.
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 a minute, 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
@@ -7478,6 +7478,42 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
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 {
@@ -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: 6e4,
7580
7616
  fetchTimeoutMs: 15e3
7581
7617
  };
7582
7618
  }
@@ -8545,6 +8581,18 @@ var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/g;
8545
8581
  function printable(text2) {
8546
8582
  return text2.replace(CONTROL_RE, "");
8547
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\\`;
8595
+ }
8548
8596
  function createOutput(io, json, noColor) {
8549
8597
  const color = !noColor && !io.env.NO_COLOR && io.env.TERM !== "dumb" && io.stderrIsTTY && !json;
8550
8598
  return {
@@ -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
  }
@@ -10363,7 +10420,7 @@ async function workspaceUse(ctx, args) {
10363
10420
  }
10364
10421
 
10365
10422
  // src/version.ts
10366
- var VERSION = "0.3.2";
10423
+ var VERSION = "0.3.3";
10367
10424
 
10368
10425
  // src/main.ts
10369
10426
  var COMMANDS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amdahl/cli",
3
- "version": "0.3.2",
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",