@wrangled/cli 0.3.44 → 0.3.45

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/main.js CHANGED
@@ -4,7 +4,7 @@ import { mkdir, readFile, stat } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { basename, dirname, extname, join, resolve } from "node:path";
6
6
  import { parseArgs } from "node:util";
7
- import { api, ApiError, apiDownload, apiText, apiUpload, CliError, exitCodes, getSigned, IMPORT_LIMITS, IMPORT_NAME_PATTERN, putSigned, readCredentials, removeCredentials, resolveApiUrl, resolveConnection, UnreadableCredentialsError, writeCredentials, entryTagFlags, streamSigned, } from "./api.js";
7
+ import { api, ApiError, apiDownload, apiText, apiUpload, CliError, exitCodes, getSigned, IMPORT_LIMITS, IMPORT_NAME_PATTERN, putSigned, CLIENT_FILE, parseClientName, readSavedLogins, removeCredentials, resolveApiUrl, resolveConnection, resolveLogin, UnreadableCredentialsError, writeCredentials, entryTagFlags, streamSigned, } from "./api.js";
8
8
  import { brand, envPrefix } from "./brand.js";
9
9
  import { firstSymlink, inRunFolder, writeFileAtomic } from "./files.js";
10
10
  import { guide, skill } from "./guide.js";
@@ -19,41 +19,47 @@ const SERVER_WAIT_CAP = 50;
19
19
  const VOICE_USAGE = "voice [--kind opener|objection|pain|desire|phrase] [--since <YYYY-MM-DD>] [--sort common|useful] [--limit <n>] [--json]";
