@curviate/cli 0.15.1 → 0.15.2

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/CHANGELOG.md CHANGED
@@ -6,6 +6,26 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
  Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html):
7
7
  a new command or flag is a minor; a breaking command/flag/exit-code change is a major; a fix is a patch.
8
8
 
9
+ ## [0.15.2] - 2026-07-12
10
+
11
+ A patch release fixing an interactive-terminal hang on `account link`.
12
+
13
+ ### Fixed
14
+
15
+ - **`account link --password-stdin` / `--li-at-stdin` no longer hang on an
16
+ interactive terminal.** These flags previously read stdin to EOF, which a
17
+ human paste + Enter never produces on a TTY — the command hung
18
+ indefinitely and the pasted secret echoed on-screen. The read is now
19
+ mode-aware: piped/redirected stdin (non-TTY) is unchanged (read to EOF,
20
+ trimmed); an interactive TTY now prints a single cue line, then reads one
21
+ no-echo line — paste + Enter resolves immediately, including a paste whose
22
+ clipboard content ends in a trailing newline. An empty line still falls
23
+ through to the normal resolution order (env var, then the password
24
+ prompt / `li_at` fail-fast).
25
+ - **`--preview` never blocks on a terminal read.** Under `--preview`, the
26
+ interactive stdin read is suppressed entirely, matching every other
27
+ preview-mode command.
28
+
9
29
  ## [0.15.1] - 2026-07-11
10
30
 
11
31
  A patch release of agent-experience (AX) and developer-experience (DX)
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-U7Y4EXHT.js";
5
5
  import {
6
6
  readlineSync
7
- } from "./chunk-MKXA2LAR.js";
7
+ } from "./chunk-H4KV7MIN.js";
8
8
  import {
9
9
  AUTH_NEEDED
10
10
  } from "./chunk-ZE7QGR3F.js";
