@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.
- package/CHANGELOG.md +34 -0
- package/README.md +27 -0
- package/dist/account-P5K5UDWF.js +1401 -0
- package/dist/{chunk-TDYIQHQX.js → chunk-4JHGVY7R.js} +3 -1
- package/dist/{chunk-SZB3CFRZ.js → chunk-DMQZEPQE.js} +8 -1
- package/dist/{chunk-47SYFQRF.js → chunk-JXF47TRY.js} +1 -1
- package/dist/chunk-MKXA2LAR.js +51 -0
- package/dist/{exit-codes-NFIR57ZA.js → chunk-OSQ643UW.js} +4 -1
- package/dist/{chunk-P4UYBDCB.js → chunk-SSPILTKF.js} +16 -0
- package/dist/{chunk-CHFKVAEI.js → chunk-U7Y4EXHT.js} +1 -0
- package/dist/cli.js +18 -16
- package/dist/{company-WMO42NXD.js → company-IMAVQBZL.js} +7 -7
- package/dist/{config-2GBLD4GN.js → config-Q2AEWSEC.js} +1 -1
- package/dist/{connect-35ZPFUSM.js → connect-J5NPWKAQ.js} +14 -14
- package/dist/exit-codes-2ASIFXGR.js +11 -0
- package/dist/{inbox-YV6GPRG6.js → inbox-5P5TB6AI.js} +6 -6
- package/dist/job-EIXYBDF6.js +105 -0
- package/dist/{login-POKHNDYX.js → login-BBVQLXM7.js} +4 -49
- package/dist/{message-YRMLMHK7.js → message-646BIYRH.js} +5 -5
- package/dist/{post-KBSR3ADF.js → post-TI6KV26J.js} +7 -7
- package/dist/{profile-5EPEZGUD.js → profile-TUIZZKM7.js} +14 -14
- package/dist/{recruiter-B5UIHTIN.js → recruiter-7K56E2QA.js} +61 -16
- package/dist/{sales-nav-4DYBMHKN.js → sales-nav-I2KMQFFF.js} +13 -13
- package/dist/{search-BRFUS2JW.js → search-TF6OOEUE.js} +8 -8
- package/dist/{webhook-F6CRIISR.js → webhook-VHREWB56.js} +6 -6
- package/package.json +4 -3
- package/dist/account-JCNH2HOR.js +0 -649
|
@@ -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
|
+
};
|