@wrangled/cli 0.3.44 → 0.3.46
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/api.js +140 -23
- package/dist/guide.js +10 -6
- package/dist/main.js +249 -143
- package/dist/playbooks.js +11 -11
- package/dist/runner/run.js +1 -1
- package/dist/skills.js +3 -8
- package/package.json +1 -1
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = {
|
|
@@ -539,20 +545,11 @@ const commands = {
|
|
|
539
545
|
},
|
|
540
546
|
},
|
|
541
547
|
"creatives download": {
|
|
542
|
-
usage: "creatives download <sku | asset-path | asset-link> [--out <path>] [--open]",
|
|
548
|
+
usage: "creatives download <sku | media-id | asset-path | asset-link> [--out <path>] [--open]",
|
|
543
549
|
async run(args, options) {
|
|
544
550
|
if (args.length !== 1)
|
|
545
|
-
throw new CliError("Usage: creatives download <sku | asset-path | asset-link> [--out <path>] [--open]", exitCodes.usage);
|
|
546
|
-
|
|
547
|
-
const target = await downloadTarget(args[0], connection);
|
|
548
|
-
const { bytes, contentType } = await apiDownload(connection, `${ASSET_ROUTE}${target.assetPath.split("/").map(encodeURIComponent).join("/")}`);
|
|
549
|
-
const path = await savePath(options.out, extname(target.fileName) ? target.fileName : `${target.fileName}.${extensionFor(contentType)}`);
|
|
550
|
-
await saveFile(path, bytes);
|
|
551
|
-
if (options.open)
|
|
552
|
-
openFile(resolve(path));
|
|
553
|
-
if (options.format === "json")
|
|
554
|
-
return printJson({ path: resolve(path), bytes: bytes.byteLength, contentType });
|
|
555
|
-
console.log(`Saved ${target.label} to ${path} (${fileSize(bytes.byteLength)}).`);
|
|
551
|
+
throw new CliError("Usage: creatives download <sku | media-id | asset-path | asset-link> [--out <path>] [--open]", exitCodes.usage);
|
|
552
|
+
await download(args[0], options);
|
|
556
553
|
},
|
|
557
554
|
},
|
|
558
555
|
"creatives create": {
|
|
@@ -563,7 +560,7 @@ const commands = {
|
|
|
563
560
|
}
|
|
564
561
|
const lineage = options.lineage === undefined ? undefined : await parseJson(options.lineage, "--lineage");
|
|
565
562
|
const tags = options.tags === undefined ? undefined : await parseJson(options.tags, "--tags");
|
|
566
|
-
const { creatives } = await api(await resolveConnection(options
|
|
563
|
+
const { creatives } = await api(await resolveConnection(options), "/api/v1/creatives", {
|
|
567
564
|
runId: options.run,
|
|
568
565
|
...(lineage === undefined ? {} : { lineage }),
|
|
569
566
|
...(tags === undefined ? {} : { tags }),
|
|
@@ -579,18 +576,60 @@ const commands = {
|
|
|
579
576
|
if (args.length !== 1)
|
|
580
577
|
throw new CliError("Usage: creatives tag <sku> --file <tags.json> | < tags.json", exitCodes.usage);
|
|
581
578
|
const tags = await readToolInput(options);
|
|
582
|
-
const { creative } = await api(await resolveConnection(options
|
|
579
|
+
const { creative } = await api(await resolveConnection(options), `/api/v1/creatives/${encodeURIComponent(args[0])}/tags`, { tags }, "PATCH");
|
|
583
580
|
if (options.format === "json")
|
|
584
581
|
return printJson({ creative });
|
|
585
582
|
console.log(`Tagged ${creative.sku}. The tags read ${creative.tagsStatus} until a person confirms them in the app.`);
|
|
586
583
|
},
|
|
587
584
|
},
|
|
585
|
+
"media list": {
|
|
586
|
+
usage: "media list [--sku <sku>] [--run <tool-run-id>] [--tool <tool>] [--kind image|video] [--since <date>] [--until <date>] [--limit <n>] [--cursor <media-id>]",
|
|
587
|
+
async run(args, options) {
|
|
588
|
+
if (args.length > 0)
|
|
589
|
+
throw new CliError("Usage: media list [--sku <sku>] [--run <tool-run-id>] [--tool <tool>] [--kind image|video] [--since <date>] [--until <date>] [--limit <n>] [--cursor <media-id>]", exitCodes.usage);
|
|
590
|
+
const query = new URLSearchParams();
|
|
591
|
+
for (const name of ["sku", "run", "tool", "kind", "since", "until", "limit", "cursor"]) {
|
|
592
|
+
if (options[name])
|
|
593
|
+
query.set(name, options[name]);
|
|
594
|
+
}
|
|
595
|
+
const page = await api(await resolveConnection(options), `/api/v1/media${query.size ? `?${query}` : ""}`);
|
|
596
|
+
if (options.format === "json")
|
|
597
|
+
return printJson(page);
|
|
598
|
+
if (page.media.length === 0)
|
|
599
|
+
return console.log("No made images or videos match.");
|
|
600
|
+
const rows = page.media.map((m) => ({ ...m, prompt: excerpt(m.prompt ?? ""), cost: costLabel(m), sku: m.sku ?? "" }));
|
|
601
|
+
console.log(renderTable(["id", "tool", "prompt", "cost", "sku", "createdAt", "assetUrl"], rows));
|
|
602
|
+
if (page.nextCursor)
|
|
603
|
+
console.log(`\nMore: add --cursor ${page.nextCursor}`);
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
"media get": {
|
|
607
|
+
usage: "media get <media-id>",
|
|
608
|
+
async run(args, options) {
|
|
609
|
+
if (args.length !== 1)
|
|
610
|
+
throw new CliError("Usage: media get <media-id>", exitCodes.usage);
|
|
611
|
+
const { media } = await api(await resolveConnection(options), mediaPath(args[0]));
|
|
612
|
+
if (options.format === "json")
|
|
613
|
+
return printJson({ media });
|
|
614
|
+
const fields = { ...media, cost: costLabel(media) };
|
|
615
|
+
const shown = ["id", "tool", "model", "prompt", "input", "cost", "contentType", "sku", "creativeStatus", "assetUrl", "workerRunId", "createdAt"];
|
|
616
|
+
console.log(renderTable(["field", "value"], shown.filter((f) => fields[f] != null).map((f) => ({ field: f, value: fields[f] }))));
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
"media download": {
|
|
620
|
+
usage: "media download <media-id> [--out <path>] [--open]",
|
|
621
|
+
async run(args, options) {
|
|
622
|
+
if (args.length !== 1 || !parseMediaId(args[0]))
|
|
623
|
+
throw new CliError("Usage: media download <media-id> [--out <path>] [--open]", exitCodes.usage);
|
|
624
|
+
await download(args[0], options);
|
|
625
|
+
},
|
|
626
|
+
},
|
|
588
627
|
"content create": {
|
|
589
628
|
usage: "content create --kind <kind> [--name <name>] [--subject <s>] [--preview <s>] [--headline <s>] [--body <text> | @file] [--html @file] [--fields '<json>' | @file.json] [--file <path> ...] [--tool-run <id> ...] [--run <agent-run-id>] [--note <why>] [--plan <plan-id> [--role <role>]]",
|
|
590
629
|
async run(args, options) {
|
|
591
630
|
if (args.length > 0 || !options.kind)
|
|
592
631
|
throw new CliError("Usage: content create --kind ad|email|sms|landing_page|video|social_post [...]", exitCodes.usage);
|
|
593
|
-
const connection = await resolveConnection(options
|
|
632
|
+
const connection = await resolveConnection(options);
|
|
594
633
|
const content = await api(connection, "/api/v1/content", {
|
|
595
634
|
kind: options.kind,
|
|
596
635
|
...(await contentInput(options, connection)),
|
|
@@ -608,7 +647,7 @@ const commands = {
|
|
|
608
647
|
}
|
|
609
648
|
if (options.language !== undefined && options.transcript === undefined)
|
|
610
649
|
throw new CliError("--language goes with --transcript.", exitCodes.usage);
|
|
611
|
-
const connection = await resolveConnection(options
|
|
650
|
+
const connection = await resolveConnection(options);
|
|
612
651
|
const content = await api(connection, "/api/v1/content/captures", {
|
|
613
652
|
kind: options.kind,
|
|
614
653
|
...(await contentInput(options, connection)),
|
|
@@ -624,7 +663,7 @@ const commands = {
|
|
|
624
663
|
async run(args, options) {
|
|
625
664
|
if (args.length > 0)
|
|
626
665
|
throw new CliError("Usage: guide", exitCodes.usage);
|
|
627
|
-
const guide = await api(await resolveConnection(options
|
|
666
|
+
const guide = await api(await resolveConnection(options), "/api/v1/guide");
|
|
628
667
|
if (options.format === "json")
|
|
629
668
|
return printJson(guide);
|
|
630
669
|
process.stdout.write(guide.text);
|
|
@@ -636,7 +675,7 @@ const commands = {
|
|
|
636
675
|
if (args.length > 0)
|
|
637
676
|
throw new CliError("Usage: brief [--from <date>] [--to <date>] [--channel <channel>] [--campaign <id>] [--top <n>]", exitCodes.usage);
|
|
638
677
|
const query = reportQuery(options, { channel: options.channel, campaignId: options.campaign, top: options.top });
|
|
639
|
-
const brief = await api(await resolveConnection(options
|
|
678
|
+
const brief = await api(await resolveConnection(options), `/api/v1/brief${query}`);
|
|
640
679
|
if (options.format === "json")
|
|
641
680
|
return printJson(brief);
|
|
642
681
|
process.stdout.write(brief.text);
|
|
@@ -652,7 +691,7 @@ const commands = {
|
|
|
652
691
|
for (const [name, value] of Object.entries({ step: options.step, funnel: options.funnel, page: options.page }))
|
|
653
692
|
if (value !== undefined)
|
|
654
693
|
query.set(name, value);
|
|
655
|
-
const brief = await api(await resolveConnection(options
|
|
694
|
+
const brief = await api(await resolveConnection(options), `/api/v1/pages/brief?${query}`);
|
|
656
695
|
if (options.format === "json")
|
|
657
696
|
return printJson(brief);
|
|
658
697
|
process.stdout.write(brief.text);
|
|
@@ -663,7 +702,7 @@ const commands = {
|
|
|
663
702
|
async run(args, options) {
|
|
664
703
|
if (args.length > 0)
|
|
665
704
|
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
|
|
705
|
+
const results = await api(await resolveConnection(options), `/api/v1/pages/results${reportQuery(options, { page: options.page })}`);
|
|
667
706
|
if (options.format === "json")
|
|
668
707
|
return printJson(results);
|
|
669
708
|
printPageResults(results);
|
|
@@ -674,7 +713,7 @@ const commands = {
|
|
|
674
713
|
async run(args, options) {
|
|
675
714
|
if (args.length > 0)
|
|
676
715
|
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
|
|
716
|
+
const results = await api(await resolveConnection(options), `/api/v1/messages/results${reportQuery(options, { channel: options.channel, sequence: options.sequence })}`);
|
|
678
717
|
if (options.format === "json")
|
|
679
718
|
return printJson(results);
|
|
680
719
|
printMessageResults(results);
|
|
@@ -689,7 +728,7 @@ const commands = {
|
|
|
689
728
|
for (const [name, value] of Object.entries({ ...winnersPeriod(options), top: options.top, format: options.entryTags.format }))
|
|
690
729
|
if (value !== undefined)
|
|
691
730
|
query.set(name, value);
|
|
692
|
-
const report = await api(await resolveConnection(options
|
|
731
|
+
const report = await api(await resolveConnection(options), `/api/v1/winners${query.size ? `?${query}` : ""}`);
|
|
693
732
|
if (options.format === "json")
|
|
694
733
|
return printJson(report);
|
|
695
734
|
process.stdout.write(winnersText(report));
|
|
@@ -701,7 +740,7 @@ const commands = {
|
|
|
701
740
|
if (args.length > 0)
|
|
702
741
|
throw new CliError("Usage: winners send-to-exodus [--week <date> | --days <n> | --from <date> --to <date>] [--dry-run]", exitCodes.usage);
|
|
703
742
|
const period = Object.entries(winnersPeriod(options)).filter(([, value]) => value !== undefined);
|
|
704
|
-
const sent = await api(await resolveConnection(options
|
|
743
|
+
const sent = await api(await resolveConnection(options), "/api/v1/winners/exodus", { ...Object.fromEntries(period), dryRun: options.dryRun });
|
|
705
744
|
if (options.format === "json")
|
|
706
745
|
printJson(sent);
|
|
707
746
|
else
|
|
@@ -716,7 +755,7 @@ const commands = {
|
|
|
716
755
|
async run(args, options) {
|
|
717
756
|
if (args.length > 0)
|
|
718
757
|
throw new CliError("Usage: winners goals", exitCodes.usage);
|
|
719
|
-
printWinnersGoals(await api(await resolveConnection(options
|
|
758
|
+
printWinnersGoals(await api(await resolveConnection(options), "/api/v1/winners/goals"), options.format);
|
|
720
759
|
},
|
|
721
760
|
},
|
|
722
761
|
"winners goals set": {
|
|
@@ -724,7 +763,7 @@ const commands = {
|
|
|
724
763
|
async run(args, options) {
|
|
725
764
|
if (args.length === 0)
|
|
726
765
|
throw new CliError("Usage: winners goals set <goal> [<goal> ...]", exitCodes.usage);
|
|
727
|
-
printWinnersGoals(await api(await resolveConnection(options
|
|
766
|
+
printWinnersGoals(await api(await resolveConnection(options), "/api/v1/winners/goals", { goals: args }, "PUT"), options.format);
|
|
728
767
|
},
|
|
729
768
|
},
|
|
730
769
|
"winners goals reset": {
|
|
@@ -732,7 +771,7 @@ const commands = {
|
|
|
732
771
|
async run(args, options) {
|
|
733
772
|
if (args.length > 0)
|
|
734
773
|
throw new CliError("Usage: winners goals reset", exitCodes.usage);
|
|
735
|
-
printWinnersGoals(await api(await resolveConnection(options
|
|
774
|
+
printWinnersGoals(await api(await resolveConnection(options), "/api/v1/winners/goals", { goals: null }, "PUT"), options.format);
|
|
736
775
|
},
|
|
737
776
|
},
|
|
738
777
|
examples: {
|
|
@@ -740,7 +779,7 @@ const commands = {
|
|
|
740
779
|
async run(args, options) {
|
|
741
780
|
if (args.length > 0)
|
|
742
781
|
throw new CliError("Usage: examples", exitCodes.usage);
|
|
743
|
-
const { examples } = await api(await resolveConnection(options
|
|
782
|
+
const { examples } = await api(await resolveConnection(options), "/api/v1/examples");
|
|
744
783
|
if (options.format === "json")
|
|
745
784
|
return printJson({ examples });
|
|
746
785
|
for (const e of examples) {
|
|
@@ -758,7 +797,7 @@ const commands = {
|
|
|
758
797
|
throw new CliError("Usage: content search [--text <words>] [--kind <kind>] [--sort <column>] [...]; put several words in quotes", exitCodes.usage);
|
|
759
798
|
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
799
|
const query = new URLSearchParams(Object.entries(filters).filter((entry) => entry[1] !== undefined));
|
|
761
|
-
const { results } = await api(await resolveConnection(options
|
|
800
|
+
const { results } = await api(await resolveConnection(options), `/api/v1/content${query.size ? `?${query}` : ""}`);
|
|
762
801
|
if (options.format === "json")
|
|
763
802
|
return printJson({ results });
|
|
764
803
|
if (results.length === 0)
|
|
@@ -784,7 +823,7 @@ const commands = {
|
|
|
784
823
|
async run(args, options) {
|
|
785
824
|
if (args.length !== 1)
|
|
786
825
|
throw new CliError("Usage: content show <sku | item-id>", exitCodes.usage);
|
|
787
|
-
const content = await api(await resolveConnection(options
|
|
826
|
+
const content = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}`);
|
|
788
827
|
printContent(content, options.format);
|
|
789
828
|
},
|
|
790
829
|
},
|
|
@@ -794,7 +833,7 @@ const commands = {
|
|
|
794
833
|
if (args.length !== 1)
|
|
795
834
|
throw new CliError("Usage: content pull <dir> [--kind <kind>] [--platform <platform>] [--origin discovered|made] [--media] [--full]", exitCodes.usage);
|
|
796
835
|
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
|
|
836
|
+
const report = await pullContent(await resolveConnection(options), args[0], filters, { media: options.media, full: options.full });
|
|
798
837
|
if (options.format === "json")
|
|
799
838
|
return printJson(report);
|
|
800
839
|
const media = options.media ? `, ${report.media} new media file${report.media === 1 ? "" : "s"}` : "";
|
|
@@ -810,7 +849,7 @@ const commands = {
|
|
|
810
849
|
if (args.length !== 1)
|
|
811
850
|
throw new CliError("Usage: content diff <sku | item-id> [--from <n>] [--to <n>]", exitCodes.usage);
|
|
812
851
|
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
|
|
852
|
+
const diff = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/diff${query.size ? `?${query}` : ""}`);
|
|
814
853
|
if (options.format === "json")
|
|
815
854
|
return printJson(diff);
|
|
816
855
|
printContentDiff(diff);
|
|
@@ -821,7 +860,7 @@ const commands = {
|
|
|
821
860
|
async run(args, options) {
|
|
822
861
|
if (args.length !== 1)
|
|
823
862
|
throw new CliError("Usage: content edit <sku | item-id> [--body <text> | @file] [--note <why>] [...]", exitCodes.usage);
|
|
824
|
-
const connection = await resolveConnection(options
|
|
863
|
+
const connection = await resolveConnection(options);
|
|
825
864
|
const input = await contentInput(options, connection);
|
|
826
865
|
if (Object.keys(input).every((k) => k === "runId" || k === "note")) {
|
|
827
866
|
throw new CliError("content edit needs something to change, such as --body, --subject or --file.", exitCodes.usage);
|
|
@@ -835,7 +874,7 @@ const commands = {
|
|
|
835
874
|
async run(args, options) {
|
|
836
875
|
if (args.length !== 2)
|
|
837
876
|
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
|
|
877
|
+
const content = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/status`, {
|
|
839
878
|
status: args[1],
|
|
840
879
|
...(options.platform === undefined ? {} : { platform: options.platform }),
|
|
841
880
|
...(options.objectType === undefined ? {} : { objectType: options.objectType }),
|
|
@@ -853,7 +892,7 @@ const commands = {
|
|
|
853
892
|
const version = options.version === undefined ? undefined : Number(options.version);
|
|
854
893
|
if (version !== undefined && !(Number.isInteger(version) && version >= 1))
|
|
855
894
|
throw new CliError("--version must be a whole number from 1.", exitCodes.usage);
|
|
856
|
-
const { publication } = await api(await resolveConnection(options
|
|
895
|
+
const { publication } = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/publish`, {
|
|
857
896
|
publisher: options.to,
|
|
858
897
|
target: await parseJson(options.target, "--target"),
|
|
859
898
|
idempotencyKey: options.idempotencyKey,
|
|
@@ -869,7 +908,7 @@ const commands = {
|
|
|
869
908
|
async run(args, options) {
|
|
870
909
|
if (args.length !== 1)
|
|
871
910
|
throw new CliError("Usage: content reconcile <publication-id>", exitCodes.usage);
|
|
872
|
-
const { publication } = await api(await resolveConnection(options
|
|
911
|
+
const { publication } = await api(await resolveConnection(options), `/api/v1/publications/${encodeURIComponent(args[0])}/reconcile`, {});
|
|
873
912
|
printPublication(publication, options.format);
|
|
874
913
|
},
|
|
875
914
|
},
|
|
@@ -879,7 +918,7 @@ const commands = {
|
|
|
879
918
|
if (args.length !== 0 || options.plan === undefined || !options.idempotencyKey) {
|
|
880
919
|
throw new CliError("Usage: meta adset publish --plan '<json>' --idempotency-key <key> [--run <run-id>]", exitCodes.usage);
|
|
881
920
|
}
|
|
882
|
-
const { adsetPublication } = await api(await resolveConnection(options
|
|
921
|
+
const { adsetPublication } = await api(await resolveConnection(options), "/api/v1/meta/adsets", {
|
|
883
922
|
plan: await parseJson(options.plan, "--plan"),
|
|
884
923
|
idempotencyKey: options.idempotencyKey,
|
|
885
924
|
...(options.run === undefined ? {} : { runId: options.run }),
|
|
@@ -892,7 +931,7 @@ const commands = {
|
|
|
892
931
|
async run(args, options) {
|
|
893
932
|
if (args.length !== 1)
|
|
894
933
|
throw new CliError("Usage: meta adset show <adset-publication-id>", exitCodes.usage);
|
|
895
|
-
const { adsetPublication } = await api(await resolveConnection(options
|
|
934
|
+
const { adsetPublication } = await api(await resolveConnection(options), `/api/v1/meta/adsets/${encodeURIComponent(args[0])}`);
|
|
896
935
|
printAdSetPublication(adsetPublication, options.format);
|
|
897
936
|
},
|
|
898
937
|
},
|
|
@@ -901,7 +940,7 @@ const commands = {
|
|
|
901
940
|
async run(args, options) {
|
|
902
941
|
if (args.length !== 1)
|
|
903
942
|
throw new CliError("Usage: meta adset reconcile <adset-publication-id>", exitCodes.usage);
|
|
904
|
-
const { adsetPublication } = await api(await resolveConnection(options
|
|
943
|
+
const { adsetPublication } = await api(await resolveConnection(options), `/api/v1/meta/adsets/${encodeURIComponent(args[0])}/reconcile`, {});
|
|
905
944
|
printAdSetPublication(adsetPublication, options.format);
|
|
906
945
|
},
|
|
907
946
|
},
|
|
@@ -910,7 +949,7 @@ const commands = {
|
|
|
910
949
|
async run(args, options) {
|
|
911
950
|
if (args.length !== 1)
|
|
912
951
|
throw new CliError("Usage: content unpublish <deployment-id> [--force]", exitCodes.usage);
|
|
913
|
-
const { deployment } = await api(await resolveConnection(options
|
|
952
|
+
const { deployment } = await api(await resolveConnection(options), `/api/v1/deployments/${encodeURIComponent(args[0])}/unpublish`, options.force ? { force: true } : {});
|
|
914
953
|
if (options.format === "json")
|
|
915
954
|
return printJson({ deployment });
|
|
916
955
|
printFields({
|
|
@@ -928,7 +967,7 @@ const commands = {
|
|
|
928
967
|
async run(args, options) {
|
|
929
968
|
if (args.length !== 1)
|
|
930
969
|
throw new CliError("Usage: content publish-record <sku | item-id> --file <publication.json>", exitCodes.usage);
|
|
931
|
-
const { publication } = await api(await resolveConnection(options
|
|
970
|
+
const { publication } = await api(await resolveConnection(options), `/api/v1/content/${encodeURIComponent(args[0])}/publications`, await readToolInput(options));
|
|
932
971
|
printPublication(publication, options.format);
|
|
933
972
|
},
|
|
934
973
|
},
|
|
@@ -939,7 +978,7 @@ const commands = {
|
|
|
939
978
|
throw new CliError("Usage: content publish-finish <publication-id> --status sending|published|failed|not_sent_practice [--file <outcome.json>]", exitCodes.usage);
|
|
940
979
|
}
|
|
941
980
|
const outcome = await readToolInput(options);
|
|
942
|
-
const { publication } = await api(await resolveConnection(options
|
|
981
|
+
const { publication } = await api(await resolveConnection(options), `/api/v1/publications/${encodeURIComponent(args[0])}`, { ...outcome, status: options.status }, "PATCH");
|
|
943
982
|
printPublication(publication, options.format);
|
|
944
983
|
},
|
|
945
984
|
},
|
|
@@ -948,7 +987,7 @@ const commands = {
|
|
|
948
987
|
async run(args, options) {
|
|
949
988
|
if (args.length > 0)
|
|
950
989
|
throw new CliError("Usage: agent start", exitCodes.usage);
|
|
951
|
-
const start = await api(await resolveConnection(options
|
|
990
|
+
const start = await api(await resolveConnection(options), `${agentPath(options)}/runs`, {});
|
|
952
991
|
if (options.format === "json")
|
|
953
992
|
return printJson(start);
|
|
954
993
|
const { run, quota, brandProfile } = start;
|
|
@@ -968,7 +1007,7 @@ const commands = {
|
|
|
968
1007
|
async run(args, options) {
|
|
969
1008
|
if (args.length !== 1)
|
|
970
1009
|
throw new CliError("Usage: agent show <run-id>", exitCodes.usage);
|
|
971
|
-
const shown = await api(await resolveConnection(options
|
|
1010
|
+
const shown = await api(await resolveConnection(options), `${agentPath(options)}/runs/${encodeURIComponent(args[0])}`);
|
|
972
1011
|
if (options.format === "json")
|
|
973
1012
|
return printJson(shown);
|
|
974
1013
|
const { run, proposals } = shown;
|
|
@@ -982,7 +1021,7 @@ const commands = {
|
|
|
982
1021
|
usage: "agent runs [--limit <n>]",
|
|
983
1022
|
async run(_args, options) {
|
|
984
1023
|
const query = options.limit ? `?limit=${encodeURIComponent(options.limit)}` : "";
|
|
985
|
-
const { runs } = await api(await resolveConnection(options
|
|
1024
|
+
const { runs } = await api(await resolveConnection(options), `${agentPath(options)}/runs${query}`);
|
|
986
1025
|
if (options.format === "json")
|
|
987
1026
|
return printJson({ runs });
|
|
988
1027
|
if (runs.length === 0)
|
|
@@ -997,7 +1036,7 @@ const commands = {
|
|
|
997
1036
|
throw new CliError("Usage: agent finish <run-id> --status succeeded|failed [--file report.md]", exitCodes.usage);
|
|
998
1037
|
}
|
|
999
1038
|
const report = await readText(options.file, "the report");
|
|
1000
|
-
const { run } = await api(await resolveConnection(options
|
|
1039
|
+
const { run } = await api(await resolveConnection(options), `${agentPath(options)}/runs/${encodeURIComponent(args[0])}/finish`, { status: options.status, report });
|
|
1001
1040
|
if (options.format === "json")
|
|
1002
1041
|
return printJson({ run });
|
|
1003
1042
|
console.log(`Finished run ${run.id} as ${run.status}.`);
|
|
@@ -1008,7 +1047,7 @@ const commands = {
|
|
|
1008
1047
|
async run(args, options) {
|
|
1009
1048
|
if (args.length > 0)
|
|
1010
1049
|
throw new CliError("Usage: agent draft --agent p3_refresh", exitCodes.usage);
|
|
1011
|
-
const body = await api(await resolveConnection(options
|
|
1050
|
+
const body = await api(await resolveConnection(options), `${agentPath(options)}/draft`);
|
|
1012
1051
|
if (options.format === "json")
|
|
1013
1052
|
return printJson(body);
|
|
1014
1053
|
const { draft } = body;
|
|
@@ -1049,7 +1088,7 @@ const commands = {
|
|
|
1049
1088
|
"brand get": {
|
|
1050
1089
|
usage: "brand get",
|
|
1051
1090
|
async run(_args, options) {
|
|
1052
|
-
const { profile } = await api(await resolveConnection(options
|
|
1091
|
+
const { profile } = await api(await resolveConnection(options), "/api/v1/brand-profile");
|
|
1053
1092
|
if (options.format === "json")
|
|
1054
1093
|
return printJson({ profile });
|
|
1055
1094
|
if (!profile)
|
|
@@ -1065,7 +1104,7 @@ const commands = {
|
|
|
1065
1104
|
throw new CliError("Usage: brand set [--file <path>] [--sources '<json>' | @file.json]", exitCodes.usage);
|
|
1066
1105
|
const markdown = await readText(options.file, "the brand profile");
|
|
1067
1106
|
const sources = options.sources === undefined ? [] : await parseJson(options.sources, "--sources");
|
|
1068
|
-
const { profile } = await api(await resolveConnection(options
|
|
1107
|
+
const { profile } = await api(await resolveConnection(options), "/api/v1/brand-profile", { markdown, sources }, "PUT");
|
|
1069
1108
|
if (options.format === "json")
|
|
1070
1109
|
return printJson({ profile });
|
|
1071
1110
|
console.log(`Saved brand profile version ${profile.version}.`);
|
|
@@ -1076,7 +1115,7 @@ const commands = {
|
|
|
1076
1115
|
async run(args, options) {
|
|
1077
1116
|
if (args.length !== 1)
|
|
1078
1117
|
throw new CliError("Usage: content add-page <url>", exitCodes.usage);
|
|
1079
|
-
const { page } = await api(await resolveConnection(options
|
|
1118
|
+
const { page } = await api(await resolveConnection(options), "/api/v1/content/pages", { url: args[0] });
|
|
1080
1119
|
if (options.format === "json")
|
|
1081
1120
|
return printJson({ page });
|
|
1082
1121
|
console.log(renderTable(["key", "status", "version", "changed", "headline", "blocks"], [{ ...page, blocks: page.blockCount }]));
|
|
@@ -1090,7 +1129,7 @@ const commands = {
|
|
|
1090
1129
|
async run(args, options) {
|
|
1091
1130
|
if (args.length !== 2)
|
|
1092
1131
|
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
|
|
1132
|
+
const { url } = await api(await resolveConnection(options), "/api/v1/content/track-link", {
|
|
1094
1133
|
sku: args[0],
|
|
1095
1134
|
url: args[1],
|
|
1096
1135
|
channel: options.channel,
|
|
@@ -1106,7 +1145,7 @@ const commands = {
|
|
|
1106
1145
|
async run(args, options) {
|
|
1107
1146
|
if (args.length !== 1)
|
|
1108
1147
|
throw new CliError("Usage: trail <plan-id | sku | item-id>", exitCodes.usage);
|
|
1109
|
-
const connection = await resolveConnection(options
|
|
1148
|
+
const connection = await resolveConnection(options);
|
|
1110
1149
|
const ref = args[0];
|
|
1111
1150
|
const trailOf = (query) => api(connection, `/api/v1/trail?${query}=${encodeURIComponent(ref)}`);
|
|
1112
1151
|
// Plans and items both have uuid ids: an id that names no plan is read as an item.
|
|
@@ -1126,7 +1165,7 @@ const commands = {
|
|
|
1126
1165
|
if (args.length > 0)
|
|
1127
1166
|
throw new CliError("Usage: funnels show [--version <n>]", exitCodes.usage);
|
|
1128
1167
|
const query = options.version === undefined ? "" : `?version=${wholeNumber(options.version, "--version")}`;
|
|
1129
|
-
const view = await api(await resolveConnection(options
|
|
1168
|
+
const view = await api(await resolveConnection(options), `${FUNNELS_PATH}${query}`);
|
|
1130
1169
|
if (options.format === "json")
|
|
1131
1170
|
return printJson(view);
|
|
1132
1171
|
process.stdout.write(view.text);
|
|
@@ -1137,7 +1176,7 @@ const commands = {
|
|
|
1137
1176
|
async run(args, options) {
|
|
1138
1177
|
if (args.length > 0)
|
|
1139
1178
|
throw new CliError("Usage: funnels numbers [--funnel <key>] [--from <date>] [--to <date>]", exitCodes.usage);
|
|
1140
|
-
const view = await api(await resolveConnection(options
|
|
1179
|
+
const view = await api(await resolveConnection(options), `${FUNNELS_PATH}/numbers${reportQuery(options, { funnel: options.funnel })}`);
|
|
1141
1180
|
if (options.format === "json")
|
|
1142
1181
|
return printJson(view);
|
|
1143
1182
|
process.stdout.write(view.text);
|
|
@@ -1148,7 +1187,7 @@ const commands = {
|
|
|
1148
1187
|
async run(args, options) {
|
|
1149
1188
|
if (args.length > 0 || options.from !== undefined)
|
|
1150
1189
|
throw new CliError("Usage: funnels leaks [--funnel <key>] [--to <date>]", exitCodes.usage);
|
|
1151
|
-
const view = await api(await resolveConnection(options
|
|
1190
|
+
const view = await api(await resolveConnection(options), `${FUNNELS_PATH}/leaks${reportQuery(options, { funnel: options.funnel })}`);
|
|
1152
1191
|
if (options.format === "json")
|
|
1153
1192
|
return printJson(view);
|
|
1154
1193
|
process.stdout.write(view.text);
|
|
@@ -1159,7 +1198,7 @@ const commands = {
|
|
|
1159
1198
|
async run(args, options) {
|
|
1160
1199
|
if (args.length > 0)
|
|
1161
1200
|
throw new CliError("Usage: funnels evidence", exitCodes.usage);
|
|
1162
|
-
const view = await api(await resolveConnection(options
|
|
1201
|
+
const view = await api(await resolveConnection(options), `${FUNNELS_PATH}/evidence`);
|
|
1163
1202
|
if (options.format === "json")
|
|
1164
1203
|
return printJson(view);
|
|
1165
1204
|
process.stdout.write(view.text);
|
|
@@ -1177,7 +1216,7 @@ const commands = {
|
|
|
1177
1216
|
const map = await parseJson(`@${options.file}`, "--file");
|
|
1178
1217
|
const evidence = options.evidence === undefined ? undefined : await parseJson(options.evidence, "--evidence");
|
|
1179
1218
|
const questions = options.questions === undefined ? undefined : await parseJson(options.questions, "--questions");
|
|
1180
|
-
const connection = await resolveConnection(options
|
|
1219
|
+
const connection = await resolveConnection(options);
|
|
1181
1220
|
const basedOn = options.basedOn === undefined ? null : wholeNumber(options.basedOn, "--based-on");
|
|
1182
1221
|
const saved = await api(connection, `${FUNNELS_PATH}/draft`, { map, note: options.note, evidence, based_on: basedOn, counts_from: options.countsFrom, questions }, "PUT");
|
|
1183
1222
|
if (options.format === "json")
|
|
@@ -1207,7 +1246,7 @@ const commands = {
|
|
|
1207
1246
|
"funnels versions": {
|
|
1208
1247
|
usage: "funnels versions [--diff <a> <b>] every version of the map, or what changed from version a to b",
|
|
1209
1248
|
async run(args, options) {
|
|
1210
|
-
const connection = await resolveConnection(options
|
|
1249
|
+
const connection = await resolveConnection(options);
|
|
1211
1250
|
if (options.diff !== undefined) {
|
|
1212
1251
|
if (args.length !== 1)
|
|
1213
1252
|
throw new CliError("Usage: funnels versions --diff <a> <b>", exitCodes.usage);
|
|
@@ -1254,7 +1293,7 @@ const commands = {
|
|
|
1254
1293
|
}
|
|
1255
1294
|
if (options.status)
|
|
1256
1295
|
query.set("status", options.status);
|
|
1257
|
-
const { facts } = await api(await resolveConnection(options
|
|
1296
|
+
const { facts } = await api(await resolveConnection(options), `${FACTS_PATH}${query.size ? `?${query}` : ""}`);
|
|
1258
1297
|
if (options.format === "json")
|
|
1259
1298
|
return printJson({ facts });
|
|
1260
1299
|
if (facts.length === 0)
|
|
@@ -1276,7 +1315,7 @@ const commands = {
|
|
|
1276
1315
|
query.set("sort", options.sort);
|
|
1277
1316
|
if (options.limit)
|
|
1278
1317
|
query.set("limit", options.limit);
|
|
1279
|
-
const read = await api(await resolveConnection(options
|
|
1318
|
+
const read = await api(await resolveConnection(options), `${VOICE_PATH}${query.size ? `?${query}` : ""}`);
|
|
1280
1319
|
if (options.format === "json")
|
|
1281
1320
|
return printJson(read);
|
|
1282
1321
|
if ("lines" in read) {
|
|
@@ -1301,7 +1340,7 @@ const commands = {
|
|
|
1301
1340
|
query.set("status", options.status);
|
|
1302
1341
|
if (options.limit)
|
|
1303
1342
|
query.set("limit", options.limit);
|
|
1304
|
-
const { tags } = await api(await resolveConnection(options
|
|
1343
|
+
const { tags } = await api(await resolveConnection(options), `${AD_TAGS_PATH}${query.size ? `?${query}` : ""}`);
|
|
1305
1344
|
if (options.format === "json")
|
|
1306
1345
|
return printJson({ tags });
|
|
1307
1346
|
if (tags.length === 0)
|
|
@@ -1314,7 +1353,7 @@ const commands = {
|
|
|
1314
1353
|
async run(args, options) {
|
|
1315
1354
|
if (args.length > 0)
|
|
1316
1355
|
throw new CliError("Usage: ads tag --file <tags.json> | < tags.json", exitCodes.usage);
|
|
1317
|
-
const result = await api(await resolveConnection(options
|
|
1356
|
+
const result = await api(await resolveConnection(options), AD_TAGS_PATH, await readToolInput(options));
|
|
1318
1357
|
if (options.format === "json")
|
|
1319
1358
|
return printJson(result);
|
|
1320
1359
|
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 +1367,7 @@ const commands = {
|
|
|
1328
1367
|
if (args.length > 0)
|
|
1329
1368
|
throw new CliError("Usage: ads autotag [--limit <n>] [--ad <id>] [--json]", exitCodes.usage);
|
|
1330
1369
|
const body = { ...(options.ad ? { ad_ids: [options.ad] } : {}), ...(options.limit ? { limit: Number(options.limit) } : {}) };
|
|
1331
|
-
const result = await api(await resolveConnection(options
|
|
1370
|
+
const result = await api(await resolveConnection(options), AD_AUTOTAG_PATH, body);
|
|
1332
1371
|
if (options.format === "json")
|
|
1333
1372
|
return printJson(result);
|
|
1334
1373
|
const plural = (n) => `${n} ${n === 1 ? "ad" : "ads"}`;
|
|
@@ -1355,7 +1394,7 @@ const commands = {
|
|
|
1355
1394
|
async run(args, options) {
|
|
1356
1395
|
if (args.length > 0)
|
|
1357
1396
|
throw new CliError("Usage: ads tag-formats [--limit <n>] [--json]", exitCodes.usage);
|
|
1358
|
-
const connection = await resolveConnection(options
|
|
1397
|
+
const connection = await resolveConnection(options);
|
|
1359
1398
|
const total = { tagged: 0, no_format: 0 };
|
|
1360
1399
|
let after = null;
|
|
1361
1400
|
for (;;) {
|
|
@@ -1384,7 +1423,7 @@ const commands = {
|
|
|
1384
1423
|
throw new CliError("Usage: content tags [<item-id>] [--kind email|text|page] [--status <status>] [--limit <n>] [--json]", exitCodes.usage);
|
|
1385
1424
|
const filters = { item: args[0], kind: options.kind, status: options.status, limit: options.limit };
|
|
1386
1425
|
const query = new URLSearchParams(Object.entries(filters).filter((entry) => entry[1] !== undefined));
|
|
1387
|
-
const { tags } = await api(await resolveConnection(options
|
|
1426
|
+
const { tags } = await api(await resolveConnection(options), `${CONTENT_TAGS_PATH}${query.size ? `?${query}` : ""}`);
|
|
1388
1427
|
if (options.format === "json")
|
|
1389
1428
|
return printJson({ tags });
|
|
1390
1429
|
if (tags.length === 0)
|
|
@@ -1398,7 +1437,7 @@ const commands = {
|
|
|
1398
1437
|
if (args.length > 0)
|
|
1399
1438
|
throw new CliError("Usage: content to-check [--limit <n>] [--json]", exitCodes.usage);
|
|
1400
1439
|
const query = new URLSearchParams({ to_check: "1", ...(options.limit ? { limit: options.limit } : {}) });
|
|
1401
|
-
const { tags } = await api(await resolveConnection(options
|
|
1440
|
+
const { tags } = await api(await resolveConnection(options), `${CONTENT_TAGS_PATH}?${query}`);
|
|
1402
1441
|
if (options.format === "json")
|
|
1403
1442
|
return printJson({ tags });
|
|
1404
1443
|
if (tags.length === 0)
|
|
@@ -1414,7 +1453,7 @@ const commands = {
|
|
|
1414
1453
|
...(options.kind ? { kinds: options.kind.split(",") } : {}),
|
|
1415
1454
|
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
1416
1455
|
};
|
|
1417
|
-
const result = await api(await resolveConnection(options
|
|
1456
|
+
const result = await api(await resolveConnection(options), CONTENT_AUTOTAG_PATH, body);
|
|
1418
1457
|
if (options.format === "json")
|
|
1419
1458
|
return printJson(result);
|
|
1420
1459
|
const plural = (n) => `${n} ${n === 1 ? "item" : "items"}`;
|
|
@@ -1439,7 +1478,7 @@ const commands = {
|
|
|
1439
1478
|
async run(args, options) {
|
|
1440
1479
|
if (args.length !== 1)
|
|
1441
1480
|
throw new CliError("Usage: facts show <id>", exitCodes.usage);
|
|
1442
|
-
const { fact } = await api(await resolveConnection(options
|
|
1481
|
+
const { fact } = await api(await resolveConnection(options), `${FACTS_PATH}/${encodeURIComponent(args[0])}`);
|
|
1443
1482
|
if (options.format === "json")
|
|
1444
1483
|
return printJson({ fact });
|
|
1445
1484
|
const nameKey = fact.key === NAME_KEY ? nameKeyLines(fact.value) : null;
|
|
@@ -1454,7 +1493,7 @@ const commands = {
|
|
|
1454
1493
|
async run(args, options) {
|
|
1455
1494
|
if (args.length > 0)
|
|
1456
1495
|
throw new CliError("Usage: facts propose --file <fact.json> | < fact.json", exitCodes.usage);
|
|
1457
|
-
const { fact } = await api(await resolveConnection(options
|
|
1496
|
+
const { fact } = await api(await resolveConnection(options), FACTS_PATH, await readToolInput(options));
|
|
1458
1497
|
if (options.format === "json")
|
|
1459
1498
|
return printJson({ fact });
|
|
1460
1499
|
console.log(`Proposed fact ${fact.id}. A person confirms or rejects it in the app.`);
|
|
@@ -1474,7 +1513,7 @@ const commands = {
|
|
|
1474
1513
|
for (const dimension of options.dimension ?? [])
|
|
1475
1514
|
params.append("dimension", dimension);
|
|
1476
1515
|
const query = params.size > 0 ? `?${params}` : "";
|
|
1477
|
-
const { lessons } = await api(await resolveConnection(options
|
|
1516
|
+
const { lessons } = await api(await resolveConnection(options), `${LESSONS_PATH}${query}`);
|
|
1478
1517
|
if (options.format === "json")
|
|
1479
1518
|
return printJson({ lessons });
|
|
1480
1519
|
if (lessons.length === 0) {
|
|
@@ -1489,7 +1528,7 @@ const commands = {
|
|
|
1489
1528
|
async run(args, options) {
|
|
1490
1529
|
if (args.length > 0)
|
|
1491
1530
|
throw new CliError("Usage: lessons add --file <lesson.json> | < lesson.json", exitCodes.usage);
|
|
1492
|
-
const recorded = await api(await resolveConnection(options
|
|
1531
|
+
const recorded = await api(await resolveConnection(options), LESSONS_PATH, await readToolInput(options));
|
|
1493
1532
|
if (options.format === "json")
|
|
1494
1533
|
return printJson(recorded);
|
|
1495
1534
|
const { lesson, outcome } = recorded;
|
|
@@ -1513,7 +1552,7 @@ const commands = {
|
|
|
1513
1552
|
query.set("funnel", options.funnel);
|
|
1514
1553
|
if (options.limit)
|
|
1515
1554
|
query.set("limit", options.limit);
|
|
1516
|
-
const { tests } = await api(await resolveConnection(options
|
|
1555
|
+
const { tests } = await api(await resolveConnection(options), `${TESTS_PATH}${query.size ? `?${query}` : ""}`);
|
|
1517
1556
|
if (options.format === "json")
|
|
1518
1557
|
return printJson({ tests });
|
|
1519
1558
|
if (tests.length === 0)
|
|
@@ -1526,7 +1565,7 @@ const commands = {
|
|
|
1526
1565
|
async run(args, options) {
|
|
1527
1566
|
if (args.length !== 1)
|
|
1528
1567
|
throw new CliError("Usage: tests show <id|test_id>", exitCodes.usage);
|
|
1529
|
-
const { test } = await api(await resolveConnection(options
|
|
1568
|
+
const { test } = await api(await resolveConnection(options), testPath(args[0]));
|
|
1530
1569
|
if (options.format === "json")
|
|
1531
1570
|
return printJson({ test });
|
|
1532
1571
|
printTest(test);
|
|
@@ -1537,7 +1576,7 @@ const commands = {
|
|
|
1537
1576
|
async run(args, options) {
|
|
1538
1577
|
if (args.length > 0)
|
|
1539
1578
|
throw new CliError("Usage: tests draft --file <test.json> | < test.json", exitCodes.usage);
|
|
1540
|
-
const { test } = await api(await resolveConnection(options
|
|
1579
|
+
const { test } = await api(await resolveConnection(options), TESTS_PATH, await readToolInput(options));
|
|
1541
1580
|
if (options.format === "json")
|
|
1542
1581
|
return printJson({ test });
|
|
1543
1582
|
console.log(`Drafted test ${test.test_id} (${test.id}).`);
|
|
@@ -1548,7 +1587,7 @@ const commands = {
|
|
|
1548
1587
|
async run(args, options) {
|
|
1549
1588
|
if (args.length !== 1)
|
|
1550
1589
|
throw new CliError("Usage: tests start <id|test_id>", exitCodes.usage);
|
|
1551
|
-
const { test } = await api(await resolveConnection(options
|
|
1590
|
+
const { test } = await api(await resolveConnection(options), `${testPath(args[0])}/start`, {});
|
|
1552
1591
|
if (options.format === "json")
|
|
1553
1592
|
return printJson({ test });
|
|
1554
1593
|
console.log(`Test ${test.test_id} is running from ${test.started_on}.`);
|
|
@@ -1559,7 +1598,7 @@ const commands = {
|
|
|
1559
1598
|
async run(args, options) {
|
|
1560
1599
|
if (args.length !== 1)
|
|
1561
1600
|
throw new CliError('Usage: tests abandon <id|test_id> [--note "<why>"]', exitCodes.usage);
|
|
1562
|
-
const { test } = await api(await resolveConnection(options
|
|
1601
|
+
const { test } = await api(await resolveConnection(options), `${testPath(args[0])}/abandon`, options.note ? { note: options.note } : {});
|
|
1563
1602
|
if (options.format === "json")
|
|
1564
1603
|
return printJson({ test });
|
|
1565
1604
|
console.log(`Abandoned test ${test.test_id}.`);
|
|
@@ -1570,7 +1609,7 @@ const commands = {
|
|
|
1570
1609
|
async run(args, options) {
|
|
1571
1610
|
if (args.length !== 1)
|
|
1572
1611
|
throw new CliError("Usage: tests results <id|test_id>", exitCodes.usage);
|
|
1573
|
-
const { results } = await api(await resolveConnection(options
|
|
1612
|
+
const { results } = await api(await resolveConnection(options), `${testPath(args[0])}/results`);
|
|
1574
1613
|
if (options.format === "json")
|
|
1575
1614
|
return printJson({ results });
|
|
1576
1615
|
printResults(results);
|
|
@@ -1582,7 +1621,7 @@ const commands = {
|
|
|
1582
1621
|
if (args.length !== 1 || !options.decision) {
|
|
1583
1622
|
throw new CliError('Usage: tests decide <id|test_id> --decision <scale|continue|iterate|hold|stop> [--note "<why>"]', exitCodes.usage);
|
|
1584
1623
|
}
|
|
1585
|
-
const decided = await api(await resolveConnection(options
|
|
1624
|
+
const decided = await api(await resolveConnection(options), `${testPath(args[0])}/decide`, options.note ? { decision: options.decision, note: options.note } : { decision: options.decision });
|
|
1586
1625
|
if (options.format === "json")
|
|
1587
1626
|
return printJson(decided);
|
|
1588
1627
|
const { test, lesson, next_test: next } = decided;
|
|
@@ -1598,7 +1637,7 @@ const commands = {
|
|
|
1598
1637
|
async run(args, options) {
|
|
1599
1638
|
if (args.length !== 1 || !options.note)
|
|
1600
1639
|
throw new CliError('Usage: lessons retire <id> --note "<why it is no longer true>"', exitCodes.usage);
|
|
1601
|
-
const { lesson } = await api(await resolveConnection(options
|
|
1640
|
+
const { lesson } = await api(await resolveConnection(options), `${LESSONS_PATH}/${encodeURIComponent(args[0])}/retire`, { reason: options.note });
|
|
1602
1641
|
if (options.format === "json")
|
|
1603
1642
|
return printJson({ lesson });
|
|
1604
1643
|
console.log(`Retired lesson ${lesson.id}.`);
|
|
@@ -1609,7 +1648,7 @@ const commands = {
|
|
|
1609
1648
|
async run(args, options) {
|
|
1610
1649
|
if (args.length > 0)
|
|
1611
1650
|
throw new CliError("Usage: grammar | grammar propose --file <values.json>", exitCodes.usage);
|
|
1612
|
-
const answer = await api(await resolveConnection(options
|
|
1651
|
+
const answer = await api(await resolveConnection(options), GRAMMAR_PATH);
|
|
1613
1652
|
if (options.format === "json")
|
|
1614
1653
|
return printJson(answer);
|
|
1615
1654
|
process.stdout.write(answer.text);
|
|
@@ -1620,7 +1659,7 @@ const commands = {
|
|
|
1620
1659
|
async run(args, options) {
|
|
1621
1660
|
if (args.length > 0)
|
|
1622
1661
|
throw new CliError("Usage: grammar propose --file <values.json> | < values.json", exitCodes.usage);
|
|
1623
|
-
const result = await api(await resolveConnection(options
|
|
1662
|
+
const result = await api(await resolveConnection(options), `${GRAMMAR_PATH}/proposals`, await readToolInput(options));
|
|
1624
1663
|
if (options.format === "json")
|
|
1625
1664
|
return printJson(result);
|
|
1626
1665
|
const named = (refs) => refs.map((r) => `${r.part} ${r.key}`).join(", ");
|
|
@@ -1634,7 +1673,7 @@ const commands = {
|
|
|
1634
1673
|
async run(args, options) {
|
|
1635
1674
|
if (args.length > 0)
|
|
1636
1675
|
throw new CliError("Usage: voice lists", exitCodes.usage);
|
|
1637
|
-
const lists = await api(await resolveConnection(options
|
|
1676
|
+
const lists = await api(await resolveConnection(options), VOICE_LISTS_PATH);
|
|
1638
1677
|
if (options.format === "json")
|
|
1639
1678
|
return printJson(lists);
|
|
1640
1679
|
const rows = [
|
|
@@ -1655,7 +1694,7 @@ const commands = {
|
|
|
1655
1694
|
if (!kind || !["competitor", "subreddit", "search_phrase"].includes(kind) || value === undefined || rest.length > 0) {
|
|
1656
1695
|
throw new CliError('Usage: voice suggest competitor|subreddit|search_phrase "<value>" [--note <why>]; put a value with spaces in quotes', exitCodes.usage);
|
|
1657
1696
|
}
|
|
1658
|
-
const result = await api(await resolveConnection(options
|
|
1697
|
+
const result = await api(await resolveConnection(options), VOICE_SUGGESTIONS_PATH, { kind, value, note: options.note });
|
|
1659
1698
|
if (options.format === "json")
|
|
1660
1699
|
return printJson(result);
|
|
1661
1700
|
if (result.outcome === "already_listed")
|
|
@@ -1670,7 +1709,7 @@ const commands = {
|
|
|
1670
1709
|
async run(args, options) {
|
|
1671
1710
|
if (args.length > 0)
|
|
1672
1711
|
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
|
|
1712
|
+
const { story } = await api(await resolveConnection(options), `${STORIES_PATH}/latest`);
|
|
1674
1713
|
if (options.format === "json")
|
|
1675
1714
|
return printJson({ story });
|
|
1676
1715
|
if (!story)
|
|
@@ -1683,7 +1722,7 @@ const commands = {
|
|
|
1683
1722
|
async run(args, options) {
|
|
1684
1723
|
if (args.length > 0)
|
|
1685
1724
|
throw new CliError("Usage: story list", exitCodes.usage);
|
|
1686
|
-
const { stories } = await api(await resolveConnection(options
|
|
1725
|
+
const { stories } = await api(await resolveConnection(options), STORIES_PATH);
|
|
1687
1726
|
if (options.format === "json")
|
|
1688
1727
|
return printJson({ stories });
|
|
1689
1728
|
if (stories.length === 0)
|
|
@@ -1706,7 +1745,7 @@ const commands = {
|
|
|
1706
1745
|
if (args.length !== 1)
|
|
1707
1746
|
throw new CliError("Usage: story show <version>", exitCodes.usage);
|
|
1708
1747
|
// `show latest` reaches the newest-story route, which answers null before the first story.
|
|
1709
|
-
const { story } = await api(await resolveConnection(options
|
|
1748
|
+
const { story } = await api(await resolveConnection(options), `${STORIES_PATH}/${encodeURIComponent(args[0])}`);
|
|
1710
1749
|
if (options.format === "json")
|
|
1711
1750
|
return printJson({ story });
|
|
1712
1751
|
if (!story)
|
|
@@ -1719,7 +1758,7 @@ const commands = {
|
|
|
1719
1758
|
async run(args, options) {
|
|
1720
1759
|
if (args.length > 0)
|
|
1721
1760
|
throw new CliError("Usage: story submit --file <story.json> | < story.json", exitCodes.usage);
|
|
1722
|
-
const { story } = await api(await resolveConnection(options
|
|
1761
|
+
const { story } = await api(await resolveConnection(options), STORIES_PATH, await readToolInput(options));
|
|
1723
1762
|
if (options.format === "json")
|
|
1724
1763
|
return printJson({ story });
|
|
1725
1764
|
console.log(`Saved story version ${story.version}.`);
|
|
@@ -1731,7 +1770,7 @@ const commands = {
|
|
|
1731
1770
|
if (args.length > 0 || !options.name || !options.goal || !options.channel) {
|
|
1732
1771
|
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
1772
|
}
|
|
1734
|
-
const { plan } = await api(await resolveConnection(options
|
|
1773
|
+
const { plan } = await api(await resolveConnection(options), "/api/v1/launch-plans", {
|
|
1735
1774
|
name: options.name,
|
|
1736
1775
|
goal: await textOrFile(options.goal),
|
|
1737
1776
|
channel: options.channel,
|
|
@@ -1750,7 +1789,7 @@ const commands = {
|
|
|
1750
1789
|
async run(args, options) {
|
|
1751
1790
|
if (args.length !== 1)
|
|
1752
1791
|
throw new CliError("Usage: plans show <plan-id>", exitCodes.usage);
|
|
1753
|
-
const { plan } = await api(await resolveConnection(options
|
|
1792
|
+
const { plan } = await api(await resolveConnection(options), planPath(args[0]));
|
|
1754
1793
|
printPlan(plan, options.format);
|
|
1755
1794
|
},
|
|
1756
1795
|
},
|
|
@@ -1762,7 +1801,7 @@ const commands = {
|
|
|
1762
1801
|
const position = options.position === undefined ? undefined : Number(options.position);
|
|
1763
1802
|
if (position !== undefined && (!Number.isInteger(position) || position < 0))
|
|
1764
1803
|
throw new CliError("--position must be a whole number from 0.", exitCodes.usage);
|
|
1765
|
-
const { plan } = await api(await resolveConnection(options
|
|
1804
|
+
const { plan } = await api(await resolveConnection(options), `${planPath(args[0])}/items`, {
|
|
1766
1805
|
item: args[1],
|
|
1767
1806
|
...(options.role === undefined ? {} : { role: options.role }),
|
|
1768
1807
|
...(position === undefined ? {} : { position }),
|
|
@@ -1776,7 +1815,7 @@ const commands = {
|
|
|
1776
1815
|
if (args.length !== 2)
|
|
1777
1816
|
throw new CliError("Usage: plans remove <plan-id> <sku | item-id>", exitCodes.usage);
|
|
1778
1817
|
const path = `${planPath(args[0])}/items?item=${encodeURIComponent(args[1])}`;
|
|
1779
|
-
const { plan } = await api(await resolveConnection(options
|
|
1818
|
+
const { plan } = await api(await resolveConnection(options), path, undefined, "DELETE");
|
|
1780
1819
|
printPlan(plan, options.format);
|
|
1781
1820
|
},
|
|
1782
1821
|
},
|
|
@@ -1785,7 +1824,7 @@ const commands = {
|
|
|
1785
1824
|
async run(args, options) {
|
|
1786
1825
|
if (args.length !== 2)
|
|
1787
1826
|
throw new CliError("Usage: plans status <plan-id> draft|ready|publishing|live|done|cancelled", exitCodes.usage);
|
|
1788
|
-
const { plan } = await api(await resolveConnection(options
|
|
1827
|
+
const { plan } = await api(await resolveConnection(options), planPath(args[0]), { status: args[1] }, "PATCH");
|
|
1789
1828
|
printPlan(plan, options.format);
|
|
1790
1829
|
},
|
|
1791
1830
|
},
|
|
@@ -1794,7 +1833,7 @@ const commands = {
|
|
|
1794
1833
|
async run(args, options) {
|
|
1795
1834
|
if (args.length > 0)
|
|
1796
1835
|
throw new CliError("Usage: proposals create --input '<json>'", exitCodes.usage);
|
|
1797
|
-
const { proposal } = await api(await resolveConnection(options
|
|
1836
|
+
const { proposal } = await api(await resolveConnection(options), "/api/v1/proposals", await readToolInput(options));
|
|
1798
1837
|
if (options.format === "json")
|
|
1799
1838
|
return printJson({ proposal });
|
|
1800
1839
|
console.log(renderTable(PROPOSAL_COLUMNS, [proposalRow(proposal)]));
|
|
@@ -1813,7 +1852,7 @@ const commands = {
|
|
|
1813
1852
|
if (options.limit)
|
|
1814
1853
|
query.set("limit", options.limit);
|
|
1815
1854
|
const path = `/api/v1/proposals${query.size ? `?${query}` : ""}`;
|
|
1816
|
-
const { proposals } = await api(await resolveConnection(options
|
|
1855
|
+
const { proposals } = await api(await resolveConnection(options), path);
|
|
1817
1856
|
if (options.format === "json")
|
|
1818
1857
|
return printJson({ proposals });
|
|
1819
1858
|
if (proposals.length === 0)
|
|
@@ -1841,7 +1880,7 @@ const commands = {
|
|
|
1841
1880
|
if (options.harness !== undefined)
|
|
1842
1881
|
query.set("harnesses", options.harness);
|
|
1843
1882
|
const path = `/api/operator/skills/${name}/versions${query.size ? `?${query}` : ""}`;
|
|
1844
|
-
const published = await apiUpload(await operatorConnection(options
|
|
1883
|
+
const published = await apiUpload(await operatorConnection(options), path, zip, "application/zip");
|
|
1845
1884
|
if (options.format === "json")
|
|
1846
1885
|
return printJson(published);
|
|
1847
1886
|
console.log(`${published.name} version ${published.version}`);
|
|
@@ -1853,7 +1892,7 @@ const commands = {
|
|
|
1853
1892
|
async run(args, options) {
|
|
1854
1893
|
if (args.length > 0)
|
|
1855
1894
|
throw new CliError("Usage: skill list", exitCodes.usage);
|
|
1856
|
-
const { skills } = await api(await operatorConnection(options
|
|
1895
|
+
const { skills } = await api(await operatorConnection(options), "/api/operator/skills");
|
|
1857
1896
|
if (options.format === "json")
|
|
1858
1897
|
return printJson({ skills });
|
|
1859
1898
|
if (skills.length === 0)
|
|
@@ -1893,7 +1932,7 @@ const commands = {
|
|
|
1893
1932
|
async run(args, options) {
|
|
1894
1933
|
if (args.length > 0)
|
|
1895
1934
|
throw new CliError("Usage: worker list", exitCodes.usage);
|
|
1896
|
-
const { workers } = await api(await resolveConnection(options
|
|
1935
|
+
const { workers } = await api(await resolveConnection(options), "/api/v1/workers");
|
|
1897
1936
|
if (options.format === "json")
|
|
1898
1937
|
return printJson({ workers });
|
|
1899
1938
|
if (workers.length === 0)
|
|
@@ -1917,7 +1956,7 @@ const commands = {
|
|
|
1917
1956
|
async run(args, options) {
|
|
1918
1957
|
if (args.length !== 1)
|
|
1919
1958
|
throw new CliError("Usage: worker runs <hand-id | skill> [--limit <n>]", exitCodes.usage);
|
|
1920
|
-
const connection = await resolveConnection(options
|
|
1959
|
+
const connection = await resolveConnection(options);
|
|
1921
1960
|
const id = await workerId(connection, args[0]);
|
|
1922
1961
|
const query = options.limit === undefined ? "" : `?limit=${encodeURIComponent(options.limit)}`;
|
|
1923
1962
|
const { runs } = await api(connection, `/api/v1/workers/${encodeURIComponent(id)}/runs${query}`);
|
|
@@ -1933,7 +1972,7 @@ const commands = {
|
|
|
1933
1972
|
async run(args, options) {
|
|
1934
1973
|
if (args.length !== 1)
|
|
1935
1974
|
throw new CliError("Usage: worker run-now <hand-id | skill>", exitCodes.usage);
|
|
1936
|
-
const connection = await resolveConnection(options
|
|
1975
|
+
const connection = await resolveConnection(options);
|
|
1937
1976
|
const id = await workerId(connection, args[0]);
|
|
1938
1977
|
const queued = await api(connection, `/api/v1/workers/${encodeURIComponent(id)}/run-now`, {});
|
|
1939
1978
|
if (options.format === "json")
|
|
@@ -1993,6 +2032,18 @@ const commands = {
|
|
|
1993
2032
|
},
|
|
1994
2033
|
},
|
|
1995
2034
|
};
|
|
2035
|
+
function clientReason(choice) {
|
|
2036
|
+
switch (choice.source) {
|
|
2037
|
+
case "env":
|
|
2038
|
+
return `Using the key in ${envPrefix}_API_KEY, which beats --client, client files and saved logins.`;
|
|
2039
|
+
case "flag":
|
|
2040
|
+
return `Using client ${choice.client}, from --client.`;
|
|
2041
|
+
case "folder":
|
|
2042
|
+
return `Using client ${choice.client}, from the client file ${choice.path}.`;
|
|
2043
|
+
case "default":
|
|
2044
|
+
return `Using the default login (no --client flag and no ${CLIENT_FILE} file in this folder or above it).`;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
1996
2047
|
/** Runs `work` with the runner's settings from the environment; SIGINT or SIGTERM stops the run in hand, reported failed. */
|
|
1997
2048
|
async function withRunner(options, work) {
|
|
1998
2049
|
const token = process.env[RUNNER_TOKEN_ENV];
|
|
@@ -2032,7 +2083,7 @@ async function installLibrarySkill(ref, options) {
|
|
|
2032
2083
|
if (options.codex && options.project)
|
|
2033
2084
|
throw new CliError("--codex installs a skill for every Codex run, under CODEX_HOME; drop --project", exitCodes.usage);
|
|
2034
2085
|
const query = ref.version === null ? "" : `?version=${ref.version}`;
|
|
2035
|
-
const download = await api(await operatorConnection(options
|
|
2086
|
+
const download = await api(await operatorConnection(options), `/api/operator/skills/${ref.name}/download${query}`);
|
|
2036
2087
|
const files = unpackSkill(download, await getSigned(download.url));
|
|
2037
2088
|
const { skillsDir, above } = options.codex ? { skillsDir: codexSkillsDir(), above: [] } : claudeSkillsDir(options.project);
|
|
2038
2089
|
const path = await installSkillFolder(skillsDir, download.name, files, above);
|
|
@@ -2509,6 +2560,8 @@ ${Object.values(commands)
|
|
|
2509
2560
|
Options:
|
|
2510
2561
|
--format table|json output format (default: table)
|
|
2511
2562
|
--api-url <url> API base URL
|
|
2563
|
+
--client <name> use that client's saved login (login --client <name> saves it); beats a ${CLIENT_FILE} file, which
|
|
2564
|
+
names the client for its folder and every folder below it; ${envPrefix}_API_KEY beats both
|
|
2512
2565
|
--wait <seconds> how long tools run and tools get wait for a run (default: ${DEFAULT_WAIT_SECONDS}; 0 returns at once)
|
|
2513
2566
|
--agent <kind> which agent the agent commands use: meta_ads (default) or p3_refresh; in a scheduled hand's run, skill
|
|
2514
2567
|
(the run's skill's agent) is the default
|
|
@@ -2519,11 +2572,11 @@ Options:
|
|
|
2519
2572
|
for warehouse schema, a table to describe in full; repeat for several
|
|
2520
2573
|
--summary warehouse schema: one short row per table (kind, source, column count, summary)
|
|
2521
2574
|
--search <words> warehouse schema: describe in full the tables whose name, description or columns mention a word
|
|
2522
|
-
--out <path> the file, or existing folder, creatives download and warehouse query save to; warehouse query
|
|
2575
|
+
--out <path> the file, or existing folder, creatives download, media download and warehouse query save to; warehouse query
|
|
2523
2576
|
writes CSV for a .csv path, otherwise JSON
|
|
2524
2577
|
--all with warehouse query --out, save the whole answer (up to 1,000,000 rows or 50 MiB) instead of
|
|
2525
2578
|
the first 1,000 rows or 5 MiB; .ndjson or .jsonl saves one JSON row per line
|
|
2526
|
-
--open open the file creatives download saved
|
|
2579
|
+
--open open the file creatives download or media download saved
|
|
2527
2580
|
--media with content pull, also save each item's files and HTML under media/ in the folder
|
|
2528
2581
|
--full with content pull, fetch every item, not only what changed since the last pull (brings new numbers)
|
|
2529
2582
|
--force content unpublish: take the page down even while delivering Meta ads link to it
|
|
@@ -2548,7 +2601,14 @@ Options:
|
|
|
2548
2601
|
--source <text> where the content content capture saves came from, in words
|
|
2549
2602
|
--url <url> the address of the content content capture saves
|
|
2550
2603
|
--tool-run <id> a succeeded tool run content create and edit link; repeat for several
|
|
2551
|
-
--run <id> the agent run making the content (content),
|
|
2604
|
+
--run <id> the agent run making the content (content), the tool run to record (creatives create), or the tool
|
|
2605
|
+
run whose outputs media list shows
|
|
2606
|
+
--sku <sku> with media list, only the output that became this creative
|
|
2607
|
+
--tool <tool> with media list, only this tool's outputs, such as kie:nano-banana
|
|
2608
|
+
--kind image|video with media list, only images or only videos
|
|
2609
|
+
--since, --until <YYYY-MM-DD | ISO time> with media list, made at or after --since and before --until (a day: through
|
|
2610
|
+
its end, in UTC)
|
|
2611
|
+
--cursor <media-id> with media list, the next page: the id it printed after "More"
|
|
2552
2612
|
--platform, --external-id where content status launched says it was published, and that platform's id for it
|
|
2553
2613
|
--object-type <type> what that platform's sync calls it (campaign_message or flow_message on klaviyo); needed where
|
|
2554
2614
|
the sync matches several kinds, so its next read joins the item
|
|
@@ -2580,9 +2640,9 @@ Options:
|
|
|
2580
2640
|
--from, --to <n> with content diff, the two versions to compare (default: the one before the current one, and the current one)
|
|
2581
2641
|
--sort <column> how content search sorts: recent (default) or a content_scorecard number, such as click_rate or roas
|
|
2582
2642
|
--order desc|asc content search's sort direction (default: desc, best or newest first)
|
|
2583
|
-
--limit <n> how many results content search, creatives list, campaigns, voice, ads tags, content tags
|
|
2584
|
-
return, how many ads ads autotag tags and how many items content autotag
|
|
2585
|
-
ads each ads tag-formats batch looks at (default 2000)
|
|
2643
|
+
--limit <n> how many results content search, creatives list, media list, campaigns, voice, ads tags, content tags
|
|
2644
|
+
and content to-check return, how many ads ads autotag tags and how many items content autotag
|
|
2645
|
+
tags, or how many ads each ads tag-formats batch looks at (default 2000)
|
|
2586
2646
|
--library <slug> with knowledge search, only this library: creative-cartel, exodus or meta-policy
|
|
2587
2647
|
--angle, --hook-kind, --ad-format, --awareness <value> with knowledge search, only library entries tagged that way, and
|
|
2588
2648
|
your own ads tagged the same way with their results; with knowledge tag, the tags to put on an entry;
|
|
@@ -2619,6 +2679,7 @@ function parseOptions(argv) {
|
|
|
2619
2679
|
format: { type: "string", default: "table" },
|
|
2620
2680
|
json: { type: "boolean", default: false },
|
|
2621
2681
|
"api-url": { type: "string" },
|
|
2682
|
+
client: { type: "string" },
|
|
2622
2683
|
file: { type: "string", multiple: true },
|
|
2623
2684
|
project: { type: "boolean", default: false },
|
|
2624
2685
|
input: { type: "string" },
|
|
@@ -2648,6 +2709,10 @@ function parseOptions(argv) {
|
|
|
2648
2709
|
yes: { type: "boolean", default: false },
|
|
2649
2710
|
from: { type: "string" },
|
|
2650
2711
|
since: { type: "string" },
|
|
2712
|
+
until: { type: "string" },
|
|
2713
|
+
sku: { type: "string" },
|
|
2714
|
+
tool: { type: "string" },
|
|
2715
|
+
cursor: { type: "string" },
|
|
2651
2716
|
to: { type: "string" },
|
|
2652
2717
|
grain: { type: "string" },
|
|
2653
2718
|
channel: { type: "string" },
|
|
@@ -2727,6 +2792,7 @@ function parseOptions(argv) {
|
|
|
2727
2792
|
options: {
|
|
2728
2793
|
format: values.json ? "json" : values.format,
|
|
2729
2794
|
apiUrl: values["api-url"],
|
|
2795
|
+
client: values.client,
|
|
2730
2796
|
file: values.file?.at(-1),
|
|
2731
2797
|
files: values.file ?? [],
|
|
2732
2798
|
project: values.project,
|
|
@@ -2758,6 +2824,10 @@ function parseOptions(argv) {
|
|
|
2758
2824
|
yes: values.yes,
|
|
2759
2825
|
from: values.from,
|
|
2760
2826
|
since: values.since,
|
|
2827
|
+
until: values.until,
|
|
2828
|
+
sku: values.sku,
|
|
2829
|
+
tool: values.tool,
|
|
2830
|
+
cursor: values.cursor,
|
|
2761
2831
|
to: values.to,
|
|
2762
2832
|
grain: values.grain,
|
|
2763
2833
|
channel: values.channel,
|
|
@@ -2822,7 +2892,7 @@ function parseOptions(argv) {
|
|
|
2822
2892
|
async function changeCandidate(change, args, options) {
|
|
2823
2893
|
if (args.length !== 1 || !options.workspace)
|
|
2824
2894
|
throw new CliError(`Usage: worker ${change} <skill> --workspace <slug>`, exitCodes.usage);
|
|
2825
|
-
return api(await operatorConnection(options
|
|
2895
|
+
return api(await operatorConnection(options), `/api/operator/workers/${change}`, { workspace: options.workspace, skill: args[0] });
|
|
2826
2896
|
}
|
|
2827
2897
|
function findCommand(positionals) {
|
|
2828
2898
|
const three = positionals.slice(0, 3).join(" ");
|
|
@@ -3369,8 +3439,44 @@ async function readImportFile(path) {
|
|
|
3369
3439
|
return readFile(path);
|
|
3370
3440
|
}
|
|
3371
3441
|
const ASSET_ROUTE = "/api/v1/assets/";
|
|
3372
|
-
/**
|
|
3442
|
+
/** Saves a creative's or a made output's file, as `creatives download` and `media download` do. */
|
|
3443
|
+
async function download(arg, options) {
|
|
3444
|
+
const connection = await resolveConnection(options);
|
|
3445
|
+
const target = await downloadTarget(arg, connection);
|
|
3446
|
+
const { bytes, contentType } = await apiDownload(connection, `${ASSET_ROUTE}${target.assetPath.split("/").map(encodeURIComponent).join("/")}`);
|
|
3447
|
+
const path = await savePath(options.out, extname(target.fileName) ? target.fileName : `${target.fileName}.${extensionFor(contentType)}`);
|
|
3448
|
+
await saveFile(path, bytes);
|
|
3449
|
+
if (options.open)
|
|
3450
|
+
openFile(resolve(path));
|
|
3451
|
+
if (options.format === "json")
|
|
3452
|
+
return printJson({ path: resolve(path), bytes: bytes.byteLength, contentType });
|
|
3453
|
+
console.log(`Saved ${target.label} to ${path} (${fileSize(bytes.byteLength)}).`);
|
|
3454
|
+
}
|
|
3455
|
+
const MEDIA_ID = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):(0|[1-9]\d{0,3})$/i;
|
|
3456
|
+
function parseMediaId(raw) {
|
|
3457
|
+
const match = MEDIA_ID.exec(raw);
|
|
3458
|
+
return match ? { runId: match[1].toLowerCase(), index: Number(match[2]) } : null;
|
|
3459
|
+
}
|
|
3460
|
+
// A media id's colon is sent as it is, so the route reads the id whether or not the server decodes the path.
|
|
3461
|
+
function mediaPath(raw) {
|
|
3462
|
+
const id = parseMediaId(raw);
|
|
3463
|
+
if (!id)
|
|
3464
|
+
throw new CliError(`"${excerpt(raw)}" is not a media id; media list prints them as <run id>:<index>.`, exitCodes.usage);
|
|
3465
|
+
return `/api/v1/media/${id.runId}:${id.index}`;
|
|
3466
|
+
}
|
|
3467
|
+
function costLabel(media) {
|
|
3468
|
+
return `${media.costCents}¢${media.costIsEstimate ? " (estimate)" : ""}`;
|
|
3469
|
+
}
|
|
3470
|
+
/** What `creatives download` fetches, from a SKU, a media id, an asset path, or an asset link as `creatives show` prints it. */
|
|
3373
3471
|
async function downloadTarget(arg, connection) {
|
|
3472
|
+
const output = parseMediaId(arg);
|
|
3473
|
+
if (output) {
|
|
3474
|
+
const { media } = await api(connection, mediaPath(arg));
|
|
3475
|
+
if (!media.assetPath)
|
|
3476
|
+
throw new CliError(`${media.id} has no stored file to download.`, exitCodes.error);
|
|
3477
|
+
const name = media.sku ?? `${output.runId}-${output.index}`;
|
|
3478
|
+
return { label: media.sku ?? media.id, assetPath: media.assetPath, fileName: `${name}${extname(media.assetPath)}` };
|
|
3479
|
+
}
|
|
3374
3480
|
const routeAt = arg.indexOf(ASSET_ROUTE);
|
|
3375
3481
|
if (routeAt !== -1 || arg.includes("/")) {
|
|
3376
3482
|
const assetPath = routeAt === -1 ? arg : decodeLinkPath(arg.slice(routeAt + ASSET_ROUTE.length).replace(/[?#].*$/, ""));
|
|
@@ -3571,7 +3677,7 @@ function printIntakeToken({ endpoint, address, token, link, subscription }, form
|
|
|
3571
3677
|
console.error(`The token for ${endpoint.label} is shown only now. Rotate it if it is lost.`);
|
|
3572
3678
|
}
|
|
3573
3679
|
async function patchIntake(id, body, options, done) {
|
|
3574
|
-
const { endpoint } = await api(await resolveConnection(options
|
|
3680
|
+
const { endpoint } = await api(await resolveConnection(options), intakePath(id), body, "PATCH");
|
|
3575
3681
|
if (options.format === "json")
|
|
3576
3682
|
return printJson({ endpoint });
|
|
3577
3683
|
console.log(done(endpoint));
|