@@ -37,16 +37,33 @@ import {
37
37
  import { defineCommand } from "citty";
38
38
 
39
39
  // src/lib/credential-resolve.ts
40
+ var STDIN_TTY_CUE = "Reading secret from stdin (paste + Enter): ";
41
+ function defaultReadSingleLine() {
42
+ return readlineSync("", { mask: true });
43
+ }
40
44
  async function resolveSecret(params) {
41
45
  if (params.flagValue !== void 0 && params.flagValue !== "") {
42
46
  return params.flagValue;
43
47
  }
44
48
  if (params.stdinRequested) {
45
- const reader = params.readStdin ?? defaultReadStdin;
46
- const raw = await reader();
47
- const trimmed = raw.trim();
48
- if (trimmed !== "") {
49
- return trimmed;
49
+ if (params.isTTY) {
50
+ const stdinReadAllowed = params.allowInteractiveStdinRead ?? params.allowInteractive;
51
+ if (stdinReadAllowed !== false) {
52
+ params.out.stderr.write(STDIN_TTY_CUE);
53
+ const reader = params.readSingleLine ?? defaultReadSingleLine;
54
+ const raw = await reader(STDIN_TTY_CUE);
55
+ const trimmed = raw.trim();
56
+ if (trimmed !== "") {
57
+ return trimmed;
58
+ }
59
+ }
60
+ } else {
61
+ const reader = params.readStdin ?? defaultReadStdin;
62
+ const raw = await reader();
63
+ const trimmed = raw.trim();
64
+ if (trimmed !== "") {
65
+ return trimmed;
66
+ }
50
67
  }
51
68
  }
52
69
  const envValue = process.env[params.envVar];
@@ -262,12 +279,20 @@ async function buildAuthBody(flags, ctx) {
262
279
  flagValue: flags.password,
263
280
  stdinRequested: flags["password-stdin"],
264
281
  envVar: "CURVIATE_LINKEDIN_PASSWORD",
282
+ isTTY: ctx.isTTY,
265
283
  readStdin: ctx.readStdin,
284
+ readSingleLine: ctx.readSingleLine,
266
285
  required: true,
267
- // The interactive prompt/fail-fast only engages once --email is present
268
- // (nothing meaningful to prompt toward yet otherwise) — and never
269
- // under --preview (a client-side render must not prompt or exit).
286
+ // The masked-prompt/fail-fast tiers (3/4) only engage once --email is
287
+ // present (nothing meaningful to prompt toward yet otherwise) — and
288
+ // never under --preview (a client-side render must not prompt or
289
+ // exit).
270
290
  allowInteractive: !ctx.previewMode && Boolean(flags.email),
291
+ // Tier-1b's own gate is preview-only, deliberately NOT email-gated: a
292
+ // user who passed --password-stdin on a TTY gets the read regardless
293
+ // of --email — an email-less body still fails downstream validation,
294
+ // as expected, rather than the flag being silently ignored.
295
+ allowInteractiveStdinRead: !ctx.previewMode,
271
296
  failMessage: "no password \u2014 pass --password, --password-stdin, or set CURVIATE_LINKEDIN_PASSWORD",
272
297
  prompt: { isTTY: ctx.isTTY, readline: ctx.readline, promptText: "LinkedIn password: " },
273
298
  out: ctx.out
@@ -284,7 +309,9 @@ async function buildAuthBody(flags, ctx) {
284
309
  flagValue: flags["li-at"],
285
310
  stdinRequested: flags["li-at-stdin"],
286
311
  envVar: "CURVIATE_LINKEDIN_LI_AT",
312
+ isTTY: ctx.isTTY,
287
313
  readStdin: ctx.readStdin,
314
+ readSingleLine: ctx.readSingleLine,
288
315
  required: true,
289
316
  allowInteractive: !ctx.previewMode,
290
317
  failMessage: "no li_at \u2014 pass --li-at, --li-at-stdin, or set CURVIATE_LINKEDIN_LI_AT",
@@ -330,6 +357,13 @@ function resolveCredentialIO(io) {
330
357
  isOutputTTY: io.isOutputTTY ?? (process.stdout.isTTY ?? false),
331
358
  readline: io.readline ?? readlineSync,
332
359
  readStdin: io.readStdin ?? defaultReadStdin,
360
+ // Always the masked, no-echo raw-mode branch — never a bare readlineSync
361
+ // pass-through, which would default mask to false and echo the secret.
362
+ // Prompt is deliberately EMPTY, not `cue`: credential-resolve.ts already
363
+ // wrote STDIN_TTY_CUE to out.stderr before calling this reader, and
364
+ // readlineSync itself writes its `prompt` argument to stderr too — the
365
+ // cue text would otherwise print twice on a real terminal.
366
+ readSingleLine: io.readSingleLine ?? (() => readlineSync("", { mask: true })),
333
367
  sleep: io.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),
334
368
  now: io.now ?? (() => Date.now()),
335
369
  open: io.open ?? defaultOpen
@@ -464,6 +498,7 @@ async function runAccountLink(client, flags, out, io = {}) {
464
498
  isTTY: resolvedIo.isTTY,
465
499
  readline: resolvedIo.readline,
466
500
  readStdin: resolvedIo.readStdin,
501
+ readSingleLine: resolvedIo.readSingleLine,
467
502
  previewMode: flags.preview ?? false
468
503
  });
469
504
  const body = {
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/readline.ts
4
+ import { createInterface } from "readline";
5
+ async function readlineSync(prompt, opts = {}) {
6
+ const stdin = opts.stdin ?? process.stdin;
7
+ return new Promise((resolve, reject) => {
8
+ process.stderr.write(prompt);
9
+ if (opts.mask && typeof stdin.setRawMode === "function") {
10
+ stdin.setRawMode(true);
11
+ stdin.resume();
12
+ stdin.setEncoding("utf8");
13
+ let input = "";
14
+ const onData = (chunk) => {
15
+ for (let i = 0; i < chunk.length; i++) {
16
+ const char = chunk[i];
17
+ if (char === "\r" || char === "\n") {
18
+ stdin.setRawMode?.(false);
19
+ stdin.pause();
20
+ stdin.removeListener("data", onData);
21
+ process.stderr.write("\n");
22
+ resolve(input);
23
+ return;
24
+ } else if (char === "") {
25
+ stdin.setRawMode?.(false);
26
+ stdin.pause();
27
+ stdin.removeListener("data", onData);
28
+ reject(new Error("Interrupted."));
29
+ return;
30
+ } else if (char === "\x7F" || char === "\b") {
31
+ input = input.slice(0, -1);
32
+ } else {
33
+ input += char;
34
+ }
35
+ }
36
+ };
37
+ stdin.on("data", onData);
38
+ } else {
39
+ const rl = createInterface({
40
+ input: stdin,
41
+ output: void 0,
42
+ // suppress default echo (prompt already on stderr)
43
+ terminal: false
44
+ });
45
+ rl.once("line", (line) => {
46
+ rl.close();
47
+ resolve(line.trim());
48
+ });
49
+ rl.once("error", reject);
50
+ rl.once("close", () => resolve(""));
51
+ }
52
+ });
53
+ }
54
+
55
+ export {
56
+ readlineSync
57
+ };
package/dist/cli.js CHANGED
@@ -250,7 +250,7 @@ var main = defineCommand({
250
250
  // Subcommand registry — names and descriptions are static for help rendering;
251
251
  // the handler implementation is loaded lazily on first invocation.
252
252
  subCommands: {
253
- login: () => import("./login-MFB45PVZ.js").then((m) => m.loginCommand),
253
+ login: () => import("./login-GDLWR542.js").then((m) => m.loginCommand),
254
254
  config: () => import("./config-P5CPF5WC.js").then((m) => m.configCommand),
255
255
  // ---------------------------------------------------------------------------
256
256
  // Noun groups — lazy-loaded on first invocation.
@@ -264,7 +264,7 @@ var main = defineCommand({
264
264
  message: () => import("./message-2DLIQIE6.js").then((m) => m.messageCommand),
265
265
  post: () => import("./post-GRIJ7X52.js").then((m) => m.postCommand),
266
266
  comment: () => import("./comment-NCDXERSI.js").then((m) => m.commentCommand),
267
- account: () => import("./account-FM473HEK.js").then((m) => m.accountCommand),
267
+ account: () => import("./account-PD3QLOTG.js").then((m) => m.accountCommand),
268
268
  webhook: () => import("./webhook-CEP7SGP5.js").then((m) => m.webhookCommand),
269
269
  "sales-nav": () => import("./sales-nav-NUMEYOEO.js").then((m) => m.salesNavCommand),
270
270
  recruiter: () => import("./recruiter-CTYH7AYG.js").then((m) => m.recruiterCommand)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readlineSync
4
- } from "./chunk-MKXA2LAR.js";
4
+ } from "./chunk-H4KV7MIN.js";
5
5
  import {
6
6
  GLOBAL_FLAGS,
7
7
  writeProfile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curviate/cli",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "private": false,
5
5
  "description": "Official command-line interface for the Curviate API.",
6
6
  "license": "MIT",
@@ -1,51 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/lib/readline.ts
4
- import { createInterface } from "readline";
5
- async function readlineSync(prompt, opts = {}) {
6
- return new Promise((resolve, reject) => {
7
- process.stderr.write(prompt);
8
- if (opts.mask && typeof process.stdin.setRawMode === "function") {
9
- process.stdin.setRawMode(true);
10
- process.stdin.resume();
11
- process.stdin.setEncoding("utf8");
12
- let input = "";
13
- const onData = (char) => {
14
- if (char === "\r" || char === "\n") {
15
- process.stdin.setRawMode(false);
16
- process.stdin.pause();
17
- process.stdin.removeListener("data", onData);
18
- process.stderr.write("\n");
19
- resolve(input);
20
- } else if (char === "") {
21
- process.stdin.setRawMode(false);
22
- process.stdin.pause();
23
- process.stdin.removeListener("data", onData);
24
- reject(new Error("Interrupted."));
25
- } else if (char === "\x7F" || char === "\b") {
26
- input = input.slice(0, -1);
27
- } else {
28
- input += char;
29
- }
30
- };
31
- process.stdin.on("data", onData);
32
- } else {
33
- const rl = createInterface({
34
- input: process.stdin,
35
- output: void 0,
36
- // suppress default echo (prompt already on stderr)
37
- terminal: false
38
- });
39
- rl.once("line", (line) => {
40
- rl.close();
41
- resolve(line.trim());
42
- });
43
- rl.once("error", reject);
44
- rl.once("close", () => resolve(""));
45
- }
46
- });
47
- }
48
-
49
- export {
50
- readlineSync
51
- };