@curviate/cli 0.9.0 → 0.11.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.
@@ -0,0 +1,1401 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ defaultReadStdin
4
+ } from "./chunk-U7Y4EXHT.js";
5
+ import {
6
+ readlineSync
7
+ } from "./chunk-MKXA2LAR.js";
8
+ import {
9
+ AUTH_NEEDED
10
+ } from "./chunk-OSQ643UW.js";
11
+ import {
12
+ streamAll
13
+ } from "./chunk-GXTZION6.js";
14
+ import {
15
+ buildPreviewOutput
16
+ } from "./chunk-R3VLWLVV.js";
17
+ import {
18
+ slimAccountGet,
19
+ slimAccountList,
20
+ slimAccountListItem
21
+ } from "./chunk-SSPILTKF.js";
22
+ import {
23
+ createClient,
24
+ renderError,
25
+ renderSuccess,
26
+ renderUnexpectedError,
27
+ resolveEffectiveConfig
28
+ } from "./chunk-JXF47TRY.js";
29
+ import {
30
+ GLOBAL_FLAGS,
31
+ READ_SINGLE_FLAGS,
32
+ WRITE_SINGLE_FLAGS
33
+ } from "./chunk-4JHGVY7R.js";
34
+
35
+ // src/commands/account.ts
36
+ import { defineCommand } from "citty";
37
+
38
+ // src/lib/credential-resolve.ts
39
+ async function resolveSecret(params) {
40
+ if (params.flagValue !== void 0 && params.flagValue !== "") {
41
+ return params.flagValue;
42
+ }
43
+ if (params.stdinRequested) {
44
+ const reader = params.readStdin ?? defaultReadStdin;
45
+ const raw = await reader();
46
+ const trimmed = raw.trim();
47
+ if (trimmed !== "") {
48
+ return trimmed;
49
+ }
50
+ }
51
+ const envValue = process.env[params.envVar];
52
+ if (envValue !== void 0 && envValue !== "") {
53
+ return envValue;
54
+ }
55
+ if (!params.required) {
56
+ return void 0;
57
+ }
58
+ if (params.allowInteractive === false) {
59
+ return void 0;
60
+ }
61
+ if (params.prompt && params.prompt.isTTY) {
62
+ const value = await params.prompt.readline(params.prompt.promptText, { mask: true });
63
+ if (value) {
64
+ return value;
65
+ }
66
+ }
67
+ params.out.stderr.write(`error: ${params.failMessage ?? "missing required credential"}
68
+ `);
69
+ process.exit(2);
70
+ }
71
+ function checkCredentialConflicts(flags, out) {
72
+ const passwordStdin = flags["password-stdin"] ?? false;
73
+ const liAtStdin = flags["li-at-stdin"] ?? false;
74
+ if (flags.password !== void 0 && passwordStdin) {
75
+ failConflict(out, "--password and --password-stdin cannot both be set \u2014 choose one source.");
76
+ }
77
+ if (flags["li-at"] !== void 0 && liAtStdin) {
78
+ failConflict(out, "--li-at and --li-at-stdin cannot both be set \u2014 choose one source.");
79
+ }
80
+ if (passwordStdin && liAtStdin) {
81
+ failConflict(
82
+ out,
83
+ "--password-stdin and --li-at-stdin cannot both be set \u2014 stdin can feed only one secret per invocation."
84
+ );
85
+ }
86
+ if (liAtStdin && flags["auth-method"] === "credentials") {
87
+ failConflict(out, "--li-at-stdin requires --auth-method cookie (it does not apply to the credentials method).");
88
+ }
89
+ if (passwordStdin && flags["auth-method"] === "cookie") {
90
+ failConflict(out, "--password-stdin requires --auth-method credentials (it does not apply to the cookie method).");
91
+ }
92
+ }
93
+ function failConflict(out, message) {
94
+ out.stderr.write(`error: ${message}
95
+ `);
96
+ process.exit(2);
97
+ }
98
+ var SECRET_MASK = "\u2022\u2022\u2022\u2022";
99
+ function isRecord(value) {
100
+ return typeof value === "object" && value !== null && !Array.isArray(value);
101
+ }
102
+ function maskCredentialSecretsForPreview(body) {
103
+ const masked = { ...body };
104
+ const credentials = masked["credentials"];
105
+ if (isRecord(credentials) && "password" in credentials) {
106
+ masked["credentials"] = { ...credentials, password: SECRET_MASK };
107
+ }
108
+ const cookie = masked["cookie"];
109
+ if (isRecord(cookie)) {
110
+ const maskedCookie = { ...cookie };
111
+ if ("li_at" in maskedCookie) maskedCookie["li_at"] = SECRET_MASK;
112
+ if ("li_a" in maskedCookie) maskedCookie["li_a"] = SECRET_MASK;
113
+ masked["cookie"] = maskedCookie;
114
+ }
115
+ const proxy = masked["proxy"];
116
+ if (isRecord(proxy) && "password" in proxy) {
117
+ masked["proxy"] = { ...proxy, password: SECRET_MASK };
118
+ }
119
+ return masked;
120
+ }
121
+
122
+ // src/lib/checkpoint-copy.ts
123
+ var RESENDABLE_CHALLENGE_TYPES = /* @__PURE__ */ new Set([
124
+ "otp",
125
+ "two_factor_sms",
126
+ "mobile_app_approval"
127
+ ]);
128
+ function challengeTitle(type) {
129
+ switch (type) {
130
+ case "otp":
131
+ return "Check your email";
132
+ case "two_factor_sms":
133
+ return "Enter SMS code";
134
+ case "two_factor_app":
135
+ return "Enter authenticator code";
136
+ case "mobile_app_approval":
137
+ return "Approve in LinkedIn app";
138
+ }
139
+ }
140
+ function challengeDescription(type) {
141
+ switch (type) {
142
+ case "otp":
143
+ return "LinkedIn sent a one-time code to your registered email address.";
144
+ case "two_factor_sms":
145
+ return "LinkedIn sent a verification code to your registered phone number.";
146
+ case "two_factor_app":
147
+ return "Enter the current code from your authenticator app.";
148
+ case "mobile_app_approval":
149
+ return "Open your LinkedIn mobile app and approve this sign-in \u2014 this will continue automatically once you tap approve.";
150
+ }
151
+ }
152
+ function printChallengeCopy(type, out) {
153
+ out.stderr.write(`${challengeTitle(type)}
154
+ ${challengeDescription(type)}
155
+ `);
156
+ }
157
+ function printResendHintIfApplicable(type, accountId, out) {
158
+ if (!RESENDABLE_CHALLENGE_TYPES.has(type)) return;
159
+ out.stderr.write(
160
+ `No code/notification? Re-send it: curviate account checkpoint resend --checkpoint ${accountId}
161
+ `
162
+ );
163
+ }
164
+
165
+ // src/lib/checkpoint-cadence.ts
166
+ var CHECKPOINT_POLL_FIRST_DELAY_MS = 1e3;
167
+ var CHECKPOINT_POLL_FAST_INTERVAL_MS = 1500;
168
+ var CHECKPOINT_POLL_FAST_WINDOW_MS = 3e4;
169
+ var CHECKPOINT_POLL_SLOW_INTERVAL_MS = 3e3;
170
+ function nextCheckpointPollDelayMs(elapsedMs) {
171
+ return elapsedMs < CHECKPOINT_POLL_FAST_WINDOW_MS ? CHECKPOINT_POLL_FAST_INTERVAL_MS : CHECKPOINT_POLL_SLOW_INTERVAL_MS;
172
+ }
173
+
174
+ // src/commands/account.ts
175
+ var PW_WARNING = (stdinFlag, envVar) => `Note: a value on the command line is visible to other processes via \`ps\` and saved in shell history; prefer \`${stdinFlag}\` or the ${envVar} env var.`;
176
+ var OPTIONAL_SECRET_WARNING = (envVar) => `Note: a value on the command line is visible to other processes via \`ps\` and saved in shell history; prefer the ${envVar} env var.`;
177
+ function rejectPreviewOnRead(preview, out) {
178
+ if (preview) {
179
+ out.stderr.write("error: --preview is only valid on write commands (mutations). Reads just run.\n");
180
+ process.exit(2);
181
+ }
182
+ }
183
+ function rejectAllOnNonPaginated(all, out) {
184
+ if (all) {
185
+ out.stderr.write("error: --all is not supported on non-paginated commands.\n");
186
+ process.exit(2);
187
+ }
188
+ }
189
+ function buildOutputStreams() {
190
+ return {
191
+ stdout: { write: (s) => process.stdout.write(s) },
192
+ stderr: { write: (s) => process.stderr.write(s) }
193
+ };
194
+ }
195
+ function resolveOutputOpts(flags) {
196
+ return {
197
+ json: (flags.json ?? false) || !process.stdout.isTTY,
198
+ isTTY: process.stdout.isTTY ?? false,
199
+ fields: flags.fields,
200
+ verbose: flags.verbose ?? false
201
+ };
202
+ }
203
+ async function handleError(err, outOpts, out) {
204
+ const { CurviateError } = await import("@curviate/sdk");
205
+ if (err instanceof CurviateError) {
206
+ const { getExitCode } = await import("./exit-codes-2ASIFXGR.js");
207
+ renderError(err, outOpts, out);
208
+ process.exit(getExitCode(err.code));
209
+ }
210
+ renderUnexpectedError(err, out);
211
+ process.exit(1);
212
+ }
213
+ async function runAccountList(client, flags, out) {
214
+ rejectPreviewOnRead(flags.preview, out);
215
+ const outOpts = resolveOutputOpts(flags);
216
+ const all = flags.all ?? false;
217
+ const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
218
+ const limit = flags.limit ? parseInt(flags.limit, 10) : void 0;
219
+ const cursor = flags.cursor;
220
+ const params = {};
221
+ if (limit !== void 0) params["limit"] = limit;
222
+ if (cursor) params["cursor"] = cursor;
223
+ try {
224
+ if (all) {
225
+ const fn = (p) => client.accounts.list(p);
226
+ for await (const item of streamAll(fn, params, {
227
+ maxPages,
228
+ onTruncated: (n) => out.stderr.write(`Streaming truncated at ${n} page(s). Use --all --max-pages or --cursor for manual paging.
229
+ `)
230
+ })) {
231
+ const projected = outOpts.verbose ? item : slimAccountListItem(item);
232
+ out.stdout.write(JSON.stringify(projected) + "\n");
233
+ }
234
+ } else {
235
+ const result = await client.accounts.list(params);
236
+ renderSuccess(result, { ...outOpts, slim: slimAccountList }, out);
237
+ }
238
+ } catch (err) {
239
+ await handleError(err, outOpts, out);
240
+ }
241
+ }
242
+ async function runAccountGet(client, flags, out) {
243
+ rejectPreviewOnRead(flags.preview, out);
244
+ rejectAllOnNonPaginated(flags.all, out);
245
+ const accountId = flags["account-id"] ?? "";
246
+ const outOpts = resolveOutputOpts(flags);
247
+ try {
248
+ const result = await client.accounts.get(accountId);
249
+ renderSuccess(result, { ...outOpts, slim: slimAccountGet }, out);
250
+ } catch (err) {
251
+ await handleError(err, outOpts, out);
252
+ }
253
+ }
254
+ async function buildAuthBody(flags, ctx) {
255
+ const body = {};
256
+ if (flags["auth-method"]) body["auth_method"] = flags["auth-method"];
257
+ if (flags["user-agent"]) body["user_agent"] = flags["user-agent"];
258
+ if (flags["recruiter-contract-id"]) body["recruiter_contract_id"] = flags["recruiter-contract-id"];
259
+ if (flags["auth-method"] === "credentials") {
260
+ const password = await resolveSecret({
261
+ flagValue: flags.password,
262
+ stdinRequested: flags["password-stdin"],
263
+ envVar: "CURVIATE_LINKEDIN_PASSWORD",
264
+ readStdin: ctx.readStdin,
265
+ required: true,
266
+ // The interactive prompt/fail-fast only engages once --email is present
267
+ // (nothing meaningful to prompt toward yet otherwise) — and never
268
+ // under --preview (a client-side render must not prompt or exit).
269
+ allowInteractive: !ctx.previewMode && Boolean(flags.email),
270
+ failMessage: "no password \u2014 pass --password, --password-stdin, or set CURVIATE_LINKEDIN_PASSWORD",
271
+ prompt: { isTTY: ctx.isTTY, readline: ctx.readline, promptText: "LinkedIn password: " },
272
+ out: ctx.out
273
+ });
274
+ if (flags.email || password !== void 0) {
275
+ body["credentials"] = {
276
+ ...flags.email ? { email: flags.email } : {},
277
+ ...password !== void 0 ? { password } : {}
278
+ };
279
+ }
280
+ }
281
+ if (flags["auth-method"] === "cookie") {
282
+ const liAt = await resolveSecret({
283
+ flagValue: flags["li-at"],
284
+ stdinRequested: flags["li-at-stdin"],
285
+ envVar: "CURVIATE_LINKEDIN_LI_AT",
286
+ readStdin: ctx.readStdin,
287
+ required: true,
288
+ allowInteractive: !ctx.previewMode,
289
+ failMessage: "no li_at \u2014 pass --li-at, --li-at-stdin, or set CURVIATE_LINKEDIN_LI_AT",
290
+ out: ctx.out
291
+ });
292
+ const liA = await resolveSecret({
293
+ flagValue: flags["li-a"],
294
+ envVar: "CURVIATE_LINKEDIN_LI_A",
295
+ out: ctx.out
296
+ });
297
+ if (liAt !== void 0 || liA !== void 0) {
298
+ body["cookie"] = {
299
+ ...liAt !== void 0 ? { li_at: liAt } : {},
300
+ ...liA !== void 0 ? { li_a: liA } : {}
301
+ };
302
+ }
303
+ }
304
+ if (flags.country) body["country"] = flags.country;
305
+ if (flags.ip) body["ip"] = flags.ip;
306
+ if (flags["proxy-host"]) {
307
+ const proxyPassword = await resolveSecret({
308
+ flagValue: flags["proxy-password"],
309
+ envVar: "CURVIATE_PROXY_PASSWORD",
310
+ out: ctx.out
311
+ });
312
+ body["proxy"] = {
313
+ protocol: flags["proxy-protocol"] ?? "http",
314
+ host: flags["proxy-host"],
315
+ port: flags["proxy-port"] ? parseInt(flags["proxy-port"], 10) : 80,
316
+ ...flags["proxy-username"] ? { username: flags["proxy-username"] } : {},
317
+ ...proxyPassword !== void 0 ? { password: proxyPassword } : {}
318
+ };
319
+ }
320
+ return body;
321
+ }
322
+ async function defaultOpen(url) {
323
+ const open = (await import("open")).default;
324
+ return open(url);
325
+ }
326
+ function resolveCredentialIO(io) {
327
+ return {
328
+ isTTY: io.isTTY ?? (process.stdin.isTTY ?? false),
329
+ isOutputTTY: io.isOutputTTY ?? (process.stdout.isTTY ?? false),
330
+ readline: io.readline ?? readlineSync,
331
+ readStdin: io.readStdin ?? defaultReadStdin,
332
+ sleep: io.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),
333
+ now: io.now ?? (() => Date.now()),
334
+ open: io.open ?? defaultOpen
335
+ };
336
+ }
337
+ function printConnected(result, ctx) {
338
+ const r = result;
339
+ ctx.out.stderr.write(`Account ${ctx.successVerb}: ${r.account_id ?? ""}
340
+ `);
341
+ renderSuccess(result, ctx.outOpts, ctx.out);
342
+ }
343
+ function printInvalidCodeRetryHint(err, out) {
344
+ const attemptsRemaining = err.attempts_remaining;
345
+ const message = typeof attemptsRemaining === "number" ? `That code was not accepted \u2014 ${attemptsRemaining} attempt(s) remaining.` : "That code was not accepted \u2014 please try again.";
346
+ out.stderr.write(`${message}
347
+ `);
348
+ }
349
+ async function waitForMobileApproval(client, accountId, expiresAt, ctx) {
350
+ const startedAt = ctx.now();
351
+ const timeoutAt = expiresAt ? new Date(expiresAt).getTime() : startedAt + 10 * 6e4;
352
+ await ctx.sleep(CHECKPOINT_POLL_FIRST_DELAY_MS);
353
+ for (; ; ) {
354
+ let result;
355
+ try {
356
+ result = await client.accounts.pollCheckpoint({ account_id: accountId });
357
+ } catch (err) {
358
+ return await handleError(err, ctx.outOpts, ctx.out);
359
+ }
360
+ const status = result.status;
361
+ if (status === "active") return { kind: "active", result };
362
+ if (status === "expired" || status === "failed") {
363
+ return { kind: "terminal_failure", status };
364
+ }
365
+ const n = ctx.now();
366
+ if (n >= timeoutAt) return { kind: "timeout" };
367
+ await ctx.sleep(nextCheckpointPollDelayMs(n - startedAt));
368
+ }
369
+ }
370
+ async function runInteractiveCheckpointLoop(client, initial, ctx) {
371
+ const { CurviateError } = await import("@curviate/sdk");
372
+ let current = initial;
373
+ for (; ; ) {
374
+ const challengeType = current.challenge_type;
375
+ const accountId = current.account_id ?? "";
376
+ printChallengeCopy(challengeType, ctx.out);
377
+ printResendHintIfApplicable(challengeType, accountId, ctx.out);
378
+ if (challengeType === "mobile_app_approval") {
379
+ const outcome = await waitForMobileApproval(client, accountId, current.expires_at, {
380
+ out: ctx.out,
381
+ outOpts: ctx.outOpts,
382
+ sleep: ctx.sleep,
383
+ now: ctx.now
384
+ });
385
+ if (outcome.kind === "active") {
386
+ printConnected(outcome.result, { out: ctx.out, outOpts: ctx.outOpts, successVerb: ctx.successVerb });
387
+ return;
388
+ }
389
+ if (outcome.kind === "terminal_failure") {
390
+ ctx.out.stderr.write(
391
+ `This checkpoint has ${outcome.status}. Run the connect command again to restart.
392
+ `
393
+ );
394
+ process.exit(9);
395
+ return;
396
+ }
397
+ ctx.out.stderr.write(
398
+ `Still waiting for approval. Finish out-of-band: curviate account checkpoint poll --checkpoint ${accountId}
399
+ `
400
+ );
401
+ process.exit(AUTH_NEEDED);
402
+ return;
403
+ }
404
+ for (; ; ) {
405
+ const code = await ctx.readline("Enter the code: ");
406
+ try {
407
+ const result = await client.accounts.submitCheckpoint({ account_id: accountId, code });
408
+ const r = result;
409
+ if (r.status === "checkpoint_required") {
410
+ current = { ...r, account_id: r.account_id ?? accountId };
411
+ break;
412
+ }
413
+ printConnected(result, { out: ctx.out, outOpts: ctx.outOpts, successVerb: ctx.successVerb });
414
+ return;
415
+ } catch (err) {
416
+ if (err instanceof CurviateError && err.code === "CHECKPOINT_INVALID_CODE") {
417
+ printInvalidCodeRetryHint(err, ctx.out);
418
+ continue;
419
+ }
420
+ await handleError(err, ctx.outOpts, ctx.out);
421
+ }
422
+ }
423
+ }
424
+ }
425
+ async function handleAccountConnectResult(client, result, ctx) {
426
+ const envelope = result;
427
+ if (envelope.status !== "checkpoint_required") {
428
+ renderSuccess(result, ctx.outOpts, ctx.out);
429
+ return;
430
+ }
431
+ const isInteractive = ctx.io.isTTY && ctx.io.isOutputTTY && !(ctx.flags["no-interactive"] ?? false);
432
+ if (!isInteractive) {
433
+ renderSuccess(result, ctx.outOpts, ctx.out);
434
+ process.exit(AUTH_NEEDED);
435
+ return;
436
+ }
437
+ await runInteractiveCheckpointLoop(client, envelope, {
438
+ out: ctx.out,
439
+ outOpts: ctx.outOpts,
440
+ readline: ctx.io.readline,
441
+ sleep: ctx.io.sleep,
442
+ now: ctx.io.now,
443
+ successVerb: ctx.successVerb
444
+ });
445
+ }
446
+ async function runAccountLink(client, flags, out, io = {}) {
447
+ if (!flags["seat-id"]) {
448
+ out.stderr.write("error: --seat-id is required for account link.\n");
449
+ process.exit(2);
450
+ }
451
+ if (!flags["auth-method"]) {
452
+ out.stderr.write("error: --auth-method is required for account link (credentials | cookie).\n");
453
+ process.exit(2);
454
+ }
455
+ checkCredentialConflicts(flags, out);
456
+ const resolvedIo = resolveCredentialIO(io);
457
+ const authBody = await buildAuthBody(flags, {
458
+ out,
459
+ isTTY: resolvedIo.isTTY,
460
+ readline: resolvedIo.readline,
461
+ readStdin: resolvedIo.readStdin,
462
+ previewMode: flags.preview ?? false
463
+ });
464
+ const body = {
465
+ seat_id: flags["seat-id"],
466
+ ...authBody
467
+ };
468
+ const outOpts = resolveOutputOpts(flags);
469
+ if (flags.preview) {
470
+ const preview = buildPreviewOutput({ method: "accounts.link", args: {}, body: maskCredentialSecretsForPreview(body) });
471
+ out.stdout.write(JSON.stringify(preview) + "\n");
472
+ return;
473
+ }
474
+ let result;
475
+ try {
476
+ result = await client.accounts.link(body);
477
+ } catch (err) {
478
+ await handleError(err, outOpts, out);
479
+ return;
480
+ }
481
+ await handleAccountConnectResult(client, result, {
482
+ out,
483
+ flags,
484
+ outOpts,
485
+ io: resolvedIo,
486
+ successVerb: "linked"
487
+ });
488
+ }
489
+ async function runConnectSessionWaitLoop(client, params, ctx) {
490
+ const startedAt = ctx.now();
491
+ let timeoutAt = ctx.timeoutOverrideMs !== void 0 ? startedAt + ctx.timeoutOverrideMs : void 0;
492
+ await ctx.sleep(CHECKPOINT_POLL_FIRST_DELAY_MS);
493
+ for (; ; ) {
494
+ const result = await client.accounts.getConnectSession(params);
495
+ const r = result;
496
+ if (r.status === "resolved") return { kind: "resolved", result };
497
+ if (r.status === "expired" || r.status === "failed") {
498
+ return { kind: "terminal_failure", status: r.status, result };
499
+ }
500
+ if (timeoutAt === void 0) {
501
+ const expiresAtMs = r.expires_at ? new Date(r.expires_at).getTime() : NaN;
502
+ timeoutAt = Number.isNaN(expiresAtMs) ? startedAt + 10 * 6e4 : expiresAtMs;
503
+ }
504
+ const n = ctx.now();
505
+ if (n >= timeoutAt) return { kind: "timeout", result };
506
+ if (ctx.showTicker) {
507
+ ctx.out.stderr.write(`\rWaiting for the account to connect\u2026 ${formatRemaining(timeoutAt - n)} remaining`);
508
+ }
509
+ await ctx.sleep(nextCheckpointPollDelayMs(n - startedAt));
510
+ }
511
+ }
512
+ function printConnectSessionResolved(result, ctx) {
513
+ const r = result;
514
+ ctx.out.stderr.write(`Account connected: ${r.account_id ?? ""}
515
+ `);
516
+ renderSuccess(result, ctx.outOpts, ctx.out);
517
+ }
518
+ function resolveWaitTimeoutOverrideMs(flags, out) {
519
+ if (flags.timeout === void 0) return void 0;
520
+ const parsed = Number(flags.timeout);
521
+ if (Number.isNaN(parsed)) {
522
+ out.stderr.write("error: --timeout must be a number of milliseconds.\n");
523
+ process.exit(2);
524
+ }
525
+ return parsed;
526
+ }
527
+ async function runAccountConnectLink(client, flags, out, io = {}) {
528
+ const body = {};
529
+ if (flags["seat-id"]) body["seat_id"] = flags["seat-id"];
530
+ if (flags["account-id"]) body["account_id"] = flags["account-id"];
531
+ if (flags.purpose) body["purpose"] = flags.purpose;
532
+ if (flags["expires-in-seconds"]) body["expires_in_seconds"] = parseInt(flags["expires-in-seconds"], 10);
533
+ if (flags["redirect-url"]) body["redirect_url"] = flags["redirect-url"];
534
+ const outOpts = resolveOutputOpts(flags);
535
+ if (flags.preview) {
536
+ const preview = buildPreviewOutput({ method: "accounts.createConnectLink", args: {}, body });
537
+ out.stdout.write(JSON.stringify(preview) + "\n");
538
+ return;
539
+ }
540
+ let result;
541
+ try {
542
+ result = await client.accounts.createConnectLink(body);
543
+ } catch (err) {
544
+ await handleError(err, outOpts, out);
545
+ return;
546
+ }
547
+ const mint = result;
548
+ const resolvedIo = resolveCredentialIO(io);
549
+ const isInteractive = resolvedIo.isTTY && resolvedIo.isOutputTTY && !(flags["no-interactive"] ?? false);
550
+ if (!isInteractive) {
551
+ if (mint.url) {
552
+ out.stderr.write(`Open this URL in a browser to connect the account: ${mint.url}
553
+ `);
554
+ }
555
+ if (mint.session_id) {
556
+ out.stderr.write(
557
+ `Session: ${mint.session_id} \u2014 check it later with: curviate account connect-session poll --session ${mint.session_id} --wait
558
+ `
559
+ );
560
+ }
561
+ renderSuccess(result, outOpts, out);
562
+ return;
563
+ }
564
+ if ((flags.open ?? true) && mint.url) {
565
+ await resolvedIo.open(mint.url);
566
+ }
567
+ if (!(flags.wait ?? true)) {
568
+ renderSuccess(result, outOpts, out);
569
+ return;
570
+ }
571
+ const timeoutOverrideMs = resolveWaitTimeoutOverrideMs(flags, out);
572
+ const showTicker = resolvedIo.isOutputTTY && !(flags.json ?? false);
573
+ const params = { session_id: mint.session_id };
574
+ let outcome;
575
+ try {
576
+ outcome = await runConnectSessionWaitLoop(client, params, {
577
+ out,
578
+ sleep: resolvedIo.sleep,
579
+ now: resolvedIo.now,
580
+ showTicker,
581
+ timeoutOverrideMs
582
+ });
583
+ } catch (err) {
584
+ await handleError(err, outOpts, out);
585
+ return;
586
+ }
587
+ if (outcome.kind === "resolved") {
588
+ printConnectSessionResolved(outcome.result, { out, outOpts });
589
+ return;
590
+ }
591
+ if (outcome.kind === "terminal_failure") {
592
+ renderSuccess(outcome.result, outOpts, out);
593
+ out.stderr.write(
594
+ `This connect session has ${outcome.status}. Generate a new link: curviate account connect-link.
595
+ `
596
+ );
597
+ process.exit(9);
598
+ return;
599
+ }
600
+ renderSuccess(outcome.result, outOpts, out);
601
+ out.stderr.write(
602
+ `Still waiting for the account to connect \u2014 the wait window elapsed. Check again: curviate account connect-session poll --session ${mint.session_id} --wait
603
+ `
604
+ );
605
+ process.exit(AUTH_NEEDED);
606
+ }
607
+ async function runAccountConnectSessionPoll(client, flags, out, io = {}) {
608
+ if (!flags.session) {
609
+ out.stderr.write("error: --session is required (the session_id from `account connect-link`).\n");
610
+ process.exit(2);
611
+ }
612
+ const params = { session_id: flags.session };
613
+ const outOpts = resolveOutputOpts(flags);
614
+ if (flags.preview) {
615
+ const preview = buildPreviewOutput({ method: "accounts.getConnectSession", args: {}, body: params });
616
+ out.stdout.write(JSON.stringify(preview) + "\n");
617
+ return;
618
+ }
619
+ if (!flags.wait) {
620
+ try {
621
+ const result = await client.accounts.getConnectSession(params);
622
+ renderSuccess(result, outOpts, out);
623
+ } catch (err) {
624
+ await handleError(err, outOpts, out);
625
+ }
626
+ return;
627
+ }
628
+ const timeoutOverrideMs = resolveWaitTimeoutOverrideMs(flags, out);
629
+ const resolvedIo = resolveCredentialIO(io);
630
+ const showTicker = resolvedIo.isOutputTTY && !(flags.json ?? false);
631
+ let outcome;
632
+ try {
633
+ outcome = await runConnectSessionWaitLoop(client, params, {
634
+ out,
635
+ sleep: resolvedIo.sleep,
636
+ now: resolvedIo.now,
637
+ showTicker,
638
+ timeoutOverrideMs
639
+ });
640
+ } catch (err) {
641
+ await handleError(err, outOpts, out);
642
+ return;
643
+ }
644
+ if (outcome.kind === "resolved") {
645
+ printConnectSessionResolved(outcome.result, { out, outOpts });
646
+ return;
647
+ }
648
+ if (outcome.kind === "terminal_failure") {
649
+ renderSuccess(outcome.result, outOpts, out);
650
+ out.stderr.write(
651
+ `This connect session has ${outcome.status}. Generate a new link: curviate account connect-link.
652
+ `
653
+ );
654
+ process.exit(9);
655
+ return;
656
+ }
657
+ renderSuccess(outcome.result, outOpts, out);
658
+ out.stderr.write(
659
+ `Still waiting for the account to connect \u2014 the wait window elapsed. Check again: curviate account connect-session poll --session ${flags.session} --wait
660
+ `
661
+ );
662
+ process.exit(AUTH_NEEDED);
663
+ }
664
+ async function runAccountReconnect(client, flags, out, io = {}) {
665
+ if (!flags["auth-method"]) {
666
+ out.stderr.write("error: --auth-method is required for account reconnect (credentials | cookie).\n");
667
+ process.exit(2);
668
+ }
669
+ checkCredentialConflicts(flags, out);
670
+ const accountId = flags["account-id"] ?? "";
671
+ const resolvedIo = resolveCredentialIO(io);
672
+ const body = await buildAuthBody(flags, {
673
+ out,
674
+ isTTY: resolvedIo.isTTY,
675
+ readline: resolvedIo.readline,
676
+ readStdin: resolvedIo.readStdin,
677
+ previewMode: flags.preview ?? false
678
+ });
679
+ const outOpts = resolveOutputOpts(flags);
680
+ if (flags.preview) {
681
+ const preview = buildPreviewOutput({
682
+ method: "accounts.reconnect",
683
+ args: { accountId },
684
+ body: maskCredentialSecretsForPreview(body)
685
+ });
686
+ out.stdout.write(JSON.stringify(preview) + "\n");
687
+ return;
688
+ }
689
+ let result;
690
+ try {
691
+ result = await client.accounts.reconnect(accountId, body);
692
+ } catch (err) {
693
+ await handleError(err, outOpts, out);
694
+ return;
695
+ }
696
+ await handleAccountConnectResult(client, result, {
697
+ out,
698
+ flags,
699
+ outOpts,
700
+ io: resolvedIo,
701
+ successVerb: "reconnected"
702
+ });
703
+ }
704
+ async function runAccountRefresh(client, flags, out) {
705
+ const accountId = flags["account-id"] ?? "";
706
+ const outOpts = resolveOutputOpts(flags);
707
+ if (flags.preview) {
708
+ const preview = buildPreviewOutput({
709
+ method: "accounts.refresh",
710
+ args: { accountId },
711
+ body: {}
712
+ });
713
+ out.stdout.write(JSON.stringify(preview) + "\n");
714
+ return;
715
+ }
716
+ try {
717
+ const result = await client.accounts.refresh(accountId);
718
+ renderSuccess(result, outOpts, out);
719
+ } catch (err) {
720
+ await handleError(err, outOpts, out);
721
+ }
722
+ }
723
+ async function runAccountUpdate(client, flags, out) {
724
+ const accountId = flags["account-id"] ?? "";
725
+ const body = {};
726
+ if (flags.country) body["country"] = flags.country;
727
+ if (flags.ip) body["ip"] = flags.ip;
728
+ if (flags["proxy-host"]) {
729
+ const proxyPassword = await resolveSecret({
730
+ flagValue: flags["proxy-password"],
731
+ envVar: "CURVIATE_PROXY_PASSWORD",
732
+ out
733
+ });
734
+ body["proxy"] = {
735
+ protocol: flags["proxy-protocol"] ?? "http",
736
+ host: flags["proxy-host"],
737
+ port: flags["proxy-port"] ? parseInt(flags["proxy-port"], 10) : 80,
738
+ ...flags["proxy-username"] ? { username: flags["proxy-username"] } : {},
739
+ ...proxyPassword !== void 0 ? { password: proxyPassword } : {}
740
+ };
741
+ }
742
+ const outOpts = resolveOutputOpts(flags);
743
+ if (flags.preview) {
744
+ const preview = buildPreviewOutput({
745
+ method: "accounts.update",
746
+ args: { accountId },
747
+ body: maskCredentialSecretsForPreview(body)
748
+ });
749
+ out.stdout.write(JSON.stringify(preview) + "\n");
750
+ return;
751
+ }
752
+ try {
753
+ const result = await client.accounts.update(accountId, body);
754
+ renderSuccess(result, outOpts, out);
755
+ } catch (err) {
756
+ await handleError(err, outOpts, out);
757
+ }
758
+ }
759
+ async function runAccountDisconnect(client, flags, out) {
760
+ const accountId = flags["account-id"] ?? "";
761
+ const outOpts = resolveOutputOpts(flags);
762
+ if (flags.preview) {
763
+ const preview = buildPreviewOutput({
764
+ method: "accounts.disconnect",
765
+ args: { accountId },
766
+ body: {}
767
+ });
768
+ out.stdout.write(JSON.stringify(preview) + "\n");
769
+ return;
770
+ }
771
+ try {
772
+ const result = await client.accounts.disconnect(accountId);
773
+ renderSuccess(result, outOpts, out);
774
+ } catch (err) {
775
+ await handleError(err, outOpts, out);
776
+ }
777
+ }
778
+ async function runAccountCheckpointSubmit(client, flags, out) {
779
+ if (!flags.checkpoint) {
780
+ out.stderr.write("error: --checkpoint is required (the provisional account_id from the 202 response).\n");
781
+ process.exit(2);
782
+ }
783
+ if (!flags.code) {
784
+ out.stderr.write("error: --code is required (the OTP / 2FA code).\n");
785
+ process.exit(2);
786
+ }
787
+ const body = {
788
+ account_id: flags.checkpoint,
789
+ code: flags.code
790
+ };
791
+ const outOpts = resolveOutputOpts(flags);
792
+ if (flags.preview) {
793
+ const preview = buildPreviewOutput({
794
+ method: "accounts.submitCheckpoint",
795
+ args: {},
796
+ body
797
+ });
798
+ out.stdout.write(JSON.stringify(preview) + "\n");
799
+ return;
800
+ }
801
+ let chained = false;
802
+ try {
803
+ const result = await client.accounts.submitCheckpoint(body);
804
+ const r = result;
805
+ renderSuccess(result, outOpts, out);
806
+ chained = r.status === "checkpoint_required";
807
+ } catch (err) {
808
+ await handleError(err, outOpts, out);
809
+ return;
810
+ }
811
+ if (chained) {
812
+ process.exit(AUTH_NEEDED);
813
+ }
814
+ }
815
+ async function runAccountCheckpointResend(client, flags, out) {
816
+ if (!flags.checkpoint) {
817
+ out.stderr.write("error: --checkpoint is required (the provisional account_id from the 202 response).\n");
818
+ process.exit(2);
819
+ }
820
+ const body = {
821
+ account_id: flags.checkpoint
822
+ };
823
+ const outOpts = resolveOutputOpts(flags);
824
+ if (flags.preview) {
825
+ const preview = buildPreviewOutput({
826
+ method: "accounts.resendCheckpoint",
827
+ args: {},
828
+ body
829
+ });
830
+ out.stdout.write(JSON.stringify(preview) + "\n");
831
+ return;
832
+ }
833
+ try {
834
+ const result = await client.accounts.resendCheckpoint(body);
835
+ renderSuccess(result, outOpts, out);
836
+ } catch (err) {
837
+ await handleError(err, outOpts, out);
838
+ }
839
+ }
840
+ function formatRemaining(ms) {
841
+ const totalSec = Math.max(0, Math.ceil(ms / 1e3));
842
+ const minutes = Math.floor(totalSec / 60);
843
+ const seconds = totalSec % 60;
844
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
845
+ }
846
+ async function runCheckpointPollWaitLoop(client, body, ctx) {
847
+ const startedAt = ctx.now();
848
+ let timeoutAt = ctx.timeoutOverrideMs !== void 0 ? startedAt + ctx.timeoutOverrideMs : void 0;
849
+ await ctx.sleep(CHECKPOINT_POLL_FIRST_DELAY_MS);
850
+ for (; ; ) {
851
+ const result = await client.accounts.pollCheckpoint(body);
852
+ const r = result;
853
+ if (r.status === "active") return { kind: "active", result };
854
+ if (r.status === "expired" || r.status === "failed") {
855
+ return { kind: "terminal_failure", status: r.status, result };
856
+ }
857
+ if (timeoutAt === void 0) {
858
+ const expiresAtMs = r.expires_at ? new Date(r.expires_at).getTime() : NaN;
859
+ timeoutAt = Number.isNaN(expiresAtMs) ? startedAt + 10 * 6e4 : expiresAtMs;
860
+ }
861
+ const n = ctx.now();
862
+ if (n >= timeoutAt) return { kind: "timeout", result };
863
+ if (ctx.showTicker) {
864
+ ctx.out.stderr.write(`\rWaiting for approval\u2026 ${formatRemaining(timeoutAt - n)} remaining`);
865
+ }
866
+ await ctx.sleep(nextCheckpointPollDelayMs(n - startedAt));
867
+ }
868
+ }
869
+ async function runAccountCheckpointPoll(client, flags, out, io = {}) {
870
+ if (!flags.checkpoint) {
871
+ out.stderr.write("error: --checkpoint is required (the provisional account_id from the 202 response).\n");
872
+ process.exit(2);
873
+ }
874
+ const body = {
875
+ account_id: flags.checkpoint
876
+ };
877
+ const outOpts = resolveOutputOpts(flags);
878
+ if (flags.preview) {
879
+ const preview = buildPreviewOutput({
880
+ method: "accounts.pollCheckpoint",
881
+ args: {},
882
+ body
883
+ });
884
+ out.stdout.write(JSON.stringify(preview) + "\n");
885
+ return;
886
+ }
887
+ if (!flags.wait) {
888
+ try {
889
+ const result = await client.accounts.pollCheckpoint(body);
890
+ renderSuccess(result, outOpts, out);
891
+ } catch (err) {
892
+ await handleError(err, outOpts, out);
893
+ }
894
+ return;
895
+ }
896
+ let timeoutOverrideMs;
897
+ if (flags.timeout !== void 0) {
898
+ const parsed = Number(flags.timeout);
899
+ if (Number.isNaN(parsed)) {
900
+ out.stderr.write("error: --timeout must be a number of milliseconds.\n");
901
+ process.exit(2);
902
+ }
903
+ timeoutOverrideMs = parsed;
904
+ }
905
+ const resolvedIo = resolveCredentialIO(io);
906
+ const showTicker = resolvedIo.isOutputTTY && !(flags.json ?? false);
907
+ printResendHintIfApplicable("mobile_app_approval", flags.checkpoint, out);
908
+ let outcome;
909
+ try {
910
+ outcome = await runCheckpointPollWaitLoop(client, body, {
911
+ out,
912
+ sleep: resolvedIo.sleep,
913
+ now: resolvedIo.now,
914
+ showTicker,
915
+ timeoutOverrideMs
916
+ });
917
+ } catch (err) {
918
+ await handleError(err, outOpts, out);
919
+ return;
920
+ }
921
+ if (outcome.kind === "active") {
922
+ printConnected(outcome.result, { out, outOpts, successVerb: "linked" });
923
+ return;
924
+ }
925
+ if (outcome.kind === "terminal_failure") {
926
+ renderSuccess(outcome.result, outOpts, out);
927
+ out.stderr.write(`This checkpoint has ${outcome.status}. Start over: curviate account link or curviate account reconnect.
928
+ `);
929
+ process.exit(9);
930
+ return;
931
+ }
932
+ renderSuccess(outcome.result, outOpts, out);
933
+ out.stderr.write(
934
+ `Still waiting for approval \u2014 the wait window elapsed. Check again: curviate account checkpoint poll --checkpoint ${flags.checkpoint} --wait
935
+ `
936
+ );
937
+ process.exit(AUTH_NEEDED);
938
+ }
939
+ var accountListCommand = defineCommand({
940
+ meta: { name: "list", description: "List connected LinkedIn accounts." },
941
+ args: { ...GLOBAL_FLAGS },
942
+ async run({ args }) {
943
+ const flags = args;
944
+ const cfg = await resolveEffectiveConfig({
945
+ apiKey: flags["api-key"],
946
+ baseUrl: flags["base-url"],
947
+ timeout: flags.timeout,
948
+ profile: flags.profile
949
+ });
950
+ if (!cfg.apiKey) {
951
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
952
+ process.exit(3);
953
+ }
954
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
955
+ const out = buildOutputStreams();
956
+ await runAccountList(client, flags, out);
957
+ }
958
+ });
959
+ var accountGetCommand = defineCommand({
960
+ meta: { name: "get", description: "Get a connected LinkedIn account." },
961
+ args: {
962
+ // Single-object read: READ_SINGLE_FLAGS omits pagination flags, keeps --fields
963
+ ...READ_SINGLE_FLAGS,
964
+ "account-id": { type: "positional", description: "Account id (acc_\u2026)." }
965
+ },
966
+ async run({ args }) {
967
+ const flags = args;
968
+ const cfg = await resolveEffectiveConfig({
969
+ apiKey: flags["api-key"],
970
+ baseUrl: flags["base-url"],
971
+ timeout: flags.timeout,
972
+ profile: flags.profile
973
+ });
974
+ if (!cfg.apiKey) {
975
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
976
+ process.exit(3);
977
+ }
978
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
979
+ const out = buildOutputStreams();
980
+ await runAccountGet(client, flags, out);
981
+ }
982
+ });
983
+ var accountLinkCommand = defineCommand({
984
+ meta: {
985
+ name: "link",
986
+ description: "Connect a LinkedIn account to an empty seat. If LinkedIn requires verification you'll be prompted for the code interactively; in a non-interactive shell the command exits 12 and you finish with `curviate account checkpoint submit`."
987
+ },
988
+ args: {
989
+ ...WRITE_SINGLE_FLAGS,
990
+ "seat-id": { type: "string", description: "Empty seat to bind the account to.", required: true },
991
+ "auth-method": { type: "string", description: "Authentication method: credentials | cookie.", required: true },
992
+ email: { type: "string", description: "LinkedIn email (credentials method)." },
993
+ password: { type: "string", description: `LinkedIn password (credentials method). ${PW_WARNING("--password-stdin", "CURVIATE_LINKEDIN_PASSWORD")}` },
994
+ "password-stdin": { type: "boolean", description: "Read the LinkedIn password from stdin (one line, trimmed).", default: false },
995
+ "li-at": { type: "string", description: `LinkedIn session cookie li_at (cookie method). ${PW_WARNING("--li-at-stdin", "CURVIATE_LINKEDIN_LI_AT")}` },
996
+ "li-at-stdin": { type: "boolean", description: "Read the li_at session cookie from stdin (one line, trimmed).", default: false },
997
+ "li-a": { type: "string", description: `Optional premium session cookie li_a (cookie method). ${OPTIONAL_SECRET_WARNING("CURVIATE_LINKEDIN_LI_A")}` },
998
+ country: { type: "string", description: "Proxy location hint (ISO 3166-1 alpha-2)." },
999
+ ip: { type: "string", description: "IP to infer the managed proxy location." },
1000
+ "proxy-protocol": { type: "string", description: "Proxy protocol: http | https | socks5." },
1001
+ "proxy-host": { type: "string", description: "Proxy host or IP." },
1002
+ "proxy-port": { type: "string", description: "Proxy port." },
1003
+ "proxy-username": { type: "string", description: "Proxy auth username." },
1004
+ "proxy-password": { type: "string", description: `Proxy auth password. ${OPTIONAL_SECRET_WARNING("CURVIATE_PROXY_PASSWORD")}` },
1005
+ "user-agent": { type: "string", description: "Browser User-Agent to pin for this account." },
1006
+ "recruiter-contract-id": { type: "string", description: "Recruiter contract to bind to (Recruiter tier only)." },
1007
+ "no-interactive": {
1008
+ type: "boolean",
1009
+ description: "Never prompt for a checkpoint code \u2014 on a checkpoint, always render the envelope and exit 12, even on a TTY.",
1010
+ default: false
1011
+ }
1012
+ },
1013
+ async run({ args }) {
1014
+ const flags = args;
1015
+ const cfg = await resolveEffectiveConfig({
1016
+ apiKey: flags["api-key"],
1017
+ baseUrl: flags["base-url"],
1018
+ timeout: flags.timeout,
1019
+ profile: flags.profile
1020
+ });
1021
+ if (!cfg.apiKey) {
1022
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1023
+ process.exit(3);
1024
+ }
1025
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1026
+ const out = buildOutputStreams();
1027
+ await runAccountLink(client, flags, out);
1028
+ }
1029
+ });
1030
+ var accountConnectLinkCommand = defineCommand({
1031
+ meta: {
1032
+ name: "connect-link",
1033
+ description: "Generate a one-time hosted account connection URL. On an interactive TTY the URL auto-opens in your browser and the command waits for the account to connect (exit 0 on success, 9 if the link expires or fails, 12 if the wait window elapses first). Non-interactively it never opens a browser and returns immediately with the url and session_id \u2014 poll completion later with `account connect-session poll`."
1034
+ },
1035
+ args: {
1036
+ ...WRITE_SINGLE_FLAGS,
1037
+ "seat-id": { type: "string", description: "Target seat (required when purpose=create)." },
1038
+ "account-id": { type: "string", description: "Account to reconnect (required when purpose=reconnect)." },
1039
+ purpose: { type: "string", description: "create | reconnect (default: create)." },
1040
+ "expires-in-seconds": { type: "string", description: "Link expiry in seconds (60\u20133600, default 900)." },
1041
+ "redirect-url": { type: "string", description: "Browser return URL after the hosted flow." },
1042
+ open: {
1043
+ type: "boolean",
1044
+ description: "Auto-open the URL in your default browser. Default: on for an interactive TTY, always off otherwise (ignored non-interactively)."
1045
+ },
1046
+ wait: {
1047
+ type: "boolean",
1048
+ description: "Poll for the account to connect on an adaptive cadence (1000ms, then 1500ms for 30s, then 3000ms) after opening the URL. Default: on for an interactive TTY, always off otherwise (the agent polls later via `account connect-session poll`)."
1049
+ },
1050
+ // Override WRITE_SINGLE_FLAGS.timeout: on this command --timeout is the
1051
+ // TTY+interactive wait loop's own wall-clock bound in MILLISECONDS
1052
+ // (meaningless outside that branch) — NOT the SDK request timeout.
1053
+ // Default: the time remaining to the link's own expiry.
1054
+ timeout: {
1055
+ type: "string",
1056
+ description: "Wait-loop timeout in milliseconds (only meaningful under the interactive TTY wait; default: time remaining to the link's expiry). Note the unit: this is milliseconds."
1057
+ }
1058
+ },
1059
+ async run({ args }) {
1060
+ const flags = args;
1061
+ const cfg = await resolveEffectiveConfig({
1062
+ apiKey: flags["api-key"],
1063
+ baseUrl: flags["base-url"],
1064
+ profile: flags.profile
1065
+ });
1066
+ if (!cfg.apiKey) {
1067
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1068
+ process.exit(3);
1069
+ }
1070
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1071
+ const out = buildOutputStreams();
1072
+ await runAccountConnectLink(client, flags, out);
1073
+ }
1074
+ });
1075
+ var accountConnectSessionPollCommand = defineCommand({
1076
+ meta: {
1077
+ name: "poll",
1078
+ description: "Poll a hosted connect-link session for completion. Without --wait: a single poll \u2014 the JSON `status` field (pending | resolved | expired | failed) tells you what to do next. With --wait: block on the same adaptive cadence as `account connect-link` until a terminal state."
1079
+ },
1080
+ args: {
1081
+ ...WRITE_SINGLE_FLAGS,
1082
+ session: {
1083
+ type: "string",
1084
+ description: "The session_id returned by `account connect-link`.",
1085
+ required: true
1086
+ },
1087
+ wait: {
1088
+ type: "boolean",
1089
+ description: "Poll on an adaptive cadence (1000ms, then 1500ms for 30s, then 3000ms) until a terminal state, instead of a single poll.",
1090
+ default: false
1091
+ },
1092
+ // Override WRITE_SINGLE_FLAGS.timeout: on this command --timeout is the
1093
+ // --wait loop's own wall-clock bound in MILLISECONDS (requires --wait) —
1094
+ // NOT the SDK request timeout. Default: the time remaining to the
1095
+ // session's own expiry.
1096
+ timeout: {
1097
+ type: "string",
1098
+ description: "Wait-loop timeout in milliseconds (requires --wait; default: time remaining to the session's expiry). Note the unit: this is milliseconds."
1099
+ }
1100
+ },
1101
+ async run({ args }) {
1102
+ const flags = args;
1103
+ const cfg = await resolveEffectiveConfig({
1104
+ apiKey: flags["api-key"],
1105
+ baseUrl: flags["base-url"],
1106
+ profile: flags.profile
1107
+ });
1108
+ if (!cfg.apiKey) {
1109
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1110
+ process.exit(3);
1111
+ }
1112
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1113
+ const out = buildOutputStreams();
1114
+ await runAccountConnectSessionPoll(client, flags, out);
1115
+ }
1116
+ });
1117
+ var accountConnectSessionCommand = defineCommand({
1118
+ meta: { name: "connect-session", description: "Hosted connect-link session operations (poll for completion)." },
1119
+ subCommands: {
1120
+ poll: accountConnectSessionPollCommand
1121
+ },
1122
+ async run() {
1123
+ process.stderr.write("Usage: curviate account connect-session poll --session <session_id>\n");
1124
+ }
1125
+ });
1126
+ var accountReconnectCommand = defineCommand({
1127
+ meta: {
1128
+ name: "reconnect",
1129
+ description: "Re-authorize a disconnected account in place. If LinkedIn requires verification you'll be prompted for the code interactively; in a non-interactive shell the command exits 12 and you finish with `curviate account checkpoint submit`."
1130
+ },
1131
+ args: {
1132
+ ...WRITE_SINGLE_FLAGS,
1133
+ "account-id": { type: "positional", description: "Account id (acc_\u2026)." },
1134
+ "auth-method": { type: "string", description: "Authentication method: credentials | cookie.", required: true },
1135
+ email: { type: "string", description: "LinkedIn email (credentials method)." },
1136
+ password: { type: "string", description: `LinkedIn password (credentials method). ${PW_WARNING("--password-stdin", "CURVIATE_LINKEDIN_PASSWORD")}` },
1137
+ "password-stdin": { type: "boolean", description: "Read the LinkedIn password from stdin (one line, trimmed).", default: false },
1138
+ "li-at": { type: "string", description: `LinkedIn session cookie li_at (cookie method). ${PW_WARNING("--li-at-stdin", "CURVIATE_LINKEDIN_LI_AT")}` },
1139
+ "li-at-stdin": { type: "boolean", description: "Read the li_at session cookie from stdin (one line, trimmed).", default: false },
1140
+ "li-a": { type: "string", description: `Optional premium session cookie li_a (cookie method). ${OPTIONAL_SECRET_WARNING("CURVIATE_LINKEDIN_LI_A")}` },
1141
+ country: { type: "string", description: "Proxy location hint (ISO 3166-1 alpha-2)." },
1142
+ ip: { type: "string", description: "IP to infer the managed proxy location." },
1143
+ "proxy-protocol": { type: "string", description: "Proxy protocol: http | https | socks5." },
1144
+ "proxy-host": { type: "string", description: "Proxy host or IP." },
1145
+ "proxy-port": { type: "string", description: "Proxy port." },
1146
+ "proxy-username": { type: "string", description: "Proxy auth username." },
1147
+ "proxy-password": { type: "string", description: `Proxy auth password. ${OPTIONAL_SECRET_WARNING("CURVIATE_PROXY_PASSWORD")}` },
1148
+ "user-agent": { type: "string", description: "Browser User-Agent to pin." },
1149
+ "recruiter-contract-id": { type: "string", description: "Recruiter contract id (Recruiter tier only)." },
1150
+ "no-interactive": {
1151
+ type: "boolean",
1152
+ description: "Never prompt for a checkpoint code \u2014 on a checkpoint, always render the envelope and exit 12, even on a TTY.",
1153
+ default: false
1154
+ }
1155
+ },
1156
+ async run({ args }) {
1157
+ const flags = args;
1158
+ const cfg = await resolveEffectiveConfig({
1159
+ apiKey: flags["api-key"],
1160
+ baseUrl: flags["base-url"],
1161
+ timeout: flags.timeout,
1162
+ profile: flags.profile
1163
+ });
1164
+ if (!cfg.apiKey) {
1165
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1166
+ process.exit(3);
1167
+ }
1168
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1169
+ const out = buildOutputStreams();
1170
+ await runAccountReconnect(client, flags, out);
1171
+ }
1172
+ });
1173
+ var accountRefreshCommand = defineCommand({
1174
+ meta: { name: "refresh", description: "Refresh an account's synced data sources." },
1175
+ args: {
1176
+ ...WRITE_SINGLE_FLAGS,
1177
+ "account-id": { type: "positional", description: "Account id (acc_\u2026)." }
1178
+ },
1179
+ async run({ args }) {
1180
+ const flags = args;
1181
+ const cfg = await resolveEffectiveConfig({
1182
+ apiKey: flags["api-key"],
1183
+ baseUrl: flags["base-url"],
1184
+ timeout: flags.timeout,
1185
+ profile: flags.profile
1186
+ });
1187
+ if (!cfg.apiKey) {
1188
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1189
+ process.exit(3);
1190
+ }
1191
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1192
+ const out = buildOutputStreams();
1193
+ await runAccountRefresh(client, flags, out);
1194
+ }
1195
+ });
1196
+ var accountUpdateCommand = defineCommand({
1197
+ meta: { name: "update", description: "Update managed-proxy configuration for an account." },
1198
+ args: {
1199
+ ...WRITE_SINGLE_FLAGS,
1200
+ "account-id": { type: "positional", description: "Account id (acc_\u2026)." },
1201
+ country: { type: "string", description: "Proxy location hint (ISO 3166-1 alpha-2)." },
1202
+ ip: { type: "string", description: "IP to infer the managed proxy location." },
1203
+ "proxy-protocol": { type: "string", description: "Proxy protocol: http | https | socks5." },
1204
+ "proxy-host": { type: "string", description: "Proxy host or IP." },
1205
+ "proxy-port": { type: "string", description: "Proxy port." },
1206
+ "proxy-username": { type: "string", description: "Proxy auth username." },
1207
+ "proxy-password": { type: "string", description: `Proxy auth password. ${OPTIONAL_SECRET_WARNING("CURVIATE_PROXY_PASSWORD")}` }
1208
+ },
1209
+ async run({ args }) {
1210
+ const flags = args;
1211
+ const cfg = await resolveEffectiveConfig({
1212
+ apiKey: flags["api-key"],
1213
+ baseUrl: flags["base-url"],
1214
+ timeout: flags.timeout,
1215
+ profile: flags.profile
1216
+ });
1217
+ if (!cfg.apiKey) {
1218
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1219
+ process.exit(3);
1220
+ }
1221
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1222
+ const out = buildOutputStreams();
1223
+ await runAccountUpdate(client, flags, out);
1224
+ }
1225
+ });
1226
+ var accountDisconnectCommand = defineCommand({
1227
+ meta: { name: "disconnect", description: "Hard-disconnect a LinkedIn account and release its seat." },
1228
+ args: {
1229
+ ...WRITE_SINGLE_FLAGS,
1230
+ "account-id": { type: "positional", description: "Account id (acc_\u2026)." }
1231
+ },
1232
+ async run({ args }) {
1233
+ const flags = args;
1234
+ const cfg = await resolveEffectiveConfig({
1235
+ apiKey: flags["api-key"],
1236
+ baseUrl: flags["base-url"],
1237
+ timeout: flags.timeout,
1238
+ profile: flags.profile
1239
+ });
1240
+ if (!cfg.apiKey) {
1241
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1242
+ process.exit(3);
1243
+ }
1244
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1245
+ const out = buildOutputStreams();
1246
+ await runAccountDisconnect(client, flags, out);
1247
+ }
1248
+ });
1249
+ var accountCheckpointSubmitCommand = defineCommand({
1250
+ meta: { name: "submit", description: "Submit an OTP / 2FA code to resolve a checkpoint challenge." },
1251
+ args: {
1252
+ ...WRITE_SINGLE_FLAGS,
1253
+ checkpoint: {
1254
+ type: "string",
1255
+ description: "The provisional account_id from the 202 checkpoint_required response.",
1256
+ required: true
1257
+ },
1258
+ code: {
1259
+ type: "string",
1260
+ description: "The OTP / 2FA verification code.",
1261
+ required: true
1262
+ }
1263
+ },
1264
+ async run({ args }) {
1265
+ const flags = args;
1266
+ const cfg = await resolveEffectiveConfig({
1267
+ apiKey: flags["api-key"],
1268
+ baseUrl: flags["base-url"],
1269
+ timeout: flags.timeout,
1270
+ profile: flags.profile
1271
+ });
1272
+ if (!cfg.apiKey) {
1273
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1274
+ process.exit(3);
1275
+ }
1276
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1277
+ const out = buildOutputStreams();
1278
+ await runAccountCheckpointSubmit(client, flags, out);
1279
+ }
1280
+ });
1281
+ var accountCheckpointPollCommand = defineCommand({
1282
+ meta: {
1283
+ name: "poll",
1284
+ description: "Poll for mobile-app approval of a pending checkpoint challenge. With --wait, blocks on an adaptive cadence until a terminal state (or --timeout elapses) instead of a single poll."
1285
+ },
1286
+ args: {
1287
+ ...WRITE_SINGLE_FLAGS,
1288
+ checkpoint: {
1289
+ type: "string",
1290
+ description: "The provisional account_id from the 202 checkpoint_required response.",
1291
+ required: true
1292
+ },
1293
+ wait: {
1294
+ type: "boolean",
1295
+ description: "Poll on an adaptive cadence (1000ms, then 1500ms for 30s, then 3000ms) until a terminal state, instead of a single poll.",
1296
+ default: false
1297
+ },
1298
+ // Override WRITE_SINGLE_FLAGS.timeout: on this command --timeout is the
1299
+ // --wait loop's own wall-clock bound in MILLISECONDS (requires --wait) —
1300
+ // NOT the SDK request timeout, and NOT seconds like `inbox sync-chat
1301
+ // --timeout`. Default: the time remaining to the checkpoint's own expiry.
1302
+ timeout: {
1303
+ type: "string",
1304
+ description: "Wait-loop timeout in milliseconds (requires --wait; default: time remaining to the checkpoint's expiry). Note the unit: this is milliseconds \u2014 `inbox sync-chat --timeout` is seconds."
1305
+ }
1306
+ },
1307
+ async run({ args }) {
1308
+ const flags = args;
1309
+ const cfg = await resolveEffectiveConfig({
1310
+ apiKey: flags["api-key"],
1311
+ baseUrl: flags["base-url"],
1312
+ profile: flags.profile
1313
+ });
1314
+ if (!cfg.apiKey) {
1315
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1316
+ process.exit(3);
1317
+ }
1318
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1319
+ const out = buildOutputStreams();
1320
+ await runAccountCheckpointPoll(client, flags, out);
1321
+ }
1322
+ });
1323
+ var accountCheckpointResendCommand = defineCommand({
1324
+ meta: {
1325
+ name: "resend",
1326
+ description: "Re-send the challenge notification for a pending checkpoint (e.g. re-send an OTP email, SMS code, or mobile-app approval push). Not every challenge type supports resend \u2014 an authenticator- app code has nothing to re-send. The response's `resent` boolean tells you honestly whether a new notification actually went out; the command still exits 0 either way."
1327
+ },
1328
+ args: {
1329
+ ...WRITE_SINGLE_FLAGS,
1330
+ checkpoint: {
1331
+ type: "string",
1332
+ description: "The provisional account_id from the 202 checkpoint_required response.",
1333
+ required: true
1334
+ }
1335
+ },
1336
+ async run({ args }) {
1337
+ const flags = args;
1338
+ const cfg = await resolveEffectiveConfig({
1339
+ apiKey: flags["api-key"],
1340
+ baseUrl: flags["base-url"],
1341
+ timeout: flags.timeout,
1342
+ profile: flags.profile
1343
+ });
1344
+ if (!cfg.apiKey) {
1345
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
1346
+ process.exit(3);
1347
+ }
1348
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
1349
+ const out = buildOutputStreams();
1350
+ await runAccountCheckpointResend(client, flags, out);
1351
+ }
1352
+ });
1353
+ var accountCheckpointCommand = defineCommand({
1354
+ meta: {
1355
+ name: "checkpoint",
1356
+ description: "Checkpoint challenge operations (submit OTP, poll mobile-app approval, or resend the challenge notification)."
1357
+ },
1358
+ subCommands: {
1359
+ submit: accountCheckpointSubmitCommand,
1360
+ poll: accountCheckpointPollCommand,
1361
+ resend: accountCheckpointResendCommand
1362
+ },
1363
+ async run() {
1364
+ process.stderr.write("Usage: curviate account checkpoint submit | poll | resend\n");
1365
+ }
1366
+ });
1367
+ var accountCommand = defineCommand({
1368
+ meta: { name: "account", description: "LinkedIn account connection management." },
1369
+ subCommands: {
1370
+ list: accountListCommand,
1371
+ get: accountGetCommand,
1372
+ link: accountLinkCommand,
1373
+ "connect-link": accountConnectLinkCommand,
1374
+ "connect-session": accountConnectSessionCommand,
1375
+ reconnect: accountReconnectCommand,
1376
+ refresh: accountRefreshCommand,
1377
+ update: accountUpdateCommand,
1378
+ disconnect: accountDisconnectCommand,
1379
+ checkpoint: accountCheckpointCommand
1380
+ },
1381
+ async run() {
1382
+ process.stderr.write(
1383
+ "Usage: curviate account <subcommand>\n list | get | link | connect-link | connect-session poll | reconnect | refresh | update | disconnect | checkpoint\n"
1384
+ );
1385
+ }
1386
+ });
1387
+ export {
1388
+ accountCommand,
1389
+ runAccountCheckpointPoll,
1390
+ runAccountCheckpointResend,
1391
+ runAccountCheckpointSubmit,
1392
+ runAccountConnectLink,
1393
+ runAccountConnectSessionPoll,
1394
+ runAccountDisconnect,
1395
+ runAccountGet,
1396
+ runAccountLink,
1397
+ runAccountList,
1398
+ runAccountReconnect,
1399
+ runAccountRefresh,
1400
+ runAccountUpdate
1401
+ };