@applyguy/sdk 0.0.0-stage → 0.2.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/dist/cli.js ADDED
@@ -0,0 +1,687 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import { basename } from "node:path";
4
+ import process from "node:process";
5
+ import { setTimeout as delay } from "node:timers/promises";
6
+ import { ApplyGuy, ApplyGuyApiError, } from "./index.js";
7
+ const HELP = [
8
+ "Apply Guy CLI — JSON-first job search and application automation",
9
+ "",
10
+ "Usage:",
11
+ " applyguy <command> [options]",
12
+ "",
13
+ "Discovery and setup:",
14
+ " openapi",
15
+ " bootstrap --input <file|->",
16
+ " auth refresh",
17
+ " keys list|create|revoke",
18
+ " integrations status|connect-gmail",
19
+ "",
20
+ "Jobs and profile:",
21
+ " jobs search [--q text] [--ats name] [--remote remote|hybrid|onsite] [--limit n] [--cursor n]",
22
+ " jobs get <job-id>",
23
+ " profile get",
24
+ " profile update --input <file|->",
25
+ " profile resume upload <path|-> [--filename name]",
26
+ "",
27
+ "Applications:",
28
+ " applications preflight (--job-id id | --job-url url) [--mode managed|agent]",
29
+ " applications create (--job-id id | --job-url url) --idempotency-key key [--mode managed|agent] [--wait]",
30
+ " applications list",
31
+ " applications get|wait|answer-request|cancel <application-id>",
32
+ " applications answer <application-id> --input <file|->",
33
+ "",
34
+ "Credits, usage, and webhooks:",
35
+ " credits balance",
36
+ " credits checkout --amount-cents n",
37
+ " credits checkout-status <session-id>",
38
+ " usage get [--range 24h|7d|30d|90d]",
39
+ " usage links list|create|revoke|exchange",
40
+ " webhooks list|create|update|delete",
41
+ "",
42
+ "Global options:",
43
+ " --base-url url Override APPLYGUY_BASE_URL",
44
+ " --pretty Indent JSON output",
45
+ " --help Show this help",
46
+ " --version Show the package version",
47
+ "",
48
+ "Authentication:",
49
+ " APPLYGUY_API_KEY or APPLYGUY_API_KEY_FILE",
50
+ " APPLYGUY_ACCESS_TOKEN or APPLYGUY_ACCESS_TOKEN_FILE",
51
+ " APPLYGUY_REFRESH_TOKEN or APPLYGUY_REFRESH_TOKEN_FILE",
52
+ "",
53
+ "Output:",
54
+ " Success is one JSON document on stdout. Errors are one JSON document on stderr.",
55
+ " The CLI never prompts, colors output, or accepts secrets as command arguments.",
56
+ ].join("\n");
57
+ const BOOLEAN_FLAGS = new Set([
58
+ "help",
59
+ "pretty",
60
+ "version",
61
+ "wait",
62
+ "require-success",
63
+ ]);
64
+ const GLOBAL_FLAGS = new Set([
65
+ "base-url",
66
+ "help",
67
+ "pretty",
68
+ "version",
69
+ ]);
70
+ class CliError extends Error {
71
+ code;
72
+ exitCode;
73
+ details;
74
+ constructor(code, message, exitCode, details) {
75
+ super(message);
76
+ this.code = code;
77
+ this.exitCode = exitCode;
78
+ this.details = details;
79
+ this.name = "CliError";
80
+ }
81
+ }
82
+ function usage(message, details) {
83
+ throw new CliError("invalid_cli_input", message, 2, details);
84
+ }
85
+ function parseArguments(args) {
86
+ const positionals = [];
87
+ const flags = new Map();
88
+ for (let index = 0; index < args.length; index += 1) {
89
+ const argument = args[index];
90
+ if (argument === "--") {
91
+ positionals.push(...args.slice(index + 1));
92
+ break;
93
+ }
94
+ if (argument === "-h") {
95
+ flags.set("help", true);
96
+ continue;
97
+ }
98
+ if (argument === "-V") {
99
+ flags.set("version", true);
100
+ continue;
101
+ }
102
+ if (!argument.startsWith("--")) {
103
+ positionals.push(argument);
104
+ continue;
105
+ }
106
+ const raw = argument.slice(2);
107
+ const separator = raw.indexOf("=");
108
+ const name = separator >= 0 ? raw.slice(0, separator) : raw;
109
+ if (!/^[a-z][a-z0-9-]*$/.test(name))
110
+ usage("Invalid option name: " + argument);
111
+ if (flags.has(name))
112
+ usage("Option may be provided only once: --" + name);
113
+ if (BOOLEAN_FLAGS.has(name)) {
114
+ if (separator >= 0)
115
+ usage("Boolean option does not accept a value: --" + name);
116
+ flags.set(name, true);
117
+ continue;
118
+ }
119
+ const attached = separator >= 0 ? raw.slice(separator + 1) : undefined;
120
+ const next = attached ?? args[index + 1];
121
+ if (next === undefined || (attached === undefined && next.startsWith("--"))) {
122
+ usage("Missing value for --" + name);
123
+ }
124
+ if (attached === undefined)
125
+ index += 1;
126
+ flags.set(name, next);
127
+ }
128
+ return { positionals, flags };
129
+ }
130
+ function assertFlags(parsed, allowed) {
131
+ const accepted = new Set([...GLOBAL_FLAGS, ...allowed]);
132
+ for (const name of parsed.flags.keys()) {
133
+ if (!accepted.has(name))
134
+ usage("Unknown option for this command: --" + name);
135
+ }
136
+ }
137
+ function assertPositionals(parsed, count, usageText) {
138
+ if (parsed.positionals.length !== count)
139
+ usage("Usage: applyguy " + usageText);
140
+ }
141
+ function stringFlag(parsed, name) {
142
+ const value = parsed.flags.get(name);
143
+ if (value === undefined)
144
+ return undefined;
145
+ if (value === true)
146
+ usage("--" + name + " requires a value");
147
+ return value;
148
+ }
149
+ function requiredFlag(parsed, name) {
150
+ const value = stringFlag(parsed, name)?.trim();
151
+ if (!value)
152
+ usage("--" + name + " is required");
153
+ return value;
154
+ }
155
+ function numberFlag(parsed, name, fallback, minimum, maximum) {
156
+ const raw = stringFlag(parsed, name);
157
+ if (raw === undefined)
158
+ return fallback;
159
+ const value = Number(raw);
160
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
161
+ usage("--" + name + " must be an integer from " + minimum + " through " + maximum);
162
+ }
163
+ return value;
164
+ }
165
+ function enumFlag(parsed, name, values, fallback) {
166
+ const raw = stringFlag(parsed, name);
167
+ if (raw === undefined)
168
+ return fallback;
169
+ if (!values.includes(raw)) {
170
+ usage("--" + name + " must be one of: " + values.join(", "));
171
+ }
172
+ return raw;
173
+ }
174
+ function isRecord(value) {
175
+ return value !== null && typeof value === "object" && !Array.isArray(value);
176
+ }
177
+ async function readStdin(limitBytes) {
178
+ const chunks = [];
179
+ let size = 0;
180
+ for await (const chunk of process.stdin) {
181
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
182
+ size += buffer.length;
183
+ if (size > limitBytes) {
184
+ throw new CliError("input_too_large", "Standard input exceeded the local size limit.", 2, { limitBytes });
185
+ }
186
+ chunks.push(buffer);
187
+ }
188
+ return Buffer.concat(chunks);
189
+ }
190
+ async function readBytes(path, limitBytes) {
191
+ try {
192
+ const value = path === "-" ? await readStdin(limitBytes) : await readFile(path);
193
+ if (value.length > limitBytes) {
194
+ throw new CliError("input_too_large", "Input file exceeded the local size limit.", 2, { limitBytes });
195
+ }
196
+ return value;
197
+ }
198
+ catch (error) {
199
+ if (error instanceof CliError)
200
+ throw error;
201
+ throw new CliError("input_read_failed", "Could not read input: " + path, 2);
202
+ }
203
+ }
204
+ async function readJsonInput(parsed, required = true) {
205
+ const path = stringFlag(parsed, "input");
206
+ if (!path) {
207
+ if (!required)
208
+ return {};
209
+ usage("--input <file|-> is required");
210
+ }
211
+ const bytes = await readBytes(path, 1_048_576);
212
+ let value;
213
+ try {
214
+ value = JSON.parse(bytes.toString("utf8"));
215
+ }
216
+ catch {
217
+ throw new CliError("invalid_json", "Input is not valid JSON.", 2);
218
+ }
219
+ if (!isRecord(value)) {
220
+ throw new CliError("invalid_json", "Input JSON must be an object.", 2);
221
+ }
222
+ return value;
223
+ }
224
+ async function secretFromEnvironment(valueName, fileName) {
225
+ const direct = process.env[valueName]?.trim();
226
+ if (direct)
227
+ return direct;
228
+ const path = process.env[fileName]?.trim();
229
+ if (!path)
230
+ return undefined;
231
+ try {
232
+ const value = (await readFile(path, "utf8")).trim();
233
+ return value || undefined;
234
+ }
235
+ catch {
236
+ throw new CliError("secret_file_unreadable", "Could not read " + fileName + ".", 3);
237
+ }
238
+ }
239
+ function baseUrl(parsed) {
240
+ return stringFlag(parsed, "base-url") ?? process.env.APPLYGUY_BASE_URL?.trim() ?? undefined;
241
+ }
242
+ async function createClient(parsed, requirements = { apiKey: true }) {
243
+ const apiKey = await secretFromEnvironment("APPLYGUY_API_KEY", "APPLYGUY_API_KEY_FILE");
244
+ const accessToken = await secretFromEnvironment("APPLYGUY_ACCESS_TOKEN", "APPLYGUY_ACCESS_TOKEN_FILE");
245
+ if (requirements.apiKey && !apiKey) {
246
+ throw new CliError("api_key_required", "Set APPLYGUY_API_KEY or APPLYGUY_API_KEY_FILE.", 3);
247
+ }
248
+ if (requirements.accessToken && !accessToken) {
249
+ throw new CliError("access_token_required", "Set APPLYGUY_ACCESS_TOKEN or APPLYGUY_ACCESS_TOKEN_FILE.", 3);
250
+ }
251
+ try {
252
+ return new ApplyGuy({
253
+ apiKey: apiKey ?? "unused",
254
+ accessToken,
255
+ baseUrl: baseUrl(parsed),
256
+ });
257
+ }
258
+ catch (error) {
259
+ throw new CliError("invalid_base_url", error instanceof Error ? error.message : "The API base URL is invalid.", 2);
260
+ }
261
+ }
262
+ function applicationInput(parsed) {
263
+ const jobId = stringFlag(parsed, "job-id")?.trim();
264
+ const jobUrl = stringFlag(parsed, "job-url")?.trim();
265
+ if (Boolean(jobId) === Boolean(jobUrl)) {
266
+ usage("Provide exactly one of --job-id or --job-url.");
267
+ }
268
+ const mode = enumFlag(parsed, "mode", ["managed", "agent"], "managed");
269
+ const ats = stringFlag(parsed, "ats")?.trim() || undefined;
270
+ return jobId
271
+ ? { jobId, mode, ...(ats ? { ats } : {}) }
272
+ : { jobUrl: jobUrl, mode, ...(ats ? { ats } : {}) };
273
+ }
274
+ function successEnvelope(value) {
275
+ if (isRecord(value) && Object.prototype.hasOwnProperty.call(value, "data")) {
276
+ const { data, ...metadata } = value;
277
+ return Object.keys(metadata).length > 0
278
+ ? { ok: true, data, meta: metadata }
279
+ : { ok: true, data };
280
+ }
281
+ return { ok: true, data: value ?? null };
282
+ }
283
+ function writeJson(stream, value, pretty) {
284
+ stream.write(JSON.stringify(value, null, pretty ? 2 : 0) + "\n");
285
+ }
286
+ function apiExitCode(status) {
287
+ if (status === 401 || status === 403)
288
+ return 3;
289
+ if (status === 404)
290
+ return 4;
291
+ if (status === 409)
292
+ return 5;
293
+ if (status === 402)
294
+ return 6;
295
+ if (status === 429)
296
+ return 7;
297
+ if (status >= 500)
298
+ return 8;
299
+ if (status === 400 || status === 413 || status === 422)
300
+ return 2;
301
+ return 10;
302
+ }
303
+ function handleFailure(error, pretty) {
304
+ if (error instanceof ApplyGuyApiError) {
305
+ writeJson(process.stderr, {
306
+ ok: false,
307
+ error: {
308
+ code: error.code,
309
+ message: error.message,
310
+ status: error.status,
311
+ requestId: error.requestId,
312
+ retryAfterSeconds: error.retryAfterSeconds,
313
+ details: error.details,
314
+ },
315
+ }, pretty);
316
+ process.exit(apiExitCode(error.status));
317
+ }
318
+ if (error instanceof CliError) {
319
+ writeJson(process.stderr, {
320
+ ok: false,
321
+ error: {
322
+ code: error.code,
323
+ message: error.message,
324
+ details: error.details,
325
+ },
326
+ }, pretty);
327
+ process.exit(error.exitCode);
328
+ }
329
+ const isNetworkError = error instanceof TypeError
330
+ && (error.message.includes("fetch") || error.message.includes("network"));
331
+ writeJson(process.stderr, {
332
+ ok: false,
333
+ error: {
334
+ code: isNetworkError ? "network_error" : "cli_internal_error",
335
+ message: error instanceof Error ? error.message : "Unexpected CLI failure.",
336
+ ...(process.env.APPLYGUY_DEBUG === "1" && error instanceof Error
337
+ ? { stack: error.stack }
338
+ : {}),
339
+ },
340
+ }, pretty);
341
+ process.exit(isNetworkError ? 8 : 10);
342
+ }
343
+ async function fetchOpenApi(parsed) {
344
+ const rawBase = baseUrl(parsed) ?? "https://api.applyguy.ai";
345
+ let url;
346
+ try {
347
+ url = new URL("/v1/openapi.json", rawBase);
348
+ }
349
+ catch {
350
+ throw new CliError("invalid_base_url", "The API base URL is invalid.", 2);
351
+ }
352
+ const response = await fetch(url);
353
+ if (!response.ok) {
354
+ throw new CliError("openapi_unavailable", "OpenAPI request failed with HTTP " + response.status + ".", response.status >= 500 ? 8 : 10);
355
+ }
356
+ return response.json();
357
+ }
358
+ async function waitForApplication(client, id, parsed) {
359
+ const timeoutMs = numberFlag(parsed, "timeout-ms", 600_000, 1_000, 3_600_000);
360
+ const initialIntervalMs = numberFlag(parsed, "interval-ms", 1_000, 250, 60_000);
361
+ const requested = stringFlag(parsed, "until") ?? "awaiting_answers,success,failed,cancelled";
362
+ const validStatuses = [
363
+ "queued",
364
+ "running",
365
+ "awaiting_answers",
366
+ "success",
367
+ "failed",
368
+ "cancelled",
369
+ ];
370
+ const until = new Set(requested.split(",").map((value) => value.trim()).filter(Boolean));
371
+ if (until.size === 0 || [...until].some((status) => !validStatuses.includes(status))) {
372
+ usage("--until must be a comma-separated list of application statuses.");
373
+ }
374
+ const startedAt = Date.now();
375
+ let intervalMs = initialIntervalMs;
376
+ let lastStatus;
377
+ let lastApplication;
378
+ while (Date.now() - startedAt < timeoutMs) {
379
+ try {
380
+ const result = await client.applications.get(id);
381
+ lastApplication = result.data;
382
+ if (lastApplication.status !== lastStatus) {
383
+ lastStatus = lastApplication.status;
384
+ intervalMs = initialIntervalMs;
385
+ }
386
+ if (until.has(lastApplication.status)) {
387
+ if (parsed.flags.has("require-success") && lastApplication.status !== "success") {
388
+ throw new CliError("application_not_successful", "Application reached status " + lastApplication.status + ".", 5, { application: lastApplication });
389
+ }
390
+ return result;
391
+ }
392
+ }
393
+ catch (error) {
394
+ if (!(error instanceof ApplyGuyApiError) || error.status !== 429)
395
+ throw error;
396
+ intervalMs = Math.max(intervalMs, (error.retryAfterSeconds ?? 1) * 1_000);
397
+ }
398
+ const remaining = timeoutMs - (Date.now() - startedAt);
399
+ if (remaining <= 0)
400
+ break;
401
+ await delay(Math.min(intervalMs, remaining));
402
+ intervalMs = Math.min(10_000, Math.ceil(intervalMs * 1.6));
403
+ }
404
+ throw new CliError("wait_timeout", "Timed out waiting for the application. The remote application was not cancelled.", 9, { application: lastApplication ?? null });
405
+ }
406
+ async function dispatch(parsed) {
407
+ const [group, action, subject] = parsed.positionals;
408
+ if (group === "openapi") {
409
+ assertPositionals(parsed, 1, "openapi");
410
+ assertFlags(parsed, []);
411
+ return fetchOpenApi(parsed);
412
+ }
413
+ if (group === "bootstrap") {
414
+ assertPositionals(parsed, 1, "bootstrap --input <file|->");
415
+ assertFlags(parsed, ["input"]);
416
+ const input = await readJsonInput(parsed);
417
+ return ApplyGuy.bootstrap(input, {
418
+ baseUrl: baseUrl(parsed),
419
+ });
420
+ }
421
+ if (group === "auth" && action === "refresh") {
422
+ assertPositionals(parsed, 2, "auth refresh");
423
+ assertFlags(parsed, []);
424
+ const token = await secretFromEnvironment("APPLYGUY_REFRESH_TOKEN", "APPLYGUY_REFRESH_TOKEN_FILE");
425
+ if (!token) {
426
+ throw new CliError("refresh_token_required", "Set APPLYGUY_REFRESH_TOKEN or APPLYGUY_REFRESH_TOKEN_FILE.", 3);
427
+ }
428
+ return ApplyGuy.refresh(token, { baseUrl: baseUrl(parsed) });
429
+ }
430
+ if (group === "jobs" && action === "search") {
431
+ assertPositionals(parsed, 2, "jobs search [options]");
432
+ assertFlags(parsed, ["q", "ats", "country", "remote", "limit", "cursor"]);
433
+ const client = await createClient(parsed);
434
+ const country = enumFlag(parsed, "country", ["US"]);
435
+ const remote = enumFlag(parsed, "remote", ["remote", "hybrid", "onsite"]);
436
+ const limit = numberFlag(parsed, "limit", undefined, 1, 100);
437
+ const cursor = numberFlag(parsed, "cursor", undefined, 0, 10_000);
438
+ return client.jobs.search({
439
+ ...(stringFlag(parsed, "q") ? { q: stringFlag(parsed, "q") } : {}),
440
+ ...(stringFlag(parsed, "ats") ? { ats: stringFlag(parsed, "ats") } : {}),
441
+ ...(country ? { country } : {}),
442
+ ...(remote ? { remote } : {}),
443
+ ...(limit !== undefined ? { limit } : {}),
444
+ ...(cursor !== undefined ? { cursor } : {}),
445
+ });
446
+ }
447
+ if (group === "jobs" && action === "get") {
448
+ assertPositionals(parsed, 3, "jobs get <job-id>");
449
+ assertFlags(parsed, []);
450
+ return (await createClient(parsed)).jobs.get(subject);
451
+ }
452
+ if (group === "profile" && action === "get") {
453
+ assertPositionals(parsed, 2, "profile get");
454
+ assertFlags(parsed, []);
455
+ return (await createClient(parsed)).profile.get();
456
+ }
457
+ if (group === "profile" && action === "update") {
458
+ assertPositionals(parsed, 2, "profile update --input <file|->");
459
+ assertFlags(parsed, ["input"]);
460
+ const input = await readJsonInput(parsed);
461
+ return (await createClient(parsed)).profile.update(input);
462
+ }
463
+ if (group === "profile" && action === "resume" && subject === "upload") {
464
+ assertPositionals(parsed, 4, "profile resume upload <path|-> [--filename name]");
465
+ assertFlags(parsed, ["filename"]);
466
+ const path = parsed.positionals[3];
467
+ const filename = stringFlag(parsed, "filename")
468
+ ?? (path === "-" ? undefined : basename(path));
469
+ if (!filename)
470
+ usage("--filename is required when uploading a resume from stdin.");
471
+ const bytes = await readBytes(path, 25 * 1_048_576);
472
+ const copy = new Uint8Array(new ArrayBuffer(bytes.length));
473
+ copy.set(bytes);
474
+ return (await createClient(parsed)).profile.uploadResume(new Blob([copy.buffer]), filename);
475
+ }
476
+ if (group === "credits" && action === "balance") {
477
+ assertPositionals(parsed, 2, "credits balance");
478
+ assertFlags(parsed, []);
479
+ return (await createClient(parsed)).credits.get();
480
+ }
481
+ if (group === "credits" && action === "checkout") {
482
+ assertPositionals(parsed, 2, "credits checkout --amount-cents n");
483
+ assertFlags(parsed, ["amount-cents"]);
484
+ const amount = numberFlag(parsed, "amount-cents", undefined, 1, 10_000_000);
485
+ if (amount === undefined)
486
+ usage("--amount-cents is required.");
487
+ return (await createClient(parsed)).credits.checkout(amount);
488
+ }
489
+ if (group === "credits" && action === "checkout-status") {
490
+ assertPositionals(parsed, 3, "credits checkout-status <session-id>");
491
+ assertFlags(parsed, []);
492
+ return (await createClient(parsed)).credits.checkoutStatus(subject);
493
+ }
494
+ if (group === "applications" && action === "preflight") {
495
+ assertPositionals(parsed, 2, "applications preflight (--job-id id | --job-url url)");
496
+ assertFlags(parsed, ["job-id", "job-url", "ats", "mode"]);
497
+ const input = applicationInput(parsed);
498
+ return (await createClient(parsed)).applications.preflight(input);
499
+ }
500
+ if (group === "applications" && action === "create") {
501
+ assertPositionals(parsed, 2, "applications create (--job-id id | --job-url url) --idempotency-key key");
502
+ assertFlags(parsed, [
503
+ "job-id",
504
+ "job-url",
505
+ "ats",
506
+ "mode",
507
+ "idempotency-key",
508
+ "wait",
509
+ "until",
510
+ "timeout-ms",
511
+ "interval-ms",
512
+ "require-success",
513
+ ]);
514
+ const input = applicationInput(parsed);
515
+ const key = requiredFlag(parsed, "idempotency-key");
516
+ if (key.length > 200)
517
+ usage("--idempotency-key must be at most 200 characters.");
518
+ const client = await createClient(parsed);
519
+ const created = await client.applications.create(input, key);
520
+ return parsed.flags.has("wait")
521
+ ? waitForApplication(client, created.data.id, parsed)
522
+ : created;
523
+ }
524
+ if (group === "applications" && action === "list") {
525
+ assertPositionals(parsed, 2, "applications list");
526
+ assertFlags(parsed, []);
527
+ return (await createClient(parsed)).applications.list();
528
+ }
529
+ if (group === "applications" && action === "get") {
530
+ assertPositionals(parsed, 3, "applications get <application-id>");
531
+ assertFlags(parsed, []);
532
+ return (await createClient(parsed)).applications.get(subject);
533
+ }
534
+ if (group === "applications" && action === "wait") {
535
+ assertPositionals(parsed, 3, "applications wait <application-id>");
536
+ assertFlags(parsed, ["until", "timeout-ms", "interval-ms", "require-success"]);
537
+ return waitForApplication(await createClient(parsed), subject, parsed);
538
+ }
539
+ if (group === "applications" && action === "answer-request") {
540
+ assertPositionals(parsed, 3, "applications answer-request <application-id>");
541
+ assertFlags(parsed, []);
542
+ return (await createClient(parsed)).applications.answerRequest(subject);
543
+ }
544
+ if (group === "applications" && action === "answer") {
545
+ assertPositionals(parsed, 3, "applications answer <application-id> --input <file|->");
546
+ assertFlags(parsed, ["input"]);
547
+ const input = await readJsonInput(parsed);
548
+ const requestId = typeof input.requestId === "string" ? input.requestId : undefined;
549
+ const fingerprint = typeof input.fingerprint === "string" ? input.fingerprint : undefined;
550
+ if (!requestId || !fingerprint || !Object.prototype.hasOwnProperty.call(input, "answer")) {
551
+ usage("Answer input requires requestId, fingerprint, and answer.");
552
+ }
553
+ return (await createClient(parsed)).applications.answer(subject, {
554
+ requestId,
555
+ fingerprint,
556
+ answer: input.answer,
557
+ });
558
+ }
559
+ if (group === "applications" && action === "cancel") {
560
+ assertPositionals(parsed, 3, "applications cancel <application-id>");
561
+ assertFlags(parsed, []);
562
+ return (await createClient(parsed)).applications.cancel(subject);
563
+ }
564
+ if (group === "usage" && action === "get") {
565
+ assertPositionals(parsed, 2, "usage get [--range 24h|7d|30d|90d]");
566
+ assertFlags(parsed, ["range"]);
567
+ const range = enumFlag(parsed, "range", ["24h", "7d", "30d", "90d"], "30d");
568
+ return (await createClient(parsed)).usage.get(range);
569
+ }
570
+ if (group === "usage" && action === "links" && subject === "list") {
571
+ assertPositionals(parsed, 3, "usage links list");
572
+ assertFlags(parsed, []);
573
+ return (await createClient(parsed)).usage.shareLinks.list();
574
+ }
575
+ if (group === "usage" && action === "links" && subject === "create") {
576
+ assertPositionals(parsed, 3, "usage links create [--input <file|->]");
577
+ assertFlags(parsed, ["input"]);
578
+ const input = await readJsonInput(parsed, false);
579
+ return (await createClient(parsed)).usage.shareLinks.create(input);
580
+ }
581
+ if (group === "usage" && action === "links" && subject === "revoke") {
582
+ assertPositionals(parsed, 4, "usage links revoke <link-id>");
583
+ assertFlags(parsed, []);
584
+ return (await createClient(parsed)).usage.shareLinks.revoke(parsed.positionals[3]);
585
+ }
586
+ if (group === "usage" && action === "links" && subject === "exchange") {
587
+ assertPositionals(parsed, 3, "usage links exchange --input <file|->");
588
+ assertFlags(parsed, ["input"]);
589
+ const input = await readJsonInput(parsed);
590
+ if (typeof input.token !== "string" || !input.token)
591
+ usage("Exchange input requires token.");
592
+ return ApplyGuy.exchangeUsageShareLink(input.token, { baseUrl: baseUrl(parsed) });
593
+ }
594
+ if (group === "webhooks" && action === "list") {
595
+ assertPositionals(parsed, 2, "webhooks list");
596
+ assertFlags(parsed, []);
597
+ return (await createClient(parsed)).webhooks.list();
598
+ }
599
+ if (group === "webhooks" && action === "create") {
600
+ assertPositionals(parsed, 2, "webhooks create --input <file|->");
601
+ assertFlags(parsed, ["input"]);
602
+ const input = await readJsonInput(parsed);
603
+ if (typeof input.url !== "string")
604
+ usage("Webhook input requires url.");
605
+ return (await createClient(parsed)).webhooks.create({
606
+ url: input.url,
607
+ ...(Array.isArray(input.events) ? { events: input.events } : {}),
608
+ });
609
+ }
610
+ if (group === "webhooks" && action === "update") {
611
+ assertPositionals(parsed, 3, "webhooks update <webhook-id> --input <file|->");
612
+ assertFlags(parsed, ["input"]);
613
+ const input = await readJsonInput(parsed);
614
+ return (await createClient(parsed)).webhooks.update(subject, input);
615
+ }
616
+ if (group === "webhooks" && action === "delete") {
617
+ assertPositionals(parsed, 3, "webhooks delete <webhook-id>");
618
+ assertFlags(parsed, []);
619
+ return (await createClient(parsed)).webhooks.delete(subject);
620
+ }
621
+ if (group === "keys" && action === "list") {
622
+ assertPositionals(parsed, 2, "keys list");
623
+ assertFlags(parsed, []);
624
+ return (await createClient(parsed, { accessToken: true })).apiKeys.list();
625
+ }
626
+ if (group === "keys" && action === "create") {
627
+ assertPositionals(parsed, 2, "keys create [--input <file|->]");
628
+ assertFlags(parsed, ["input"]);
629
+ const input = await readJsonInput(parsed, false);
630
+ return (await createClient(parsed, { accessToken: true })).apiKeys.create(input);
631
+ }
632
+ if (group === "keys" && action === "revoke") {
633
+ assertPositionals(parsed, 3, "keys revoke <key-id>");
634
+ assertFlags(parsed, []);
635
+ return (await createClient(parsed, { accessToken: true })).apiKeys.revoke(subject);
636
+ }
637
+ if (group === "integrations" && action === "status") {
638
+ assertPositionals(parsed, 2, "integrations status");
639
+ assertFlags(parsed, []);
640
+ return (await createClient(parsed, { accessToken: true })).integrations.status();
641
+ }
642
+ if (group === "integrations" && action === "connect-gmail") {
643
+ assertPositionals(parsed, 2, "integrations connect-gmail --input <file|->");
644
+ assertFlags(parsed, ["input"]);
645
+ const input = await readJsonInput(parsed);
646
+ if (typeof input.gmailUser !== "string" || typeof input.gmailAppPassword !== "string") {
647
+ usage("Gmail input requires gmailUser and gmailAppPassword.");
648
+ }
649
+ return (await createClient(parsed, { accessToken: true })).integrations.connectGmail({
650
+ gmailUser: input.gmailUser,
651
+ gmailAppPassword: input.gmailAppPassword,
652
+ });
653
+ }
654
+ usage("Unknown command. Run applyguy --help.");
655
+ }
656
+ async function packageVersion() {
657
+ const manifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
658
+ return typeof manifest.version === "string" ? manifest.version : "unknown";
659
+ }
660
+ async function run() {
661
+ const parsed = parseArguments(process.argv.slice(2));
662
+ const pretty = parsed.flags.has("pretty");
663
+ if (parsed.flags.has("version")) {
664
+ process.stdout.write(await packageVersion() + "\n");
665
+ return;
666
+ }
667
+ if (parsed.flags.has("help") || parsed.positionals.length === 0) {
668
+ process.stdout.write(HELP + "\n");
669
+ return;
670
+ }
671
+ const result = await dispatch(parsed);
672
+ writeJson(process.stdout, successEnvelope(result), pretty);
673
+ }
674
+ process.once("SIGINT", () => {
675
+ writeJson(process.stderr, {
676
+ ok: false,
677
+ error: {
678
+ code: "interrupted",
679
+ message: "CLI interrupted. No remote application was cancelled.",
680
+ },
681
+ }, false);
682
+ process.exit(130);
683
+ });
684
+ run().catch((error) => {
685
+ handleFailure(error, process.argv.slice(2).includes("--pretty"));
686
+ });
687
+ //# sourceMappingURL=cli.js.map