primcli 1.69.0 → 1.70.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.
Files changed (2) hide show
  1. package/dist/oclif/index.js +166 -24
  2. package/package.json +1 -1
@@ -44956,7 +44956,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
44956
44956
  url: "https://primitive.dev"
44957
44957
  };
44958
44958
  const SDK_VERSION_RANGE = "^2.0.0";
44959
- const CLI_VERSION_RANGE = "^1.69.0";
44959
+ const CLI_VERSION_RANGE = "^1.70.0";
44960
44960
  const ESBUILD_VERSION_RANGE = "^0.27.0";
44961
44961
  function renderHandler() {
44962
44962
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -50376,14 +50376,7 @@ var ListenPendingCommand = class ListenPendingCommand extends Command {
50376
50376
  }
50377
50377
  };
50378
50378
  //#endregion
50379
- //#region src/oclif/commands/login.ts
50380
- const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
50381
- function cliError$3(message) {
50382
- return new Errors.CLIError(message, { exit: 1 });
50383
- }
50384
- function sleep(ms) {
50385
- return new Promise((resolve) => setTimeout(resolve, ms));
50386
- }
50379
+ //#region src/oclif/browser-login-prompt.ts
50387
50380
  function openBrowser(url) {
50388
50381
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
50389
50382
  const args = process.platform === "win32" ? [
@@ -50399,6 +50392,143 @@ function openBrowser(url) {
50399
50392
  child.on("error", () => void 0);
50400
50393
  child.unref();
50401
50394
  }
50395
+ /**
50396
+ * The copy-URL hotkey needs raw keypresses from a terminal and a terminal
50397
+ * to show the hint on. Anything else (CI, agents, piped output) gets the
50398
+ * plain URL and never blocks on input.
50399
+ */
50400
+ function canOfferCopyHotkey(input, output) {
50401
+ return Boolean(input.isTTY && output.isTTY && typeof input.setRawMode === "function");
50402
+ }
50403
+ function formatBrowserLoginInstructions(params) {
50404
+ const { copyHotkey, openedBrowser, start } = params;
50405
+ const copyHint = copyHotkey ? " (c to copy)" : "";
50406
+ const lines = [`Your sign-in code is: ${start.user_code}`];
50407
+ if (openedBrowser) lines.push("Opening Primitive in your browser...", "", `Browser didn't open, or want a different one? Use this URL${copyHint}:`);
50408
+ else lines.push("", `Open this URL in any browser to sign in${copyHint}:`);
50409
+ lines.push(` ${start.verification_uri_complete}`);
50410
+ if (start.verification_uri !== start.verification_uri_complete) lines.push(`On another device, go to ${start.verification_uri} and enter ${start.user_code}.`);
50411
+ lines.push("");
50412
+ return `${lines.join("\n")}\n`;
50413
+ }
50414
+ /**
50415
+ * OSC 52 asks the terminal emulator itself to set the clipboard, so it
50416
+ * works over SSH, where a local clipboard command would copy on the
50417
+ * remote host instead of the machine the user is sitting at.
50418
+ */
50419
+ function osc52Sequence(text) {
50420
+ return `\u001b]52;c;${Buffer.from(text, "utf8").toString("base64")}\u0007`;
50421
+ }
50422
+ function nativeClipboardCommand() {
50423
+ if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return null;
50424
+ if (process.platform === "darwin") return ["pbcopy", []];
50425
+ if (process.platform === "win32") return ["clip", []];
50426
+ if (process.env.WAYLAND_DISPLAY) return ["wl-copy", []];
50427
+ if (process.env.DISPLAY) return ["xclip", ["-selection", "clipboard"]];
50428
+ return null;
50429
+ }
50430
+ /**
50431
+ * Resolves false on any failure, including a tool that exits before
50432
+ * reading its input (EPIPE on stdin), so the caller falls back to OSC 52
50433
+ * instead of the error taking down login.
50434
+ */
50435
+ function runClipboardCommand(command, args, text) {
50436
+ return new Promise((resolve) => {
50437
+ let inputFailed = false;
50438
+ const child = spawn(command, args, { stdio: [
50439
+ "pipe",
50440
+ "ignore",
50441
+ "ignore"
50442
+ ] });
50443
+ child.on("error", () => resolve(false));
50444
+ child.stdin.on("error", () => {
50445
+ inputFailed = true;
50446
+ });
50447
+ child.on("close", (code) => resolve(code === 0 && !inputFailed));
50448
+ child.stdin.end(text);
50449
+ });
50450
+ }
50451
+ function copyWithNativeClipboard(text) {
50452
+ const command = nativeClipboardCommand();
50453
+ if (!command) return Promise.resolve(false);
50454
+ return runClipboardCommand(command[0], command[1], text);
50455
+ }
50456
+ /**
50457
+ * Copies via the native clipboard when running locally and always emits
50458
+ * OSC 52 as well. Terminals without OSC 52 support (macOS Terminal.app)
50459
+ * are covered by the native path; SSH sessions are covered by OSC 52.
50460
+ */
50461
+ async function copyToClipboard(text, output, native = copyWithNativeClipboard) {
50462
+ output.write(osc52Sequence(text));
50463
+ return await native(text) ? "native" : "terminal";
50464
+ }
50465
+ const CTRL_C = "";
50466
+ const CTRL_Z = "";
50467
+ /**
50468
+ * Listens for `c` while the CLI polls for browser approval. Raw mode turns
50469
+ * Ctrl+C and Ctrl+Z into plain input, so they are handled here: Ctrl+C is
50470
+ * re-raised as SIGINT, and Ctrl+Z restores the terminal, suspends with
50471
+ * SIGTSTP, and re-enters raw mode on SIGCONT. Several keys can arrive in
50472
+ * one chunk (pastes, SSH), so every character is checked. Returns a
50473
+ * disposer that restores the terminal.
50474
+ */
50475
+ function listenForCopyHotkey(params) {
50476
+ const { input, output, url } = params;
50477
+ const copy = params.copy ?? copyToClipboard;
50478
+ let disposed = false;
50479
+ const copyUrl = () => {
50480
+ copy(url, output).then((how) => {
50481
+ if (disposed) return;
50482
+ output.write(how === "native" ? "Copied the sign-in URL to your clipboard.\n" : "Sent the sign-in URL to your terminal's clipboard. If nothing was copied, select the URL above instead.\n");
50483
+ });
50484
+ };
50485
+ const onContinue = () => {
50486
+ if (!disposed) input.setRawMode?.(true);
50487
+ };
50488
+ const suspend = () => {
50489
+ input.setRawMode?.(false);
50490
+ process.once("SIGCONT", onContinue);
50491
+ process.kill(process.pid, "SIGTSTP");
50492
+ };
50493
+ const onData = (chunk) => {
50494
+ let wantsCopy = false;
50495
+ for (const key of chunk.toString()) {
50496
+ if (key === CTRL_C) {
50497
+ dispose();
50498
+ process.kill(process.pid, "SIGINT");
50499
+ return;
50500
+ }
50501
+ if (key === CTRL_Z && process.platform !== "win32") {
50502
+ if (wantsCopy) copyUrl();
50503
+ suspend();
50504
+ return;
50505
+ }
50506
+ if (key === "c" || key === "C") wantsCopy = true;
50507
+ }
50508
+ if (wantsCopy) copyUrl();
50509
+ };
50510
+ const dispose = () => {
50511
+ if (disposed) return;
50512
+ disposed = true;
50513
+ input.off("data", onData);
50514
+ process.off("SIGCONT", onContinue);
50515
+ input.setRawMode?.(false);
50516
+ input.pause();
50517
+ };
50518
+ input.setRawMode?.(true);
50519
+ input.on("data", onData);
50520
+ input.resume();
50521
+ return dispose;
50522
+ }
50523
+ //#endregion
50524
+ //#region src/oclif/commands/login.ts
50525
+ const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
50526
+ function cliError$3(message) {
50527
+ return new Errors.CLIError(message, { exit: 1 });
50528
+ }
50529
+ function sleep(ms) {
50530
+ return new Promise((resolve) => setTimeout(resolve, ms));
50531
+ }
50402
50532
  function unwrapData$2(value) {
50403
50533
  return value?.data ?? null;
50404
50534
  }
@@ -50584,22 +50714,34 @@ var LoginCommand$1 = class extends Command {
50584
50714
  }
50585
50715
  const start = unwrapData$2(started.data);
50586
50716
  if (!start) throw cliError$3("Primitive API returned an empty CLI login response.");
50587
- process.stderr.write(`Your sign-in code is: ${start.user_code}\n`);
50588
- if (!flags["no-browser"]) {
50589
- openBrowser(start.verification_uri_complete);
50590
- process.stderr.write("Opening Primitive in your browser...\n");
50591
- }
50592
- process.stderr.write(`If the browser did not open, visit: ${start.verification_uri_complete}\n`);
50593
- process.stderr.write("Waiting for browser approval...\n");
50594
- const login = await pollCliLoginUntilApproved({
50595
- poll: (deviceCode) => pollCliLogin({
50596
- body: { device_code: deviceCode },
50597
- client: apiClient.client,
50598
- responseStyle: "fields"
50599
- }),
50600
- retryCommand,
50717
+ const openedBrowser = !flags["no-browser"];
50718
+ if (openedBrowser) openBrowser(start.verification_uri_complete);
50719
+ const copyHotkey = canOfferCopyHotkey(process.stdin, process.stderr);
50720
+ const stopCopyHotkey = copyHotkey ? listenForCopyHotkey({
50721
+ input: process.stdin,
50722
+ output: process.stderr,
50723
+ url: start.verification_uri_complete
50724
+ }) : () => void 0;
50725
+ process.stderr.write(formatBrowserLoginInstructions({
50726
+ copyHotkey,
50727
+ openedBrowser,
50601
50728
  start
50602
- });
50729
+ }));
50730
+ process.stderr.write("Waiting for browser approval...\n");
50731
+ let login;
50732
+ try {
50733
+ login = await pollCliLoginUntilApproved({
50734
+ poll: (deviceCode) => pollCliLogin({
50735
+ body: { device_code: deviceCode },
50736
+ client: apiClient.client,
50737
+ responseStyle: "fields"
50738
+ }),
50739
+ retryCommand,
50740
+ start
50741
+ });
50742
+ } finally {
50743
+ stopCopyHotkey();
50744
+ }
50603
50745
  deleteChatState(this.config.configDir);
50604
50746
  saveCliCredentials(this.config.configDir, {
50605
50747
  access_token: login.access_token,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.69.0",
3
+ "version": "1.70.0",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,