20
20
  const commands = {
21
21
  login: {
22
- usage: `login (paste the key when asked, pipe it on stdin, or set ${envPrefix}_API_KEY)`,
22
+ usage: `login [--client <name>] (paste the key when asked, pipe it on stdin, or set ${envPrefix}_API_KEY); --client saves it under that client's name`,
23
23
  async run(args, options) {
24
24
  if (args.length > 0) {
25
25
  throw new CliError(`login takes no arguments. Pipe the key on stdin or set ${envPrefix}_API_KEY.`, exitCodes.usage);
26
26
  }
27
+ const client = options.client === undefined ? undefined : parseClientName(options.client);
27
28
  const apiKey = await readKeyFromUser();
28
- const stored = await readCredentials().catch((error) => {
29
+ const saved = await readSavedLogins().catch((error) => {
29
30
  if (error instanceof UnreadableCredentialsError)
30
31
  return null;
31
32
  throw error;
32
33
  });
33
- const apiUrl = resolveApiUrl(options.apiUrl, stored);
34
+ const apiUrl = resolveApiUrl(options.apiUrl, (client === undefined ? undefined : saved?.clients[client]) ?? saved?.default ?? null);
34
35
  const me = await api({ apiUrl, apiKey }, "/api/v1/me");
35
- const path = await writeCredentials({ apiKey, apiUrl });
36
+ const path = await writeCredentials({ apiKey, apiUrl }, { client });
36
37
  if (options.format === "json")
37
38
  return printJson(me);
38
- console.log(`Logged in to ${me.workspace.name} (${me.workspace.slug}) at ${apiUrl}.`);
39
+ console.log(`Logged in to ${me.workspace.name} (${me.workspace.slug}) at ${apiUrl}${client === undefined ? "" : ` as client ${client}`}.`);
39
40
  console.log(`Saved credentials to ${path}.`);
40
41
  },
41
42
  },
42
43
  logout: {
43
- usage: "logout",
44
- async run() {
45
- await removeCredentials();
46
- console.log("Logged out.");
44
+ usage: "logout [--client <name>] (without --client, removes the default login and keeps named clients)",
45
+ async run(_args, options) {
46
+ const client = options.client === undefined ? undefined : parseClientName(options.client);
47
+ await removeCredentials(client);
48
+ console.log(client === undefined ? "Logged out." : `Logged out of client ${client}.`);
47
49
  },
48
50
  },
49
51
  whoami: {
50
- usage: "whoami",
52
+ usage: "whoami (also says which client's key is in use and why)",
51
53
  async run(_args, options) {
52
- const me = await api(await resolveConnection(options.apiUrl), "/api/v1/me");
54
+ const { connection, choice } = await resolveLogin(options);
55
+ const me = await api(connection, "/api/v1/me");
56
+ const client = "client" in choice ? choice.client : null;
57
+ const clientFile = choice.source === "folder" ? choice.path : null;
53
58
  if (options.format === "json")
54
- return printJson(me);
59
+ return printJson({ ...me, client, source: choice.source, clientFile });
55
60
  const { name, slug, id } = me.workspace;
56
61
  console.log(renderTable(["workspace", "slug", "id", "library access"], [{ workspace: name, slug, id, "library access": me.knowledge ? "on" : "off" }]));
62
+ console.log(`\n${clientReason(choice)}`);
57
63
  },
58
64
  },
59
65
  "knowledge search": {
@@ -72,7 +78,7 @@ const commands = {
72
78
  query.set("library", options.library);
73
79
  for (const [field, value] of filters)
74
80
  query.set(field, value);
75
- const answer = await api(await resolveConnection(options.apiUrl), `/api/v1/knowledge/search?${query}`);
81
+ const answer = await api(await resolveConnection(options), `/api/v1/knowledge/search?${query}`);
76
82
  if (options.format === "json")
77
83
  return printJson(answer);
78
84
  console.log(answer.results.length === 0 ? "Nothing in the library matched." : answer.results.map(knowledgeBlock).join("\n\n"));
@@ -102,7 +108,7 @@ const commands = {
102
108
  })),
103
109
  }
104
110
  : await readToolInput(options);
105
- const result = await api(await resolveConnection(options.apiUrl), KNOWLEDGE_TAGS_PATH, body);
111
+ const result = await api(await resolveConnection(options), KNOWLEDGE_TAGS_PATH, body);
106
112
  if (options.format === "json")
107
113
  return printJson(result);
108
114
  console.log(`Tagged ${result.tagged}, took off ${result.removed}.`);
@@ -121,7 +127,7 @@ const commands = {
121
127
  if (options.search !== undefined)
122
128
  query.set("search", options.search);
123
129
  const path = `/api/v1/warehouse/schema${query.size > 0 ? `?${query}` : ""}`;
124
- const connection = await resolveConnection(options.apiUrl);
130
+ const connection = await resolveConnection(options);
125
131
  if (options.summary) {
126
132
  const summary = await api(connection, path);
127
133
  if (options.format === "json")
@@ -162,7 +168,7 @@ const commands = {
162
168
  throw new CliError("--all saves to a file: add --out <file.csv | file.ndjson | file.json>.", exitCodes.usage);
163
169
  }
164
170
  const sql = await readSql(args, options.file);
165
- const connection = await resolveConnection(options.apiUrl);
171
+ const connection = await resolveConnection(options);
166
172
  if (options.all)
167
173
  return saveWholeAnswer(connection, sql, options.out, options.format);
168
174
  const text = await apiText(connection, "/api/v1/warehouse/query", { sql });
@@ -182,7 +188,7 @@ const commands = {
182
188
  if (args.length > 0)
183
189
  throw new CliError("Usage: funnel [--from <date>] [--to <date>] [--grain day|week|month]", exitCodes.usage);
184
190
  const query = reportQuery(options, { grain: options.grain });
185
- const { funnel } = await api(await resolveConnection(options.apiUrl), `/api/v1/reports/funnel${query}`);
191
+ const { funnel } = await api(await resolveConnection(options), `/api/v1/reports/funnel${query}`);
186
192
  if (options.format === "json")
187
193
  return printJson({ funnel });
188
194
  printReport(`Funnel by ${funnel.grain}`, funnel, ["period", "currency"], FUNNEL_NUMBERS);
@@ -195,7 +201,7 @@ const commands = {
195
201
  if (args.length > 0)
196
202
  throw new CliError("Usage: money [--from <date>] [--to <date>] [--grain day|week|month]", exitCodes.usage);
197
203
  const query = reportQuery(options, { grain: options.grain });
198
- const { money } = await api(await resolveConnection(options.apiUrl), `/api/v1/reports/money${query}`);
204
+ const { money } = await api(await resolveConnection(options), `/api/v1/reports/money${query}`);
199
205
  if (options.format === "json")
200
206
  return printJson({ money });
201
207
  printReport(`Money by ${money.grain}`, money, ["period", "currency"], MONEY_NUMBERS);
@@ -218,7 +224,7 @@ const commands = {
218
224
  if (args.length > 0)
219
225
  throw new CliError("Usage: campaigns [--from <date>] [--to <date>] [--channel <channel>] [--limit <n>]", exitCodes.usage);
220
226
  const query = reportQuery(options, { channel: options.channel, limit: options.limit });
221
- const { campaignResults } = await api(await resolveConnection(options.apiUrl), `/api/v1/reports/campaigns${query}`);
227
+ const { campaignResults } = await api(await resolveConnection(options), `/api/v1/reports/campaigns${query}`);
222
228
  if (options.format === "json")
223
229
  return printJson({ campaignResults });
224
230
  const title = campaignResults.channel === null ? "Campaigns, best first" : `${campaignResults.channel} campaigns, best first`;
@@ -229,7 +235,7 @@ const commands = {
229
235
  "tools list": {
230
236
  usage: "tools list",
231
237
  async run(_args, options) {
232
- const { tools } = await api(await resolveConnection(options.apiUrl), "/api/v1/tools");
238
+ const { tools } = await api(await resolveConnection(options), "/api/v1/tools");
233
239
  if (options.format === "json")
234
240
  return printJson({ tools });
235
241
  console.log(renderTable(["id", "mode", "output", "title"], tools));
@@ -242,7 +248,7 @@ const commands = {
242
248
  if (!tool || rest.length > 0)
243
249
  throw new CliError("Usage: tools run <tool> [--input '<json>']", exitCodes.usage);
244
250
  const input = await readToolInput(options);
245
- const connection = await resolveConnection(options.apiUrl);
251
+ const connection = await resolveConnection(options);
246
252
  const body = {
247
253
  input,
248
254
  ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
@@ -257,7 +263,7 @@ const commands = {
257
263
  async run(args, options) {
258
264
  if (args.length !== 1)
259
265
  throw new CliError("Usage: tools get <run-id>", exitCodes.usage);
260
- const connection = await resolveConnection(options.apiUrl);
266
+ const connection = await resolveConnection(options);
261
267
  const { run } = await api(connection, `/api/v1/runs/${encodeURIComponent(args[0])}`);
262
268
  printRun(await waitForRun(connection, run, options.wait), options.format);
263
269
  },
@@ -267,7 +273,7 @@ const commands = {
267
273
  async run(args, options) {
268
274
  if (args.length > 0)
269
275
  throw new CliError("Usage: sources list", exitCodes.usage);
270
- const { sources } = await api(await resolveConnection(options.apiUrl), "/api/v1/sources");
276
+ const { sources } = await api(await resolveConnection(options), "/api/v1/sources");
271
277
  if (options.format === "json")
272
278
  return printJson({ sources });
273
279
  if (sources.length === 0)
@@ -296,7 +302,7 @@ const commands = {
296
302
  async run(args, options) {
297
303
  if (args.length !== 1)
298
304
  throw new CliError("Usage: sources show <id>", exitCodes.usage);
299
- const connection = await resolveConnection(options.apiUrl);
305
+ const connection = await resolveConnection(options);
300
306
  const path = `/api/v1/sources/${encodeURIComponent(args[0])}`;
301
307
  const { source } = await api(connection, path);
302
308
  const { runs } = await api(connection, `${path}/runs?limit=${SOURCE_RUNS_SHOWN}`);
@@ -340,7 +346,7 @@ const commands = {
340
346
  const [id, ...assignments] = args;
341
347
  if (!id)
342
348
  throw new CliError("Usage: sources settings <id> [<key>=<value> ...]", exitCodes.usage);
343
- const connection = await resolveConnection(options.apiUrl);
349
+ const connection = await resolveConnection(options);
344
350
  const path = `/api/v1/sources/${encodeURIComponent(id)}/settings`;
345
351
  let { settings } = await api(connection, path);
346
352
  if (assignments.length > 0) {
@@ -366,7 +372,7 @@ const commands = {
366
372
  async run(args, options) {
367
373
  if (args.length > 0)
368
374
  throw new CliError("Usage: intake list", exitCodes.usage);
369
- const { endpoints } = await api(await resolveConnection(options.apiUrl), INTAKE_PATH);
375
+ const { endpoints } = await api(await resolveConnection(options), INTAKE_PATH);
370
376
  if (options.format === "json")
371
377
  return printJson({ endpoints });
372
378
  if (endpoints.length === 0)
@@ -396,7 +402,7 @@ const commands = {
396
402
  ...(retentionDays === undefined ? {} : { retentionDays }),
397
403
  ...(signingKey === undefined ? {} : { signingKey }),
398
404
  };
399
- printIntakeToken(await api(await resolveConnection(options.apiUrl), INTAKE_PATH, body), options.format);
405
+ printIntakeToken(await api(await resolveConnection(options), INTAKE_PATH, body), options.format);
400
406
  },
401
407
  },
402
408
  "intake rotate": {
@@ -405,7 +411,7 @@ const commands = {
405
411
  if (args.length !== 1)
406
412
  throw new CliError("Usage: intake rotate <id>", exitCodes.usage);
407
413
  const path = `${intakePath(args[0])}/rotate`;
408
- printIntakeToken(await api(await resolveConnection(options.apiUrl), path, {}), options.format);
414
+ printIntakeToken(await api(await resolveConnection(options), path, {}), options.format);
409
415
  },
410
416
  },
411
417
  "intake signing-key": {
@@ -415,7 +421,7 @@ const commands = {
415
421
  throw new CliError("Usage: intake signing-key <id> --signing-key-stdin", exitCodes.usage);
416
422
  const signingKey = await readSigningKey();
417
423
  const path = `${intakePath(args[0])}/signing-key`;
418
- const { endpoint } = await api(await resolveConnection(options.apiUrl), path, { signingKey }, "PUT");
424
+ const { endpoint } = await api(await resolveConnection(options), path, { signingKey }, "PUT");
419
425
  if (options.format === "json")
420
426
  return printJson({ endpoint });
421
427
  console.log(`Replaced the ${endpoint.kind} signing key. Every ${endpoint.kind} address in the workspace now checks against it.`);
@@ -462,7 +468,7 @@ const commands = {
462
468
  if (!options.yes) {
463
469
  throw new CliError("This deletes the address and every event it has saved. Add --yes to confirm: intake delete <id> --yes", exitCodes.usage);
464
470
  }
465
- const result = await api(await resolveConnection(options.apiUrl), intakePath(args[0]), undefined, "DELETE");
471
+ const result = await api(await resolveConnection(options), intakePath(args[0]), undefined, "DELETE");
466
472
  if (options.format === "json")
467
473
  return printJson(result);
468
474
  console.log("Deleted the address and its saved events.");
@@ -476,7 +482,7 @@ const commands = {
476
482
  throw new CliError(usage, exitCodes.usage);
477
483
  const name = importName(options.table);
478
484
  const file = await readImportFile(args[0]);
479
- const connection = await resolveConnection(options.apiUrl);
485
+ const connection = await resolveConnection(options);
480
486
  const result = await uploadCsv(connection, name, file);
481
487
  if (options.format === "json")
482
488
  return printJson(result);
@@ -495,7 +501,7 @@ const commands = {
495
501
  throw new CliError(`This drops the table import_${name} and all its rows. Add --yes to confirm: import remove --table ${name} --yes`, exitCodes.usage);
496
502
  }
497
503
  const path = `/api/v1/imports/csv?table=${encodeURIComponent(name)}`;
498
- const result = await api(await resolveConnection(options.apiUrl), path, undefined, "DELETE");
504
+ const result = await api(await resolveConnection(options), path, undefined, "DELETE");
499
505
  if (options.format === "json")
500
506
  return printJson(result);
501
507
  console.log(`Removed ${result.table} and its rows.`);
@@ -512,7 +518,7 @@ const commands = {
512
518
  if (options.limit)
513
519
  query.set("limit", options.limit);
514
520
  const path = `/api/v1/creatives${query.size ? `?${query}` : ""}`;
515
- const { creatives } = await api(await resolveConnection(options.apiUrl), path);
521
+ const { creatives } = await api(await resolveConnection(options), path);
516
522
  if (options.format === "json")
517
523
  return printJson({ creatives });
518
524
  if (creatives.length === 0)
@@ -526,7 +532,7 @@ const commands = {
526
532
  async run(args, options) {
527
533
  if (args.length !== 1)
528
534
  throw new CliError("Usage: creatives show <sku>", exitCodes.usage);
529
- const { creative } = await api(await resolveConnection(options.apiUrl), `/api/v1/creatives/${encodeURIComponent(args[0])}`);
535
+ const { creative } = await api(await resolveConnection(options), `/api/v1/creatives/${encodeURIComponent(args[0])}`);
530
536
  if (options.format === "json")
531
537
  return printJson({ creative });
532
538
  const fields = {
@@ -543,7 +549,7 @@ const commands = {
543
549
  async run(args, options) {
544
550
  if (args.length !== 1)
545
551
  throw new CliError("Usage: creatives download <sku | asset-path | asset-link> [--out <path>] [--open]", exitCodes.usage);
546
- const connection = await resolveConnection(options.apiUrl);
552
+ const connection = await resolveConnection(options);
547
553
  const target = await downloadTarget(args[0], connection);
548
554
  const { bytes, contentType } = await apiDownload(connection, `${ASSET_ROUTE}${target.assetPath.split("/").map(encodeURIComponent).join("/")}`);
549
555
  const path = await savePath(options.out, extname(target.fileName) ? target.fileName : `${target.fileName}.${extensionFor(contentType)}`);
@@ -563,7 +569,7 @@ const commands = {
563
569
  }
564
570
  const lineage = options.lineage === undefined ? undefined : await parseJson(options.lineage, "--lineage");
565
571
  const tags = options.tags === undefined ? undefined : await parseJson(options.tags, "--tags");
566
- const { creatives } = await api(await resolveConnection(options.apiUrl), "/api/v1/creatives", {
572
+ const { creatives } = await api(await resolveConnection(options), "/api/v1/creatives", {
567
573
  runId: options.run,
568
574
  ...(lineage === undefined ? {} : { lineage }),
569
575
  ...(tags === undefined ? {} : { tags }),
@@ -579,7 +585,7 @@ const commands = {
579
585
  if (args.length !== 1)
580
586
  throw new CliError("Usage: creatives tag <sku> --file <tags.json> | < tags.json", exitCodes.usage);
581
587
  const tags = await readToolInput(options);
582
- const { creative } = await api(await resolveConnection(options.apiUrl), `/api/v1/creatives/${encodeURIComponent(args[0])}/tags`, { tags }, "PATCH");
588
+ const { creative } = await api(await resolveConnection(options), `/api/v1/creatives/${encodeURIComponent(args[0])}/tags`, { tags }, "PATCH");
583
589
  if (options.format === "json")
584
590
  return printJson({ creative });
585
591
  console.log(`Tagged ${creative.sku}. The tags read ${creative.tagsStatus} until a person confirms them in the app.`);
@@ -590,7 +596,7 @@ const commands = {
590
596
  async run(args, options) {
591
597
  if (args.length > 0 || !options.kind)
592
598
  throw new CliError("Usage: content create --kind ad|email|sms|landing_page|video|social_post [...]", exitCodes.usage);
593
- const connection = await resolveConnection(options.apiUrl);
599
+ const connection = await resolveConnection(options);
594
600
  const content = await api(connection, "/api/v1/content", {
595
601
  kind: options.kind,
596
602
  ...(await contentInput(options, connection)),
@@ -608,7 +614,7 @@ const commands = {
608
614
  }
609
615
  if (options.language !== undefined && options.transcript === undefined)
610
616
  throw new CliError("--language goes with --transcript.", exitCodes.usage);
611
- const connection = await resolveConnection(options.apiUrl);
617
+ const connection = await resolveConnection(options);
612
618
  const content = await api(connection, "/api/v1/content/captures", {
613
619
  kind: options.kind,
614
620
  ...(await contentInput(options, connection)),
@@ -624,7 +630,7 @@ const commands = {
624
630
  async run(args, options) {
625
631
  if (args.length > 0)
626
632
  throw new CliError("Usage: guide", exitCodes.usage);
627
- const guide = await api(await resolveConnection(options.apiUrl), "/api/v1/guide");
633
+ const guide = await api(await resolveConnection(options), "/api/v1/guide");
628
634
  if (options.format === "json")
629
635
  return printJson(guide);
630
636
  process.stdout.write(guide.text);
@@ -636,7 +642,7 @@ const commands = {
636
642
  if (args.length > 0)
637
643
  throw new CliError("Usage: brief [--from <date>] [--to <date>] [--channel <channel>] [--campaign <id>] [--top <n>]", exitCodes.usage);
638
644
  const query = reportQuery(options, { channel: options.channel, campaignId: options.campaign, top: options.top });
639
- const brief = await api(await resolveConnection(options.apiUrl), `/api/v1/brief${query}`);
645
+ const brief = await api(await resolveConnection(options), `/api/v1/brief${query}`);
640
646
  if (options.format === "json")
641
647
  return printJson(brief);
642
648
  process.stdout.write(brief.text);
@@ -652,7 +658,7 @@ const commands = {
652
658
  for (const [name, value] of Object.entries({ step: options.step, funnel: options.funnel, page: options.page }))
653
659
  if (value !== undefined)
654
660
  query.set(name, value);
655
- const brief = await api(await resolveConnection(options.apiUrl), `/api/v1/pages/brief?${query}`);
661
+ const brief = await api(await resolveConnection(options), `/api/v1/pages/brief?${query}`);
656
662
  if (options.format === "json")
657
663
  return printJson(brief);
658
664
  process.stdout.write(brief.text);
@@ -663,7 +669,7 @@ const commands = {
663
669
  async run(args, options) {
664
670
  if (args.length > 0)
665
671
  throw new CliError("Usage: pages results [--from <date>] [--to <date>] [--page <address | page key | item id>]", exitCodes.usage);
666
- const results = await api(await resolveConnection(options.apiUrl), `/api/v1/pages/results${reportQuery(options, { page: options.page })}`);
672
+ const results = await api(await resolveConnection(options), `/api/v1/pages/results${reportQuery(options, { page: options.page })}`);
667
673
  if (options.format === "json")
668
674
  return printJson(results);
669
675
  printPageResults(results);
@@ -674,7 +680,7 @@ const commands = {
674
680
  async run(args, options) {
675
681
  if (args.length > 0)
676
682
  throw new CliError("Usage: messages results [--from <date>] [--to <date>] [--channel email|text] [--sequence <workflow id>]", exitCodes.usage);
677
- const results = await api(await resolveConnection(options.apiUrl), `/api/v1/messages/results${reportQuery(options, { channel: options.channel, sequence: options.sequence })}`);
683
+ const results = await api(await resolveConnection(options), `/api/v1/messages/results${reportQuery(options, { channel: options.channel, sequence: options.sequence })}`);
678
684
  if (options.format === "json")
679
685
  return printJson(results);
680
686
  printMessageResults(results);
@@ -689,7 +695,7 @@ const commands = {
689
695
  for (const [name, value] of Object.entries({ ...winnersPeriod(options), top: options.top, format: options.entryTags.format }))
690
696
  if (value !== undefined)
691
697
  query.set(name, value);
692
- const report = await api(await resolveConnection(options.apiUrl), `/api/v1/winners${query.size ? `?${query}` : ""}`);
698
+ const report = await api(await resolveConnection(options), `/api/v1/winners${query.size ? `?${query}` : ""}`);
693
699
  if (options.format === "json")
694
700
  return printJson(report);
695
701
  process.stdout.write(winnersText(report));
@@ -701,7 +707,7 @@ const commands = {
701
707
  if (args.length > 0)
702
708
  throw new CliError("Usage: winners send-to-exodus [--week <date> | --days <n> | --from <date> --to <date>] [--dry-run]", exitCodes.usage);
703
709
  const period = Object.entries(winnersPeriod(options)).filter(([, value]) => value !== undefined);
704
- const sent = await api(await resolveConnection(options.apiUrl), "/api/v1/winners/exodus", { ...Object.fromEntries(period), dryRun: options.dryRun });
710
+ const sent = await api(await resolveConnection(options), "/api/v1/winners/exodus", { ...Object.fromEntries(period), dryRun: options.dryRun });
705
711
  if (options.format === "json")
706
712
  printJson(sent);
707
713
  else
@@ -716,7 +722,7 @@ const commands = {
716
722
  async run(args, options) {
717
723
  if (args.length > 0)
718
724
  throw new CliError("Usage: winners goals", exitCodes.usage);
719
- printWinnersGoals(await api(await resolveConnection(options.apiUrl), "/api/v1/winners/goals"), options.format);
725
+ printWinnersGoals(await api(await resolveConnection(options), "/api/v1/winners/goals"), options.format);
720
726
  },
721
727
  },
722
728
  "winners goals set": {
@@ -724,7 +730,7 @@ const commands = {
724
730
  async run(args, options) {
725
731
  if (args.length === 0)
726
732
  throw new CliError("Usage: winners goals set <goal> [<goal> ...]", exitCodes.usage);
727
- printWinnersGoals(await api(await resolveConnection(options.apiUrl), "/api/v1/winners/goals", { goals: args }, "PUT"), options.format);
733
+ printWinnersGoals(await api(await resolveConnection(options), "/api/v1/winners/goals", { goals: args }, "PUT"), options.format);
728
734
  },
729
735
  },
730
736
  "winners goals reset": {
@@ -732,7 +738,7 @@ const commands = {
732
738
  async run(args, options) {
733
739
  if (args.length > 0)
734
740
  throw new CliError("Usage: winners goals reset", exitCodes.usage);
735
- printWinnersGoals(await api(await resolveConnection(options.apiUrl), "/api/v1/winners/goals", { goals: null }, "PUT"), options.format);
741
+ printWinnersGoals(await api(await resolveConnection(options), "/api/v1/winners/goals", { goals: null }, "PUT"), options.format);
736
742
  },
737
743
  },
738
744
  examples: {
@@ -740,7 +746,7 @@ const commands = {
740
746
  async run(args, options) {
741
747
  if (args.length > 0)
742
748
  throw new CliError("Usage: examples", exitCodes.usage);
743
- const { examples } = await api(await resolveConnection(options.apiUrl), "/api/v1/examples");
749
+ const { examples } = await api(await resolveConnection(options), "/api/v1/examples");
744
750
  if (options.format === "json")
745
751
  return printJson({ examples });
746
752
  for (const e of examples) {
@@ -758,7 +764,7 @@ const commands = {
758
764
  throw new CliError("Usage: content search [--text <words>] [--kind <kind>] [--sort <column>] [...]; put several words in quotes", exitCodes.usage);
759
765
  const filters = { text: options.text, mode: options.mode, kind: options.kind, platform: options.platform, origin: options.origin, from: options.from, to: options.to, sort: options.sort, order: options.order, limit: options.limit };
760
766
  const query = new URLSearchParams(Object.entries(filters).filter((entry) => entry[1] !== undefined));
761
- const { results } = await api(await resolveConnection(options.apiUrl), `/api/v1/content${query.size ? `?${query}` : ""}`);
767
+ const { results } = await api(await resolveConnection(options), `/api/v1/content${query.size ? `?${query}` : ""}`);
762
768
  if (options.format === "json")
763
769
  return printJson({ results });
764
770
  if (results.length === 0)
@@ -784,7 +790,7 @@ const commands = {
784
790
  async run(args, options) {
785
791
  if (args.length !== 1)
786
792
  throw new CliError("Usage: content show <sku | item-id>", exitCodes.usage);
787
- const content = await api(await resolveConnection(options.apiUrl), `/api/v1/content/${encodeURIComponent(args[0])}`);
793
+ const content = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}`);
788
794
  printContent(content, options.format);
789
795
  },
790
796
  },
@@ -794,7 +800,7 @@ const commands = {
794
800
  if (args.length !== 1)
795
801
  throw new CliError("Usage: content pull <dir> [--kind <kind>] [--platform <platform>] [--origin discovered|made] [--media] [--full]", exitCodes.usage);
796
802
  const filters = Object.fromEntries(Object.entries({ kind: options.kind, platform: options.platform, origin: options.origin }).filter((e) => e[1] !== undefined));
797
- const report = await pullContent(await resolveConnection(options.apiUrl), args[0], filters, { media: options.media, full: options.full });
803
+ const report = await pullContent(await resolveConnection(options), args[0], filters, { media: options.media, full: options.full });
798
804
  if (options.format === "json")
799
805
  return printJson(report);
800
806
  const media = options.media ? `, ${report.media} new media file${report.media === 1 ? "" : "s"}` : "";
@@ -810,7 +816,7 @@ const commands = {
810
816
  if (args.length !== 1)
811
817
  throw new CliError("Usage: content diff <sku | item-id> [--from <n>] [--to <n>]", exitCodes.usage);
812
818
  const query = new URLSearchParams(Object.entries({ from: options.from, to: options.to }).filter((entry) => entry[1] !== undefined));
813
- const diff = await api(await resolveConnection(options.apiUrl), `/api/v1/content/${encodeURIComponent(args[0])}/diff${query.size ? `?${query}` : ""}`);
819
+ const diff = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/diff${query.size ? `?${query}` : ""}`);
814
820
  if (options.format === "json")
815
821
  return printJson(diff);
816
822
  printContentDiff(diff);
@@ -821,7 +827,7 @@ const commands = {
821
827
  async run(args, options) {
822
828
  if (args.length !== 1)
823
829
  throw new CliError("Usage: content edit <sku | item-id> [--body <text> | @file] [--note <why>] [...]", exitCodes.usage);
824
- const connection = await resolveConnection(options.apiUrl);
830
+ const connection = await resolveConnection(options);
825
831
  const input = await contentInput(options, connection);
826
832
  if (Object.keys(input).every((k) => k === "runId" || k === "note")) {
827
833
  throw new CliError("content edit needs something to change, such as --body, --subject or --file.", exitCodes.usage);
@@ -835,7 +841,7 @@ const commands = {
835
841
  async run(args, options) {
836
842
  if (args.length !== 2)
837
843
  throw new CliError("Usage: content status <sku | item-id> <status> [--platform <platform> [--object-type <type>] --external-id <id>]", exitCodes.usage);
838
- const content = await api(await resolveConnection(options.apiUrl), `/api/v1/content/${encodeURIComponent(args[0])}/status`, {
844
+ const content = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/status`, {
839
845
  status: args[1],
840
846
  ...(options.platform === undefined ? {} : { platform: options.platform }),
841
847
  ...(options.objectType === undefined ? {} : { objectType: options.objectType }),
@@ -853,7 +859,7 @@ const commands = {
853
859
  const version = options.version === undefined ? undefined : Number(options.version);
854
860
  if (version !== undefined && !(Number.isInteger(version) && version >= 1))
855
861
  throw new CliError("--version must be a whole number from 1.", exitCodes.usage);
856
- const { publication } = await api(await resolveConnection(options.apiUrl), `/api/v1/content/${encodeURIComponent(args[0])}/publish`, {
862
+ const { publication } = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/publish`, {
857
863
  publisher: options.to,
858
864
  target: await parseJson(options.target, "--target"),
859
865
  idempotencyKey: options.idempotencyKey,
@@ -869,7 +875,7 @@ const commands = {
869
875
  async run(args, options) {
870
876
  if (args.length !== 1)
871
877
  throw new CliError("Usage: content reconcile <publication-id>", exitCodes.usage);
872
- const { publication } = await api(await resolveConnection(options.apiUrl), `/api/v1/publications/${encodeURIComponent(args[0])}/reconcile`, {});
878
+ const { publication } = await api(await resolveConnection(options), `/api/v1/publications/${encodeURIComponent(args[0])}/reconcile`, {});
873
879
  printPublication(publication, options.format);
874
880
  },
875
881
  },
@@ -879,7 +885,7 @@ const commands = {
879
885
  if (args.length !== 0 || options.plan === undefined || !options.idempotencyKey) {
880
886
  throw new CliError("Usage: meta adset publish --plan '<json>' --idempotency-key <key> [--run <run-id>]", exitCodes.usage);
881
887
  }
882
- const { adsetPublication } = await api(await resolveConnection(options.apiUrl), "/api/v1/meta/adsets", {
888
+ const { adsetPublication } = await api(await resolveConnection(options), "/api/v1/meta/adsets", {
883
889
  plan: await parseJson(options.plan, "--plan"),
884
890
  idempotencyKey: options.idempotencyKey,
885
891
  ...(options.run === undefined ? {} : { runId: options.run }),
@@ -892,7 +898,7 @@ const commands = {
892
898
  async run(args, options) {
893
899
  if (args.length !== 1)
894
900
  throw new CliError("Usage: meta adset show <adset-publication-id>", exitCodes.usage);
895
- const { adsetPublication } = await api(await resolveConnection(options.apiUrl), `/api/v1/meta/adsets/${encodeURIComponent(args[0])}`);
901
+ const { adsetPublication } = await api(await resolveConnection(options), `/api/v1/meta/adsets/${encodeURIComponent(args[0])}`);
896
902
  printAdSetPublication(adsetPublication, options.format);
897
903
  },
898
904
  },
@@ -901,7 +907,7 @@ const commands = {
901
907
  async run(args, options) {
902
908
  if (args.length !== 1)
903
909
  throw new CliError("Usage: meta adset reconcile <adset-publication-id>", exitCodes.usage);
904
- const { adsetPublication } = await api(await resolveConnection(options.apiUrl), `/api/v1/meta/adsets/${encodeURIComponent(args[0])}/reconcile`, {});
910
+ const { adsetPublication } = await api(await resolveConnection(options), `/api/v1/meta/adsets/${encodeURIComponent(args[0])}/reconcile`, {});
905
911
  printAdSetPublication(adsetPublication, options.format);
906
912
  },
907
913
  },
@@ -910,7 +916,7 @@ const commands = {
910
916
  async run(args, options) {
911
917
  if (args.length !== 1)
912
918
  throw new CliError("Usage: content unpublish <deployment-id> [--force]", exitCodes.usage);
913
- const { deployment } = await api(await resolveConnection(options.apiUrl), `/api/v1/deployments/${encodeURIComponent(args[0])}/unpublish`, options.force ? { force: true } : {});
919
+ const { deployment } = await api(await resolveConnection(options), `/api/v1/deployments/${encodeURIComponent(args[0])}/unpublish`, options.force ? { force: true } : {});
914
920
  if (options.format === "json")
915
921
  return printJson({ deployment });
916
922
  printFields({
@@ -928,7 +934,7 @@ const commands = {
928
934
  async run(args, options) {
929
935
  if (args.length !== 1)
930
936
  throw new CliError("Usage: content publish-record <sku | item-id> --file <publication.json>", exitCodes.usage);
931
- const { publication } = await api(await resolveConnection(options.apiUrl), `/api/v1/content/${encodeURIComponent(args[0])}/publications`, await readToolInput(options));
937
+ const { publication } = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/publications`, await readToolInput(options));
932
938
  printPublication(publication, options.format);
933
939
  },
934
940
  },
@@ -939,7 +945,7 @@ const commands = {
939
945
  throw new CliError("Usage: content publish-finish <publication-id> --status sending|published|failed|not_sent_practice [--file <outcome.json>]", exitCodes.usage);
940
946
  }
941
947
  const outcome = await readToolInput(options);
942
- const { publication } = await api(await resolveConnection(options.apiUrl), `/api/v1/publications/${encodeURIComponent(args[0])}`, { ...outcome, status: options.status }, "PATCH");
948
+ const { publication } = await api(await resolveConnection(options), `/api/v1/publications/${encodeURIComponent(args[0])}`, { ...outcome, status: options.status }, "PATCH");
943
949
  printPublication(publication, options.format);
944
950
  },
945
951
  },
@@ -948,7 +954,7 @@ const commands = {
948
954
  async run(args, options) {
949
955
  if (args.length > 0)
950
956
  throw new CliError("Usage: agent start", exitCodes.usage);
951
- const start = await api(await resolveConnection(options.apiUrl), `${agentPath(options)}/runs`, {});
957
+ const start = await api(await resolveConnection(options), `${agentPath(options)}/runs`, {});
952
958
  if (options.format === "json")
953
959
  return printJson(start);
954
960
  const { run, quota, brandProfile } = start;
@@ -968,7 +974,7 @@ const commands = {
968
974
  async run(args, options) {
969
975
  if (args.length !== 1)
970
976
  throw new CliError("Usage: agent show <run-id>", exitCodes.usage);
971
- const shown = await api(await resolveConnection(options.apiUrl), `${agentPath(options)}/runs/${encodeURIComponent(args[0])}`);
977
+ const shown = await api(await resolveConnection(options), `${agentPath(options)}/runs/${encodeURIComponent(args[0])}`);
972
978
  if (options.format === "json")
973
979
  return printJson(shown);
974
980
  const { run, proposals } = shown;
@@ -982,7 +988,7 @@ const commands = {
982
988
  usage: "agent runs [--limit <n>]",
983
989
  async run(_args, options) {
984
990
  const query = options.limit ? `?limit=${encodeURIComponent(options.limit)}` : "";
985
- const { runs } = await api(await resolveConnection(options.apiUrl), `${agentPath(options)}/runs${query}`);
991
+ const { runs } = await api(await resolveConnection(options), `${agentPath(options)}/runs${query}`);
986
992
  if (options.format === "json")
987
993
  return printJson({ runs });
988
994
  if (runs.length === 0)
@@ -997,7 +1003,7 @@ const commands = {
997
1003
  throw new CliError("Usage: agent finish <run-id> --status succeeded|failed [--file report.md]", exitCodes.usage);
998
1004
  }
999
1005
  const report = await readText(options.file, "the report");
1000
- const { run } = await api(await resolveConnection(options.apiUrl), `${agentPath(options)}/runs/${encodeURIComponent(args[0])}/finish`, { status: options.status, report });
1006
+ const { run } = await api(await resolveConnection(options), `${agentPath(options)}/runs/${encodeURIComponent(args[0])}/finish`, { status: options.status, report });
1001
1007
  if (options.format === "json")
1002
1008
  return printJson({ run });
1003
1009
  console.log(`Finished run ${run.id} as ${run.status}.`);
@@ -1008,7 +1014,7 @@ const commands = {
1008
1014
  async run(args, options) {
1009
1015
  if (args.length > 0)
1010
1016
  throw new CliError("Usage: agent draft --agent p3_refresh", exitCodes.usage);
1011
- const body = await api(await resolveConnection(options.apiUrl), `${agentPath(options)}/draft`);
1017
+ const body = await api(await resolveConnection(options), `${agentPath(options)}/draft`);
1012
1018
  if (options.format === "json")
1013
1019
  return printJson(body);
1014
1020
  const { draft } = body;
@@ -1049,7 +1055,7 @@ const commands = {
1049
1055
  "brand get": {
1050
1056
  usage: "brand get",
1051
1057
  async run(_args, options) {
1052
- const { profile } = await api(await resolveConnection(options.apiUrl), "/api/v1/brand-profile");
1058
+ const { profile } = await api(await resolveConnection(options), "/api/v1/brand-profile");
1053
1059
  if (options.format === "json")
1054
1060
  return printJson({ profile });
1055
1061
  if (!profile)
@@ -1065,7 +1071,7 @@ const commands = {
1065
1071
  throw new CliError("Usage: brand set [--file <path>] [--sources '<json>' | @file.json]", exitCodes.usage);
1066
1072
  const markdown = await readText(options.file, "the brand profile");
1067
1073
  const sources = options.sources === undefined ? [] : await parseJson(options.sources, "--sources");
1068
- const { profile } = await api(await resolveConnection(options.apiUrl), "/api/v1/brand-profile", { markdown, sources }, "PUT");
1074
+ const { profile } = await api(await resolveConnection(options), "/api/v1/brand-profile", { markdown, sources }, "PUT");
1069
1075
  if (options.format === "json")
1070
1076
  return printJson({ profile });
1071
1077
  console.log(`Saved brand profile version ${profile.version}.`);
@@ -1076,7 +1082,7 @@ const commands = {
1076
1082
  async run(args, options) {
1077
1083
  if (args.length !== 1)
1078
1084
  throw new CliError("Usage: content add-page <url>", exitCodes.usage);
1079
- const { page } = await api(await resolveConnection(options.apiUrl), "/api/v1/content/pages", { url: args[0] });
1085
+ const { page } = await api(await resolveConnection(options), "/api/v1/content/pages", { url: args[0] });
1080
1086
  if (options.format === "json")
1081
1087
  return printJson({ page });
1082
1088
  console.log(renderTable(["key", "status", "version", "changed", "headline", "blocks"], [{ ...page, blocks: page.blockCount }]));
@@ -1090,7 +1096,7 @@ const commands = {
1090
1096
  async run(args, options) {
1091
1097
  if (args.length !== 2)
1092
1098
  throw new CliError("Usage: content track-link <sku> <url> [--channel facebook|email|sms|web] [--campaign <id>]", exitCodes.usage);
1093
- const { url } = await api(await resolveConnection(options.apiUrl), "/api/v1/content/track-link", {
1099
+ const { url } = await api(await resolveConnection(options), "/api/v1/content/track-link", {
1094
1100
  sku: args[0],
1095
1101
  url: args[1],
1096
1102
  channel: options.channel,
@@ -1106,7 +1112,7 @@ const commands = {
1106
1112
  async run(args, options) {
1107
1113
  if (args.length !== 1)
1108
1114
  throw new CliError("Usage: trail <plan-id | sku | item-id>", exitCodes.usage);
1109
- const connection = await resolveConnection(options.apiUrl);
1115
+ const connection = await resolveConnection(options);
1110
1116
  const ref = args[0];
1111
1117
  const trailOf = (query) => api(connection, `/api/v1/trail?${query}=${encodeURIComponent(ref)}`);
1112
1118
  // Plans and items both have uuid ids: an id that names no plan is read as an item.
@@ -1126,7 +1132,7 @@ const commands = {
1126
1132
  if (args.length > 0)
1127
1133
  throw new CliError("Usage: funnels show [--version <n>]", exitCodes.usage);
1128
1134
  const query = options.version === undefined ? "" : `?version=${wholeNumber(options.version, "--version")}`;
1129
- const view = await api(await resolveConnection(options.apiUrl), `${FUNNELS_PATH}${query}`);
1135
+ const view = await api(await resolveConnection(options), `${FUNNELS_PATH}${query}`);
1130
1136
  if (options.format === "json")
1131
1137
  return printJson(view);
1132
1138
  process.stdout.write(view.text);
@@ -1137,7 +1143,7 @@ const commands = {
1137
1143
  async run(args, options) {
1138
1144
  if (args.length > 0)
1139
1145
  throw new CliError("Usage: funnels numbers [--funnel <key>] [--from <date>] [--to <date>]", exitCodes.usage);
1140
- const view = await api(await resolveConnection(options.apiUrl), `${FUNNELS_PATH}/numbers${reportQuery(options, { funnel: options.funnel })}`);
1146
+ const view = await api(await resolveConnection(options), `${FUNNELS_PATH}/numbers${reportQuery(options, { funnel: options.funnel })}`);
1141
1147
  if (options.format === "json")
1142
1148
  return printJson(view);
1143
1149
  process.stdout.write(view.text);
@@ -1148,7 +1154,7 @@ const commands = {
1148
1154
  async run(args, options) {
1149
1155
  if (args.length > 0 || options.from !== undefined)
1150
1156
  throw new CliError("Usage: funnels leaks [--funnel <key>] [--to <date>]", exitCodes.usage);
1151
- const view = await api(await resolveConnection(options.apiUrl), `${FUNNELS_PATH}/leaks${reportQuery(options, { funnel: options.funnel })}`);
1157
+ const view = await api(await resolveConnection(options), `${FUNNELS_PATH}/leaks${reportQuery(options, { funnel: options.funnel })}`);
1152
1158
  if (options.format === "json")
1153
1159
  return printJson(view);
1154
1160
  process.stdout.write(view.text);
@@ -1159,7 +1165,7 @@ const commands = {
1159
1165
  async run(args, options) {
1160
1166
  if (args.length > 0)
1161
1167
  throw new CliError("Usage: funnels evidence", exitCodes.usage);
1162
- const view = await api(await resolveConnection(options.apiUrl), `${FUNNELS_PATH}/evidence`);
1168
+ const view = await api(await resolveConnection(options), `${FUNNELS_PATH}/evidence`);
1163
1169
  if (options.format === "json")
1164
1170
  return printJson(view);
1165
1171
  process.stdout.write(view.text);
@@ -1177,7 +1183,7 @@ const commands = {
1177
1183
  const map = await parseJson(`@${options.file}`, "--file");
1178
1184
  const evidence = options.evidence === undefined ? undefined : await parseJson(options.evidence, "--evidence");
1179
1185
  const questions = options.questions === undefined ? undefined : await parseJson(options.questions, "--questions");
1180
- const connection = await resolveConnection(options.apiUrl);
1186
+ const connection = await resolveConnection(options);
1181
1187
  const basedOn = options.basedOn === undefined ? null : wholeNumber(options.basedOn, "--based-on");
1182
1188
  const saved = await api(connection, `${FUNNELS_PATH}/draft`, { map, note: options.note, evidence, based_on: basedOn, counts_from: options.countsFrom, questions }, "PUT");
1183
1189
  if (options.format === "json")
@@ -1207,7 +1213,7 @@ const commands = {
1207
1213
  "funnels versions": {
1208
1214
  usage: "funnels versions [--diff <a> <b>] every version of the map, or what changed from version a to b",
1209
1215
  async run(args, options) {
1210
- const connection = await resolveConnection(options.apiUrl);
1216
+ const connection = await resolveConnection(options);
1211
1217
  if (options.diff !== undefined) {
1212
1218
  if (args.length !== 1)
1213
1219
  throw new CliError("Usage: funnels versions --diff <a> <b>", exitCodes.usage);
@@ -1254,7 +1260,7 @@ const commands = {
1254
1260
  }
1255
1261
  if (options.status)
1256
1262
  query.set("status", options.status);
1257
- const { facts } = await api(await resolveConnection(options.apiUrl), `${FACTS_PATH}${query.size ? `?${query}` : ""}`);
1263
+ const { facts } = await api(await resolveConnection(options), `${FACTS_PATH}${query.size ? `?${query}` : ""}`);
1258
1264
  if (options.format === "json")
1259
1265
  return printJson({ facts });
1260
1266
  if (facts.length === 0)
@@ -1276,7 +1282,7 @@ const commands = {
1276
1282
  query.set("sort", options.sort);
1277
1283
  if (options.limit)
1278
1284
  query.set("limit", options.limit);
1279
- const read = await api(await resolveConnection(options.apiUrl), `${VOICE_PATH}${query.size ? `?${query}` : ""}`);
1285
+ const read = await api(await resolveConnection(options), `${VOICE_PATH}${query.size ? `?${query}` : ""}`);
1280
1286
  if (options.format === "json")
1281
1287
  return printJson(read);
1282
1288
  if ("lines" in read) {
@@ -1301,7 +1307,7 @@ const commands = {
1301
1307
  query.set("status", options.status);
1302
1308
  if (options.limit)
1303
1309
  query.set("limit", options.limit);
1304
- const { tags } = await api(await resolveConnection(options.apiUrl), `${AD_TAGS_PATH}${query.size ? `?${query}` : ""}`);
1310
+ const { tags } = await api(await resolveConnection(options), `${AD_TAGS_PATH}${query.size ? `?${query}` : ""}`);
1305
1311
  if (options.format === "json")
1306
1312
  return printJson({ tags });
1307
1313
  if (tags.length === 0)
@@ -1314,7 +1320,7 @@ const commands = {
1314
1320
  async run(args, options) {
1315
1321
  if (args.length > 0)
1316
1322
  throw new CliError("Usage: ads tag --file <tags.json> | < tags.json", exitCodes.usage);
1317
- const result = await api(await resolveConnection(options.apiUrl), AD_TAGS_PATH, await readToolInput(options));
1323
+ const result = await api(await resolveConnection(options), AD_TAGS_PATH, await readToolInput(options));
1318
1324
  if (options.format === "json")
1319
1325
  return printJson(result);
1320
1326
  console.log(`Proposed tags for ${result.proposed.length} ${result.proposed.length === 1 ? "ad" : "ads"}. A person confirms or rejects them in the app.`);
@@ -1328,7 +1334,7 @@ const commands = {
1328
1334
  if (args.length > 0)
1329
1335
  throw new CliError("Usage: ads autotag [--limit <n>] [--ad <id>] [--json]", exitCodes.usage);
1330
1336
  const body = { ...(options.ad ? { ad_ids: [options.ad] } : {}), ...(options.limit ? { limit: Number(options.limit) } : {}) };
1331
- const result = await api(await resolveConnection(options.apiUrl), AD_AUTOTAG_PATH, body);
1337
+ const result = await api(await resolveConnection(options), AD_AUTOTAG_PATH, body);
1332
1338
  if (options.format === "json")
1333
1339
  return printJson(result);
1334
1340
  const plural = (n) => `${n} ${n === 1 ? "ad" : "ads"}`;
@@ -1355,7 +1361,7 @@ const commands = {
1355
1361
  async run(args, options) {
1356
1362
  if (args.length > 0)
1357
1363
  throw new CliError("Usage: ads tag-formats [--limit <n>] [--json]", exitCodes.usage);
1358
- const connection = await resolveConnection(options.apiUrl);
1364
+ const connection = await resolveConnection(options);
1359
1365
  const total = { tagged: 0, no_format: 0 };
1360
1366
  let after = null;
1361
1367
  for (;;) {
@@ -1384,7 +1390,7 @@ const commands = {
1384
1390
  throw new CliError("Usage: content tags [<item-id>] [--kind email|text|page] [--status <status>] [--limit <n>] [--json]", exitCodes.usage);
1385
1391
  const filters = { item: args[0], kind: options.kind, status: options.status, limit: options.limit };
1386
1392
  const query = new URLSearchParams(Object.entries(filters).filter((entry) => entry[1] !== undefined));
1387
- const { tags } = await api(await resolveConnection(options.apiUrl), `${CONTENT_TAGS_PATH}${query.size ? `?${query}` : ""}`);
1393
+ const { tags } = await api(await resolveConnection(options), `${CONTENT_TAGS_PATH}${query.size ? `?${query}` : ""}`);
1388
1394
  if (options.format === "json")
1389
1395
  return printJson({ tags });
1390
1396
  if (tags.length === 0)
@@ -1398,7 +1404,7 @@ const commands = {
1398
1404
  if (args.length > 0)
1399
1405
  throw new CliError("Usage: content to-check [--limit <n>] [--json]", exitCodes.usage);
1400
1406
  const query = new URLSearchParams({ to_check: "1", ...(options.limit ? { limit: options.limit } : {}) });
1401
- const { tags } = await api(await resolveConnection(options.apiUrl), `${CONTENT_TAGS_PATH}?${query}`);
1407
+ const { tags } = await api(await resolveConnection(options), `${CONTENT_TAGS_PATH}?${query}`);
1402
1408
  if (options.format === "json")
1403
1409
  return printJson({ tags });
1404
1410
  if (tags.length === 0)
@@ -1414,7 +1420,7 @@ const commands = {
1414
1420
  ...(options.kind ? { kinds: options.kind.split(",") } : {}),
1415
1421
  ...(options.limit ? { limit: Number(options.limit) } : {}),
1416
1422
  };
1417
- const result = await api(await resolveConnection(options.apiUrl), CONTENT_AUTOTAG_PATH, body);
1423
+ const result = await api(await resolveConnection(options), CONTENT_AUTOTAG_PATH, body);
1418
1424
  if (options.format === "json")
1419
1425
  return printJson(result);
1420
1426
  const plural = (n) => `${n} ${n === 1 ? "item" : "items"}`;
@@ -1439,7 +1445,7 @@ const commands = {
1439
1445
  async run(args, options) {
1440
1446
  if (args.length !== 1)
1441
1447
  throw new CliError("Usage: facts show <id>", exitCodes.usage);
1442
- const { fact } = await api(await resolveConnection(options.apiUrl), `${FACTS_PATH}/${encodeURIComponent(args[0])}`);
1448
+ const { fact } = await api(await resolveConnection(options), `${FACTS_PATH}/${encodeURIComponent(args[0])}`);
1443
1449
  if (options.format === "json")
1444
1450
  return printJson({ fact });
1445
1451
  const nameKey = fact.key === NAME_KEY ? nameKeyLines(fact.value) : null;
@@ -1454,7 +1460,7 @@ const commands = {
1454
1460
  async run(args, options) {
1455
1461
  if (args.length > 0)
1456
1462
  throw new CliError("Usage: facts propose --file <fact.json> | < fact.json", exitCodes.usage);
1457
- const { fact } = await api(await resolveConnection(options.apiUrl), FACTS_PATH, await readToolInput(options));
1463
+ const { fact } = await api(await resolveConnection(options), FACTS_PATH, await readToolInput(options));
1458
1464
  if (options.format === "json")
1459
1465
  return printJson({ fact });
1460
1466
  console.log(`Proposed fact ${fact.id}. A person confirms or rejects it in the app.`);
@@ -1474,7 +1480,7 @@ const commands = {
1474
1480
  for (const dimension of options.dimension ?? [])
1475
1481
  params.append("dimension", dimension);
1476
1482
  const query = params.size > 0 ? `?${params}` : "";
1477
- const { lessons } = await api(await resolveConnection(options.apiUrl), `${LESSONS_PATH}${query}`);
1483
+ const { lessons } = await api(await resolveConnection(options), `${LESSONS_PATH}${query}`);
1478
1484
  if (options.format === "json")
1479
1485
  return printJson({ lessons });
1480
1486
  if (lessons.length === 0) {
@@ -1489,7 +1495,7 @@ const commands = {
1489
1495
  async run(args, options) {
1490
1496
  if (args.length > 0)
1491
1497
  throw new CliError("Usage: lessons add --file <lesson.json> | < lesson.json", exitCodes.usage);
1492
- const recorded = await api(await resolveConnection(options.apiUrl), LESSONS_PATH, await readToolInput(options));
1498
+ const recorded = await api(await resolveConnection(options), LESSONS_PATH, await readToolInput(options));
1493
1499
  if (options.format === "json")
1494
1500
  return printJson(recorded);
1495
1501
  const { lesson, outcome } = recorded;
@@ -1513,7 +1519,7 @@ const commands = {
1513
1519
  query.set("funnel", options.funnel);
1514
1520
  if (options.limit)
1515
1521
  query.set("limit", options.limit);
1516
- const { tests } = await api(await resolveConnection(options.apiUrl), `${TESTS_PATH}${query.size ? `?${query}` : ""}`);
1522
+ const { tests } = await api(await resolveConnection(options), `${TESTS_PATH}${query.size ? `?${query}` : ""}`);
1517
1523
  if (options.format === "json")
1518
1524
  return printJson({ tests });
1519
1525
  if (tests.length === 0)
@@ -1526,7 +1532,7 @@ const commands = {
1526
1532
  async run(args, options) {
1527
1533
  if (args.length !== 1)
1528
1534
  throw new CliError("Usage: tests show <id|test_id>", exitCodes.usage);
1529
- const { test } = await api(await resolveConnection(options.apiUrl), testPath(args[0]));
1535
+ const { test } = await api(await resolveConnection(options), testPath(args[0]));
1530
1536
  if (options.format === "json")
1531
1537
  return printJson({ test });
1532
1538
  printTest(test);
@@ -1537,7 +1543,7 @@ const commands = {
1537
1543
  async run(args, options) {
1538
1544
  if (args.length > 0)
1539
1545
  throw new CliError("Usage: tests draft --file <test.json> | < test.json", exitCodes.usage);
1540
- const { test } = await api(await resolveConnection(options.apiUrl), TESTS_PATH, await readToolInput(options));
1546
+ const { test } = await api(await resolveConnection(options), TESTS_PATH, await readToolInput(options));
1541
1547
  if (options.format === "json")
1542
1548
  return printJson({ test });
1543
1549
  console.log(`Drafted test ${test.test_id} (${test.id}).`);
@@ -1548,7 +1554,7 @@ const commands = {
1548
1554
  async run(args, options) {
1549
1555
  if (args.length !== 1)
1550
1556
  throw new CliError("Usage: tests start <id|test_id>", exitCodes.usage);
1551
- const { test } = await api(await resolveConnection(options.apiUrl), `${testPath(args[0])}/start`, {});
1557
+ const { test } = await api(await resolveConnection(options), `${testPath(args[0])}/start`, {});
1552
1558
  if (options.format === "json")
1553
1559
  return printJson({ test });
1554
1560
  console.log(`Test ${test.test_id} is running from ${test.started_on}.`);
@@ -1559,7 +1565,7 @@ const commands = {
1559
1565
  async run(args, options) {
1560
1566
  if (args.length !== 1)
1561
1567
  throw new CliError('Usage: tests abandon <id|test_id> [--note "<why>"]', exitCodes.usage);
1562
- const { test } = await api(await resolveConnection(options.apiUrl), `${testPath(args[0])}/abandon`, options.note ? { note: options.note } : {});
1568
+ const { test } = await api(await resolveConnection(options), `${testPath(args[0])}/abandon`, options.note ? { note: options.note } : {});
1563
1569
  if (options.format === "json")
1564
1570
  return printJson({ test });
1565
1571
  console.log(`Abandoned test ${test.test_id}.`);
@@ -1570,7 +1576,7 @@ const commands = {
1570
1576
  async run(args, options) {
1571
1577
  if (args.length !== 1)
1572
1578
  throw new CliError("Usage: tests results <id|test_id>", exitCodes.usage);
1573
- const { results } = await api(await resolveConnection(options.apiUrl), `${testPath(args[0])}/results`);
1579
+ const { results } = await api(await resolveConnection(options), `${testPath(args[0])}/results`);
1574
1580
  if (options.format === "json")
1575
1581
  return printJson({ results });
1576
1582
  printResults(results);
@@ -1582,7 +1588,7 @@ const commands = {
1582
1588
  if (args.length !== 1 || !options.decision) {
1583
1589
  throw new CliError('Usage: tests decide <id|test_id> --decision <scale|continue|iterate|hold|stop> [--note "<why>"]', exitCodes.usage);
1584
1590
  }
1585
- const decided = await api(await resolveConnection(options.apiUrl), `${testPath(args[0])}/decide`, options.note ? { decision: options.decision, note: options.note } : { decision: options.decision });
1591
+ const decided = await api(await resolveConnection(options), `${testPath(args[0])}/decide`, options.note ? { decision: options.decision, note: options.note } : { decision: options.decision });
1586
1592
  if (options.format === "json")
1587
1593
  return printJson(decided);
1588
1594
  const { test, lesson, next_test: next } = decided;
@@ -1598,7 +1604,7 @@ const commands = {
1598
1604
  async run(args, options) {
1599
1605
  if (args.length !== 1 || !options.note)
1600
1606
  throw new CliError('Usage: lessons retire <id> --note "<why it is no longer true>"', exitCodes.usage);
1601
- const { lesson } = await api(await resolveConnection(options.apiUrl), `${LESSONS_PATH}/${encodeURIComponent(args[0])}/retire`, { reason: options.note });
1607
+ const { lesson } = await api(await resolveConnection(options), `${LESSONS_PATH}/${encodeURIComponent(args[0])}/retire`, { reason: options.note });
1602
1608
  if (options.format === "json")
1603
1609
  return printJson({ lesson });
1604
1610
  console.log(`Retired lesson ${lesson.id}.`);
@@ -1609,7 +1615,7 @@ const commands = {
1609
1615
  async run(args, options) {
1610
1616
  if (args.length > 0)
1611
1617
  throw new CliError("Usage: grammar | grammar propose --file <values.json>", exitCodes.usage);
1612
- const answer = await api(await resolveConnection(options.apiUrl), GRAMMAR_PATH);
1618
+ const answer = await api(await resolveConnection(options), GRAMMAR_PATH);
1613
1619
  if (options.format === "json")
1614
1620
  return printJson(answer);
1615
1621
  process.stdout.write(answer.text);
@@ -1620,7 +1626,7 @@ const commands = {
1620
1626
  async run(args, options) {
1621
1627
  if (args.length > 0)
1622
1628
  throw new CliError("Usage: grammar propose --file <values.json> | < values.json", exitCodes.usage);
1623
- const result = await api(await resolveConnection(options.apiUrl), `${GRAMMAR_PATH}/proposals`, await readToolInput(options));
1629
+ const result = await api(await resolveConnection(options), `${GRAMMAR_PATH}/proposals`, await readToolInput(options));
1624
1630
  if (options.format === "json")
1625
1631
  return printJson(result);
1626
1632
  const named = (refs) => refs.map((r) => `${r.part} ${r.key}`).join(", ");
@@ -1634,7 +1640,7 @@ const commands = {
1634
1640
  async run(args, options) {
1635
1641
  if (args.length > 0)
1636
1642
  throw new CliError("Usage: voice lists", exitCodes.usage);
1637
- const lists = await api(await resolveConnection(options.apiUrl), VOICE_LISTS_PATH);
1643
+ const lists = await api(await resolveConnection(options), VOICE_LISTS_PATH);
1638
1644
  if (options.format === "json")
1639
1645
  return printJson(lists);
1640
1646
  const rows = [
@@ -1655,7 +1661,7 @@ const commands = {
1655
1661
  if (!kind || !["competitor", "subreddit", "search_phrase"].includes(kind) || value === undefined || rest.length > 0) {
1656
1662
  throw new CliError('Usage: voice suggest competitor|subreddit|search_phrase "<value>" [--note <why>]; put a value with spaces in quotes', exitCodes.usage);
1657
1663
  }
1658
- const result = await api(await resolveConnection(options.apiUrl), VOICE_SUGGESTIONS_PATH, { kind, value, note: options.note });
1664
+ const result = await api(await resolveConnection(options), VOICE_SUGGESTIONS_PATH, { kind, value, note: options.note });
1659
1665
  if (options.format === "json")
1660
1666
  return printJson(result);
1661
1667
  if (result.outcome === "already_listed")
@@ -1670,7 +1676,7 @@ const commands = {
1670
1676
  async run(args, options) {
1671
1677
  if (args.length > 0)
1672
1678
  throw new CliError("Usage: story | story list | story show <version> | story submit --file <story.json>", exitCodes.usage);
1673
- const { story } = await api(await resolveConnection(options.apiUrl), `${STORIES_PATH}/latest`);
1679
+ const { story } = await api(await resolveConnection(options), `${STORIES_PATH}/latest`);
1674
1680
  if (options.format === "json")
1675
1681
  return printJson({ story });
1676
1682
  if (!story)
@@ -1683,7 +1689,7 @@ const commands = {
1683
1689
  async run(args, options) {
1684
1690
  if (args.length > 0)
1685
1691
  throw new CliError("Usage: story list", exitCodes.usage);
1686
- const { stories } = await api(await resolveConnection(options.apiUrl), STORIES_PATH);
1692
+ const { stories } = await api(await resolveConnection(options), STORIES_PATH);
1687
1693
  if (options.format === "json")
1688
1694
  return printJson({ stories });
1689
1695
  if (stories.length === 0)
@@ -1706,7 +1712,7 @@ const commands = {
1706
1712
  if (args.length !== 1)
1707
1713
  throw new CliError("Usage: story show <version>", exitCodes.usage);
1708
1714
  // `show latest` reaches the newest-story route, which answers null before the first story.
1709
- const { story } = await api(await resolveConnection(options.apiUrl), `${STORIES_PATH}/${encodeURIComponent(args[0])}`);
1715
+ const { story } = await api(await resolveConnection(options), `${STORIES_PATH}/${encodeURIComponent(args[0])}`);
1710
1716
  if (options.format === "json")
1711
1717
  return printJson({ story });
1712
1718
  if (!story)
@@ -1719,7 +1725,7 @@ const commands = {
1719
1725
  async run(args, options) {
1720
1726
  if (args.length > 0)
1721
1727
  throw new CliError("Usage: story submit --file <story.json> | < story.json", exitCodes.usage);
1722
- const { story } = await api(await resolveConnection(options.apiUrl), STORIES_PATH, await readToolInput(options));
1728
+ const { story } = await api(await resolveConnection(options), STORIES_PATH, await readToolInput(options));
1723
1729
  if (options.format === "json")
1724
1730
  return printJson({ story });
1725
1731
  console.log(`Saved story version ${story.version}.`);
@@ -1731,7 +1737,7 @@ const commands = {
1731
1737
  if (args.length > 0 || !options.name || !options.goal || !options.channel) {
1732
1738
  throw new CliError("Usage: plans create --name <name> --goal <text> --channel meta|ghl|klaviyo|web [--target '<json>'] [--landing <sku> | --landing-url <url>] [--funnel <key> --step <key>]", exitCodes.usage);
1733
1739
  }
1734
- const { plan } = await api(await resolveConnection(options.apiUrl), "/api/v1/launch-plans", {
1740
+ const { plan } = await api(await resolveConnection(options), "/api/v1/launch-plans", {
1735
1741
  name: options.name,
1736
1742
  goal: await textOrFile(options.goal),
1737
1743
  channel: options.channel,
@@ -1750,7 +1756,7 @@ const commands = {
1750
1756
  async run(args, options) {
1751
1757
  if (args.length !== 1)
1752
1758
  throw new CliError("Usage: plans show <plan-id>", exitCodes.usage);
1753
- const { plan } = await api(await resolveConnection(options.apiUrl), planPath(args[0]));
1759
+ const { plan } = await api(await resolveConnection(options), planPath(args[0]));
1754
1760
  printPlan(plan, options.format);
1755
1761
  },
1756
1762
  },
@@ -1762,7 +1768,7 @@ const commands = {
1762
1768
  const position = options.position === undefined ? undefined : Number(options.position);
1763
1769
  if (position !== undefined && (!Number.isInteger(position) || position < 0))
1764
1770
  throw new CliError("--position must be a whole number from 0.", exitCodes.usage);
1765
- const { plan } = await api(await resolveConnection(options.apiUrl), `${planPath(args[0])}/items`, {
1771
+ const { plan } = await api(await resolveConnection(options), `${planPath(args[0])}/items`, {
1766
1772
  item: args[1],
1767
1773
  ...(options.role === undefined ? {} : { role: options.role }),
1768
1774
  ...(position === undefined ? {} : { position }),
@@ -1776,7 +1782,7 @@ const commands = {
1776
1782
  if (args.length !== 2)
1777
1783
  throw new CliError("Usage: plans remove <plan-id> <sku | item-id>", exitCodes.usage);
1778
1784
  const path = `${planPath(args[0])}/items?item=${encodeURIComponent(args[1])}`;
1779
- const { plan } = await api(await resolveConnection(options.apiUrl), path, undefined, "DELETE");
1785
+ const { plan } = await api(await resolveConnection(options), path, undefined, "DELETE");
1780
1786
  printPlan(plan, options.format);
1781
1787
  },
1782
1788
  },
@@ -1785,7 +1791,7 @@ const commands = {
1785
1791
  async run(args, options) {
1786
1792
  if (args.length !== 2)
1787
1793
  throw new CliError("Usage: plans status <plan-id> draft|ready|publishing|live|done|cancelled", exitCodes.usage);
1788
- const { plan } = await api(await resolveConnection(options.apiUrl), planPath(args[0]), { status: args[1] }, "PATCH");
1794
+ const { plan } = await api(await resolveConnection(options), planPath(args[0]), { status: args[1] }, "PATCH");
1789
1795
  printPlan(plan, options.format);
1790
1796
  },
1791
1797
  },
@@ -1794,7 +1800,7 @@ const commands = {
1794
1800
  async run(args, options) {
1795
1801
  if (args.length > 0)
1796
1802
  throw new CliError("Usage: proposals create --input '<json>'", exitCodes.usage);
1797
- const { proposal } = await api(await resolveConnection(options.apiUrl), "/api/v1/proposals", await readToolInput(options));
1803
+ const { proposal } = await api(await resolveConnection(options), "/api/v1/proposals", await readToolInput(options));
1798
1804
  if (options.format === "json")
1799
1805
  return printJson({ proposal });
1800
1806
  console.log(renderTable(PROPOSAL_COLUMNS, [proposalRow(proposal)]));
@@ -1813,7 +1819,7 @@ const commands = {
1813
1819
  if (options.limit)
1814
1820
  query.set("limit", options.limit);
1815
1821
  const path = `/api/v1/proposals${query.size ? `?${query}` : ""}`;
1816
- const { proposals } = await api(await resolveConnection(options.apiUrl), path);
1822
+ const { proposals } = await api(await resolveConnection(options), path);
1817
1823
  if (options.format === "json")
1818
1824
  return printJson({ proposals });
1819
1825
  if (proposals.length === 0)
@@ -1841,7 +1847,7 @@ const commands = {
1841
1847
  if (options.harness !== undefined)
1842
1848
  query.set("harnesses", options.harness);
1843
1849
  const path = `/api/operator/skills/${name}/versions${query.size ? `?${query}` : ""}`;
1844
- const published = await apiUpload(await operatorConnection(options.apiUrl), path, zip, "application/zip");
1850
+ const published = await apiUpload(await operatorConnection(options), path, zip, "application/zip");
1845
1851
  if (options.format === "json")
1846
1852
  return printJson(published);
1847
1853
  console.log(`${published.name} version ${published.version}`);
@@ -1853,7 +1859,7 @@ const commands = {
1853
1859
  async run(args, options) {
1854
1860
  if (args.length > 0)
1855
1861
  throw new CliError("Usage: skill list", exitCodes.usage);
1856
- const { skills } = await api(await operatorConnection(options.apiUrl), "/api/operator/skills");
1862
+ const { skills } = await api(await operatorConnection(options), "/api/operator/skills");
1857
1863
  if (options.format === "json")
1858
1864
  return printJson({ skills });
1859
1865
  if (skills.length === 0)
@@ -1893,7 +1899,7 @@ const commands = {
1893
1899
  async run(args, options) {
1894
1900
  if (args.length > 0)
1895
1901
  throw new CliError("Usage: worker list", exitCodes.usage);
1896
- const { workers } = await api(await resolveConnection(options.apiUrl), "/api/v1/workers");
1902
+ const { workers } = await api(await resolveConnection(options), "/api/v1/workers");
1897
1903
  if (options.format === "json")
1898
1904
  return printJson({ workers });
1899
1905
  if (workers.length === 0)
@@ -1917,7 +1923,7 @@ const commands = {
1917
1923
  async run(args, options) {
1918
1924
  if (args.length !== 1)
1919
1925
  throw new CliError("Usage: worker runs <hand-id | skill> [--limit <n>]", exitCodes.usage);
1920
- const connection = await resolveConnection(options.apiUrl);
1926
+ const connection = await resolveConnection(options);
1921
1927
  const id = await workerId(connection, args[0]);
1922
1928
  const query = options.limit === undefined ? "" : `?limit=${encodeURIComponent(options.limit)}`;
1923
1929
  const { runs } = await api(connection, `/api/v1/workers/${encodeURIComponent(id)}/runs${query}`);
@@ -1933,7 +1939,7 @@ const commands = {
1933
1939
  async run(args, options) {
1934
1940
  if (args.length !== 1)
1935
1941
  throw new CliError("Usage: worker run-now <hand-id | skill>", exitCodes.usage);
1936
- const connection = await resolveConnection(options.apiUrl);
1942
+ const connection = await resolveConnection(options);
1937
1943
  const id = await workerId(connection, args[0]);
1938
1944
  const queued = await api(connection, `/api/v1/workers/${encodeURIComponent(id)}/run-now`, {});
1939
1945
  if (options.format === "json")
@@ -1993,6 +1999,18 @@ const commands = {
1993
1999
  },
1994
2000
  },
1995
2001
  };
2002
+ function clientReason(choice) {
2003
+ switch (choice.source) {
2004
+ case "env":
2005
+ return `Using the key in ${envPrefix}_API_KEY, which beats --client, client files and saved logins.`;
2006
+ case "flag":
2007
+ return `Using client ${choice.client}, from --client.`;
2008
+ case "folder":
2009
+ return `Using client ${choice.client}, from the client file ${choice.path}.`;
2010
+ case "default":
2011
+ return `Using the default login (no --client flag and no ${CLIENT_FILE} file in this folder or above it).`;
2012
+ }
2013
+ }
1996
2014
  /** Runs `work` with the runner's settings from the environment; SIGINT or SIGTERM stops the run in hand, reported failed. */
1997
2015
  async function withRunner(options, work) {
1998
2016
  const token = process.env[RUNNER_TOKEN_ENV];
@@ -2032,7 +2050,7 @@ async function installLibrarySkill(ref, options) {
2032
2050
  if (options.codex && options.project)
2033
2051
  throw new CliError("--codex installs a skill for every Codex run, under CODEX_HOME; drop --project", exitCodes.usage);
2034
2052
  const query = ref.version === null ? "" : `?version=${ref.version}`;
2035
- const download = await api(await operatorConnection(options.apiUrl), `/api/operator/skills/${ref.name}/download${query}`);
2053
+ const download = await api(await operatorConnection(options), `/api/operator/skills/${ref.name}/download${query}`);
2036
2054
  const files = unpackSkill(download, await getSigned(download.url));
2037
2055
  const { skillsDir, above } = options.codex ? { skillsDir: codexSkillsDir(), above: [] } : claudeSkillsDir(options.project);
2038
2056
  const path = await installSkillFolder(skillsDir, download.name, files, above);
@@ -2509,6 +2527,8 @@ ${Object.values(commands)
2509
2527
  Options:
2510
2528
  --format table|json output format (default: table)
2511
2529
  --api-url <url> API base URL
2530
+ --client <name> use that client's saved login (login --client <name> saves it); beats a ${CLIENT_FILE} file, which
2531
+ names the client for its folder and every folder below it; ${envPrefix}_API_KEY beats both
2512
2532
  --wait <seconds> how long tools run and tools get wait for a run (default: ${DEFAULT_WAIT_SECONDS}; 0 returns at once)
2513
2533
  --agent <kind> which agent the agent commands use: meta_ads (default) or p3_refresh; in a scheduled hand's run, skill
2514
2534
  (the run's skill's agent) is the default
@@ -2619,6 +2639,7 @@ function parseOptions(argv) {
2619
2639
  format: { type: "string", default: "table" },
2620
2640
  json: { type: "boolean", default: false },
2621
2641
  "api-url": { type: "string" },
2642
+ client: { type: "string" },
2622
2643
  file: { type: "string", multiple: true },
2623
2644
  project: { type: "boolean", default: false },
2624
2645
  input: { type: "string" },
@@ -2727,6 +2748,7 @@ function parseOptions(argv) {
2727
2748
  options: {
2728
2749
  format: values.json ? "json" : values.format,
2729
2750
  apiUrl: values["api-url"],
2751
+ client: values.client,
2730
2752
  file: values.file?.at(-1),
2731
2753
  files: values.file ?? [],
2732
2754
  project: values.project,
@@ -2822,7 +2844,7 @@ function parseOptions(argv) {
2822
2844
  async function changeCandidate(change, args, options) {
2823
2845
  if (args.length !== 1 || !options.workspace)
2824
2846
  throw new CliError(`Usage: worker ${change} <skill> --workspace <slug>`, exitCodes.usage);
2825
- return api(await operatorConnection(options.apiUrl), `/api/operator/workers/${change}`, { workspace: options.workspace, skill: args[0] });
2847
+ return api(await operatorConnection(options), `/api/operator/workers/${change}`, { workspace: options.workspace, skill: args[0] });
2826
2848
  }
2827
2849
  function findCommand(positionals) {
2828
2850
  const three = positionals.slice(0, 3).join(" ");
@@ -3571,7 +3593,7 @@ function printIntakeToken({ endpoint, address, token, link, subscription }, form
3571
3593
  console.error(`The token for ${endpoint.label} is shown only now. Rotate it if it is lost.`);
3572
3594
  }
3573
3595
  async function patchIntake(id, body, options, done) {
3574
- const { endpoint } = await api(await resolveConnection(options.apiUrl), intakePath(id), body, "PATCH");
3596
+ const { endpoint } = await api(await resolveConnection(options), intakePath(id), body, "PATCH");
3575
3597
  if (options.format === "json")
3576
3598
  return printJson({ endpoint });
3577
3599
  console.log(done(endpoint));