@oxygen-agent/cli 1.164.30 → 1.177.1
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/README.md +1 -1
- package/dist/index.js +1195 -507
- package/dist/run-wait.d.ts +23 -0
- package/dist/run-wait.js +57 -0
- package/node_modules/@oxygen/shared/dist/cell-format.d.ts +2 -14
- package/node_modules/@oxygen/shared/dist/cell-format.js +3 -10
- package/node_modules/@oxygen/shared/dist/cli-envelope.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/cli-result.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +52 -0
- package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +1 -1
- package/node_modules/@oxygen/shared/dist/file-import.js +1 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -39
- package/node_modules/@oxygen/shared/dist/index.js +2 -44
- package/node_modules/@oxygen/shared/dist/log.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/log.js +8 -3
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +0 -3
- package/node_modules/@oxygen/shared/dist/object-storage.js +1 -24
- package/node_modules/@oxygen/shared/dist/search-vocab.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/search-vocab.js +151 -0
- package/node_modules/@oxygen/shared/dist/select-options.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/select-options.js +121 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +1 -1
- package/node_modules/@oxygen/shared/dist/sql-error.d.ts +0 -6
- package/node_modules/@oxygen/shared/dist/sql-error.js +67 -58
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/telemetry.js +23 -18
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/worker-failures-queue.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/worker-failures-queue.js +56 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,12 +16,49 @@ import { isRecipeDefinition } from "@oxygen/recipe-sdk";
|
|
|
16
16
|
import { createBrowserLoginSession, openBrowser } from "./browser-login.js";
|
|
17
17
|
import { clearCredentials, defaultApiUrl, listCredentialProfiles, loadCredentials, normalizeApiUrl, pickProfileNameForIdentity, pickProfileNameForUserSession, resolveActiveProfile, saveCredentials, switchCredentialProfile, updateActiveOrganizationForProfile, } from "./credentials.js";
|
|
18
18
|
import { ensureFreshCliForApiUrl, requestOxygen } from "./http-client.js";
|
|
19
|
+
import { waitForCliRun } from "./run-wait.js";
|
|
19
20
|
import { runLocalCustomHttpColumn } from "./local-custom-http-column.js";
|
|
20
21
|
import { captureCurrentTranscript, collectFeedbackEnvironment, TranscriptCaptureError, } from "./transcript.js";
|
|
21
22
|
import { addSessionOutput, addSessionStatus, getSessionUsage, startSession, updateSessionStep, } from "./session.js";
|
|
22
23
|
import { doctorAgentSkills, installAgentSkills, listAgentSkills, runAutomaticSkillsInstall, } from "./skills.js";
|
|
23
24
|
import { resolveCliBinaryName } from "./runtime.js";
|
|
24
25
|
import { updateCli } from "./update.js";
|
|
26
|
+
function buildFindBody(capability, options) {
|
|
27
|
+
const body = { capability };
|
|
28
|
+
const set = (key, value) => {
|
|
29
|
+
if (value)
|
|
30
|
+
body[key] = value;
|
|
31
|
+
};
|
|
32
|
+
if (capability === "company") {
|
|
33
|
+
set("domain", options.domain);
|
|
34
|
+
set("name", options.name);
|
|
35
|
+
set("linkedin_url", options.linkedinUrl);
|
|
36
|
+
if (options.fields) {
|
|
37
|
+
const fields = options.fields.split(",").map((field) => field.trim()).filter(Boolean);
|
|
38
|
+
if (fields.length > 0)
|
|
39
|
+
body.fields = fields;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
set("linkedin_url", options.linkedinUrl);
|
|
44
|
+
set("full_name", options.fullName);
|
|
45
|
+
set("first_name", options.firstName);
|
|
46
|
+
set("last_name", options.lastName);
|
|
47
|
+
set("email", options.email);
|
|
48
|
+
set("company_domain", options.companyDomain);
|
|
49
|
+
set("company_name", options.companyName);
|
|
50
|
+
set("company_linkedin_url", options.companyLinkedinUrl);
|
|
51
|
+
}
|
|
52
|
+
if (options.mode)
|
|
53
|
+
body.mode = options.mode;
|
|
54
|
+
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
55
|
+
if (maxCredits)
|
|
56
|
+
body.max_credits = maxCredits;
|
|
57
|
+
// Phone-only opt-in; the route ignores it for other capabilities.
|
|
58
|
+
if (options.verify)
|
|
59
|
+
body.verify = true;
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
25
62
|
const BROWSER_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
26
63
|
const OXYGEN_SPINNER_INTERVAL_MS = 90;
|
|
27
64
|
const INITIAL_OXYGEN_PROFILE_ENV = process.env.OXYGEN_PROFILE?.trim() || null;
|
|
@@ -54,6 +91,14 @@ const TABLE_INGESTION_WAIT_DEFAULT_TIMEOUT_SECONDS = 600;
|
|
|
54
91
|
const TABLE_INGESTION_WAIT_DEFAULT_INTERVAL_SECONDS = 5;
|
|
55
92
|
const WORKFLOW_TAIL_DEFAULT_TIMEOUT_SECONDS = 600;
|
|
56
93
|
const WORKFLOW_TAIL_DEFAULT_INTERVAL_SECONDS = 2;
|
|
94
|
+
// Cloudflare domain sync returns partial pages under the request budget; the
|
|
95
|
+
// CLI auto-continues up to this many follow-up POSTs before handing back a
|
|
96
|
+
// still-partial summary.
|
|
97
|
+
const DOMAINS_SYNC_MAX_CONTINUATIONS = 20;
|
|
98
|
+
// Cloudflare's domain-check endpoint accepts at most 20 domains per call.
|
|
99
|
+
const DOMAINS_CHECK_MAX_DOMAINS = 20;
|
|
100
|
+
const DOMAIN_REGISTRATION_WAIT_TIMEOUT_SECONDS = 600;
|
|
101
|
+
const DOMAIN_REGISTRATION_WAIT_INTERVAL_SECONDS = 10;
|
|
57
102
|
const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
58
103
|
const RECIPE_ESBUILD_NODE_PATHS = [
|
|
59
104
|
resolve("node_modules"),
|
|
@@ -162,6 +207,33 @@ function parseJsonArray(value) {
|
|
|
162
207
|
}
|
|
163
208
|
return parsed;
|
|
164
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Parse a JSON command-input value, mapping `JSON.parse` syntax failures to a
|
|
212
|
+
* typed `invalid_json` envelope. A raw `JSON.parse` throw is an ordinary `Error`,
|
|
213
|
+
* which `@oxygen/shared`'s `toFailure` flattens to a machine-hostile
|
|
214
|
+
* `unexpected_error`; routing through here keeps malformed user JSON actionable.
|
|
215
|
+
* Shape validation, where a caller needs it, stays with the caller. `inputName`
|
|
216
|
+
* is the user-facing flag or source label, e.g. "--steps-file".
|
|
217
|
+
*/
|
|
218
|
+
function parseJsonValue(value, inputName) {
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(value);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
throw new OxygenError("invalid_json", `${inputName} must be valid JSON.`, {
|
|
224
|
+
details: { reason: error instanceof Error ? error.message : "unknown" },
|
|
225
|
+
exitCode: 1,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Read a local file and parse it as JSON command input, surfacing syntax
|
|
231
|
+
* failures as a typed `invalid_json` envelope tagged with `inputName`. File-read
|
|
232
|
+
* errors (missing/unreadable path) propagate unchanged — they are not JSON.
|
|
233
|
+
*/
|
|
234
|
+
function readJsonFileValue(path, inputName) {
|
|
235
|
+
return parseJsonValue(readFileSync(path, "utf8"), inputName);
|
|
236
|
+
}
|
|
165
237
|
async function readDeleteRowIdsOption(options) {
|
|
166
238
|
const rowIdsJson = readOption(options.rowIdsJson);
|
|
167
239
|
const rowIdsFile = readOption(options.rowIdsFile);
|
|
@@ -284,6 +356,54 @@ function resolveComposioRunMode(options) {
|
|
|
284
356
|
}
|
|
285
357
|
return "dry_run";
|
|
286
358
|
}
|
|
359
|
+
const DEFAULT_CRM_SETUP_OBJECTS = ["companies", "people"];
|
|
360
|
+
function buildCrmSetupBody(options) {
|
|
361
|
+
return {
|
|
362
|
+
objects: readCrmSetupObjects(options.objects),
|
|
363
|
+
mode: resolveCrmSetupMode(options),
|
|
364
|
+
...(readOption(options.project) ? { project: readOption(options.project) } : {}),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function readCrmSetupObjects(value) {
|
|
368
|
+
const objects = readCsvOption(value);
|
|
369
|
+
return objects.length > 0 ? [...new Set(objects)] : DEFAULT_CRM_SETUP_OBJECTS;
|
|
370
|
+
}
|
|
371
|
+
function resolveCrmSetupMode(options) {
|
|
372
|
+
if (options.live === true && options.dryRun === true) {
|
|
373
|
+
throw new OxygenError("conflicting_flags", "Pass either --live or --dry-run, not both.", { exitCode: 1 });
|
|
374
|
+
}
|
|
375
|
+
return options.live === true ? "live" : "dry_run";
|
|
376
|
+
}
|
|
377
|
+
function buildCrmAssertBody(object, options) {
|
|
378
|
+
return {
|
|
379
|
+
object,
|
|
380
|
+
identity: parseCrmIdentityOption(options.identity),
|
|
381
|
+
values: options.valuesJson ? parseJsonObject(options.valuesJson) : {},
|
|
382
|
+
mode: resolveCrmSetupMode(options),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function buildCrmRelationshipUpsertBody(object, rowId, options) {
|
|
386
|
+
return {
|
|
387
|
+
object,
|
|
388
|
+
row_id: rowId,
|
|
389
|
+
relationship: options.relationship,
|
|
390
|
+
target: {
|
|
391
|
+
...(readOption(options.targetObject) ? { object: readOption(options.targetObject) } : {}),
|
|
392
|
+
row_id: options.targetRowId,
|
|
393
|
+
},
|
|
394
|
+
mode: resolveCrmSetupMode(options),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function parseCrmIdentityOption(value) {
|
|
398
|
+
const separator = value.indexOf("=");
|
|
399
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
400
|
+
throw new OxygenError("invalid_identity", "--identity must be formatted as key=value, for example domain=acme.com or email=ceo@acme.com.", { exitCode: 1 });
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
key: value.slice(0, separator).trim(),
|
|
404
|
+
value: value.slice(separator + 1).trim(),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
287
407
|
function readSpecFileBody(path) {
|
|
288
408
|
const text = readFileSync(resolve(path), "utf8");
|
|
289
409
|
try {
|
|
@@ -296,6 +416,88 @@ function readSpecFileBody(path) {
|
|
|
296
416
|
throw error;
|
|
297
417
|
}
|
|
298
418
|
}
|
|
419
|
+
// Builds the `prompts` command tree (list/get/upsert/archive). The
|
|
420
|
+
// deprecated `templates` alias is the identical tree — same subcommands,
|
|
421
|
+
// options, request bodies, and routes — differing only in the parent command
|
|
422
|
+
// name, its description, and the `handleAsyncAction` operation labels, so both
|
|
423
|
+
// surfaces are generated here to stop them drifting (the alias previously
|
|
424
|
+
// duplicated every subcommand). All subcommands hit the shared
|
|
425
|
+
// /api/cli/templates/* routes.
|
|
426
|
+
function buildPromptTemplatesCommand(surface, description) {
|
|
427
|
+
return new Command(surface)
|
|
428
|
+
.description(description)
|
|
429
|
+
.addCommand(new Command("list")
|
|
430
|
+
.description("List prompt templates in the workspace.")
|
|
431
|
+
.option("--kind <kind>", "Filter by ai_column_system, scoring_rubric, or other.")
|
|
432
|
+
.option("--include-archived", "Include archived templates.")
|
|
433
|
+
.option("--json", "Print a JSON envelope.")
|
|
434
|
+
.action(async (options) => {
|
|
435
|
+
await handleAsyncAction(`${surface} list`, options, () => {
|
|
436
|
+
const params = new URLSearchParams();
|
|
437
|
+
const kind = readOption(options.kind);
|
|
438
|
+
if (kind)
|
|
439
|
+
params.set("kind", kind);
|
|
440
|
+
if (options.includeArchived)
|
|
441
|
+
params.set("include_archived", "true");
|
|
442
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
443
|
+
return requestOxygen(`/api/cli/templates${qs}`);
|
|
444
|
+
});
|
|
445
|
+
}))
|
|
446
|
+
.addCommand(new Command("get")
|
|
447
|
+
.description("Read one prompt template by id or slug.")
|
|
448
|
+
.argument("<id_or_slug>", "Template UUID or slug.")
|
|
449
|
+
.option("--json", "Print a JSON envelope.")
|
|
450
|
+
.action(async (idOrSlug, options) => {
|
|
451
|
+
await handleAsyncAction(`${surface} get`, options, () => requestOxygen("/api/cli/templates/get", {
|
|
452
|
+
method: "POST",
|
|
453
|
+
body: idOrSlug.includes("-") && idOrSlug.length >= 32
|
|
454
|
+
? { id: idOrSlug }
|
|
455
|
+
: { slug: idOrSlug },
|
|
456
|
+
}));
|
|
457
|
+
}))
|
|
458
|
+
.addCommand(new Command("upsert")
|
|
459
|
+
.description("Create or update a prompt template.")
|
|
460
|
+
.option("--id <id>", "Existing template UUID to update. Omit to create.")
|
|
461
|
+
.option("--slug <slug>", "Stable slug (kebab-case).")
|
|
462
|
+
.option("--name <name>", "Human-readable name.")
|
|
463
|
+
.option("--description <text>", "Short description.")
|
|
464
|
+
.option("--kind <kind>", "Template kind: ai_column_system, scoring_rubric, or other.")
|
|
465
|
+
.option("--body <text>", "Prompt body.")
|
|
466
|
+
.option("--body-file <path>", "Path to a file containing the prompt body.")
|
|
467
|
+
.option("--json", "Print a JSON envelope.")
|
|
468
|
+
.action(async (options) => {
|
|
469
|
+
await handleAsyncAction(`${surface} upsert`, options, async () => {
|
|
470
|
+
const body = {};
|
|
471
|
+
if (readOption(options.id))
|
|
472
|
+
body.id = readOption(options.id);
|
|
473
|
+
if (readOption(options.slug))
|
|
474
|
+
body.slug = readOption(options.slug);
|
|
475
|
+
if (readOption(options.name))
|
|
476
|
+
body.name = readOption(options.name);
|
|
477
|
+
if (readOption(options.description) !== undefined)
|
|
478
|
+
body.description = readOption(options.description);
|
|
479
|
+
if (readOption(options.kind))
|
|
480
|
+
body.kind = readOption(options.kind);
|
|
481
|
+
if (readOption(options.body))
|
|
482
|
+
body.body = readOption(options.body);
|
|
483
|
+
else {
|
|
484
|
+
const path = readOption(options.bodyFile);
|
|
485
|
+
if (path) {
|
|
486
|
+
const fs = await import("node:fs/promises");
|
|
487
|
+
body.body = await fs.readFile(path, "utf8");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return requestOxygen("/api/cli/templates/upsert", { method: "POST", body });
|
|
491
|
+
});
|
|
492
|
+
}))
|
|
493
|
+
.addCommand(new Command("archive")
|
|
494
|
+
.description("Archive a prompt template. Seeded templates cannot be archived.")
|
|
495
|
+
.argument("<id>", "Template UUID.")
|
|
496
|
+
.option("--json", "Print a JSON envelope.")
|
|
497
|
+
.action(async (id, options) => {
|
|
498
|
+
await handleAsyncAction(`${surface} archive`, options, () => requestOxygen("/api/cli/templates/archive", { method: "POST", body: { id } }));
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
299
501
|
export function createProgram() {
|
|
300
502
|
const program = new Command();
|
|
301
503
|
const binaryName = resolveCliBinaryName();
|
|
@@ -396,8 +598,8 @@ export function createProgram() {
|
|
|
396
598
|
.option("--package <npm_spec>", "Override the npm package spec.")
|
|
397
599
|
.option("--dry-run", "Print the update command without running it.")
|
|
398
600
|
.option("--json", "Print a JSON envelope.")
|
|
399
|
-
.action(
|
|
400
|
-
|
|
601
|
+
.action((options) => {
|
|
602
|
+
handleUpdateAction(options);
|
|
401
603
|
});
|
|
402
604
|
program
|
|
403
605
|
.command("api-keys")
|
|
@@ -406,7 +608,7 @@ export function createProgram() {
|
|
|
406
608
|
.description("List active CLI API keys for the current user and organization.")
|
|
407
609
|
.option("--json", "Print a JSON envelope.")
|
|
408
610
|
.action(async (options) => {
|
|
409
|
-
await handleAsyncAction("api-keys list", options,
|
|
611
|
+
await handleAsyncAction("api-keys list", options, () => requestOxygen("/api/cli/api-keys"));
|
|
410
612
|
}))
|
|
411
613
|
.addCommand(new Command("create")
|
|
412
614
|
.description("Create a CLI API key. The token is shown once.")
|
|
@@ -415,7 +617,7 @@ export function createProgram() {
|
|
|
415
617
|
.option("--expires-in-days <days>", "Expire the key after this many days.")
|
|
416
618
|
.option("--json", "Print a JSON envelope.")
|
|
417
619
|
.action(async (options) => {
|
|
418
|
-
await handleAsyncAction("api-keys create", options,
|
|
620
|
+
await handleAsyncAction("api-keys create", options, () => requestOxygen("/api/cli/api-keys", {
|
|
419
621
|
method: "POST",
|
|
420
622
|
body: buildApiKeyCreateBody(options),
|
|
421
623
|
}));
|
|
@@ -425,7 +627,7 @@ export function createProgram() {
|
|
|
425
627
|
.argument("<key-id>", "CLI API key id.")
|
|
426
628
|
.option("--json", "Print a JSON envelope.")
|
|
427
629
|
.action(async (keyId, options) => {
|
|
428
|
-
await handleAsyncAction("api-keys revoke", options,
|
|
630
|
+
await handleAsyncAction("api-keys revoke", options, () => requestOxygen(`/api/cli/api-keys/${encodeURIComponent(keyId)}`, {
|
|
429
631
|
method: "DELETE",
|
|
430
632
|
}));
|
|
431
633
|
}));
|
|
@@ -489,7 +691,7 @@ export function createProgram() {
|
|
|
489
691
|
.description("List organizations available to the current CLI identity.")
|
|
490
692
|
.option("--json", "Print a JSON envelope.")
|
|
491
693
|
.action(async (options) => {
|
|
492
|
-
await handleAsyncAction("orgs list", options,
|
|
694
|
+
await handleAsyncAction("orgs list", options, () => requestOxygen("/api/cli/orgs"));
|
|
493
695
|
}))
|
|
494
696
|
.addCommand(new Command("use")
|
|
495
697
|
.description("Select the active organization for this CLI profile.")
|
|
@@ -642,20 +844,20 @@ export function createProgram() {
|
|
|
642
844
|
.description("Show the current organization's tenant database status. Staff can pass global --org to inspect another org.")
|
|
643
845
|
.option("--json", "Print a JSON envelope.")
|
|
644
846
|
.action(async (options) => {
|
|
645
|
-
await handleAsyncAction("db status", options,
|
|
847
|
+
await handleAsyncAction("db status", options, () => requestOxygen("/api/cli/db/status"));
|
|
646
848
|
}))
|
|
647
849
|
.addCommand(new Command("provision")
|
|
648
850
|
.description("Provision a managed Neon tenant database for the current organization. Staff can pass global --org to repair another org.")
|
|
649
851
|
.option("--json", "Print a JSON envelope.")
|
|
650
852
|
.action(async (options) => {
|
|
651
|
-
await handleAsyncAction("db provision", options,
|
|
853
|
+
await handleAsyncAction("db provision", options, () => requestOxygen("/api/cli/db/provision", { method: "POST", body: {} }));
|
|
652
854
|
}))
|
|
653
855
|
.addCommand(new Command("migrate")
|
|
654
856
|
.description("Apply pending tenant database migrations for the current organization.")
|
|
655
857
|
.option("--rotate-credentials", "Rotate the tenant runtime/read DB passwords and rewrite stored credentials. Use only to repair a tenant whose stored credentials are out of sync; routine migrations do not need it.")
|
|
656
858
|
.option("--json", "Print a JSON envelope.")
|
|
657
859
|
.action(async (options) => {
|
|
658
|
-
await handleAsyncAction("db migrate", options,
|
|
860
|
+
await handleAsyncAction("db migrate", options, () => requestOxygen("/api/cli/db/migrate", {
|
|
659
861
|
method: "POST",
|
|
660
862
|
body: options.rotateCredentials ? { rotate_credentials: true } : {},
|
|
661
863
|
}));
|
|
@@ -666,7 +868,7 @@ export function createProgram() {
|
|
|
666
868
|
.option("--dry-run", "List pending tenants without applying migrations.")
|
|
667
869
|
.option("--json", "Print a JSON envelope.")
|
|
668
870
|
.action(async (options) => {
|
|
669
|
-
await handleAsyncAction("db migrate-all", options,
|
|
871
|
+
await handleAsyncAction("db migrate-all", options, () => {
|
|
670
872
|
const limit = readPositiveInt(options.limit);
|
|
671
873
|
return requestOxygen("/api/cli/db/migrate-all", {
|
|
672
874
|
method: "POST",
|
|
@@ -742,14 +944,14 @@ export function createProgram() {
|
|
|
742
944
|
.description("Show tenant database cost controls and reconciliation status.")
|
|
743
945
|
.option("--json", "Print a JSON envelope.")
|
|
744
946
|
.action(async (options) => {
|
|
745
|
-
await handleAsyncAction("db cost-policy", options,
|
|
947
|
+
await handleAsyncAction("db cost-policy", options, () => requestOxygen("/api/cli/db/cost-policy"));
|
|
746
948
|
}))
|
|
747
949
|
.addCommand(new Command("reconcile-cost-policy")
|
|
748
950
|
.description("Apply tenant database cost controls to the managed Neon endpoint.")
|
|
749
951
|
.option("--suspend-idle", "Request immediate Neon compute suspension when the tenant is idle.")
|
|
750
952
|
.option("--json", "Print a JSON envelope.")
|
|
751
953
|
.action(async (options) => {
|
|
752
|
-
await handleAsyncAction("db reconcile-cost-policy", options,
|
|
954
|
+
await handleAsyncAction("db reconcile-cost-policy", options, () => requestOxygen("/api/cli/db/reconcile-cost-policy", {
|
|
753
955
|
method: "POST",
|
|
754
956
|
body: {
|
|
755
957
|
...(options.suspendIdle ? { suspend_idle: true } : {}),
|
|
@@ -761,7 +963,7 @@ export function createProgram() {
|
|
|
761
963
|
.requiredOption("--database-url <url>", "Owner connection string for the tenant Postgres database.")
|
|
762
964
|
.option("--json", "Print a JSON envelope.")
|
|
763
965
|
.action(async (options) => {
|
|
764
|
-
await handleAsyncAction("db attach", options,
|
|
966
|
+
await handleAsyncAction("db attach", options, () => requestOxygen("/api/cli/db/attach", {
|
|
765
967
|
method: "POST",
|
|
766
968
|
body: { database_url: options.databaseUrl },
|
|
767
969
|
}));
|
|
@@ -772,7 +974,7 @@ export function createProgram() {
|
|
|
772
974
|
.option("--max-rows <n>", "Maximum rows to return. Defaults to 100; hard cap is 1000.")
|
|
773
975
|
.option("--json", "Print a JSON envelope.")
|
|
774
976
|
.action(async (options) => {
|
|
775
|
-
await handleAsyncAction("db query", options,
|
|
977
|
+
await handleAsyncAction("db query", options, () => {
|
|
776
978
|
const maxRows = readPositiveInt(options.maxRows);
|
|
777
979
|
return requestOxygen("/api/cli/db/query", {
|
|
778
980
|
method: "POST",
|
|
@@ -790,18 +992,87 @@ export function createProgram() {
|
|
|
790
992
|
.description("List table projects in the current tenant database.")
|
|
791
993
|
.option("--json", "Print a JSON envelope.")
|
|
792
994
|
.action(async (options) => {
|
|
793
|
-
await handleAsyncAction("projects list", options,
|
|
995
|
+
await handleAsyncAction("projects list", options, () => requestOxygen("/api/cli/projects"));
|
|
794
996
|
}))
|
|
795
997
|
.addCommand(new Command("create")
|
|
796
998
|
.description("Create a schema-backed table project.")
|
|
797
999
|
.argument("<name>", "Display name for the project.")
|
|
798
1000
|
.option("--json", "Print a JSON envelope.")
|
|
799
1001
|
.action(async (name, options) => {
|
|
800
|
-
await handleAsyncAction("projects create", options,
|
|
1002
|
+
await handleAsyncAction("projects create", options, () => requestOxygen("/api/cli/projects", {
|
|
801
1003
|
method: "POST",
|
|
802
1004
|
body: { name },
|
|
803
1005
|
}));
|
|
804
1006
|
}));
|
|
1007
|
+
program
|
|
1008
|
+
.command("crm")
|
|
1009
|
+
.description("Agent-native CRM object setup and metadata commands.")
|
|
1010
|
+
.addCommand(new Command("setup")
|
|
1011
|
+
.description("Create or repair standard CRM object-backed tables. Defaults to dry-run.")
|
|
1012
|
+
.option("--objects <objects>", "Comma-separated standard CRM objects to set up. Defaults to companies,people.")
|
|
1013
|
+
.option("--project <project>", "Project id or slug for created CRM tables.")
|
|
1014
|
+
.option("--dry-run", "Preview CRM setup without creating or repairing tables.")
|
|
1015
|
+
.option("--live", "Apply CRM setup changes. Default is dry-run.")
|
|
1016
|
+
.option("--json", "Print a JSON envelope.")
|
|
1017
|
+
.action(async (options) => {
|
|
1018
|
+
await handleAsyncAction("crm setup", options, () => requestOxygen("/api/cli/crm/setup", {
|
|
1019
|
+
method: "POST",
|
|
1020
|
+
body: buildCrmSetupBody(options),
|
|
1021
|
+
}));
|
|
1022
|
+
}))
|
|
1023
|
+
.addCommand(new Command("objects")
|
|
1024
|
+
.description("List configured CRM objects in the current tenant database.")
|
|
1025
|
+
.option("--json", "Print a JSON envelope.")
|
|
1026
|
+
.action(async (options) => {
|
|
1027
|
+
await handleAsyncAction("crm objects", options, () => requestOxygen("/api/cli/crm/objects"));
|
|
1028
|
+
}))
|
|
1029
|
+
.addCommand(new Command("assert")
|
|
1030
|
+
.description("Create or update one CRM record by object identity. Defaults to dry-run.")
|
|
1031
|
+
.argument("<object>", "CRM object slug, such as companies or people.")
|
|
1032
|
+
.requiredOption("--identity <key=value>", "Identity key/value, for example domain=acme.com or email=ceo@acme.com.")
|
|
1033
|
+
.option("--values-json <json>", "JSON object of CRM attribute values keyed by column key.")
|
|
1034
|
+
.option("--dry-run", "Preview the assert without writing a row.")
|
|
1035
|
+
.option("--live", "Apply the assert. Default is dry-run.")
|
|
1036
|
+
.option("--json", "Print a JSON envelope.")
|
|
1037
|
+
.action(async (object, options) => {
|
|
1038
|
+
await handleAsyncAction("crm assert", options, () => requestOxygen("/api/cli/crm/assert", {
|
|
1039
|
+
method: "POST",
|
|
1040
|
+
body: buildCrmAssertBody(object, options),
|
|
1041
|
+
}));
|
|
1042
|
+
}))
|
|
1043
|
+
.addCommand(new Command("get")
|
|
1044
|
+
.description("Get one CRM record aggregate with row values, identities, and relationships.")
|
|
1045
|
+
.argument("<object>", "CRM object slug, such as companies or people.")
|
|
1046
|
+
.argument("<row_id>", "CRM record row id.")
|
|
1047
|
+
.option("--json", "Print a JSON envelope.")
|
|
1048
|
+
.action(async (object, rowId, options) => {
|
|
1049
|
+
await handleAsyncAction("crm get", options, () => requestOxygen(`/api/cli/crm/objects/${encodeURIComponent(object)}/records/${encodeURIComponent(rowId)}`));
|
|
1050
|
+
}))
|
|
1051
|
+
.addCommand(new Command("relationships")
|
|
1052
|
+
.description("Manage CRM record relationships.")
|
|
1053
|
+
.addCommand(new Command("upsert")
|
|
1054
|
+
.description("Create or replace a CRM relationship edge. Defaults to dry-run.")
|
|
1055
|
+
.argument("<object>", "Source CRM object slug, such as companies or people.")
|
|
1056
|
+
.argument("<row_id>", "Source CRM record row id.")
|
|
1057
|
+
.requiredOption("--relationship <slug>", "Relationship slug from the source object, such as team or company.")
|
|
1058
|
+
.option("--target-object <object>", "Target CRM object slug. Optional when the relationship has one target.")
|
|
1059
|
+
.requiredOption("--target-row-id <row_id>", "Target CRM record row id.")
|
|
1060
|
+
.option("--dry-run", "Preview the relationship write without changing edges.")
|
|
1061
|
+
.option("--live", "Apply the relationship write. Default is dry-run.")
|
|
1062
|
+
.option("--json", "Print a JSON envelope.")
|
|
1063
|
+
.action(async (object, rowId, options) => {
|
|
1064
|
+
await handleAsyncAction("crm relationships upsert", options, () => requestOxygen("/api/cli/crm/relationships/upsert", {
|
|
1065
|
+
method: "POST",
|
|
1066
|
+
body: buildCrmRelationshipUpsertBody(object, rowId, options),
|
|
1067
|
+
}));
|
|
1068
|
+
})))
|
|
1069
|
+
.addCommand(new Command("describe")
|
|
1070
|
+
.description("Describe one configured CRM object.")
|
|
1071
|
+
.argument("<object>", "CRM object slug, such as companies or people.")
|
|
1072
|
+
.option("--json", "Print a JSON envelope.")
|
|
1073
|
+
.action(async (object, options) => {
|
|
1074
|
+
await handleAsyncAction("crm describe", options, () => requestOxygen(`/api/cli/crm/objects/${encodeURIComponent(object)}`));
|
|
1075
|
+
}));
|
|
805
1076
|
const tablesCommand = program
|
|
806
1077
|
.command("tables")
|
|
807
1078
|
.description("Tenant workspace table commands.")
|
|
@@ -812,7 +1083,7 @@ export function createProgram() {
|
|
|
812
1083
|
.option("--project <project>", "Project id or slug. Defaults to General.")
|
|
813
1084
|
.option("--json", "Print a JSON envelope.")
|
|
814
1085
|
.action(async (name, options) => {
|
|
815
|
-
await handleAsyncAction("tables create", options,
|
|
1086
|
+
await handleAsyncAction("tables create", options, () => requestOxygen("/api/cli/tables", {
|
|
816
1087
|
method: "POST",
|
|
817
1088
|
body: {
|
|
818
1089
|
name,
|
|
@@ -828,7 +1099,7 @@ export function createProgram() {
|
|
|
828
1099
|
.option("--schema-only", "Duplicate columns and definitions without copying row values.")
|
|
829
1100
|
.option("--json", "Print a JSON envelope.")
|
|
830
1101
|
.action(async (table, options) => {
|
|
831
|
-
await handleAsyncAction("tables duplicate", options,
|
|
1102
|
+
await handleAsyncAction("tables duplicate", options, () => requestOxygen("/api/cli/tables/duplicate", {
|
|
832
1103
|
method: "POST",
|
|
833
1104
|
body: {
|
|
834
1105
|
table,
|
|
@@ -843,7 +1114,7 @@ export function createProgram() {
|
|
|
843
1114
|
.requiredOption("--rows-json <json>", "JSON array of row objects keyed by column key.")
|
|
844
1115
|
.option("--json", "Print a JSON envelope.")
|
|
845
1116
|
.action(async (table, options) => {
|
|
846
|
-
await handleAsyncAction("tables insert", options,
|
|
1117
|
+
await handleAsyncAction("tables insert", options, () => requestOxygen("/api/cli/tables/rows", {
|
|
847
1118
|
method: "POST",
|
|
848
1119
|
body: {
|
|
849
1120
|
table,
|
|
@@ -858,7 +1129,7 @@ export function createProgram() {
|
|
|
858
1129
|
.requiredOption("--values-json <json>", "JSON object of values keyed by column key.")
|
|
859
1130
|
.option("--json", "Print a JSON envelope.")
|
|
860
1131
|
.action(async (table, rowId, options) => {
|
|
861
|
-
await handleAsyncAction("tables update", options,
|
|
1132
|
+
await handleAsyncAction("tables update", options, () => requestOxygen("/api/cli/tables/rows/update", {
|
|
862
1133
|
method: "POST",
|
|
863
1134
|
body: {
|
|
864
1135
|
table,
|
|
@@ -873,7 +1144,7 @@ export function createProgram() {
|
|
|
873
1144
|
.argument("<row_id>", "Workspace row UUID.")
|
|
874
1145
|
.option("--json", "Print a JSON envelope.")
|
|
875
1146
|
.action(async (table, rowId, options) => {
|
|
876
|
-
await handleAsyncAction("tables delete-row", options,
|
|
1147
|
+
await handleAsyncAction("tables delete-row", options, () => requestOxygen("/api/cli/tables/rows/delete", {
|
|
877
1148
|
method: "POST",
|
|
878
1149
|
body: {
|
|
879
1150
|
table,
|
|
@@ -911,7 +1182,7 @@ export function createProgram() {
|
|
|
911
1182
|
.option("--dry-run", "Preview inserts, updates, duplicate keys, and field conflicts without writing rows.")
|
|
912
1183
|
.option("--json", "Print a JSON envelope.")
|
|
913
1184
|
.action(async (table, options) => {
|
|
914
|
-
await handleAsyncAction("tables upsert", options,
|
|
1185
|
+
await handleAsyncAction("tables upsert", options, () => requestOxygen("/api/cli/tables/rows/upsert", {
|
|
915
1186
|
method: "POST",
|
|
916
1187
|
body: {
|
|
917
1188
|
table,
|
|
@@ -937,7 +1208,7 @@ export function createProgram() {
|
|
|
937
1208
|
.option("--max-concurrency <n>", "Maximum concurrent import chunks for background mode. Defaults to 5.")
|
|
938
1209
|
.option("--json", "Print a JSON envelope.")
|
|
939
1210
|
.action(async (table, options) => {
|
|
940
|
-
await handleAsyncAction("tables import", options,
|
|
1211
|
+
await handleAsyncAction("tables import", options, () => importRows(table, options));
|
|
941
1212
|
}))
|
|
942
1213
|
.addCommand(new Command("export")
|
|
943
1214
|
.description("Export workspace table rows as JSON, JSONL, CSV, or a human-readable table.")
|
|
@@ -947,7 +1218,7 @@ export function createProgram() {
|
|
|
947
1218
|
.option("--limit <n>", "Maximum rows to export. Defaults to 100; hard cap is 1000.")
|
|
948
1219
|
.option("--json", "Print a JSON envelope.")
|
|
949
1220
|
.action(async (table, options) => {
|
|
950
|
-
await handleAsyncAction("tables export", options,
|
|
1221
|
+
await handleAsyncAction("tables export", options, () => exportRows(table, options));
|
|
951
1222
|
}))
|
|
952
1223
|
.addCommand(new Command("export-bundle")
|
|
953
1224
|
.description("Export a workspace table as a portable bundle (schema + every row) for cross-environment / cross-org copies.")
|
|
@@ -956,7 +1227,7 @@ export function createProgram() {
|
|
|
956
1227
|
.option("--page-size <n>", "Rows per cursor-paginated request. Defaults to 500; hard cap is 1000.")
|
|
957
1228
|
.option("--json", "Print a JSON envelope (omit row payload — use --output to keep the rows).")
|
|
958
1229
|
.action(async (table, options) => {
|
|
959
|
-
await handleAsyncAction("tables export-bundle", options,
|
|
1230
|
+
await handleAsyncAction("tables export-bundle", options, () => exportTableBundle(table, options));
|
|
960
1231
|
}))
|
|
961
1232
|
.addCommand(new Command("import-bundle")
|
|
962
1233
|
.description("Recreate a workspace table from an export-bundle file in this org. Restores columns (incl. enrichment/tool definitions) and inserts every row. Pass --key to make the import idempotent (re-runnable), and --into to resume a failed import into the table it already created.")
|
|
@@ -968,14 +1239,14 @@ export function createProgram() {
|
|
|
968
1239
|
.option("--batch-size <n>", "Rows per write request. Defaults to 500; paid orgs may use up to 5000.")
|
|
969
1240
|
.option("--json", "Print a JSON envelope.")
|
|
970
1241
|
.action(async (options) => {
|
|
971
|
-
await handleAsyncAction("tables import-bundle", options,
|
|
1242
|
+
await handleAsyncAction("tables import-bundle", options, () => importTableBundle(options));
|
|
972
1243
|
}))
|
|
973
1244
|
.addCommand(new Command("list")
|
|
974
1245
|
.description("List workspace tables in the current tenant database.")
|
|
975
1246
|
.option("--project <project>", "Project id or slug to filter by.")
|
|
976
1247
|
.option("--json", "Print a JSON envelope.")
|
|
977
1248
|
.action(async (options) => {
|
|
978
|
-
await handleAsyncAction("tables list", options,
|
|
1249
|
+
await handleAsyncAction("tables list", options, () => requestOxygen(readOption(options.project)
|
|
979
1250
|
? `/api/cli/tables?project=${encodeURIComponent(readOption(options.project))}`
|
|
980
1251
|
: "/api/cli/tables"));
|
|
981
1252
|
}))
|
|
@@ -985,12 +1256,20 @@ export function createProgram() {
|
|
|
985
1256
|
.option("--limit <n>", "Maximum rows to return. Defaults to 100; hard cap is 1000.")
|
|
986
1257
|
.option("--cursor <cursor>", "Pagination cursor returned by a previous query.")
|
|
987
1258
|
.option("--fields <columns>", "Comma-separated column keys or ids to include.")
|
|
988
|
-
.option("--filter-json <json>", "
|
|
1259
|
+
.option("--filter-json <json>", "Legacy filter object or array, e.g. '{\"column\":\"mobile_phone_e164\",\"op\":\"is_null\"}'. Mutually exclusive with --filter-tree-json/--sort-json.")
|
|
1260
|
+
.option("--filter-tree-json <json>", "Airtable-style filter group, e.g. '{\"type\":\"group\",\"conjunction\":\"and\",\"children\":[{\"type\":\"leaf\",\"columnKey\":\"stage\",\"operator\":\"is\",\"value\":\"won\"}]}'. Mutually exclusive with --filter-json.")
|
|
1261
|
+
.option("--sort-json <json>", "Ordered sort rules, e.g. '[{\"columnKey\":\"created_at\",\"direction\":\"desc\"}]'. Earlier rules dominate. Mutually exclusive with --filter-json.")
|
|
1262
|
+
.option("--no-system-fields", "Omit _row_id, _created_at, and _updated_at from returned rows (included by default).")
|
|
989
1263
|
.option("--json", "Print a JSON envelope.")
|
|
990
1264
|
.action(async (table, options) => {
|
|
991
|
-
await handleAsyncAction("tables query", options,
|
|
1265
|
+
await handleAsyncAction("tables query", options, () => {
|
|
992
1266
|
const limit = readPositiveInt(options.limit);
|
|
993
1267
|
const filters = readFilterJsonOption(options.filterJson);
|
|
1268
|
+
const filterTree = readFilterTreeJsonOption(options.filterTreeJson);
|
|
1269
|
+
const sorts = readSortJsonOption(options.sortJson);
|
|
1270
|
+
if (filters && (filterTree || sorts)) {
|
|
1271
|
+
throw new OxygenError("invalid_filter", "Pass either --filter-json (legacy) or --filter-tree-json/--sort-json, not both.", { exitCode: 1 });
|
|
1272
|
+
}
|
|
994
1273
|
return requestOxygen("/api/cli/tables/query", {
|
|
995
1274
|
method: "POST",
|
|
996
1275
|
body: {
|
|
@@ -999,6 +1278,9 @@ export function createProgram() {
|
|
|
999
1278
|
...(readOption(options.cursor) ? { cursor: readOption(options.cursor) } : {}),
|
|
1000
1279
|
...(readOption(options.fields) ? { fields: readCsvOption(options.fields) } : {}),
|
|
1001
1280
|
...(filters ? { filters } : {}),
|
|
1281
|
+
...(filterTree ? { filterTree } : {}),
|
|
1282
|
+
...(sorts ? { sorts } : {}),
|
|
1283
|
+
...(options.systemFields === false ? { include_system_fields: false } : {}),
|
|
1002
1284
|
},
|
|
1003
1285
|
});
|
|
1004
1286
|
});
|
|
@@ -1034,13 +1316,27 @@ export function createProgram() {
|
|
|
1034
1316
|
.option("--include-archived", "Include archived columns.")
|
|
1035
1317
|
.option("--json", "Print a JSON envelope.")
|
|
1036
1318
|
.action(async (table, options) => {
|
|
1037
|
-
await handleAsyncAction("tables describe", options,
|
|
1319
|
+
await handleAsyncAction("tables describe", options, () => requestOxygen("/api/cli/tables/describe", {
|
|
1038
1320
|
method: "POST",
|
|
1039
1321
|
body: {
|
|
1040
1322
|
table,
|
|
1041
1323
|
...(options.includeArchived ? { include_archived: true } : {}),
|
|
1042
1324
|
},
|
|
1043
1325
|
}));
|
|
1326
|
+
}))
|
|
1327
|
+
.addCommand(new Command("activity")
|
|
1328
|
+
.description("Show background runs active on a workspace table right now, with a worker-queue health rollup.")
|
|
1329
|
+
.argument("<table>", "Table id or slug.")
|
|
1330
|
+
.option("--limit <n>", "Maximum active runs to return (1-100). Defaults to 20.")
|
|
1331
|
+
.option("--json", "Print a JSON envelope.")
|
|
1332
|
+
.action(async (table, options) => {
|
|
1333
|
+
await handleAsyncAction("tables activity", options, () => {
|
|
1334
|
+
const params = new URLSearchParams({ table });
|
|
1335
|
+
const limit = readPositiveInt(options.limit);
|
|
1336
|
+
if (limit)
|
|
1337
|
+
params.set("limit", String(limit));
|
|
1338
|
+
return requestOxygen(`/api/cli/tables/activity?${params.toString()}`);
|
|
1339
|
+
});
|
|
1044
1340
|
}))
|
|
1045
1341
|
.addCommand(new Command("rename")
|
|
1046
1342
|
.description("Rename a workspace table display name and slug.")
|
|
@@ -1048,7 +1344,7 @@ export function createProgram() {
|
|
|
1048
1344
|
.requiredOption("--name <name>", "New table display name.")
|
|
1049
1345
|
.option("--json", "Print a JSON envelope.")
|
|
1050
1346
|
.action(async (table, options) => {
|
|
1051
|
-
await handleAsyncAction("tables rename", options,
|
|
1347
|
+
await handleAsyncAction("tables rename", options, () => requestOxygen("/api/cli/tables/rename", {
|
|
1052
1348
|
method: "POST",
|
|
1053
1349
|
body: { table, name: options.name },
|
|
1054
1350
|
}));
|
|
@@ -1058,7 +1354,7 @@ export function createProgram() {
|
|
|
1058
1354
|
.argument("<table>", "Table id or slug.")
|
|
1059
1355
|
.option("--json", "Print a JSON envelope.")
|
|
1060
1356
|
.action(async (table, options) => {
|
|
1061
|
-
await handleAsyncAction("tables archive", options,
|
|
1357
|
+
await handleAsyncAction("tables archive", options, () => requestOxygen("/api/cli/tables/archive", {
|
|
1062
1358
|
method: "POST",
|
|
1063
1359
|
body: { table },
|
|
1064
1360
|
}));
|
|
@@ -1069,7 +1365,7 @@ export function createProgram() {
|
|
|
1069
1365
|
.requiredOption("--project <project>", "Destination project id or slug.")
|
|
1070
1366
|
.option("--json", "Print a JSON envelope.")
|
|
1071
1367
|
.action(async (table, options) => {
|
|
1072
|
-
await handleAsyncAction("tables move", options,
|
|
1368
|
+
await handleAsyncAction("tables move", options, () => requestOxygen("/api/cli/tables/move", {
|
|
1073
1369
|
method: "POST",
|
|
1074
1370
|
body: { table, project: options.project },
|
|
1075
1371
|
}));
|
|
@@ -1080,7 +1376,7 @@ export function createProgram() {
|
|
|
1080
1376
|
.option("--max-items <n>", "Max pending cache rows to poll in this run. Defaults to 1000.")
|
|
1081
1377
|
.option("--json", "Print a JSON envelope.")
|
|
1082
1378
|
.action(async (table, options) => {
|
|
1083
|
-
await handleAsyncAction("tables recover-pending", options,
|
|
1379
|
+
await handleAsyncAction("tables recover-pending", options, () => {
|
|
1084
1380
|
const maxItems = options.maxItems ? Number.parseInt(options.maxItems, 10) : undefined;
|
|
1085
1381
|
return requestOxygen("/api/cli/tables/recover-pending", {
|
|
1086
1382
|
method: "POST",
|
|
@@ -1196,6 +1492,69 @@ export function createProgram() {
|
|
|
1196
1492
|
autoRunStatus: options.autoRunStatus,
|
|
1197
1493
|
limit: options.limit,
|
|
1198
1494
|
}))))));
|
|
1495
|
+
tablesCommand.addCommand(new Command("views")
|
|
1496
|
+
.description("Create and manage saved table views (grid / kanban configurations).")
|
|
1497
|
+
.addCommand(new Command("list")
|
|
1498
|
+
.description("List a table's saved views.")
|
|
1499
|
+
.argument("<table>", "Table id or slug.")
|
|
1500
|
+
.option("--json", "Print a JSON envelope.")
|
|
1501
|
+
.action((table, options) => handleAsyncAction("tables views list", options, () => requestOxygen(`/api/cli/tables/views?table=${encodeURIComponent(table)}`))))
|
|
1502
|
+
.addCommand(new Command("get")
|
|
1503
|
+
.description("Get one saved view by id.")
|
|
1504
|
+
.argument("<table>", "Table id or slug.")
|
|
1505
|
+
.argument("<view>", "View id.")
|
|
1506
|
+
.option("--json", "Print a JSON envelope.")
|
|
1507
|
+
.action((table, view, options) => handleAsyncAction("tables views get", options, () => requestOxygen(`/api/cli/tables/views?table=${encodeURIComponent(table)}&view=${encodeURIComponent(view)}`))))
|
|
1508
|
+
.addCommand(new Command("create")
|
|
1509
|
+
.description("Create a saved view.")
|
|
1510
|
+
.argument("<table>", "Table id or slug.")
|
|
1511
|
+
.requiredOption("--name <name>", "View name.")
|
|
1512
|
+
.option("--view-type <type>", "table or kanban. Defaults to table.")
|
|
1513
|
+
.option("--config <json>", "Advanced: raw JSON view config (filters, sorts, columns, group). Usually written by the app.")
|
|
1514
|
+
.option("--default", "Make this the table's default view.")
|
|
1515
|
+
.option("--json", "Print a JSON envelope.")
|
|
1516
|
+
.action((table, options) => handleAsyncAction("tables views create", options, () => requestOxygen("/api/cli/tables/views", {
|
|
1517
|
+
method: "POST",
|
|
1518
|
+
body: {
|
|
1519
|
+
table,
|
|
1520
|
+
name: readOption(options.name),
|
|
1521
|
+
...(readOption(options.viewType) ? { view_type: readOption(options.viewType) } : {}),
|
|
1522
|
+
...(readOption(options.config)
|
|
1523
|
+
? { config: parseJsonValue(readOption(options.config) ?? "", "--config") }
|
|
1524
|
+
: {}),
|
|
1525
|
+
...(options.default ? { is_default: true } : {}),
|
|
1526
|
+
},
|
|
1527
|
+
}))))
|
|
1528
|
+
.addCommand(new Command("update")
|
|
1529
|
+
.description("Update a saved view.")
|
|
1530
|
+
.argument("<table>", "Table id or slug.")
|
|
1531
|
+
.argument("<view>", "View id.")
|
|
1532
|
+
.option("--name <name>", "Rename the view.")
|
|
1533
|
+
.option("--view-type <type>", "table or kanban.")
|
|
1534
|
+
.option("--config <json>", "Advanced: raw JSON view config. Replaces the stored config.")
|
|
1535
|
+
.option("--default", "Make this the table's default view.")
|
|
1536
|
+
.option("--position <n>", "0-based order among the table's views.")
|
|
1537
|
+
.option("--json", "Print a JSON envelope.")
|
|
1538
|
+
.action((table, view, options) => handleAsyncAction("tables views update", options, () => requestOxygen("/api/cli/tables/views", {
|
|
1539
|
+
method: "PATCH",
|
|
1540
|
+
body: {
|
|
1541
|
+
table,
|
|
1542
|
+
view,
|
|
1543
|
+
...(readOption(options.name) ? { name: readOption(options.name) } : {}),
|
|
1544
|
+
...(readOption(options.viewType) ? { view_type: readOption(options.viewType) } : {}),
|
|
1545
|
+
...(readOption(options.config)
|
|
1546
|
+
? { config: parseJsonValue(readOption(options.config) ?? "", "--config") }
|
|
1547
|
+
: {}),
|
|
1548
|
+
...(options.default ? { is_default: true } : {}),
|
|
1549
|
+
...(readOption(options.position) ? { position: Number(readOption(options.position)) } : {}),
|
|
1550
|
+
},
|
|
1551
|
+
}))))
|
|
1552
|
+
.addCommand(new Command("delete")
|
|
1553
|
+
.description("Delete a saved view by id.")
|
|
1554
|
+
.argument("<table>", "Table id or slug.")
|
|
1555
|
+
.argument("<view>", "View id.")
|
|
1556
|
+
.option("--json", "Print a JSON envelope.")
|
|
1557
|
+
.action((table, view, options) => handleAsyncAction("tables views delete", options, () => requestOxygen(`/api/cli/tables/views?table=${encodeURIComponent(table)}&view=${encodeURIComponent(view)}`, { method: "DELETE" })))));
|
|
1199
1558
|
program
|
|
1200
1559
|
.command("context")
|
|
1201
1560
|
.description("Workspace-level GTM context commands.")
|
|
@@ -1210,7 +1569,7 @@ export function createProgram() {
|
|
|
1210
1569
|
.option("--require-ready", "Exit with a conflict error when required context sections are missing.")
|
|
1211
1570
|
.option("--json", "Print a JSON envelope.")
|
|
1212
1571
|
.action(async (options) => {
|
|
1213
|
-
await handleAsyncAction("context resolve", options,
|
|
1572
|
+
await handleAsyncAction("context resolve", options, () => requestOxygen("/api/cli/context/resolve", {
|
|
1214
1573
|
method: "POST",
|
|
1215
1574
|
body: buildContextResolveBody(options),
|
|
1216
1575
|
}));
|
|
@@ -1221,7 +1580,7 @@ export function createProgram() {
|
|
|
1221
1580
|
.description("Read the current workspace company profile.")
|
|
1222
1581
|
.option("--json", "Print a JSON envelope.")
|
|
1223
1582
|
.action(async (options) => {
|
|
1224
|
-
await handleAsyncAction("context profile get", options,
|
|
1583
|
+
await handleAsyncAction("context profile get", options, () => requestOxygen("/api/cli/context/profile"));
|
|
1225
1584
|
}))
|
|
1226
1585
|
.addCommand(new Command("update")
|
|
1227
1586
|
.description("Merge one or more profile sections into workspace GTM memory.")
|
|
@@ -1229,7 +1588,7 @@ export function createProgram() {
|
|
|
1229
1588
|
.option("--summary <text>", "Optional concise summary for the profile.")
|
|
1230
1589
|
.option("--json", "Print a JSON envelope.")
|
|
1231
1590
|
.action(async (options) => {
|
|
1232
|
-
await handleAsyncAction("context profile update", options,
|
|
1591
|
+
await handleAsyncAction("context profile update", options, () => requestOxygen("/api/cli/context/profile/update", {
|
|
1233
1592
|
method: "POST",
|
|
1234
1593
|
body: {
|
|
1235
1594
|
data: parseJsonObject(options.dataJson ?? "{}"),
|
|
@@ -1247,14 +1606,14 @@ export function createProgram() {
|
|
|
1247
1606
|
.option("--include-archived", "Include archived assets when no status filter is set.")
|
|
1248
1607
|
.option("--json", "Print a JSON envelope.")
|
|
1249
1608
|
.action(async (options) => {
|
|
1250
|
-
await handleAsyncAction("context assets list", options,
|
|
1609
|
+
await handleAsyncAction("context assets list", options, () => requestOxygen(`/api/cli/context/assets${contextAssetsQuery(options)}`));
|
|
1251
1610
|
}))
|
|
1252
1611
|
.addCommand(new Command("get")
|
|
1253
1612
|
.description("Read one context asset.")
|
|
1254
1613
|
.argument("<asset_id>", "Context asset UUID.")
|
|
1255
1614
|
.option("--json", "Print a JSON envelope.")
|
|
1256
1615
|
.action(async (assetId, options) => {
|
|
1257
|
-
await handleAsyncAction("context asset get", options,
|
|
1616
|
+
await handleAsyncAction("context asset get", options, () => requestOxygen("/api/cli/context/assets/get", {
|
|
1258
1617
|
method: "POST",
|
|
1259
1618
|
body: { id: assetId },
|
|
1260
1619
|
}));
|
|
@@ -1272,7 +1631,7 @@ export function createProgram() {
|
|
|
1272
1631
|
.option("--asset-json <json>", "Full asset JSON object. CLI flags override matching fields.")
|
|
1273
1632
|
.option("--json", "Print a JSON envelope.")
|
|
1274
1633
|
.action(async (options) => {
|
|
1275
|
-
await handleAsyncAction("context asset upsert", options,
|
|
1634
|
+
await handleAsyncAction("context asset upsert", options, () => requestOxygen("/api/cli/context/assets/upsert", {
|
|
1276
1635
|
method: "POST",
|
|
1277
1636
|
body: buildContextAssetUpsertBody(options),
|
|
1278
1637
|
}));
|
|
@@ -1282,7 +1641,7 @@ export function createProgram() {
|
|
|
1282
1641
|
.argument("<asset_id>", "Context asset UUID.")
|
|
1283
1642
|
.option("--json", "Print a JSON envelope.")
|
|
1284
1643
|
.action(async (assetId, options) => {
|
|
1285
|
-
await handleAsyncAction("context asset archive", options,
|
|
1644
|
+
await handleAsyncAction("context asset archive", options, () => requestOxygen("/api/cli/context/assets/archive", {
|
|
1286
1645
|
method: "POST",
|
|
1287
1646
|
body: { id: assetId },
|
|
1288
1647
|
}));
|
|
@@ -1336,10 +1695,10 @@ export function createProgram() {
|
|
|
1336
1695
|
const body = { workflow_id: options.workflow };
|
|
1337
1696
|
const tables = readOption(options.tables);
|
|
1338
1697
|
if (tables)
|
|
1339
|
-
body.table_ids =
|
|
1698
|
+
body.table_ids = readCsvOption(tables);
|
|
1340
1699
|
const prompts = readOption(options.prompts);
|
|
1341
1700
|
if (prompts)
|
|
1342
|
-
body.prompt_slugs =
|
|
1701
|
+
body.prompt_slugs = readCsvOption(prompts);
|
|
1343
1702
|
const blueprintId = readOption(options.blueprintId);
|
|
1344
1703
|
if (blueprintId)
|
|
1345
1704
|
body.blueprint_id = blueprintId;
|
|
@@ -1351,7 +1710,7 @@ export function createProgram() {
|
|
|
1351
1710
|
body.blueprint_summary = blueprintSummary;
|
|
1352
1711
|
const blueprintTags = readOption(options.blueprintTags);
|
|
1353
1712
|
if (blueprintTags)
|
|
1354
|
-
body.blueprint_tags =
|
|
1713
|
+
body.blueprint_tags = readCsvOption(blueprintTags);
|
|
1355
1714
|
const result = await requestOxygen("/api/cli/blueprints/export", {
|
|
1356
1715
|
method: "POST",
|
|
1357
1716
|
body,
|
|
@@ -1415,7 +1774,7 @@ export function createProgram() {
|
|
|
1415
1774
|
throw new Error("--file is required for blueprints save");
|
|
1416
1775
|
const fs = await import("node:fs/promises");
|
|
1417
1776
|
const raw = await fs.readFile(filePath, "utf8");
|
|
1418
|
-
const envelope =
|
|
1777
|
+
const envelope = parseJsonValue(raw, "--file");
|
|
1419
1778
|
const body = { envelope };
|
|
1420
1779
|
const slug = readOption(options.slug);
|
|
1421
1780
|
if (slug)
|
|
@@ -1518,154 +1877,8 @@ export function createProgram() {
|
|
|
1518
1877
|
return requestOxygen(`/api/blueprints/marketplace${qs}`, { requireAuth: false });
|
|
1519
1878
|
});
|
|
1520
1879
|
}));
|
|
1521
|
-
program
|
|
1522
|
-
|
|
1523
|
-
.description("Reusable prompt templates layered into AI columns at run time.")
|
|
1524
|
-
.addCommand(new Command("list")
|
|
1525
|
-
.description("List prompt templates in the workspace.")
|
|
1526
|
-
.option("--kind <kind>", "Filter by ai_column_system, scoring_rubric, or other.")
|
|
1527
|
-
.option("--include-archived", "Include archived templates.")
|
|
1528
|
-
.option("--json", "Print a JSON envelope.")
|
|
1529
|
-
.action(async (options) => {
|
|
1530
|
-
await handleAsyncAction("prompts list", options, () => {
|
|
1531
|
-
const params = new URLSearchParams();
|
|
1532
|
-
const kind = readOption(options.kind);
|
|
1533
|
-
if (kind)
|
|
1534
|
-
params.set("kind", kind);
|
|
1535
|
-
if (options.includeArchived)
|
|
1536
|
-
params.set("include_archived", "true");
|
|
1537
|
-
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1538
|
-
return requestOxygen(`/api/cli/templates${qs}`);
|
|
1539
|
-
});
|
|
1540
|
-
}))
|
|
1541
|
-
.addCommand(new Command("get")
|
|
1542
|
-
.description("Read one prompt template by id or slug.")
|
|
1543
|
-
.argument("<id_or_slug>", "Template UUID or slug.")
|
|
1544
|
-
.option("--json", "Print a JSON envelope.")
|
|
1545
|
-
.action(async (idOrSlug, options) => {
|
|
1546
|
-
await handleAsyncAction("prompts get", options, () => requestOxygen("/api/cli/templates/get", {
|
|
1547
|
-
method: "POST",
|
|
1548
|
-
body: idOrSlug.includes("-") && idOrSlug.length >= 32
|
|
1549
|
-
? { id: idOrSlug }
|
|
1550
|
-
: { slug: idOrSlug },
|
|
1551
|
-
}));
|
|
1552
|
-
}))
|
|
1553
|
-
.addCommand(new Command("upsert")
|
|
1554
|
-
.description("Create or update a prompt template.")
|
|
1555
|
-
.option("--id <id>", "Existing template UUID to update. Omit to create.")
|
|
1556
|
-
.option("--slug <slug>", "Stable slug (kebab-case).")
|
|
1557
|
-
.option("--name <name>", "Human-readable name.")
|
|
1558
|
-
.option("--description <text>", "Short description.")
|
|
1559
|
-
.option("--kind <kind>", "Template kind: ai_column_system, scoring_rubric, or other.")
|
|
1560
|
-
.option("--body <text>", "Prompt body.")
|
|
1561
|
-
.option("--body-file <path>", "Path to a file containing the prompt body.")
|
|
1562
|
-
.option("--json", "Print a JSON envelope.")
|
|
1563
|
-
.action(async (options) => {
|
|
1564
|
-
await handleAsyncAction("prompts upsert", options, async () => {
|
|
1565
|
-
const body = {};
|
|
1566
|
-
if (readOption(options.id))
|
|
1567
|
-
body.id = readOption(options.id);
|
|
1568
|
-
if (readOption(options.slug))
|
|
1569
|
-
body.slug = readOption(options.slug);
|
|
1570
|
-
if (readOption(options.name))
|
|
1571
|
-
body.name = readOption(options.name);
|
|
1572
|
-
if (readOption(options.description) !== undefined)
|
|
1573
|
-
body.description = readOption(options.description);
|
|
1574
|
-
if (readOption(options.kind))
|
|
1575
|
-
body.kind = readOption(options.kind);
|
|
1576
|
-
if (readOption(options.body))
|
|
1577
|
-
body.body = readOption(options.body);
|
|
1578
|
-
else {
|
|
1579
|
-
const path = readOption(options.bodyFile);
|
|
1580
|
-
if (path) {
|
|
1581
|
-
const fs = await import("node:fs/promises");
|
|
1582
|
-
body.body = await fs.readFile(path, "utf8");
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
return requestOxygen("/api/cli/templates/upsert", { method: "POST", body });
|
|
1586
|
-
});
|
|
1587
|
-
}))
|
|
1588
|
-
.addCommand(new Command("archive")
|
|
1589
|
-
.description("Archive a prompt template. Seeded templates cannot be archived.")
|
|
1590
|
-
.argument("<id>", "Template UUID.")
|
|
1591
|
-
.option("--json", "Print a JSON envelope.")
|
|
1592
|
-
.action(async (id, options) => {
|
|
1593
|
-
await handleAsyncAction("prompts archive", options, () => requestOxygen("/api/cli/templates/archive", { method: "POST", body: { id } }));
|
|
1594
|
-
}));
|
|
1595
|
-
program
|
|
1596
|
-
.command("templates")
|
|
1597
|
-
.description("Deprecated alias for 'oxygen prompts'. Will be removed in a future release.")
|
|
1598
|
-
.addCommand(new Command("list")
|
|
1599
|
-
.description("List prompt templates in the workspace.")
|
|
1600
|
-
.option("--kind <kind>", "Filter by ai_column_system, scoring_rubric, or other.")
|
|
1601
|
-
.option("--include-archived", "Include archived templates.")
|
|
1602
|
-
.option("--json", "Print a JSON envelope.")
|
|
1603
|
-
.action(async (options) => {
|
|
1604
|
-
await handleAsyncAction("templates list", options, () => {
|
|
1605
|
-
const params = new URLSearchParams();
|
|
1606
|
-
const kind = readOption(options.kind);
|
|
1607
|
-
if (kind)
|
|
1608
|
-
params.set("kind", kind);
|
|
1609
|
-
if (options.includeArchived)
|
|
1610
|
-
params.set("include_archived", "true");
|
|
1611
|
-
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1612
|
-
return requestOxygen(`/api/cli/templates${qs}`);
|
|
1613
|
-
});
|
|
1614
|
-
}))
|
|
1615
|
-
.addCommand(new Command("get")
|
|
1616
|
-
.description("Read one prompt template by id or slug.")
|
|
1617
|
-
.argument("<id_or_slug>", "Template UUID or slug.")
|
|
1618
|
-
.option("--json", "Print a JSON envelope.")
|
|
1619
|
-
.action(async (idOrSlug, options) => {
|
|
1620
|
-
await handleAsyncAction("templates get", options, async () => requestOxygen("/api/cli/templates/get", {
|
|
1621
|
-
method: "POST",
|
|
1622
|
-
body: idOrSlug.includes("-") && idOrSlug.length >= 32
|
|
1623
|
-
? { id: idOrSlug }
|
|
1624
|
-
: { slug: idOrSlug },
|
|
1625
|
-
}));
|
|
1626
|
-
}))
|
|
1627
|
-
.addCommand(new Command("upsert")
|
|
1628
|
-
.description("Create or update a prompt template.")
|
|
1629
|
-
.option("--id <id>", "Existing template UUID to update. Omit to create.")
|
|
1630
|
-
.option("--slug <slug>", "Stable slug (kebab-case).")
|
|
1631
|
-
.option("--name <name>", "Human-readable name.")
|
|
1632
|
-
.option("--description <text>", "Short description.")
|
|
1633
|
-
.option("--kind <kind>", "Template kind: ai_column_system, scoring_rubric, or other.")
|
|
1634
|
-
.option("--body <text>", "Prompt body.")
|
|
1635
|
-
.option("--body-file <path>", "Path to a file containing the prompt body.")
|
|
1636
|
-
.option("--json", "Print a JSON envelope.")
|
|
1637
|
-
.action(async (options) => {
|
|
1638
|
-
await handleAsyncAction("templates upsert", options, async () => {
|
|
1639
|
-
const body = {};
|
|
1640
|
-
if (readOption(options.id))
|
|
1641
|
-
body.id = readOption(options.id);
|
|
1642
|
-
if (readOption(options.slug))
|
|
1643
|
-
body.slug = readOption(options.slug);
|
|
1644
|
-
if (readOption(options.name))
|
|
1645
|
-
body.name = readOption(options.name);
|
|
1646
|
-
if (readOption(options.description) !== undefined)
|
|
1647
|
-
body.description = readOption(options.description);
|
|
1648
|
-
if (readOption(options.kind))
|
|
1649
|
-
body.kind = readOption(options.kind);
|
|
1650
|
-
if (readOption(options.body))
|
|
1651
|
-
body.body = readOption(options.body);
|
|
1652
|
-
else {
|
|
1653
|
-
const path = readOption(options.bodyFile);
|
|
1654
|
-
if (path) {
|
|
1655
|
-
const fs = await import("node:fs/promises");
|
|
1656
|
-
body.body = await fs.readFile(path, "utf8");
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
return requestOxygen("/api/cli/templates/upsert", { method: "POST", body });
|
|
1660
|
-
});
|
|
1661
|
-
}))
|
|
1662
|
-
.addCommand(new Command("archive")
|
|
1663
|
-
.description("Archive a prompt template. Seeded templates cannot be archived.")
|
|
1664
|
-
.argument("<id>", "Template UUID.")
|
|
1665
|
-
.option("--json", "Print a JSON envelope.")
|
|
1666
|
-
.action(async (id, options) => {
|
|
1667
|
-
await handleAsyncAction("templates archive", options, async () => requestOxygen("/api/cli/templates/archive", { method: "POST", body: { id } }));
|
|
1668
|
-
}));
|
|
1880
|
+
program.addCommand(buildPromptTemplatesCommand("prompts", "Reusable prompt templates layered into AI columns at run time."));
|
|
1881
|
+
program.addCommand(buildPromptTemplatesCommand("templates", "Deprecated alias for 'oxygen prompts'. Will be removed in a future release."));
|
|
1669
1882
|
program
|
|
1670
1883
|
.command("reviews")
|
|
1671
1884
|
.description("Human-in-the-loop reviews for AI-generated outreach messages.")
|
|
@@ -1710,7 +1923,7 @@ export function createProgram() {
|
|
|
1710
1923
|
.argument("<review_id>", "Message review UUID.")
|
|
1711
1924
|
.option("--json", "Print a JSON envelope.")
|
|
1712
1925
|
.action(async (reviewId, options) => {
|
|
1713
|
-
await handleAsyncAction("reviews accept", options,
|
|
1926
|
+
await handleAsyncAction("reviews accept", options, () => requestOxygen("/api/cli/message-reviews/decide", {
|
|
1714
1927
|
method: "POST",
|
|
1715
1928
|
body: { id: reviewId, decision: "accept" },
|
|
1716
1929
|
}));
|
|
@@ -1726,7 +1939,7 @@ export function createProgram() {
|
|
|
1726
1939
|
const body = { id: reviewId, decision: "reject" };
|
|
1727
1940
|
const highlightsJson = readOption(options.highlightsJson);
|
|
1728
1941
|
if (highlightsJson) {
|
|
1729
|
-
body.highlights =
|
|
1942
|
+
body.highlights = parseJsonValue(highlightsJson, "--highlights-json");
|
|
1730
1943
|
}
|
|
1731
1944
|
if (options.autoRerun)
|
|
1732
1945
|
body.auto_rerun = true;
|
|
@@ -1747,7 +1960,14 @@ export function createProgram() {
|
|
|
1747
1960
|
.option("--definition-json <json>", "Optional JSON object with column definition metadata.")
|
|
1748
1961
|
.option("--prompt-key <key>", "OXYGEN prompt-library key (e.g. email_draft_v1). Materializes prompt + output_schema and forces kind=ai.")
|
|
1749
1962
|
.option("--input-mapping <json>", "Required with --prompt-key. JSON object mapping prompt input names to column or literal refs.")
|
|
1750
|
-
.option("--
|
|
1963
|
+
.option("--model <id>", "AI column model id (e.g. claude-sonnet-4-5). Explicit models require credentialMode byok unless allow-listed managed.")
|
|
1964
|
+
.option("--reasoning-level <level>", "AI column reasoning level: low, medium, or high.")
|
|
1965
|
+
.option("--run-condition <formula>", "Formula expression gating whether the AI column runs per row.")
|
|
1966
|
+
.option("--run-condition-columns <csv>", "Comma-separated column keys referenced by --run-condition.")
|
|
1967
|
+
.option("--output-schema-json <json>", "AI column output JSON schema as inline JSON.")
|
|
1968
|
+
.option("--output-schema-file <path>", "AI column output JSON schema read from a file path.")
|
|
1969
|
+
.option("--json", "Print a JSON envelope.")
|
|
1970
|
+
// skipcq: JS-R1005 — intentional per-option branching to assemble the columns-add request body
|
|
1751
1971
|
.action(async (table, options) => {
|
|
1752
1972
|
if (!options.promptKey && !options.label) {
|
|
1753
1973
|
throw new OxygenError("invalid_request", "--label is required.", { exitCode: 1 });
|
|
@@ -1768,12 +1988,20 @@ export function createProgram() {
|
|
|
1768
1988
|
column.semantic_type = options.semanticType;
|
|
1769
1989
|
if (options.definitionJson)
|
|
1770
1990
|
column.definition = parseJsonObject(options.definitionJson);
|
|
1991
|
+
if (readOption(options.model) ||
|
|
1992
|
+
readOption(options.reasoningLevel) ||
|
|
1993
|
+
readOption(options.runCondition) ||
|
|
1994
|
+
readOption(options.outputSchemaJson) ||
|
|
1995
|
+
readOption(options.outputSchemaFile)) {
|
|
1996
|
+
const definition = isRecord(column.definition) ? column.definition : {};
|
|
1997
|
+
column.definition = applyAiColumnConfig(definition, options);
|
|
1998
|
+
}
|
|
1771
1999
|
const body = { table, column };
|
|
1772
2000
|
if (options.promptKey)
|
|
1773
2001
|
body.prompt_key = options.promptKey;
|
|
1774
2002
|
if (options.inputMapping)
|
|
1775
2003
|
body.input_mapping = parseJsonObject(options.inputMapping);
|
|
1776
|
-
await handleAsyncAction("columns add", options,
|
|
2004
|
+
await handleAsyncAction("columns add", options, () => requestOxygen("/api/cli/tables/columns", {
|
|
1777
2005
|
method: "POST",
|
|
1778
2006
|
body,
|
|
1779
2007
|
}));
|
|
@@ -1793,6 +2021,7 @@ export function createProgram() {
|
|
|
1793
2021
|
.option("--max-concurrency <n>", "Maximum concurrent row items for a background run. Defaults to 250 for AI columns and 50 otherwise.")
|
|
1794
2022
|
.option("--local", "Run a custom HTTP column in this CLI process so env-var secrets stay local.")
|
|
1795
2023
|
.option("--local-concurrency <n>", "Maximum concurrent custom HTTP requests for --local. Defaults to 3.")
|
|
2024
|
+
.option("--dry-run", "Preview resolved model, credit estimate, and run-condition posture without spending any credits.")
|
|
1796
2025
|
.option("--json", "Print a JSON envelope.")
|
|
1797
2026
|
.action(async (table, column, options) => {
|
|
1798
2027
|
const limit = readPositiveInt(options.limit);
|
|
@@ -1816,7 +2045,8 @@ export function createProgram() {
|
|
|
1816
2045
|
exitCode: 1,
|
|
1817
2046
|
});
|
|
1818
2047
|
}
|
|
1819
|
-
|
|
2048
|
+
// skipcq: JS-R1005 — intentional branching for local/background/filter column-run modes
|
|
2049
|
+
await handleAsyncAction("columns run", options, () => {
|
|
1820
2050
|
if (options.local) {
|
|
1821
2051
|
if (options.background) {
|
|
1822
2052
|
throw new OxygenError("invalid_column_run", "Pass either --local or --background, not both.", {
|
|
@@ -1847,6 +2077,7 @@ export function createProgram() {
|
|
|
1847
2077
|
...(options.background ? { background: true } : {}),
|
|
1848
2078
|
...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
|
|
1849
2079
|
...(maxConcurrency ? { max_concurrency: maxConcurrency } : {}),
|
|
2080
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
1850
2081
|
};
|
|
1851
2082
|
return requestColumnsRun(body, table, {
|
|
1852
2083
|
background: Boolean(options.background),
|
|
@@ -1859,15 +2090,17 @@ export function createProgram() {
|
|
|
1859
2090
|
.requiredOption("--column <column>", "Column id or key.")
|
|
1860
2091
|
.requiredOption("--row <row_id>", "Row UUID.")
|
|
1861
2092
|
.option("--from-review-id <review_id>", "Prior message review whose feedback to thread into the regeneration prompt.")
|
|
2093
|
+
.option("--dry-run", "Preview the single-row credit estimate and posture without spending credits or writing a review.")
|
|
1862
2094
|
.option("--json", "Print a JSON envelope.")
|
|
1863
2095
|
.action(async (options) => {
|
|
1864
|
-
await handleAsyncAction("columns rerun", options,
|
|
2096
|
+
await handleAsyncAction("columns rerun", options, () => requestOxygen("/api/cli/columns/rerun", {
|
|
1865
2097
|
method: "POST",
|
|
1866
2098
|
body: {
|
|
1867
2099
|
table: options.table,
|
|
1868
2100
|
column: options.column,
|
|
1869
2101
|
row_id: options.row,
|
|
1870
2102
|
...(readOption(options.fromReviewId) ? { from_review_id: readOption(options.fromReviewId) } : {}),
|
|
2103
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
1871
2104
|
},
|
|
1872
2105
|
}));
|
|
1873
2106
|
}))
|
|
@@ -1882,7 +2115,7 @@ export function createProgram() {
|
|
|
1882
2115
|
.action(async (table, sourceColumn, options) => {
|
|
1883
2116
|
const mappings = options.mappingsJson ? parseJsonArray(options.mappingsJson) : undefined;
|
|
1884
2117
|
const preset = readOption(options.preset);
|
|
1885
|
-
await handleAsyncAction("columns materialize", options,
|
|
2118
|
+
await handleAsyncAction("columns materialize", options, () => requestOxygen("/api/cli/tables/columns/materialize", {
|
|
1886
2119
|
method: "POST",
|
|
1887
2120
|
body: {
|
|
1888
2121
|
table,
|
|
@@ -1901,7 +2134,7 @@ export function createProgram() {
|
|
|
1901
2134
|
.option("--label <label>", "Optional new display label.")
|
|
1902
2135
|
.option("--json", "Print a JSON envelope.")
|
|
1903
2136
|
.action(async (table, column, options) => {
|
|
1904
|
-
await handleAsyncAction("columns rename", options,
|
|
2137
|
+
await handleAsyncAction("columns rename", options, () => requestOxygen("/api/cli/tables/columns/rename", {
|
|
1905
2138
|
method: "POST",
|
|
1906
2139
|
body: {
|
|
1907
2140
|
table,
|
|
@@ -1918,9 +2151,26 @@ export function createProgram() {
|
|
|
1918
2151
|
.option("--label <label>", "New display label.")
|
|
1919
2152
|
.option("--semantic-type <type>", "New semantic type.")
|
|
1920
2153
|
.option("--definition-json <json>", "Definition metadata to shallow-merge into the column; arrays are replaced wholesale and missing keys leave existing fields unchanged.")
|
|
2154
|
+
.option("--model <id>", "AI column model id (e.g. claude-sonnet-4-5). Explicit models require credentialMode byok unless allow-listed managed.")
|
|
2155
|
+
.option("--reasoning-level <level>", "AI column reasoning level: low, medium, or high.")
|
|
2156
|
+
.option("--run-condition <formula>", "Formula expression gating whether the AI column runs per row.")
|
|
2157
|
+
.option("--run-condition-columns <csv>", "Comma-separated column keys referenced by --run-condition.")
|
|
2158
|
+
.option("--output-schema-json <json>", "AI column output JSON schema as inline JSON.")
|
|
2159
|
+
.option("--output-schema-file <path>", "AI column output JSON schema read from a file path.")
|
|
1921
2160
|
.option("--dry-run", "Return the would-be merged definition without writing.")
|
|
1922
2161
|
.option("--json", "Print a JSON envelope.")
|
|
1923
2162
|
.action(async (table, column, options) => {
|
|
2163
|
+
const hasAiConfig = readOption(options.model) ||
|
|
2164
|
+
readOption(options.reasoningLevel) ||
|
|
2165
|
+
readOption(options.runCondition) ||
|
|
2166
|
+
readOption(options.outputSchemaJson) ||
|
|
2167
|
+
readOption(options.outputSchemaFile);
|
|
2168
|
+
let definition = options.definitionJson
|
|
2169
|
+
? parseJsonObject(options.definitionJson)
|
|
2170
|
+
: undefined;
|
|
2171
|
+
if (hasAiConfig) {
|
|
2172
|
+
definition = applyAiColumnConfig(definition ?? {}, options);
|
|
2173
|
+
}
|
|
1924
2174
|
await handleAsyncAction("columns update", options, () => requestOxygen("/api/cli/tables/columns/update", {
|
|
1925
2175
|
method: "POST",
|
|
1926
2176
|
body: {
|
|
@@ -1928,7 +2178,7 @@ export function createProgram() {
|
|
|
1928
2178
|
column,
|
|
1929
2179
|
...(readOption(options.label) ? { label: readOption(options.label) } : {}),
|
|
1930
2180
|
...(readOption(options.semanticType) ? { semantic_type: readOption(options.semanticType) } : {}),
|
|
1931
|
-
...(
|
|
2181
|
+
...(definition ? { definition } : {}),
|
|
1932
2182
|
...(options.dryRun ? { dry_run: true } : {}),
|
|
1933
2183
|
},
|
|
1934
2184
|
}));
|
|
@@ -1941,7 +2191,7 @@ export function createProgram() {
|
|
|
1941
2191
|
.option("--dry-run", "Preview the conversion (row counts and samples) without writing.")
|
|
1942
2192
|
.option("--json", "Print a JSON envelope.")
|
|
1943
2193
|
.action(async (table, column, options) => {
|
|
1944
|
-
await handleAsyncAction("columns retype", options,
|
|
2194
|
+
await handleAsyncAction("columns retype", options, () => requestOxygen("/api/cli/tables/columns/retype", {
|
|
1945
2195
|
method: "POST",
|
|
1946
2196
|
body: {
|
|
1947
2197
|
table,
|
|
@@ -1957,7 +2207,7 @@ export function createProgram() {
|
|
|
1957
2207
|
.argument("<column>", "Column id or key.")
|
|
1958
2208
|
.option("--json", "Print a JSON envelope.")
|
|
1959
2209
|
.action(async (table, column, options) => {
|
|
1960
|
-
await handleAsyncAction("columns archive", options,
|
|
2210
|
+
await handleAsyncAction("columns archive", options, () => requestOxygen("/api/cli/tables/columns/archive", {
|
|
1961
2211
|
method: "POST",
|
|
1962
2212
|
body: { table, column },
|
|
1963
2213
|
}));
|
|
@@ -1976,7 +2226,7 @@ export function createProgram() {
|
|
|
1976
2226
|
.option("--dry-run", "Preview the provider action column without creating it.")
|
|
1977
2227
|
.option("--json", "Print a JSON envelope.")
|
|
1978
2228
|
.action(async (table, options) => {
|
|
1979
|
-
await handleAsyncAction("action-column add-provider", options,
|
|
2229
|
+
await handleAsyncAction("action-column add-provider", options, () => {
|
|
1980
2230
|
const tableId = readOption(table) ?? readOption(options.table);
|
|
1981
2231
|
if (!tableId) {
|
|
1982
2232
|
throw new OxygenError("invalid_request", "Pass a table argument or --table <table>.", {
|
|
@@ -2021,7 +2271,7 @@ export function createProgram() {
|
|
|
2021
2271
|
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
2022
2272
|
const maxConcurrency = readPositiveInt(options.maxConcurrency);
|
|
2023
2273
|
const chainSteps = readChainStepsOption(options.thenJson);
|
|
2024
|
-
await handleAsyncAction("table-runs create", options,
|
|
2274
|
+
await handleAsyncAction("table-runs create", options, () => requestOxygen("/api/cli/table-action-runs", {
|
|
2025
2275
|
method: "POST",
|
|
2026
2276
|
body: {
|
|
2027
2277
|
table,
|
|
@@ -2050,7 +2300,7 @@ export function createProgram() {
|
|
|
2050
2300
|
.argument("<run_id>", "Table action run UUID or parent workspace run UUID.")
|
|
2051
2301
|
.option("--json", "Print a JSON envelope.")
|
|
2052
2302
|
.action(async (runId, options) => {
|
|
2053
|
-
await handleAsyncAction("table-runs get", options,
|
|
2303
|
+
await handleAsyncAction("table-runs get", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}`));
|
|
2054
2304
|
}))
|
|
2055
2305
|
.addCommand(new Command("items")
|
|
2056
2306
|
.description("List row items for a durable table action run.")
|
|
@@ -2059,7 +2309,7 @@ export function createProgram() {
|
|
|
2059
2309
|
.option("--limit <n>", "Maximum items to return. Defaults to 100.")
|
|
2060
2310
|
.option("--json", "Print a JSON envelope.")
|
|
2061
2311
|
.action(async (runId, options) => {
|
|
2062
|
-
await handleAsyncAction("table-runs items", options,
|
|
2312
|
+
await handleAsyncAction("table-runs items", options, () => {
|
|
2063
2313
|
const query = new URLSearchParams();
|
|
2064
2314
|
if (readOption(options.status))
|
|
2065
2315
|
query.set("status", readOption(options.status) ?? "");
|
|
@@ -2077,21 +2327,21 @@ export function createProgram() {
|
|
|
2077
2327
|
.option("--interval-seconds <n>", "Polling interval. Defaults to 5.")
|
|
2078
2328
|
.option("--json", "Print a JSON envelope.")
|
|
2079
2329
|
.action(async (runId, options) => {
|
|
2080
|
-
await handleAsyncAction("table-runs wait", options,
|
|
2330
|
+
await handleAsyncAction("table-runs wait", options, () => waitForTableActionRun(runId, options));
|
|
2081
2331
|
}))
|
|
2082
2332
|
.addCommand(new Command("provider-summary")
|
|
2083
2333
|
.description("Summarize provider attempts, upstream request events, and credit capture/release for a table action run.")
|
|
2084
2334
|
.argument("<run_id>", "Table action run UUID.")
|
|
2085
2335
|
.option("--json", "Print a JSON envelope.")
|
|
2086
2336
|
.action(async (runId, options) => {
|
|
2087
|
-
await handleAsyncAction("table-runs provider-summary", options,
|
|
2337
|
+
await handleAsyncAction("table-runs provider-summary", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/provider-summary`));
|
|
2088
2338
|
}))
|
|
2089
2339
|
.addCommand(new Command("cancel")
|
|
2090
2340
|
.description("Request cancellation for a durable table action run.")
|
|
2091
2341
|
.argument("<run_id>", "Table action run UUID or parent workspace run UUID.")
|
|
2092
2342
|
.option("--json", "Print a JSON envelope.")
|
|
2093
2343
|
.action(async (runId, options) => {
|
|
2094
|
-
await handleAsyncAction("table-runs cancel", options,
|
|
2344
|
+
await handleAsyncAction("table-runs cancel", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/cancel`, {
|
|
2095
2345
|
method: "POST",
|
|
2096
2346
|
body: {},
|
|
2097
2347
|
}));
|
|
@@ -2101,7 +2351,7 @@ export function createProgram() {
|
|
|
2101
2351
|
.argument("<run_id>", "Table action run UUID.")
|
|
2102
2352
|
.option("--json", "Print a JSON envelope.")
|
|
2103
2353
|
.action(async (runId, options) => {
|
|
2104
|
-
await handleAsyncAction("table-runs pause", options,
|
|
2354
|
+
await handleAsyncAction("table-runs pause", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/pause`, {
|
|
2105
2355
|
method: "POST",
|
|
2106
2356
|
body: {},
|
|
2107
2357
|
}));
|
|
@@ -2111,7 +2361,7 @@ export function createProgram() {
|
|
|
2111
2361
|
.argument("<run_id>", "Table action run UUID.")
|
|
2112
2362
|
.option("--json", "Print a JSON envelope.")
|
|
2113
2363
|
.action(async (runId, options) => {
|
|
2114
|
-
await handleAsyncAction("table-runs resume", options,
|
|
2364
|
+
await handleAsyncAction("table-runs resume", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/resume`, {
|
|
2115
2365
|
method: "POST",
|
|
2116
2366
|
body: {},
|
|
2117
2367
|
}));
|
|
@@ -2121,7 +2371,7 @@ export function createProgram() {
|
|
|
2121
2371
|
.argument("<run_id>", "Table action run UUID.")
|
|
2122
2372
|
.option("--json", "Print a JSON envelope.")
|
|
2123
2373
|
.action(async (runId, options) => {
|
|
2124
|
-
await handleAsyncAction("table-runs retry-failed", options,
|
|
2374
|
+
await handleAsyncAction("table-runs retry-failed", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/retry-failed`, {
|
|
2125
2375
|
method: "POST",
|
|
2126
2376
|
body: {},
|
|
2127
2377
|
}));
|
|
@@ -2146,7 +2396,7 @@ export function createProgram() {
|
|
|
2146
2396
|
.option("--metadata-json <json>", "Optional metadata object to attach to the run.")
|
|
2147
2397
|
.option("--json", "Print a JSON envelope.")
|
|
2148
2398
|
.action(async (table, options) => {
|
|
2149
|
-
await handleAsyncAction("table-ingestions create-tool-page", options,
|
|
2399
|
+
await handleAsyncAction("table-ingestions create-tool-page", options, () => {
|
|
2150
2400
|
const toolId = readOption(options.tool) ?? readOption(options.toolId);
|
|
2151
2401
|
if (!toolId) {
|
|
2152
2402
|
throw new OxygenError("invalid_table_ingestion", "Pass --tool or --tool-id.", {
|
|
@@ -2193,7 +2443,7 @@ export function createProgram() {
|
|
|
2193
2443
|
.argument("<run_id>", "Table ingestion run UUID.")
|
|
2194
2444
|
.option("--json", "Print a JSON envelope.")
|
|
2195
2445
|
.action(async (runId, options) => {
|
|
2196
|
-
await handleAsyncAction("table-ingestions get", options,
|
|
2446
|
+
await handleAsyncAction("table-ingestions get", options, () => requestOxygen(`/api/cli/table-ingestion-runs/${encodeURIComponent(runId)}`));
|
|
2197
2447
|
}))
|
|
2198
2448
|
.addCommand(new Command("items")
|
|
2199
2449
|
.description("List items for a durable table ingestion run.")
|
|
@@ -2202,7 +2452,7 @@ export function createProgram() {
|
|
|
2202
2452
|
.option("--limit <n>", "Maximum items to return. Defaults to 100.")
|
|
2203
2453
|
.option("--json", "Print a JSON envelope.")
|
|
2204
2454
|
.action(async (runId, options) => {
|
|
2205
|
-
await handleAsyncAction("table-ingestions items", options,
|
|
2455
|
+
await handleAsyncAction("table-ingestions items", options, () => {
|
|
2206
2456
|
const query = new URLSearchParams();
|
|
2207
2457
|
if (readOption(options.status))
|
|
2208
2458
|
query.set("status", readOption(options.status) ?? "");
|
|
@@ -2220,14 +2470,14 @@ export function createProgram() {
|
|
|
2220
2470
|
.option("--interval-seconds <n>", "Polling interval. Defaults to 5.")
|
|
2221
2471
|
.option("--json", "Print a JSON envelope.")
|
|
2222
2472
|
.action(async (runId, options) => {
|
|
2223
|
-
await handleAsyncAction("table-ingestions wait", options,
|
|
2473
|
+
await handleAsyncAction("table-ingestions wait", options, () => waitForTableIngestionRun(runId, options));
|
|
2224
2474
|
}))
|
|
2225
2475
|
.addCommand(new Command("cancel")
|
|
2226
2476
|
.description("Request cancellation for a durable table ingestion run.")
|
|
2227
2477
|
.argument("<run_id>", "Table ingestion run UUID.")
|
|
2228
2478
|
.option("--json", "Print a JSON envelope.")
|
|
2229
2479
|
.action(async (runId, options) => {
|
|
2230
|
-
await handleAsyncAction("table-ingestions cancel", options,
|
|
2480
|
+
await handleAsyncAction("table-ingestions cancel", options, () => requestOxygen(`/api/cli/table-ingestion-runs/${encodeURIComponent(runId)}/cancel`, {
|
|
2231
2481
|
method: "POST",
|
|
2232
2482
|
body: {},
|
|
2233
2483
|
}));
|
|
@@ -2237,7 +2487,7 @@ export function createProgram() {
|
|
|
2237
2487
|
.argument("<run_id>", "Table ingestion run UUID.")
|
|
2238
2488
|
.option("--json", "Print a JSON envelope.")
|
|
2239
2489
|
.action(async (runId, options) => {
|
|
2240
|
-
await handleAsyncAction("table-ingestions retry-failed", options,
|
|
2490
|
+
await handleAsyncAction("table-ingestions retry-failed", options, () => requestOxygen(`/api/cli/table-ingestion-runs/${encodeURIComponent(runId)}/retry-failed`, {
|
|
2241
2491
|
method: "POST",
|
|
2242
2492
|
body: {},
|
|
2243
2493
|
}));
|
|
@@ -2272,7 +2522,7 @@ export function createProgram() {
|
|
|
2272
2522
|
.option("--materialize-preview", "Create a preview table for the plan. Defaults to no table side effect.")
|
|
2273
2523
|
.option("--json", "Print a JSON envelope.")
|
|
2274
2524
|
.action(async (options) => {
|
|
2275
|
-
await handleAsyncAction("lead-sourcing plan", options,
|
|
2525
|
+
await handleAsyncAction("lead-sourcing plan", options, () => requestOxygen("/api/cli/lead-sourcing/plan", {
|
|
2276
2526
|
method: "POST",
|
|
2277
2527
|
body: {
|
|
2278
2528
|
prompt: readFileIfPresent(options.prompt),
|
|
@@ -2286,7 +2536,7 @@ export function createProgram() {
|
|
|
2286
2536
|
.requiredOption("--spec <file>", "JSON LeadSourcingSpec file, or a text prompt file to compile into a spec.")
|
|
2287
2537
|
.option("--json", "Print a JSON envelope.")
|
|
2288
2538
|
.action(async (table, options) => {
|
|
2289
|
-
await handleAsyncAction("lead-sourcing audit", options,
|
|
2539
|
+
await handleAsyncAction("lead-sourcing audit", options, () => requestOxygen("/api/cli/lead-sourcing/audit", {
|
|
2290
2540
|
method: "POST",
|
|
2291
2541
|
body: {
|
|
2292
2542
|
table,
|
|
@@ -2300,7 +2550,7 @@ export function createProgram() {
|
|
|
2300
2550
|
.addCommand(new Command("plan")
|
|
2301
2551
|
.description("Plan an Oxygen search or scrape route before running provider jobs.")
|
|
2302
2552
|
.requiredOption("--goal <file|text>", "Search/scrape goal text, or a local file path containing the goal.")
|
|
2303
|
-
.option("--kind <kind>", "Route kind:
|
|
2553
|
+
.option("--kind <kind>", "Route kind: signal_search, web_search, web_scrape, local_business_search, or source_specific_scrape. For people search use 'oxygen people search plan'; for company search use 'oxygen companies search plan'.")
|
|
2304
2554
|
.option("--target-count <n>", "Optional target row count.")
|
|
2305
2555
|
.option("--geography <text>", "Optional geography hint.")
|
|
2306
2556
|
.option("--known-urls <urls>", "Comma-separated known URLs for scrape routes.")
|
|
@@ -2309,6 +2559,7 @@ export function createProgram() {
|
|
|
2309
2559
|
.action(async (options) => {
|
|
2310
2560
|
await handleAsyncAction("search plan", options, () => {
|
|
2311
2561
|
assertNotCompanySearchKind(options.kind);
|
|
2562
|
+
assertNotPeopleSearchKind(options.kind);
|
|
2312
2563
|
return requestOxygen("/api/cli/search/plan", {
|
|
2313
2564
|
method: "POST",
|
|
2314
2565
|
body: {
|
|
@@ -2423,7 +2674,7 @@ export function createProgram() {
|
|
|
2423
2674
|
.description("Compile a company-search prompt into ordered provider routes without provider calls.")
|
|
2424
2675
|
.requiredOption("--prompt <text-or-file>", "Company-search prompt, or a path to a prompt file.")
|
|
2425
2676
|
.option("--target-count <n>", "Desired company count for routing and estimates.")
|
|
2426
|
-
.option("--source-intent <intent>", "Override detected intent: sizing, structured, technology, hiring, local, known_source, concept, web, url, or fallback.")
|
|
2677
|
+
.option("--source-intent <intent>", "Override detected intent: sizing, structured, lookalike, technology, hiring, local, known_source, concept, web, url, or fallback.")
|
|
2427
2678
|
.option("--filters-json <json-or-file>", "CompanySearchFilters JSON inline or a @file/path; wins over individual flags per top-level filter path.")
|
|
2428
2679
|
.option("--industries <csv>", "Comma-separated industries to include.")
|
|
2429
2680
|
.option("--exclude-industries <csv>", "Comma-separated industries to exclude.")
|
|
@@ -2436,7 +2687,7 @@ export function createProgram() {
|
|
|
2436
2687
|
.option("--revenue <range>", "Annual revenue (USD) range: 1000000-20000000, 1000000+, or -5000000.")
|
|
2437
2688
|
.option("--founded <range>", "Founded year range: 2015-2024, 2020+, or -2010.")
|
|
2438
2689
|
.option("--lookalike <csv>", "Comma-separated lookalike company domains.")
|
|
2439
|
-
.option("--estimate", "Run a free count probe for an estimated match count.")
|
|
2690
|
+
.option("--estimate", "Run a free server-side preflight pass: resolves provider enums and a free count probe for an estimated match count (zero credits).")
|
|
2440
2691
|
.option("--materialize-preview", "Create a preview table with route rows.")
|
|
2441
2692
|
.option("--json", "Print a JSON envelope.")
|
|
2442
2693
|
.action(async (options) => {
|
|
@@ -2470,7 +2721,7 @@ export function createProgram() {
|
|
|
2470
2721
|
.option("--revenue <range>", "Annual revenue (USD) range when planning from --prompt.")
|
|
2471
2722
|
.option("--founded <range>", "Founded year range when planning from --prompt.")
|
|
2472
2723
|
.option("--lookalike <csv>", "Comma-separated lookalike company domains when planning from --prompt.")
|
|
2473
|
-
.option("--estimate", "Run a free
|
|
2724
|
+
.option("--estimate", "Run a free server-side preflight pass when planning from --prompt: resolves provider enums and a free count probe (zero credits).")
|
|
2474
2725
|
.option("--approved", "Required for live runs after inspecting dry-run output.")
|
|
2475
2726
|
.option("--json", "Print a JSON envelope.")
|
|
2476
2727
|
.action(async (options) => {
|
|
@@ -2493,7 +2744,7 @@ export function createProgram() {
|
|
|
2493
2744
|
.option("--selection-json <json>", "Raw table action selection JSON.")
|
|
2494
2745
|
.option("--json", "Print a JSON envelope.")
|
|
2495
2746
|
.action(async (table, options) => {
|
|
2496
|
-
await handleAsyncAction("companies enrich preview", options,
|
|
2747
|
+
await handleAsyncAction("companies enrich preview", options, () => requestOxygen("/api/cli/company-enrichment/preview", {
|
|
2497
2748
|
method: "POST",
|
|
2498
2749
|
body: readCompaniesEnrichBody(table, options),
|
|
2499
2750
|
}));
|
|
@@ -2513,7 +2764,7 @@ export function createProgram() {
|
|
|
2513
2764
|
.option("--force", "Re-run the waterfall audit column even when it already has a value.")
|
|
2514
2765
|
.option("--json", "Print a JSON envelope.")
|
|
2515
2766
|
.action(async (table, options) => {
|
|
2516
|
-
await handleAsyncAction("companies enrich run", options,
|
|
2767
|
+
await handleAsyncAction("companies enrich run", options, () => requestOxygen("/api/cli/company-enrichment/run", {
|
|
2517
2768
|
method: "POST",
|
|
2518
2769
|
body: readCompaniesEnrichBody(table, options),
|
|
2519
2770
|
}));
|
|
@@ -2542,7 +2793,8 @@ export function createProgram() {
|
|
|
2542
2793
|
.option("--require-email", "Only return people with a work email available (provider-dependent).")
|
|
2543
2794
|
.option("--require-phone", "Only return people with a phone/mobile available (provider-dependent).")
|
|
2544
2795
|
.option("--max-per-company <n>", "Cap on contacts per company for account-anchored searches.")
|
|
2545
|
-
.option("--estimate", "Run a free count probe for an estimated match count.")
|
|
2796
|
+
.option("--estimate", "Run a free count probe for an estimated match count (on by default).")
|
|
2797
|
+
.option("--no-estimate", "Skip the default free count probe (people-search sizing is on by default).")
|
|
2546
2798
|
.option("--materialize-preview", "Create a preview table with route rows.")
|
|
2547
2799
|
.option("--json", "Print a JSON envelope.")
|
|
2548
2800
|
.action(async (options) => {
|
|
@@ -2578,7 +2830,8 @@ export function createProgram() {
|
|
|
2578
2830
|
.option("--require-email", "Only return people with a work email available when planning from --prompt.")
|
|
2579
2831
|
.option("--require-phone", "Only return people with a phone available when planning from --prompt.")
|
|
2580
2832
|
.option("--max-per-company <n>", "Cap on contacts per company when planning from --prompt.")
|
|
2581
|
-
.option("--estimate", "Run a free count probe when planning from --prompt.")
|
|
2833
|
+
.option("--estimate", "Run a free count probe when planning from --prompt (on by default).")
|
|
2834
|
+
.option("--no-estimate", "Skip the default free count probe when planning from --prompt.")
|
|
2582
2835
|
.option("--approved", "Required for live runs after inspecting dry-run output.")
|
|
2583
2836
|
.option("--json", "Print a JSON envelope.")
|
|
2584
2837
|
.action(async (options) => {
|
|
@@ -2594,15 +2847,15 @@ export function createProgram() {
|
|
|
2594
2847
|
.description("Show background action and ingestion queue health.")
|
|
2595
2848
|
.option("--json", "Print a JSON envelope.")
|
|
2596
2849
|
.action(async (options) => {
|
|
2597
|
-
await handleAsyncAction("worker queue-stats", options,
|
|
2850
|
+
await handleAsyncAction("worker queue-stats", options, () => requestOxygen("/api/cli/worker/queue-stats"));
|
|
2598
2851
|
}))
|
|
2599
2852
|
.addCommand(new Command("failures")
|
|
2600
2853
|
.description("List failed background action and ingestion items.")
|
|
2601
|
-
.option("--queue <queue>", "all, actions, ingestions, or
|
|
2854
|
+
.option("--queue <queue>", "all, actions, ingestions, or postgres_queue. Defaults to all. Legacy aliases: postgres_jobs, bullmq, redis, jobs.")
|
|
2602
2855
|
.option("--limit <n>", "Maximum failed items per queue. Defaults to 25; server cap is 100.")
|
|
2603
2856
|
.option("--json", "Print a JSON envelope.")
|
|
2604
2857
|
.action(async (options) => {
|
|
2605
|
-
await handleAsyncAction("worker failures", options,
|
|
2858
|
+
await handleAsyncAction("worker failures", options, () => {
|
|
2606
2859
|
const query = new URLSearchParams();
|
|
2607
2860
|
if (readOption(options.queue))
|
|
2608
2861
|
query.set("queue", readOption(options.queue) ?? "");
|
|
@@ -2617,7 +2870,7 @@ export function createProgram() {
|
|
|
2617
2870
|
.description("Repair stale background action, ingestion, and workflow queue state.")
|
|
2618
2871
|
.option("--json", "Print a JSON envelope.")
|
|
2619
2872
|
.action(async (options) => {
|
|
2620
|
-
await handleAsyncAction("worker repair", options,
|
|
2873
|
+
await handleAsyncAction("worker repair", options, () => requestOxygen("/api/cli/worker/repair", {
|
|
2621
2874
|
method: "POST",
|
|
2622
2875
|
body: {},
|
|
2623
2876
|
}));
|
|
@@ -2632,22 +2885,13 @@ export function createProgram() {
|
|
|
2632
2885
|
.option("--recipe-timeout-ms <n>", "Compatibility option; ignored by the Postgres worker.")
|
|
2633
2886
|
.option("--json", "Print a JSON envelope.")
|
|
2634
2887
|
.action(async (options) => {
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
const recipeTimeoutMs = readPositiveInt(options.recipeTimeoutMs);
|
|
2641
|
-
await handleAsyncAction("worker run-once", options, async () => requestOxygen("/api/cli/worker/run-once", {
|
|
2888
|
+
// The legacy tuning options are accepted for backward compatibility but
|
|
2889
|
+
// intentionally ignored: the Postgres worker has no BullMQ knobs, and the
|
|
2890
|
+
// route runs repairTenantWorkspaceQueues regardless of the request body.
|
|
2891
|
+
// Send an empty body so we stop modeling ignored knobs as meaningful fields.
|
|
2892
|
+
await handleAsyncAction("worker run-once", options, () => requestOxygen("/api/cli/worker/run-once", {
|
|
2642
2893
|
method: "POST",
|
|
2643
|
-
body: {
|
|
2644
|
-
...(claimLimit ? { claim_limit: claimLimit } : {}),
|
|
2645
|
-
...(concurrency ? { concurrency } : {}),
|
|
2646
|
-
...(enrichmentConcurrency ? { enrichment_concurrency: enrichmentConcurrency } : {}),
|
|
2647
|
-
...(leaseSeconds ? { lease_seconds: leaseSeconds } : {}),
|
|
2648
|
-
...(providerTimeoutMs ? { provider_timeout_ms: providerTimeoutMs } : {}),
|
|
2649
|
-
...(recipeTimeoutMs ? { recipe_timeout_ms: recipeTimeoutMs } : {}),
|
|
2650
|
-
},
|
|
2894
|
+
body: {},
|
|
2651
2895
|
}));
|
|
2652
2896
|
}));
|
|
2653
2897
|
program
|
|
@@ -2657,7 +2901,7 @@ export function createProgram() {
|
|
|
2657
2901
|
.description("Show the current plan and managed credit balance.")
|
|
2658
2902
|
.option("--json", "Print a JSON envelope.")
|
|
2659
2903
|
.action(async (options) => {
|
|
2660
|
-
await handleAsyncAction("billing balance", options,
|
|
2904
|
+
await handleAsyncAction("billing balance", options, () => requestOxygen("/api/cli/billing/balance"));
|
|
2661
2905
|
}))
|
|
2662
2906
|
.addCommand(new Command("usage")
|
|
2663
2907
|
.description("Show credit ledger events.")
|
|
@@ -2675,7 +2919,7 @@ export function createProgram() {
|
|
|
2675
2919
|
.option("--nonzero", "Only include events that changed available or reserved credits.")
|
|
2676
2920
|
.option("--json", "Print a JSON envelope.")
|
|
2677
2921
|
.action(async (options) => {
|
|
2678
|
-
await handleAsyncAction("billing usage", options,
|
|
2922
|
+
await handleAsyncAction("billing usage", options, () => {
|
|
2679
2923
|
const params = buildBillingLedgerParams(options);
|
|
2680
2924
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
2681
2925
|
return requestOxygen(`/api/cli/billing/usage${suffix}`);
|
|
@@ -2697,7 +2941,7 @@ export function createProgram() {
|
|
|
2697
2941
|
.option("--nonzero", "Only include events that changed available or reserved credits. Default for audit except BYOK filters.")
|
|
2698
2942
|
.option("--json", "Print a JSON envelope.")
|
|
2699
2943
|
.action(async (options) => {
|
|
2700
|
-
await handleAsyncAction("billing audit", options,
|
|
2944
|
+
await handleAsyncAction("billing audit", options, () => {
|
|
2701
2945
|
const params = buildBillingLedgerParams(options);
|
|
2702
2946
|
if (readOption(options.groupBy))
|
|
2703
2947
|
params.set("group_by", readOption(options.groupBy));
|
|
@@ -2714,7 +2958,7 @@ export function createProgram() {
|
|
|
2714
2958
|
.option("--description <text>", "Ledger description for the admin grant.")
|
|
2715
2959
|
.option("--json", "Print a JSON envelope.")
|
|
2716
2960
|
.action(async (options) => {
|
|
2717
|
-
await handleAsyncAction("billing grant", options,
|
|
2961
|
+
await handleAsyncAction("billing grant", options, () => requestOxygen("/api/cli/billing/grant", {
|
|
2718
2962
|
method: "POST",
|
|
2719
2963
|
body: {
|
|
2720
2964
|
credits: readPositiveNumber(options.credits),
|
|
@@ -2726,6 +2970,54 @@ export function createProgram() {
|
|
|
2726
2970
|
},
|
|
2727
2971
|
}));
|
|
2728
2972
|
}));
|
|
2973
|
+
program
|
|
2974
|
+
.command("budget")
|
|
2975
|
+
.description("Standing credit caps (org/table daily/monthly hard-blocks) beyond per-run max_credits.")
|
|
2976
|
+
.addCommand(new Command("list")
|
|
2977
|
+
.description("List the organization's standing budget policies.")
|
|
2978
|
+
.option("--scope <scope>", "Filter by scope: org, table, api_key, workflow_trigger, monitor.")
|
|
2979
|
+
.option("--status <status>", "Filter by status. Defaults to all.")
|
|
2980
|
+
.option("--json", "Print a JSON envelope.")
|
|
2981
|
+
.action(async (options) => {
|
|
2982
|
+
await handleAsyncAction("budget list", options, () => {
|
|
2983
|
+
const params = new URLSearchParams();
|
|
2984
|
+
if (readOption(options.scope))
|
|
2985
|
+
params.set("scope", readOption(options.scope));
|
|
2986
|
+
if (readOption(options.status))
|
|
2987
|
+
params.set("status", readOption(options.status));
|
|
2988
|
+
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
2989
|
+
return requestOxygen(`/api/cli/budget${suffix}`);
|
|
2990
|
+
});
|
|
2991
|
+
}))
|
|
2992
|
+
.addCommand(new Command("set")
|
|
2993
|
+
.description("Set, raise, or lower a standing credit cap (idempotent per scope+window).")
|
|
2994
|
+
.requiredOption("--scope <scope>", "org, table, api_key, workflow_trigger, or monitor.")
|
|
2995
|
+
.requiredOption("--window <window>", "per_run, daily, or monthly.")
|
|
2996
|
+
.requiredOption("--max-credits <credits>", "Cap in Oxygen credits.")
|
|
2997
|
+
.option("--scope-id <id>", "Table id/slug for --scope table (or other scope id). Omit for a scope-wide cap.")
|
|
2998
|
+
.option("--action <action>", "warn or hard_block. Defaults to hard_block.")
|
|
2999
|
+
.option("--json", "Print a JSON envelope.")
|
|
3000
|
+
.action(async (options) => {
|
|
3001
|
+
await handleAsyncAction("budget set", options, () => requestOxygen("/api/cli/budget", {
|
|
3002
|
+
method: "POST",
|
|
3003
|
+
body: {
|
|
3004
|
+
scope: readOption(options.scope),
|
|
3005
|
+
window: readOption(options.window),
|
|
3006
|
+
max_credits: readPositiveNumber(options.maxCredits),
|
|
3007
|
+
...(readOption(options.scopeId) ? { scope_id: readOption(options.scopeId) } : {}),
|
|
3008
|
+
...(readOption(options.action) ? { action: readOption(options.action) } : {}),
|
|
3009
|
+
},
|
|
3010
|
+
}));
|
|
3011
|
+
}))
|
|
3012
|
+
.addCommand(new Command("delete")
|
|
3013
|
+
.description("Delete a standing budget policy by id.")
|
|
3014
|
+
.requiredOption("--id <id>", "Budget policy id.")
|
|
3015
|
+
.option("--json", "Print a JSON envelope.")
|
|
3016
|
+
.action(async (options) => {
|
|
3017
|
+
await handleAsyncAction("budget delete", options, () => requestOxygen(`/api/cli/budget?id=${encodeURIComponent(readOption(options.id))}`, {
|
|
3018
|
+
method: "DELETE",
|
|
3019
|
+
}));
|
|
3020
|
+
}));
|
|
2729
3021
|
program
|
|
2730
3022
|
.command("admin")
|
|
2731
3023
|
.description("Staff-only commands.")
|
|
@@ -2735,7 +3027,7 @@ export function createProgram() {
|
|
|
2735
3027
|
.option("--credential-mode <mode>", "managed (default; real COGS), byok (customer-supplied credentials), or all.", "managed")
|
|
2736
3028
|
.option("--json", "Print a JSON envelope.")
|
|
2737
3029
|
.action(async (options) => {
|
|
2738
|
-
await handleAsyncAction("admin costs", options,
|
|
3030
|
+
await handleAsyncAction("admin costs", options, () => {
|
|
2739
3031
|
const params = new URLSearchParams();
|
|
2740
3032
|
const top = readPositiveInt(options.top);
|
|
2741
3033
|
if (top)
|
|
@@ -2803,7 +3095,7 @@ export function createProgram() {
|
|
|
2803
3095
|
.option("--limit <n>", "Maximum events to return. Defaults to 50.")
|
|
2804
3096
|
.option("--json", "Print a JSON envelope.")
|
|
2805
3097
|
.action(async (options) => {
|
|
2806
|
-
await handleAsyncAction("observability events", options,
|
|
3098
|
+
await handleAsyncAction("observability events", options, () => {
|
|
2807
3099
|
const params = new URLSearchParams();
|
|
2808
3100
|
const status = readOption(options.status);
|
|
2809
3101
|
const traceId = readOption(options.traceId);
|
|
@@ -2829,7 +3121,7 @@ export function createProgram() {
|
|
|
2829
3121
|
.option("--limit <n>", "Maximum runs to return. Defaults to 50.")
|
|
2830
3122
|
.option("--json", "Print a JSON envelope.")
|
|
2831
3123
|
.action(async (options) => {
|
|
2832
|
-
await handleAsyncAction("runs list", options,
|
|
3124
|
+
await handleAsyncAction("runs list", options, () => {
|
|
2833
3125
|
const limit = readPositiveInt(options.limit);
|
|
2834
3126
|
return requestOxygen(`/api/cli/runs${limit ? `?limit=${limit}` : ""}`);
|
|
2835
3127
|
});
|
|
@@ -2839,7 +3131,7 @@ export function createProgram() {
|
|
|
2839
3131
|
.argument("<run_id>", "Run UUID.")
|
|
2840
3132
|
.option("--json", "Print a JSON envelope.")
|
|
2841
3133
|
.action(async (runId, options) => {
|
|
2842
|
-
await handleAsyncAction("runs get", options,
|
|
3134
|
+
await handleAsyncAction("runs get", options, () => requestOxygen("/api/cli/runs/get", {
|
|
2843
3135
|
method: "POST",
|
|
2844
3136
|
body: { run_id: runId },
|
|
2845
3137
|
}));
|
|
@@ -2854,7 +3146,7 @@ export function createProgram() {
|
|
|
2854
3146
|
.option("--limit <n>", "Maximum changes to return. Defaults to 50.")
|
|
2855
3147
|
.option("--json", "Print a JSON envelope.")
|
|
2856
3148
|
.action(async (table, rowId, options) => {
|
|
2857
|
-
await handleAsyncAction("rows history", options,
|
|
3149
|
+
await handleAsyncAction("rows history", options, () => {
|
|
2858
3150
|
const limit = readPositiveInt(options.limit);
|
|
2859
3151
|
return requestOxygen("/api/cli/tables/rows/history", {
|
|
2860
3152
|
method: "POST",
|
|
@@ -2877,7 +3169,7 @@ export function createProgram() {
|
|
|
2877
3169
|
.option("--history-limit <n>", "Maximum recent cell changes to include. Defaults to 10; cap 50.")
|
|
2878
3170
|
.option("--json", "Print a JSON envelope.")
|
|
2879
3171
|
.action(async (table, rowId, column, options) => {
|
|
2880
|
-
await handleAsyncAction("cells inspect", options,
|
|
3172
|
+
await handleAsyncAction("cells inspect", options, () => {
|
|
2881
3173
|
const historyLimit = readPositiveInt(options.historyLimit);
|
|
2882
3174
|
return requestOxygen("/api/cli/tables/cells/inspect", {
|
|
2883
3175
|
method: "POST",
|
|
@@ -2898,7 +3190,7 @@ export function createProgram() {
|
|
|
2898
3190
|
.option("--limit <n>", "Maximum changes to return. Defaults to 50.")
|
|
2899
3191
|
.option("--json", "Print a JSON envelope.")
|
|
2900
3192
|
.action(async (table, rowId, column, options) => {
|
|
2901
|
-
await handleAsyncAction("cells history", options,
|
|
3193
|
+
await handleAsyncAction("cells history", options, () => {
|
|
2902
3194
|
const limit = readPositiveInt(options.limit);
|
|
2903
3195
|
return requestOxygen("/api/cli/tables/cells/history", {
|
|
2904
3196
|
method: "POST",
|
|
@@ -2923,6 +3215,7 @@ export function createProgram() {
|
|
|
2923
3215
|
.option("--session-id <id>", "Session id to update. Defaults to the current session.")
|
|
2924
3216
|
.option("--json", "Print a JSON envelope.")
|
|
2925
3217
|
.action(async (options) => {
|
|
3218
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous session helpers to satisfy handleAsyncAction's Promise<unknown> action
|
|
2926
3219
|
await handleAsyncAction("session start", options, async () => {
|
|
2927
3220
|
const updateIndex = readNonNegativeInt(options.update);
|
|
2928
3221
|
if (updateIndex !== undefined) {
|
|
@@ -2950,6 +3243,7 @@ export function createProgram() {
|
|
|
2950
3243
|
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
2951
3244
|
.option("--json", "Print a JSON envelope.")
|
|
2952
3245
|
.action(async (options) => {
|
|
3246
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous addSessionStatus helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
2953
3247
|
await handleAsyncAction("session status", options, async () => addSessionStatus({
|
|
2954
3248
|
sessionId: readOption(options.sessionId),
|
|
2955
3249
|
message: options.message,
|
|
@@ -2966,6 +3260,7 @@ export function createProgram() {
|
|
|
2966
3260
|
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
2967
3261
|
.option("--json", "Print a JSON envelope.")
|
|
2968
3262
|
.action(async (options) => {
|
|
3263
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous addSessionOutput helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
2969
3264
|
await handleAsyncAction("session output", options, async () => addSessionOutput({
|
|
2970
3265
|
sessionId: readOption(options.sessionId),
|
|
2971
3266
|
csv: readOption(options.csv),
|
|
@@ -2980,6 +3275,7 @@ export function createProgram() {
|
|
|
2980
3275
|
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
2981
3276
|
.option("--json", "Print a JSON envelope.")
|
|
2982
3277
|
.action(async (options) => {
|
|
3278
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous getSessionUsage helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
2983
3279
|
await handleAsyncAction("session usage", options, async () => getSessionUsage({ sessionId: readOption(options.sessionId) }));
|
|
2984
3280
|
}));
|
|
2985
3281
|
program
|
|
@@ -3049,7 +3345,7 @@ export function createProgram() {
|
|
|
3049
3345
|
.argument("<tool_id>", "Tool id.")
|
|
3050
3346
|
.option("--json", "Print a JSON envelope.")
|
|
3051
3347
|
.action(async (toolId, options) => {
|
|
3052
|
-
await handleAsyncAction("tools get", options,
|
|
3348
|
+
await handleAsyncAction("tools get", options, () => requestOxygen(`/api/cli/tools/${encodeURIComponent(toolId)}`));
|
|
3053
3349
|
}))
|
|
3054
3350
|
.addCommand(new Command("enums")
|
|
3055
3351
|
.description("Provider enum catalogs for fields that accept normalized values.")
|
|
@@ -3147,6 +3443,7 @@ export function createProgram() {
|
|
|
3147
3443
|
.option("--email-pattern-validation <mode>", "Work-email pattern pre-step: leadmagic_valid_only or disabled.")
|
|
3148
3444
|
.option("--phone-waterfall-profile <profile>", "Phone waterfall profile: auto (input-aware), linkedin_url, email, or name_domain. Auto picks the cheapest cost-ordered provider set for each row's inputs (mobile match rate ~30-60%).")
|
|
3149
3445
|
.option("--verify-phone", "Validate found phone numbers with ClearoutPhone (adds phone_line_type + phone_carrier; filter phone_line_type=mobile for mobile-only).")
|
|
3446
|
+
.option("--allow-premium-lanes", "Opt in to premium high-cost lanes (e.g. >25cr/row phone reveal). Off by default: premium lanes are skipped so an unattended run can't bill-shock.")
|
|
3150
3447
|
.option("--phone-verification-credential-mode <mode>", "ClearoutPhone credential mode for phone verification: managed or user_api_key.")
|
|
3151
3448
|
.option("--limit <n>", "Rows to estimate. Defaults to 10.")
|
|
3152
3449
|
.option("--all", "Estimate all rows.")
|
|
@@ -3155,7 +3452,7 @@ export function createProgram() {
|
|
|
3155
3452
|
.option("--only-missing", "Estimate rows missing the capability's normalized output when no explicit selection is passed.")
|
|
3156
3453
|
.option("--json", "Print a JSON envelope.")
|
|
3157
3454
|
.action(async (table, options) => {
|
|
3158
|
-
await handleAsyncAction("enrich-column preview", options,
|
|
3455
|
+
await handleAsyncAction("enrich-column preview", options, () => requestOxygen("/api/cli/enrich-column/preview", {
|
|
3159
3456
|
method: "POST",
|
|
3160
3457
|
body: buildEnrichColumnBody(table, options),
|
|
3161
3458
|
}));
|
|
@@ -3181,6 +3478,7 @@ export function createProgram() {
|
|
|
3181
3478
|
.option("--email-pattern-validation <mode>", "Work-email pattern pre-step: leadmagic_valid_only or disabled.")
|
|
3182
3479
|
.option("--phone-waterfall-profile <profile>", "Phone waterfall profile: auto (input-aware), linkedin_url, email, or name_domain. Auto picks the cheapest cost-ordered provider set for each row's inputs (mobile match rate ~30-60%).")
|
|
3183
3480
|
.option("--verify-phone", "Validate found phone numbers with ClearoutPhone (adds phone_line_type + phone_carrier; filter phone_line_type=mobile for mobile-only).")
|
|
3481
|
+
.option("--allow-premium-lanes", "Opt in to premium high-cost lanes (e.g. >25cr/row phone reveal). Off by default: premium lanes are skipped so an unattended run can't bill-shock.")
|
|
3184
3482
|
.option("--phone-verification-credential-mode <mode>", "ClearoutPhone credential mode for phone verification: managed or user_api_key.")
|
|
3185
3483
|
.option("--limit <n>", "Rows to queue.")
|
|
3186
3484
|
.option("--all", "Queue all rows.")
|
|
@@ -3192,7 +3490,7 @@ export function createProgram() {
|
|
|
3192
3490
|
.option("--json", "Print a JSON envelope.")
|
|
3193
3491
|
.action(async (table, options) => {
|
|
3194
3492
|
const maxConcurrency = readPositiveInt(options.maxConcurrency);
|
|
3195
|
-
await handleAsyncAction("enrich-column run", options,
|
|
3493
|
+
await handleAsyncAction("enrich-column run", options, () => requestOxygen("/api/cli/enrich-column/run", {
|
|
3196
3494
|
method: "POST",
|
|
3197
3495
|
body: {
|
|
3198
3496
|
...buildEnrichColumnBody(table, options),
|
|
@@ -3202,6 +3500,62 @@ export function createProgram() {
|
|
|
3202
3500
|
},
|
|
3203
3501
|
}));
|
|
3204
3502
|
}));
|
|
3503
|
+
program
|
|
3504
|
+
.command("find")
|
|
3505
|
+
.description("One-shot contact and company lookups over enrichment waterfalls (no table). dry_run previews for free; live spends and needs --max-credits.")
|
|
3506
|
+
.addCommand(new Command("email")
|
|
3507
|
+
.description("Find a person's work email via an input-aware multi-provider waterfall: a LinkedIn URL, name+domain, or first/last+domain each select the best provider profile, with an email-pattern pre-step and verification. dry_run shows the resolved profile + chain for free.")
|
|
3508
|
+
.option("--linkedin-url <url>", "Person LinkedIn profile URL (strongest signal).")
|
|
3509
|
+
.option("--full-name <name>", "Person full name.")
|
|
3510
|
+
.option("--first-name <name>", "Person first name.")
|
|
3511
|
+
.option("--last-name <name>", "Person last name.")
|
|
3512
|
+
.option("--company-domain <domain>", "Company apex domain, e.g. acme.com.")
|
|
3513
|
+
.option("--company-name <name>", "Company name.")
|
|
3514
|
+
.option("--mode <mode>", "dry_run (default) or live. live spends credits.")
|
|
3515
|
+
.option("--max-credits <credits>", "Spend ceiling. Required for --mode live.")
|
|
3516
|
+
.option("--json", "Print a JSON envelope.")
|
|
3517
|
+
.action(async (options) => {
|
|
3518
|
+
await handleAsyncAction("find email", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("email", options) }));
|
|
3519
|
+
}))
|
|
3520
|
+
.addCommand(new Command("phone")
|
|
3521
|
+
.description("Find a person's mobile phone via a multi-provider waterfall. The provider chain is input-aware: a LinkedIn URL unlocks the full cost-ordered chain (Blitz first), email/name use the providers that accept them.")
|
|
3522
|
+
.option("--linkedin-url <url>", "Person LinkedIn profile URL (strongest signal for phone).")
|
|
3523
|
+
.option("--email <email>", "Known email as an identity fallback.")
|
|
3524
|
+
.option("--full-name <name>", "Person full name.")
|
|
3525
|
+
.option("--company-domain <domain>", "Company apex domain.")
|
|
3526
|
+
.option("--company-name <name>", "Company name.")
|
|
3527
|
+
.option("--verify", "Verify the found number with ClearoutPhone — adds line_type/carrier and keeps the number on a non-verdict (only a genuine 'not valid' is discarded).")
|
|
3528
|
+
.option("--mode <mode>", "dry_run (default) or live. live spends credits.")
|
|
3529
|
+
.option("--max-credits <credits>", "Spend ceiling. Required for --mode live.")
|
|
3530
|
+
.option("--json", "Print a JSON envelope.")
|
|
3531
|
+
.action(async (options) => {
|
|
3532
|
+
await handleAsyncAction("find phone", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("phone", options) }));
|
|
3533
|
+
}))
|
|
3534
|
+
.addCommand(new Command("linkedin")
|
|
3535
|
+
.description("Resolve a person's LinkedIn URL from name + company (or email), with identity validation.")
|
|
3536
|
+
.option("--full-name <name>", "Person full name.")
|
|
3537
|
+
.option("--email <email>", "Known email.")
|
|
3538
|
+
.option("--company-domain <domain>", "Company apex domain.")
|
|
3539
|
+
.option("--company-name <name>", "Company name.")
|
|
3540
|
+
.option("--company-linkedin-url <url>", "Company LinkedIn URL.")
|
|
3541
|
+
.option("--mode <mode>", "dry_run (default) or live. live spends credits.")
|
|
3542
|
+
.option("--max-credits <credits>", "Spend ceiling. Required for --mode live.")
|
|
3543
|
+
.option("--json", "Print a JSON envelope.")
|
|
3544
|
+
.action(async (options) => {
|
|
3545
|
+
await handleAsyncAction("find linkedin", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("linkedin", options) }));
|
|
3546
|
+
}))
|
|
3547
|
+
.addCommand(new Command("company")
|
|
3548
|
+
.description("Enrich a company (domain/linkedin/headcount/industry/funding/tech/hiring/profile) via a waterfall.")
|
|
3549
|
+
.option("--domain <domain>", "Company apex domain.")
|
|
3550
|
+
.option("--name <name>", "Company name.")
|
|
3551
|
+
.option("--linkedin-url <url>", "Company LinkedIn URL.")
|
|
3552
|
+
.option("--fields <fields>", "Comma-separated company fields. Defaults to domain,linkedin_url,headcount,industry.")
|
|
3553
|
+
.option("--mode <mode>", "dry_run (default) or live. live spends credits.")
|
|
3554
|
+
.option("--max-credits <credits>", "Spend ceiling. Required for --mode live.")
|
|
3555
|
+
.option("--json", "Print a JSON envelope.")
|
|
3556
|
+
.action(async (options) => {
|
|
3557
|
+
await handleAsyncAction("find company", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("company", options) }));
|
|
3558
|
+
}));
|
|
3205
3559
|
program
|
|
3206
3560
|
.command("enrichment")
|
|
3207
3561
|
.description("High-level enrichment column definition helpers.")
|
|
@@ -3233,7 +3587,7 @@ export function createProgram() {
|
|
|
3233
3587
|
.option("--toolkit <id>", "Filter by toolkit / integration id, such as gmail.")
|
|
3234
3588
|
.option("--json", "Print a JSON envelope.")
|
|
3235
3589
|
.action(async (options) => {
|
|
3236
|
-
await handleAsyncAction("integrations events list", options,
|
|
3590
|
+
await handleAsyncAction("integrations events list", options, () => {
|
|
3237
3591
|
const query = new URLSearchParams();
|
|
3238
3592
|
if (readOption(options.source))
|
|
3239
3593
|
query.set("source", readOption(options.source) ?? "");
|
|
@@ -3253,22 +3607,17 @@ export function createProgram() {
|
|
|
3253
3607
|
.option("--trigger-config <json>", "JSON object passed to the provider when registering the trigger (Composio triggers only).")
|
|
3254
3608
|
.option("--json", "Print a JSON envelope.")
|
|
3255
3609
|
.action(async (options) => {
|
|
3256
|
-
await handleAsyncAction("integrations events enable", options,
|
|
3610
|
+
await handleAsyncAction("integrations events enable", options, () => {
|
|
3257
3611
|
const triggerConfigRaw = readOption(options.triggerConfig);
|
|
3258
3612
|
let triggerConfig;
|
|
3259
3613
|
if (triggerConfigRaw) {
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
}
|
|
3265
|
-
triggerConfig = parsed;
|
|
3266
|
-
}
|
|
3267
|
-
catch (error) {
|
|
3268
|
-
throw new Error(error instanceof Error
|
|
3269
|
-
? `Invalid --trigger-config: ${error.message}`
|
|
3270
|
-
: "Invalid --trigger-config");
|
|
3614
|
+
const parsed = parseJsonValue(triggerConfigRaw, "--trigger-config");
|
|
3615
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3616
|
+
throw new OxygenError("invalid_request", "--trigger-config must be a JSON object.", {
|
|
3617
|
+
exitCode: 1,
|
|
3618
|
+
});
|
|
3271
3619
|
}
|
|
3620
|
+
triggerConfig = parsed;
|
|
3272
3621
|
}
|
|
3273
3622
|
return requestOxygen("/api/cli/integrations/events/enable", {
|
|
3274
3623
|
method: "POST",
|
|
@@ -3288,7 +3637,7 @@ export function createProgram() {
|
|
|
3288
3637
|
.option("--connection-id <connection_id>", "Specific integration connection id. Defaults to the active default connection.")
|
|
3289
3638
|
.option("--json", "Print a JSON envelope.")
|
|
3290
3639
|
.action(async (options) => {
|
|
3291
|
-
await handleAsyncAction("integrations events disable", options,
|
|
3640
|
+
await handleAsyncAction("integrations events disable", options, () => requestOxygen("/api/cli/integrations/events/disable", {
|
|
3292
3641
|
method: "POST",
|
|
3293
3642
|
body: {
|
|
3294
3643
|
source: readOption(options.source),
|
|
@@ -3328,21 +3677,24 @@ export function createProgram() {
|
|
|
3328
3677
|
.description("List supported integrations and this org's connections.")
|
|
3329
3678
|
.option("--json", "Print a JSON envelope.")
|
|
3330
3679
|
.action(async (options) => {
|
|
3331
|
-
await handleAsyncAction("integrations list", options,
|
|
3680
|
+
await handleAsyncAction("integrations list", options, () => requestOxygen("/api/cli/integrations/composio/list"));
|
|
3332
3681
|
}))
|
|
3333
3682
|
.addCommand(new Command("connect")
|
|
3334
3683
|
.description("Connect an integration. OAuth toolkits return a redirect URL; API-key integrations accept --api-key.")
|
|
3335
3684
|
.argument("<integration_id>", "Integration id, such as 'slack' or 'serpapi'.")
|
|
3336
3685
|
.option("--api-key <value>", "API key for Composio API-key toolkits (e.g. SerpAPI, Resend).")
|
|
3686
|
+
.option("--account-id <id>", "Provider account id for integrations that span multiple accounts (e.g. Cloudflare when the token can access more than one account).")
|
|
3337
3687
|
.option("--json", "Print a JSON envelope.")
|
|
3338
3688
|
.action(async (integrationId, options) => {
|
|
3339
|
-
await handleAsyncAction("integrations connect", options,
|
|
3689
|
+
await handleAsyncAction("integrations connect", options, () => {
|
|
3340
3690
|
const apiKey = readOption(options.apiKey)?.trim();
|
|
3691
|
+
const accountId = readOption(options.accountId);
|
|
3341
3692
|
return requestOxygen("/api/cli/integrations/connect", {
|
|
3342
3693
|
method: "POST",
|
|
3343
3694
|
body: {
|
|
3344
3695
|
integration_id: integrationId,
|
|
3345
3696
|
...(apiKey ? { api_key: apiKey } : {}),
|
|
3697
|
+
...(accountId ? { account_id: accountId } : {}),
|
|
3346
3698
|
},
|
|
3347
3699
|
});
|
|
3348
3700
|
});
|
|
@@ -3367,7 +3719,7 @@ export function createProgram() {
|
|
|
3367
3719
|
.argument("<integration_id>", "Integration id, such as 'slack'.")
|
|
3368
3720
|
.option("--json", "Print a JSON envelope.")
|
|
3369
3721
|
.action(async (integrationId, options) => {
|
|
3370
|
-
await handleAsyncAction("integrations actions", options,
|
|
3722
|
+
await handleAsyncAction("integrations actions", options, () => {
|
|
3371
3723
|
const params = new URLSearchParams({ integration_id: integrationId });
|
|
3372
3724
|
return requestOxygen(`/api/cli/integrations/composio/actions?${params.toString()}`);
|
|
3373
3725
|
});
|
|
@@ -3382,7 +3734,7 @@ export function createProgram() {
|
|
|
3382
3734
|
.option("--mode <mode>", "'live' or 'dry_run'. Overridden by --live/--dry-run if provided.")
|
|
3383
3735
|
.option("--json", "Print a JSON envelope.")
|
|
3384
3736
|
.action(async (integrationId, actionSlug, options) => {
|
|
3385
|
-
await handleAsyncAction("integrations run", options,
|
|
3737
|
+
await handleAsyncAction("integrations run", options, () => {
|
|
3386
3738
|
const args = readOption(options.input)
|
|
3387
3739
|
? parseJsonObject(readOption(options.input))
|
|
3388
3740
|
: {};
|
|
@@ -3437,7 +3789,7 @@ export function createProgram() {
|
|
|
3437
3789
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
3438
3790
|
.option("--json", "Print a JSON envelope.")
|
|
3439
3791
|
.action(async (id, options) => {
|
|
3440
|
-
await handleAsyncAction("senders get", options,
|
|
3792
|
+
await handleAsyncAction("senders get", options, () => requestOxygen(`/api/cli/senders/${encodeURIComponent(id)}`));
|
|
3441
3793
|
}))
|
|
3442
3794
|
.addCommand(new Command("sync")
|
|
3443
3795
|
.description("Refresh LinkedIn account state (status, profile) from Unipile. Pass --connection-id to sync one account, or omit it to sync all.")
|
|
@@ -3459,7 +3811,7 @@ export function createProgram() {
|
|
|
3459
3811
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
3460
3812
|
.option("--json", "Print a JSON envelope.")
|
|
3461
3813
|
.action(async (id, options) => {
|
|
3462
|
-
await handleAsyncAction("senders disconnect", options,
|
|
3814
|
+
await handleAsyncAction("senders disconnect", options, () => requestOxygen(`/api/cli/senders/${encodeURIComponent(id)}/disconnect`, {
|
|
3463
3815
|
method: "POST",
|
|
3464
3816
|
}));
|
|
3465
3817
|
}))
|
|
@@ -3471,7 +3823,7 @@ export function createProgram() {
|
|
|
3471
3823
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
3472
3824
|
.option("--json", "Print a JSON envelope.")
|
|
3473
3825
|
.action(async (id, options) => {
|
|
3474
|
-
await handleAsyncAction("senders limits get", options,
|
|
3826
|
+
await handleAsyncAction("senders limits get", options, () => requestOxygen(`/api/cli/senders/${encodeURIComponent(id)}/limits`));
|
|
3475
3827
|
}))
|
|
3476
3828
|
.addCommand(new Command("set")
|
|
3477
3829
|
.description("Adjust per-account daily action limits and the daily-reset timezone. Values are clamped to safe maximums (e.g. max 80 invites/day). Send windows (time of day) are set per sequence in the campaign schedule, not per account. <id> accepts a sender account id, connection id, or Unipile account id.")
|
|
@@ -3556,19 +3908,24 @@ export function createProgram() {
|
|
|
3556
3908
|
});
|
|
3557
3909
|
}))
|
|
3558
3910
|
.addCommand(new Command("send")
|
|
3559
|
-
.description("Reply into a LinkedIn conversation. Sends a real
|
|
3560
|
-
.argument("<conversation>", "Conversation id
|
|
3911
|
+
.description("Reply into a conversation. --channel email replies from the conversation's own mailbox (Zapbox/native Gmail/Graph, threaded); default replies into the LinkedIn conversation. Sends a real message — requires --approved. Without it, returns a preview.")
|
|
3912
|
+
.argument("<conversation>", "Conversation id, Unipile chat id, or (email) Zapbox thread id.")
|
|
3561
3913
|
.requiredOption("--text <message>", "Reply text to send.")
|
|
3914
|
+
.option("--channel <channel>", "Inbox channel: linkedin (default) or email.")
|
|
3562
3915
|
.option("--approved", "Approve and send the message. Without this flag, returns a preview only.")
|
|
3563
3916
|
.option("--json", "Print a JSON envelope.")
|
|
3564
3917
|
.action(async (conversation, options) => {
|
|
3565
|
-
await handleAsyncAction("inbox send", options, () =>
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3918
|
+
await handleAsyncAction("inbox send", options, () => {
|
|
3919
|
+
const channel = readOption(options.channel);
|
|
3920
|
+
return requestOxygen(`/api/cli/inbox/${encodeURIComponent(conversation)}/send`, {
|
|
3921
|
+
method: "POST",
|
|
3922
|
+
body: {
|
|
3923
|
+
text: readOption(options.text),
|
|
3924
|
+
...(channel ? { channel } : {}),
|
|
3925
|
+
...(options.approved ? { approved: true } : {}),
|
|
3926
|
+
},
|
|
3927
|
+
});
|
|
3928
|
+
});
|
|
3572
3929
|
}))
|
|
3573
3930
|
.addCommand(new Command("mark-read")
|
|
3574
3931
|
.description("Mark a conversation and all its messages as read.")
|
|
@@ -3578,6 +3935,27 @@ export function createProgram() {
|
|
|
3578
3935
|
await handleAsyncAction("inbox mark-read", options, () => requestOxygen(`/api/cli/inbox/${encodeURIComponent(conversation)}/read`, {
|
|
3579
3936
|
method: "POST",
|
|
3580
3937
|
}));
|
|
3938
|
+
}))
|
|
3939
|
+
.addCommand(new Command("analyze")
|
|
3940
|
+
.description("Run the default analysis on an email conversation now: sentiment + interest category + a drafted reply (Oxygen's default model). The worker also does this automatically on inbound mail.")
|
|
3941
|
+
.argument("<conversation>", "Conversation id or Zapbox thread id.")
|
|
3942
|
+
.option("--json", "Print a JSON envelope.")
|
|
3943
|
+
.action(async (conversation, options) => {
|
|
3944
|
+
await handleAsyncAction("inbox analyze", options, () => requestOxygen(`/api/cli/inbox/${encodeURIComponent(conversation)}/analyze`, {
|
|
3945
|
+
method: "POST",
|
|
3946
|
+
body: { channel: "email" },
|
|
3947
|
+
}));
|
|
3948
|
+
}))
|
|
3949
|
+
.addCommand(new Command("archive")
|
|
3950
|
+
.description("Archive an email conversation (triage it out of the inbox). --unarchive restores it.")
|
|
3951
|
+
.argument("<conversation>", "Conversation id or Zapbox thread id.")
|
|
3952
|
+
.option("--unarchive", "Restore the conversation instead of archiving it.")
|
|
3953
|
+
.option("--json", "Print a JSON envelope.")
|
|
3954
|
+
.action(async (conversation, options) => {
|
|
3955
|
+
await handleAsyncAction("inbox archive", options, () => requestOxygen(`/api/cli/inbox/${encodeURIComponent(conversation)}/archive`, {
|
|
3956
|
+
method: "POST",
|
|
3957
|
+
body: { channel: "email", archived: !options.unarchive },
|
|
3958
|
+
}));
|
|
3581
3959
|
}))
|
|
3582
3960
|
.addCommand(new Command("sync")
|
|
3583
3961
|
.description("Force a backstop inbox sync from Unipile for all active sender accounts (pulls recent chats + messages into the unibox).")
|
|
@@ -3661,8 +4039,7 @@ export function createProgram() {
|
|
|
3661
4039
|
const stepsPath = readOption(options.stepsFile);
|
|
3662
4040
|
if (!stepsPath)
|
|
3663
4041
|
throw new Error("--steps-file is required.");
|
|
3664
|
-
const
|
|
3665
|
-
const definition = JSON.parse(raw);
|
|
4042
|
+
const definition = readJsonFileValue(resolve(stepsPath), "--steps-file");
|
|
3666
4043
|
const channels = readCsvOption(options.channels);
|
|
3667
4044
|
const senders = readCsvOption(options.senders);
|
|
3668
4045
|
const email = readCampaignEmailBinding(options);
|
|
@@ -3704,7 +4081,7 @@ export function createProgram() {
|
|
|
3704
4081
|
body.name = name;
|
|
3705
4082
|
const stepsPath = readOption(options.stepsFile);
|
|
3706
4083
|
if (stepsPath) {
|
|
3707
|
-
body.definition =
|
|
4084
|
+
body.definition = readJsonFileValue(resolve(stepsPath), "--steps-file");
|
|
3708
4085
|
}
|
|
3709
4086
|
const channels = readCsvOption(options.channels);
|
|
3710
4087
|
if (channels.length > 0)
|
|
@@ -3749,7 +4126,7 @@ export function createProgram() {
|
|
|
3749
4126
|
const leadsPath = readOption(options.leadsFile);
|
|
3750
4127
|
if (!leadsPath)
|
|
3751
4128
|
throw new Error("--leads-file is required.");
|
|
3752
|
-
const parsed =
|
|
4129
|
+
const parsed = readJsonFileValue(resolve(leadsPath), "--leads-file");
|
|
3753
4130
|
return requestOxygen(`/api/cli/sequences/${encodeURIComponent(sequence)}/enroll`, {
|
|
3754
4131
|
method: "POST",
|
|
3755
4132
|
body: { leads: parsed.leads ?? [] },
|
|
@@ -3901,7 +4278,7 @@ export function createProgram() {
|
|
|
3901
4278
|
throw new Error("--provider only applies with --from zapmail (inline files carry a per-mailbox provider).");
|
|
3902
4279
|
if (!filePath)
|
|
3903
4280
|
throw new Error("Provide --file <path> (a { \"mailboxes\": [...] } JSON file) or --from zapmail.");
|
|
3904
|
-
const parsed =
|
|
4281
|
+
const parsed = readJsonFileValue(resolve(filePath), "--file");
|
|
3905
4282
|
return requestOxygen("/api/cli/mailboxes", {
|
|
3906
4283
|
method: "POST",
|
|
3907
4284
|
body: { mailboxes: parsed.mailboxes ?? [] },
|
|
@@ -4000,68 +4377,177 @@ export function createProgram() {
|
|
|
4000
4377
|
return requestOxygen(`/api/cli/mailboxes/warmup/status${suffix ? `?${suffix}` : ""}`);
|
|
4001
4378
|
});
|
|
4002
4379
|
}))));
|
|
4380
|
+
program.addCommand(new Command("domains")
|
|
4381
|
+
.description("Cold-email domain management on the org's own Cloudflare account (BYOK): sync zones, inspect age/warmup/DNS health, check availability and pricing, and buy domains. Purchases bill your Cloudflare payment method, never Oxygen credits.")
|
|
4382
|
+
.addCommand(new Command("list")
|
|
4383
|
+
.description("List the org's cached Cloudflare domains with cold-email metadata (age, mailboxes, warmup, sending volume, DNS health). Reads the cache only — run `domains sync` to refresh.")
|
|
4384
|
+
.option("--status <status>", "Filter by zone status: unknown, initializing, pending, active, moved, or deleted.")
|
|
4385
|
+
.option("--registrar <registrar>", "Filter by registrar name.")
|
|
4386
|
+
.option("--q <text>", "Filter by domain substring.")
|
|
4387
|
+
.option("--json", "Print a JSON envelope.")
|
|
4388
|
+
.action(async (options) => {
|
|
4389
|
+
await handleAsyncAction("domains list", options, () => {
|
|
4390
|
+
const params = new URLSearchParams();
|
|
4391
|
+
const status = readOption(options.status);
|
|
4392
|
+
if (status)
|
|
4393
|
+
params.set("status", status);
|
|
4394
|
+
const registrar = readOption(options.registrar);
|
|
4395
|
+
if (registrar)
|
|
4396
|
+
params.set("registrar", registrar);
|
|
4397
|
+
const queryText = readOption(options.q);
|
|
4398
|
+
if (queryText)
|
|
4399
|
+
params.set("q", queryText);
|
|
4400
|
+
const suffix = params.toString();
|
|
4401
|
+
return requestOxygen(`/api/cli/domains${suffix ? `?${suffix}` : ""}`);
|
|
4402
|
+
});
|
|
4403
|
+
}))
|
|
4404
|
+
.addCommand(new Command("sync")
|
|
4405
|
+
.description("Refresh the domain cache from Cloudflare: zone pages, registrar metadata, RDAP age backfill, and DNS health. Partial (paginated) syncs auto-continue until complete.")
|
|
4406
|
+
.option("--full", "Restart the sweep from the first zone page instead of resuming the cursor.")
|
|
4407
|
+
.option("--json", "Print a JSON envelope.")
|
|
4408
|
+
.action(async (options) => {
|
|
4409
|
+
await handleAsyncAction("domains sync", options, () => runDomainsSync(options));
|
|
4410
|
+
}))
|
|
4411
|
+
.addCommand(new Command("get")
|
|
4412
|
+
.description("Get one domain's full detail: zone state, age with provenance, mailboxes, sending volume, and the last DNS health check.")
|
|
4413
|
+
.argument("<domain>", "Domain name, such as acme.com.")
|
|
4414
|
+
.option("--json", "Print a JSON envelope.")
|
|
4415
|
+
.action(async (domain, options) => {
|
|
4416
|
+
await handleAsyncAction("domains get", options, () => requestOxygen(`/api/cli/domains/${encodeURIComponent(domain)}`));
|
|
4417
|
+
}))
|
|
4418
|
+
.addCommand(new Command("dns")
|
|
4419
|
+
.description("Run a live read-only DNS health check (SPF, DKIM, DMARC, MX) against the domain's Cloudflare zone and persist the result.")
|
|
4420
|
+
.argument("<domain>", "Domain name, such as acme.com.")
|
|
4421
|
+
.option("--json", "Print a JSON envelope.")
|
|
4422
|
+
.action(async (domain, options) => {
|
|
4423
|
+
await handleAsyncAction("domains dns", options, () => requestOxygen(`/api/cli/domains/${encodeURIComponent(domain)}/dns`));
|
|
4424
|
+
}))
|
|
4425
|
+
.addCommand(new Command("search")
|
|
4426
|
+
.description("Search Cloudflare Registrar for available domains with registration/renewal pricing. Free — nothing is purchased.")
|
|
4427
|
+
.argument("<query>", "Search text, such as a brand or keyword.")
|
|
4428
|
+
.option("--json", "Print a JSON envelope.")
|
|
4429
|
+
.action(async (query, options) => {
|
|
4430
|
+
await handleAsyncAction("domains search", options, () => {
|
|
4431
|
+
const params = new URLSearchParams({ q: query });
|
|
4432
|
+
return requestOxygen(`/api/cli/domains/search?${params.toString()}`);
|
|
4433
|
+
});
|
|
4434
|
+
}))
|
|
4435
|
+
.addCommand(new Command("check")
|
|
4436
|
+
.description("Check availability and pricing for up to 20 specific domains via Cloudflare Registrar. Free — nothing is purchased.")
|
|
4437
|
+
.argument("<domains...>", "Domain names to check (max 20).")
|
|
4438
|
+
.option("--json", "Print a JSON envelope.")
|
|
4439
|
+
.action(async (domains, options) => {
|
|
4440
|
+
await handleAsyncAction("domains check", options, () => {
|
|
4441
|
+
if (domains.length > DOMAINS_CHECK_MAX_DOMAINS) {
|
|
4442
|
+
throw new Error(`Cloudflare checks at most ${DOMAINS_CHECK_MAX_DOMAINS} domains per call (got ${domains.length}). Split the list and re-run.`);
|
|
4443
|
+
}
|
|
4444
|
+
return requestOxygen("/api/cli/domains/check", {
|
|
4445
|
+
method: "POST",
|
|
4446
|
+
body: { domains },
|
|
4447
|
+
});
|
|
4448
|
+
});
|
|
4449
|
+
}))
|
|
4450
|
+
.addCommand(new Command("buy")
|
|
4451
|
+
.description("Buy domains through Cloudflare Registrar. REAL MONEY, NON-REFUNDABLE — the preview determines billing: BYOK orgs (connected Cloudflare token) bill their own Cloudflare payment method (0 Oxygen credits); managed orgs (no connected token) bill Oxygen credits at a markup on the shared Oxygen Cloudflare account. The preview's `billing` note states which applies. Without --approved, returns a priced preview plus a quote_id; re-run with --approved --quote <id> to execute.")
|
|
4452
|
+
.argument("<domains...>", "Domain names to buy.")
|
|
4453
|
+
.option("--approved", "Execute the purchase. Without this flag, returns a preview only.")
|
|
4454
|
+
.option("--quote <id>", "Quote id from the preview (required with --approved).")
|
|
4455
|
+
.option("--accept-premium", "Acknowledge premium-tier pricing for premium domains.")
|
|
4456
|
+
.option("--auto-renew", "Enable auto-renew on the new registrations.")
|
|
4457
|
+
.option("--no-privacy", "Disable WHOIS privacy/redaction on the new registrations.")
|
|
4458
|
+
.option("--wait", "After an approved purchase, poll each pending registration to a terminal state (up to 10 minutes per domain).")
|
|
4459
|
+
.option("--json", "Print a JSON envelope.")
|
|
4460
|
+
.action(async (domains, options) => {
|
|
4461
|
+
await handleAsyncAction("domains buy", options, () => runDomainsBuy(domains, options));
|
|
4462
|
+
}))
|
|
4463
|
+
.addCommand(new Command("registrations")
|
|
4464
|
+
.description("List the org's domain purchase ledger: every registration attempt with status and price snapshot.")
|
|
4465
|
+
.option("--status <status>", "Filter by ledger status: submitting, in_progress, succeeded, failed, action_required, or blocked.")
|
|
4466
|
+
.option("--json", "Print a JSON envelope.")
|
|
4467
|
+
.action(async (options) => {
|
|
4468
|
+
await handleAsyncAction("domains registrations", options, () => {
|
|
4469
|
+
const params = new URLSearchParams();
|
|
4470
|
+
const status = readOption(options.status);
|
|
4471
|
+
if (status)
|
|
4472
|
+
params.set("status", status);
|
|
4473
|
+
const suffix = params.toString();
|
|
4474
|
+
return requestOxygen(`/api/cli/domains/registrations${suffix ? `?${suffix}` : ""}`);
|
|
4475
|
+
});
|
|
4476
|
+
}))
|
|
4477
|
+
.addCommand(new Command("registration-status")
|
|
4478
|
+
.description("Get one domain registration's ledger state (the server performs a gated live poll when due — safe to call repeatedly). Pass --wait to poll until it settles.")
|
|
4479
|
+
.argument("<domain>", "Domain name from a previous `domains buy`.")
|
|
4480
|
+
.option("--wait", "Poll every 10s (up to 10 minutes) until the registration reaches a terminal state.")
|
|
4481
|
+
.option("--json", "Print a JSON envelope.")
|
|
4482
|
+
.action(async (domain, options) => {
|
|
4483
|
+
await handleAsyncAction("domains registration-status", options, () => {
|
|
4484
|
+
if (options.wait)
|
|
4485
|
+
return waitForDomainRegistration(domain);
|
|
4486
|
+
return requestOxygen(`/api/cli/domains/registrations/${encodeURIComponent(domain)}`);
|
|
4487
|
+
});
|
|
4488
|
+
})));
|
|
4003
4489
|
program
|
|
4004
4490
|
.command("workflows")
|
|
4005
4491
|
.description("Durable workflow automation commands.")
|
|
4006
4492
|
.addCommand(new Command("templates")
|
|
4007
|
-
.description("
|
|
4493
|
+
.description("Deprecated aliases for `oxygen blueprints` — these route through the canonical blueprint surface (built-in seed templates plus this workspace's saved blueprints).")
|
|
4008
4494
|
.addCommand(new Command("search")
|
|
4009
|
-
.description("
|
|
4495
|
+
.description("Deprecated alias for `oxygen blueprints list`. Searches Oxygen blueprints (seed templates plus saved blueprints).")
|
|
4010
4496
|
.argument("[query]", "Search text.")
|
|
4011
|
-
.option("--tag <tag>", "Filter by
|
|
4497
|
+
.option("--tag <tag>", "Filter by blueprint tag.")
|
|
4012
4498
|
.option("--json", "Print a JSON envelope.")
|
|
4013
4499
|
.action(async (query, options) => {
|
|
4014
|
-
await handleAsyncAction("workflows templates search", options,
|
|
4500
|
+
await handleAsyncAction("workflows templates search", options, () => {
|
|
4015
4501
|
const params = new URLSearchParams();
|
|
4016
4502
|
params.set("query", query ?? "");
|
|
4017
4503
|
if (readOption(options.tag))
|
|
4018
4504
|
params.set("tag", readOption(options.tag) ?? "");
|
|
4019
|
-
return requestOxygen(`/api/cli/
|
|
4505
|
+
return requestOxygen(`/api/cli/blueprints?${params.toString()}`);
|
|
4020
4506
|
});
|
|
4021
4507
|
}))
|
|
4022
4508
|
.addCommand(new Command("describe")
|
|
4023
|
-
.description("
|
|
4024
|
-
.argument("<template_id>", "
|
|
4509
|
+
.description("Deprecated alias for `oxygen blueprints describe`.")
|
|
4510
|
+
.argument("<template_id>", "Blueprint slug (formerly workflow template id).")
|
|
4025
4511
|
.option("--json", "Print a JSON envelope.")
|
|
4026
4512
|
.action(async (templateId, options) => {
|
|
4027
|
-
await handleAsyncAction("workflows templates describe", options,
|
|
4513
|
+
await handleAsyncAction("workflows templates describe", options, () => requestOxygen("/api/cli/blueprints/get", {
|
|
4028
4514
|
method: "POST",
|
|
4029
|
-
body: {
|
|
4515
|
+
body: { slug: templateId },
|
|
4030
4516
|
}));
|
|
4031
4517
|
}))
|
|
4032
4518
|
.addCommand(new Command("preflight")
|
|
4033
|
-
.description("
|
|
4034
|
-
.argument("<template_id>", "
|
|
4035
|
-
.requiredOption("--input-json <json>", "
|
|
4036
|
-
.option("--mode <mode>", "
|
|
4037
|
-
.option("--max-credits <credits>", "Credit ceiling
|
|
4519
|
+
.description("Deprecated alias for `oxygen blueprints preflight`. Validates inputs and previews effects without running.")
|
|
4520
|
+
.argument("<template_id>", "Blueprint slug (formerly workflow template id).")
|
|
4521
|
+
.requiredOption("--input-json <json>", "Blueprint input as a JSON object.")
|
|
4522
|
+
.option("--mode <mode>", "Deprecated and ignored: blueprints validate without a run mode.")
|
|
4523
|
+
.option("--max-credits <credits>", "Credit ceiling; folded into inputs.max_credits.")
|
|
4038
4524
|
.option("--json", "Print a JSON envelope.")
|
|
4039
4525
|
.action(async (templateId, options) => {
|
|
4040
|
-
await handleAsyncAction("workflows templates preflight", options,
|
|
4526
|
+
await handleAsyncAction("workflows templates preflight", options, () => requestOxygen("/api/cli/blueprints/preflight", {
|
|
4041
4527
|
method: "POST",
|
|
4042
|
-
body:
|
|
4528
|
+
body: workflowTemplateBlueprintBody(templateId, options),
|
|
4043
4529
|
}));
|
|
4044
4530
|
}))
|
|
4045
4531
|
.addCommand(new Command("apply")
|
|
4046
|
-
.description("
|
|
4047
|
-
.argument("<template_id>", "
|
|
4048
|
-
.requiredOption("--input-json <json>", "
|
|
4049
|
-
.option("--workflow-id <workflow_id>", "
|
|
4050
|
-
.option("--workflow-name <workflow_name>", "
|
|
4051
|
-
.option("--mode <mode>", "
|
|
4052
|
-
.option("--max-credits <credits>", "Credit ceiling
|
|
4532
|
+
.description("Deprecated alias for `oxygen blueprints apply`. Creates a disabled workflow (plus its tables, columns, and prompts) from a blueprint.")
|
|
4533
|
+
.argument("<template_id>", "Blueprint slug (formerly workflow template id).")
|
|
4534
|
+
.requiredOption("--input-json <json>", "Blueprint input as a JSON object.")
|
|
4535
|
+
.option("--workflow-id <workflow_id>", "Override the resulting workflow id.")
|
|
4536
|
+
.option("--workflow-name <workflow_name>", "Override the resulting workflow name.")
|
|
4537
|
+
.option("--mode <mode>", "Deprecated and ignored: the workflow is created disabled.")
|
|
4538
|
+
.option("--max-credits <credits>", "Credit ceiling; folded into inputs.max_credits.")
|
|
4053
4539
|
.option("--include-bundle", "Include durable recipe bundles in JSON output.")
|
|
4054
4540
|
.option("--json", "Print a JSON envelope.")
|
|
4055
4541
|
.action(async (templateId, options) => {
|
|
4056
|
-
await handleAsyncAction("workflows templates apply", options, async () => prepareWorkflowCliOutput(await requestOxygen("/api/cli/
|
|
4542
|
+
await handleAsyncAction("workflows templates apply", options, async () => prepareWorkflowCliOutput(await requestOxygen("/api/cli/blueprints/apply", {
|
|
4057
4543
|
method: "POST",
|
|
4058
|
-
body:
|
|
4544
|
+
body: workflowTemplateBlueprintBody(templateId, options, true),
|
|
4059
4545
|
}), options));
|
|
4060
4546
|
}))
|
|
4061
4547
|
.addCommand(new Command("run")
|
|
4062
|
-
.description("
|
|
4063
|
-
.argument("<template_id>", "
|
|
4064
|
-
.requiredOption("--input-json <json>", "
|
|
4548
|
+
.description("Removed: returns a typed 410 deprecation. Use `oxygen blueprints apply <slug>` then `oxygen workflows call <workflow>`.")
|
|
4549
|
+
.argument("<template_id>", "Blueprint slug (formerly workflow template id).")
|
|
4550
|
+
.requiredOption("--input-json <json>", "Blueprint input as a JSON object.")
|
|
4065
4551
|
.requiredOption("--mode <mode>", "Execution mode: smoke_test, dry_run, or live.")
|
|
4066
4552
|
.option("--workflow-id <workflow_id>", "Workflow id to create or update.")
|
|
4067
4553
|
.option("--workflow-name <workflow_name>", "Workflow display name.")
|
|
@@ -4080,7 +4566,7 @@ export function createProgram() {
|
|
|
4080
4566
|
.option("--subject <subject>", "Schema subject: all, apply, call, event, trigger, or manifest.")
|
|
4081
4567
|
.option("--json", "Print a JSON envelope.")
|
|
4082
4568
|
.action(async (options) => {
|
|
4083
|
-
await handleAsyncAction("workflows schema", options,
|
|
4569
|
+
await handleAsyncAction("workflows schema", options, () => requestOxygen(`/api/cli/workflows/schema?subject=${encodeURIComponent(options.subject ?? "all")}`));
|
|
4084
4570
|
}))
|
|
4085
4571
|
.addCommand(new Command("lint")
|
|
4086
4572
|
.description("Compile and lint a workflow file without saving it.")
|
|
@@ -4113,7 +4599,7 @@ export function createProgram() {
|
|
|
4113
4599
|
.description("List workflow automations.")
|
|
4114
4600
|
.option("--json", "Print a JSON envelope.")
|
|
4115
4601
|
.action(async (options) => {
|
|
4116
|
-
await handleAsyncAction("workflows list", options,
|
|
4602
|
+
await handleAsyncAction("workflows list", options, () => requestOxygen("/api/cli/workflows"));
|
|
4117
4603
|
}))
|
|
4118
4604
|
.addCommand(new Command("get")
|
|
4119
4605
|
.description("Get one workflow automation.")
|
|
@@ -4268,14 +4754,14 @@ export function createProgram() {
|
|
|
4268
4754
|
.option("--include-bundle", "Include durable recipe bundles in JSON output.")
|
|
4269
4755
|
.option("--json", "Print a JSON envelope.")
|
|
4270
4756
|
.action(async (runId, options) => {
|
|
4271
|
-
await handleAsyncAction("workflows tail", options, async () => prepareWorkflowCliOutput(await
|
|
4757
|
+
await handleAsyncAction("workflows tail", options, async () => prepareWorkflowCliOutput(await waitForWorkflowRun(runId, options), options));
|
|
4272
4758
|
}))
|
|
4273
4759
|
.addCommand(new Command("cancel")
|
|
4274
4760
|
.description("Cancel a queued or running workflow run.")
|
|
4275
4761
|
.argument("<run_id>", "Workflow run UUID.")
|
|
4276
4762
|
.option("--json", "Print a JSON envelope.")
|
|
4277
4763
|
.action(async (runId, options) => {
|
|
4278
|
-
await handleAsyncAction("workflows cancel", options,
|
|
4764
|
+
await handleAsyncAction("workflows cancel", options, () => requestOxygen("/api/cli/workflows/cancel", {
|
|
4279
4765
|
method: "POST",
|
|
4280
4766
|
body: { run_id: runId },
|
|
4281
4767
|
}));
|
|
@@ -4285,7 +4771,7 @@ export function createProgram() {
|
|
|
4285
4771
|
.argument("<workflow>", "Workflow id, slug, or name.")
|
|
4286
4772
|
.option("--json", "Print a JSON envelope.")
|
|
4287
4773
|
.action(async (workflow, options) => {
|
|
4288
|
-
await handleAsyncAction("workflows enable", options,
|
|
4774
|
+
await handleAsyncAction("workflows enable", options, () => requestOxygen("/api/cli/workflows/enable", {
|
|
4289
4775
|
method: "POST",
|
|
4290
4776
|
body: { workflow },
|
|
4291
4777
|
}));
|
|
@@ -4295,7 +4781,7 @@ export function createProgram() {
|
|
|
4295
4781
|
.argument("<workflow>", "Workflow id, slug, or name.")
|
|
4296
4782
|
.option("--json", "Print a JSON envelope.")
|
|
4297
4783
|
.action(async (workflow, options) => {
|
|
4298
|
-
await handleAsyncAction("workflows disable", options,
|
|
4784
|
+
await handleAsyncAction("workflows disable", options, () => requestOxygen("/api/cli/workflows/disable", {
|
|
4299
4785
|
method: "POST",
|
|
4300
4786
|
body: { workflow },
|
|
4301
4787
|
}));
|
|
@@ -4308,14 +4794,14 @@ export function createProgram() {
|
|
|
4308
4794
|
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
4309
4795
|
.option("--json", "Print a JSON envelope.")
|
|
4310
4796
|
.action(async (options) => {
|
|
4311
|
-
await handleAsyncAction("skills list", options,
|
|
4797
|
+
await handleAsyncAction("skills list", options, () => listAgentSkills(options));
|
|
4312
4798
|
}))
|
|
4313
4799
|
.addCommand(new Command("doctor")
|
|
4314
4800
|
.description("Check Oxygen skill index reachability and local installer prerequisites.")
|
|
4315
4801
|
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
4316
4802
|
.option("--json", "Print a JSON envelope.")
|
|
4317
4803
|
.action(async (options) => {
|
|
4318
|
-
await handleAsyncAction("skills doctor", options,
|
|
4804
|
+
await handleAsyncAction("skills doctor", options, () => doctorAgentSkills(options));
|
|
4319
4805
|
}))
|
|
4320
4806
|
.addCommand(new Command("install")
|
|
4321
4807
|
.description("Install Oxygen agent skills into local agent skill directories.")
|
|
@@ -4326,6 +4812,7 @@ export function createProgram() {
|
|
|
4326
4812
|
.option("--copy", "Copy skill files instead of symlinking when supported by npx skills. Default on Windows, where symlinks need Developer Mode or admin.")
|
|
4327
4813
|
.option("--json", "Print a JSON envelope.")
|
|
4328
4814
|
.action(async (options) => {
|
|
4815
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous installAgentSkills helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
4329
4816
|
await handleAsyncAction("skills install", options, async () => installAgentSkills(options));
|
|
4330
4817
|
}));
|
|
4331
4818
|
return program;
|
|
@@ -4446,8 +4933,8 @@ function referencesRecipeSdk(source) {
|
|
|
4446
4933
|
}
|
|
4447
4934
|
// Escape Next static analysis (the CLI is bundled by tsc, but mirror the
|
|
4448
4935
|
// worker's escape so both load identically).
|
|
4936
|
+
// skipcq: JS-R1003 — intentional dynamic-import escape mirrored from the worker; not arbitrary code execution
|
|
4449
4937
|
const dynamicRecipeImport = new Function("specifier", "return import(specifier);");
|
|
4450
|
-
// skipcq: JS-R1003
|
|
4451
4938
|
async function importRecipeModule(specifier) {
|
|
4452
4939
|
try {
|
|
4453
4940
|
return await dynamicRecipeImport(specifier);
|
|
@@ -4594,66 +5081,64 @@ export function toolStep(input) {
|
|
|
4594
5081
|
}
|
|
4595
5082
|
`;
|
|
4596
5083
|
}
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
let
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
5084
|
+
// The workflow tailer is the one bespoke wait surface: it POSTs (rather than
|
|
5085
|
+
// GETs a run by id), reads the run nested under the response envelope, and
|
|
5086
|
+
// carries the workflow/run/deep-link URLs from that envelope into its terminal
|
|
5087
|
+
// output. The shared waitForCliRun loop drives polling; this config injects the
|
|
5088
|
+
// workflow-specific fetch, terminal shape, and queued-no-worker timeout hint.
|
|
5089
|
+
function waitForWorkflowRun(runId, options) {
|
|
5090
|
+
// Captured per poll so shapeTerminal can read the envelope-level URLs while
|
|
5091
|
+
// fetchRun returns the nested run (where the status lives).
|
|
5092
|
+
let latestEnvelope = {};
|
|
5093
|
+
return waitForCliRun({
|
|
5094
|
+
runId,
|
|
5095
|
+
requestedTimeoutSeconds: options.timeoutSeconds,
|
|
5096
|
+
requestedIntervalSeconds: options.intervalSeconds,
|
|
5097
|
+
defaultTimeoutSeconds: WORKFLOW_TAIL_DEFAULT_TIMEOUT_SECONDS,
|
|
5098
|
+
defaultIntervalSeconds: WORKFLOW_TAIL_DEFAULT_INTERVAL_SECONDS,
|
|
5099
|
+
fetchRun: async () => {
|
|
5100
|
+
latestEnvelope = await requestOxygen("/api/cli/workflows/run", {
|
|
5101
|
+
method: "POST",
|
|
5102
|
+
body: { run_id: runId },
|
|
5103
|
+
});
|
|
5104
|
+
return isRecord(latestEnvelope.run) ? latestEnvelope.run : latestEnvelope;
|
|
5105
|
+
},
|
|
5106
|
+
isTerminal: isTerminalWorkflowRunStatus,
|
|
5107
|
+
shapeTerminal: (run, status, polls, elapsedMs) => {
|
|
5108
|
+
const workflowUrl = readRecordString(latestEnvelope, "workflowUrl");
|
|
5109
|
+
const runUrl = readRecordString(latestEnvelope, "runUrl");
|
|
5110
|
+
const webUrl = readRecordString(latestEnvelope, "web_url");
|
|
5111
|
+
const deepLink = readRecordString(latestEnvelope, "deepLink");
|
|
4619
5112
|
return {
|
|
4620
5113
|
run,
|
|
4621
5114
|
workflowRunId: readRecordString(run, "id") ?? runId,
|
|
4622
5115
|
status,
|
|
4623
5116
|
terminal: true,
|
|
4624
5117
|
polls,
|
|
4625
|
-
elapsedMs
|
|
5118
|
+
elapsedMs,
|
|
4626
5119
|
...(workflowUrl ? { workflowUrl } : {}),
|
|
4627
5120
|
...(runUrl ? { runUrl } : {}),
|
|
4628
5121
|
...(webUrl ? { web_url: webUrl } : {}),
|
|
4629
5122
|
...(deepLink ? { deepLink } : {}),
|
|
4630
5123
|
};
|
|
4631
|
-
}
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
5124
|
+
},
|
|
5125
|
+
timeoutCode: "workflow_tail_timeout",
|
|
5126
|
+
timeoutMessage: "Timed out waiting for workflow run to finish.",
|
|
5127
|
+
timeoutDetailIdKey: "workflow_run_id",
|
|
5128
|
+
timeoutExtraDetails: (latestRun, status) => {
|
|
5129
|
+
const worker = latestRun && isRecord(latestRun.worker) ? latestRun.worker : null;
|
|
4635
5130
|
const queuedWithoutWorker = status === "queued"
|
|
4636
5131
|
&& worker !== null
|
|
4637
5132
|
&& worker.active === null
|
|
4638
5133
|
&& worker.lastClaim === null;
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
worker_status: "queued_no_worker",
|
|
4648
|
-
guidance: "No worker has claimed this workflow run. Check `oxygen worker queue-stats --json` for Postgres-backed worker queue health.",
|
|
4649
|
-
}
|
|
4650
|
-
: {}),
|
|
4651
|
-
},
|
|
4652
|
-
exitCode: 1,
|
|
4653
|
-
});
|
|
4654
|
-
}
|
|
4655
|
-
await sleep(Math.min(intervalSeconds * 1000, remainingMs));
|
|
4656
|
-
}
|
|
5134
|
+
return queuedWithoutWorker
|
|
5135
|
+
? {
|
|
5136
|
+
worker_status: "queued_no_worker",
|
|
5137
|
+
guidance: "No worker has claimed this workflow run. Check `oxygen worker queue-stats --json` for Postgres-backed worker queue health.",
|
|
5138
|
+
}
|
|
5139
|
+
: {};
|
|
5140
|
+
},
|
|
5141
|
+
});
|
|
4657
5142
|
}
|
|
4658
5143
|
function workflowTemplateActionBody(templateId, options) {
|
|
4659
5144
|
const inputJson = readOption(options.inputJson);
|
|
@@ -4668,6 +5153,28 @@ function workflowTemplateActionBody(templateId, options) {
|
|
|
4668
5153
|
...(options.approved ? { approved: true } : {}),
|
|
4669
5154
|
};
|
|
4670
5155
|
}
|
|
5156
|
+
// OXY-481: the deprecated `workflows templates {search,describe,preflight,apply}`
|
|
5157
|
+
// commands route through the canonical blueprint API. Map the legacy arg names
|
|
5158
|
+
// (template_id -> slug, --input-json -> inputs) and fold the old top-level
|
|
5159
|
+
// max_credits into inputs.max_credits, where seed blueprints read their cap.
|
|
5160
|
+
function workflowTemplateBlueprintBody(templateId, options, includeWorkflowOverrides = false) {
|
|
5161
|
+
const inputJson = readOption(options.inputJson);
|
|
5162
|
+
const inputs = inputJson ? parseJsonObject(inputJson) : {};
|
|
5163
|
+
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
5164
|
+
if (maxCredits !== undefined) {
|
|
5165
|
+
inputs.max_credits = maxCredits;
|
|
5166
|
+
}
|
|
5167
|
+
const body = { slug: templateId, inputs };
|
|
5168
|
+
if (includeWorkflowOverrides) {
|
|
5169
|
+
const workflowId = readOption(options.workflowId);
|
|
5170
|
+
if (workflowId)
|
|
5171
|
+
body.workflow_id = workflowId;
|
|
5172
|
+
const workflowName = readOption(options.workflowName);
|
|
5173
|
+
if (workflowName)
|
|
5174
|
+
body.workflow_name = workflowName;
|
|
5175
|
+
}
|
|
5176
|
+
return body;
|
|
5177
|
+
}
|
|
4671
5178
|
function prepareWorkflowCliOutput(value, options) {
|
|
4672
5179
|
const normalized = normalizeWorkflowRunErrors(value);
|
|
4673
5180
|
return (options.includeBundle ? normalized : stripWorkflowBundles(normalized));
|
|
@@ -4824,6 +5331,34 @@ function readTableRunSelection(options) {
|
|
|
4824
5331
|
exitCode: 1,
|
|
4825
5332
|
});
|
|
4826
5333
|
}
|
|
5334
|
+
function applyAiColumnConfig(definition, options) {
|
|
5335
|
+
const model = readOption(options.model);
|
|
5336
|
+
if (model)
|
|
5337
|
+
definition.model = model;
|
|
5338
|
+
const reasoningLevel = readOption(options.reasoningLevel);
|
|
5339
|
+
if (reasoningLevel)
|
|
5340
|
+
definition.reasoningLevel = reasoningLevel;
|
|
5341
|
+
const runCondition = readOption(options.runCondition);
|
|
5342
|
+
if (runCondition) {
|
|
5343
|
+
definition.runCondition = {
|
|
5344
|
+
type: "formula",
|
|
5345
|
+
expression: runCondition,
|
|
5346
|
+
referencedColumns: readCsvOption(options.runConditionColumns),
|
|
5347
|
+
};
|
|
5348
|
+
}
|
|
5349
|
+
const outputSchemaJson = readOption(options.outputSchemaJson);
|
|
5350
|
+
const outputSchemaFile = readOption(options.outputSchemaFile);
|
|
5351
|
+
if (outputSchemaJson && outputSchemaFile) {
|
|
5352
|
+
throw new OxygenError("invalid_request", "Pass either --output-schema-json or --output-schema-file, not both.", { exitCode: 1 });
|
|
5353
|
+
}
|
|
5354
|
+
if (outputSchemaJson) {
|
|
5355
|
+
definition.outputSchema = parseJsonValue(outputSchemaJson, "--output-schema-json");
|
|
5356
|
+
}
|
|
5357
|
+
else if (outputSchemaFile) {
|
|
5358
|
+
definition.outputSchema = readJsonFileValue(resolve(outputSchemaFile), "--output-schema-file");
|
|
5359
|
+
}
|
|
5360
|
+
return definition;
|
|
5361
|
+
}
|
|
4827
5362
|
function tableRunsListPath(options) {
|
|
4828
5363
|
const table = readOption(options.table);
|
|
4829
5364
|
if (!table) {
|
|
@@ -5023,7 +5558,7 @@ function readCompanySearchFilters(options) {
|
|
|
5023
5558
|
const fundingStages = readCsvOption(options.fundingStages);
|
|
5024
5559
|
if (fundingStages.length > 0)
|
|
5025
5560
|
filters.funding = { stages: fundingStages };
|
|
5026
|
-
const technologies = readIncludeExclude(options.technologies
|
|
5561
|
+
const technologies = readIncludeExclude(options.technologies);
|
|
5027
5562
|
if (technologies)
|
|
5028
5563
|
filters.technologies = technologies;
|
|
5029
5564
|
const revenue = readRangeOption(options.revenue, "--revenue");
|
|
@@ -5112,6 +5647,22 @@ function assertNotCompanySearchKind(kind) {
|
|
|
5112
5647
|
});
|
|
5113
5648
|
}
|
|
5114
5649
|
}
|
|
5650
|
+
// People search has moved to the dedicated people-search surface. The CLI rejects
|
|
5651
|
+
// `oxygen search plan --kind people_search` client-side so a stale agent gets a typed,
|
|
5652
|
+
// self-correcting error instead of an opaque server 400. Message mirrors
|
|
5653
|
+
// PEOPLE_SEARCH_MOVED_MESSAGE in @oxygen/tools (CLI does not import that package).
|
|
5654
|
+
const PEOPLE_SEARCH_MOVED_MESSAGE = "People search is handled by the dedicated people-search surface. Use: oxygen people search plan --prompt \"<goal>\" (CLI) or oxygen_people_search_plan (MCP).";
|
|
5655
|
+
function assertNotPeopleSearchKind(kind) {
|
|
5656
|
+
if (readOption(kind) === "people_search") {
|
|
5657
|
+
throw new OxygenError("use_people_search", PEOPLE_SEARCH_MOVED_MESSAGE, {
|
|
5658
|
+
details: {
|
|
5659
|
+
equivalent_cli: "oxygen people search plan --prompt <goal>",
|
|
5660
|
+
equivalent_mcp: "oxygen_people_search_plan",
|
|
5661
|
+
},
|
|
5662
|
+
exitCode: 1,
|
|
5663
|
+
});
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5115
5666
|
function readCompanySearchPlanJson(value) {
|
|
5116
5667
|
const parsed = parseJsonObject(readFileIfPresent(value));
|
|
5117
5668
|
const data = parsed.data;
|
|
@@ -5127,7 +5678,8 @@ function readPeopleSearchPlanBody(options) {
|
|
|
5127
5678
|
...(targetCount !== undefined ? { target_count: targetCount } : {}),
|
|
5128
5679
|
...(options.sourceIntent ? { source_intent: options.sourceIntent } : {}),
|
|
5129
5680
|
...(filters ? { filters } : {}),
|
|
5130
|
-
|
|
5681
|
+
// Free sizing is on by default; only --no-estimate (options.estimate === false) opts out.
|
|
5682
|
+
estimate: options.estimate !== false,
|
|
5131
5683
|
...(options.materializePreview ? { materialize_preview: true } : {}),
|
|
5132
5684
|
};
|
|
5133
5685
|
}
|
|
@@ -5154,7 +5706,8 @@ function readPeopleSearchRunBody(options) {
|
|
|
5154
5706
|
...(targetCount !== undefined ? { target_count: targetCount } : {}),
|
|
5155
5707
|
...(options.sourceIntent ? { source_intent: options.sourceIntent } : {}),
|
|
5156
5708
|
...(filters ? { filters } : {}),
|
|
5157
|
-
|
|
5709
|
+
// Free sizing is on by default when planning from a prompt; --no-estimate opts out.
|
|
5710
|
+
...(prompt ? { estimate: options.estimate !== false } : {}),
|
|
5158
5711
|
...(options.approved ? { approved: true } : {}),
|
|
5159
5712
|
};
|
|
5160
5713
|
}
|
|
@@ -5307,6 +5860,26 @@ function readFilterJsonOption(value) {
|
|
|
5307
5860
|
}
|
|
5308
5861
|
return filters;
|
|
5309
5862
|
}
|
|
5863
|
+
function readFilterTreeJsonOption(value) {
|
|
5864
|
+
const raw = readOption(value);
|
|
5865
|
+
if (!raw)
|
|
5866
|
+
return undefined;
|
|
5867
|
+
return parseJsonObject(raw);
|
|
5868
|
+
}
|
|
5869
|
+
function readSortJsonOption(value) {
|
|
5870
|
+
const raw = readOption(value);
|
|
5871
|
+
if (!raw)
|
|
5872
|
+
return undefined;
|
|
5873
|
+
const parsed = parseJsonArray(raw);
|
|
5874
|
+
if (parsed.length === 0)
|
|
5875
|
+
return undefined;
|
|
5876
|
+
if (parsed.some((entry) => !entry || typeof entry !== "object" || Array.isArray(entry))) {
|
|
5877
|
+
throw new OxygenError("invalid_sort", "--sort-json must be an array of sort-rule objects.", {
|
|
5878
|
+
exitCode: 1,
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5881
|
+
return parsed;
|
|
5882
|
+
}
|
|
5310
5883
|
function parseStringArray(value) {
|
|
5311
5884
|
const parsed = parseJsonArray(value);
|
|
5312
5885
|
const labels = parsed
|
|
@@ -5333,6 +5906,7 @@ function normalizeSessionStepStatus(value) {
|
|
|
5333
5906
|
exitCode: 1,
|
|
5334
5907
|
});
|
|
5335
5908
|
}
|
|
5909
|
+
// skipcq: JS-R1005 — intentional branching over file format, create/upsert, and sync/background import modes
|
|
5336
5910
|
async function importRows(table, options) {
|
|
5337
5911
|
const format = normalizeRowsFormat(options.format, inferRowsFileFormat(options.file));
|
|
5338
5912
|
const parsedRows = await readRowsFile(options.file, format, options.sheet);
|
|
@@ -5737,118 +6311,249 @@ function emitQueueWaitStderrNote(queueWait) {
|
|
|
5737
6311
|
if (note)
|
|
5738
6312
|
process.stderr.write(`note: ${note}\n`);
|
|
5739
6313
|
}
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
polls
|
|
5750
|
-
|
|
5751
|
-
const status = readRecordString(latestRun, "status");
|
|
5752
|
-
if (isTerminalTableIngestionStatus(status)) {
|
|
6314
|
+
function buildGetSurfaceRunWaitConfig(params) {
|
|
6315
|
+
return {
|
|
6316
|
+
runId: params.runId,
|
|
6317
|
+
requestedTimeoutSeconds: params.requestedTimeoutSeconds,
|
|
6318
|
+
requestedIntervalSeconds: params.requestedIntervalSeconds,
|
|
6319
|
+
defaultTimeoutSeconds: params.defaultTimeoutSeconds,
|
|
6320
|
+
defaultIntervalSeconds: params.defaultIntervalSeconds,
|
|
6321
|
+
fetchRun: () => requestOxygen(`${params.endpoint}/${encodeURIComponent(params.runId)}`),
|
|
6322
|
+
isTerminal: params.isTerminal,
|
|
6323
|
+
shapeTerminal: (run, status, polls, elapsedMs) => {
|
|
6324
|
+
const webUrl = readRecordString(run, "web_url");
|
|
5753
6325
|
return {
|
|
5754
|
-
|
|
5755
|
-
|
|
6326
|
+
[params.runKey]: run,
|
|
6327
|
+
[params.runIdKey]: readRecordString(run, "id") ?? params.runId,
|
|
5756
6328
|
status,
|
|
5757
6329
|
terminal: true,
|
|
5758
6330
|
polls,
|
|
5759
|
-
elapsedMs
|
|
5760
|
-
...(
|
|
6331
|
+
elapsedMs,
|
|
6332
|
+
...(webUrl ? { web_url: webUrl } : {}),
|
|
5761
6333
|
};
|
|
6334
|
+
},
|
|
6335
|
+
timeoutCode: params.timeoutCode,
|
|
6336
|
+
timeoutMessage: params.timeoutMessage,
|
|
6337
|
+
timeoutDetailIdKey: params.timeoutDetailIdKey,
|
|
6338
|
+
};
|
|
6339
|
+
}
|
|
6340
|
+
function waitForTableIngestionRun(runId, options) {
|
|
6341
|
+
return waitForCliRun(buildGetSurfaceRunWaitConfig({
|
|
6342
|
+
runId,
|
|
6343
|
+
endpoint: "/api/cli/table-ingestion-runs",
|
|
6344
|
+
requestedTimeoutSeconds: options.timeoutSeconds,
|
|
6345
|
+
requestedIntervalSeconds: options.intervalSeconds,
|
|
6346
|
+
defaultTimeoutSeconds: TABLE_INGESTION_WAIT_DEFAULT_TIMEOUT_SECONDS,
|
|
6347
|
+
defaultIntervalSeconds: TABLE_INGESTION_WAIT_DEFAULT_INTERVAL_SECONDS,
|
|
6348
|
+
isTerminal: isTerminalTableIngestionStatus,
|
|
6349
|
+
runKey: "ingestionRun",
|
|
6350
|
+
runIdKey: "ingestionRunId",
|
|
6351
|
+
timeoutCode: "table_ingestion_wait_timeout",
|
|
6352
|
+
timeoutMessage: "Timed out waiting for table ingestion run to finish.",
|
|
6353
|
+
timeoutDetailIdKey: "ingestion_run_id",
|
|
6354
|
+
}));
|
|
6355
|
+
}
|
|
6356
|
+
function waitForSearchRun(runId, options) {
|
|
6357
|
+
return waitForCliRun(buildGetSurfaceRunWaitConfig({
|
|
6358
|
+
runId,
|
|
6359
|
+
endpoint: "/api/cli/search/runs",
|
|
6360
|
+
requestedTimeoutSeconds: options.timeoutSeconds,
|
|
6361
|
+
requestedIntervalSeconds: options.intervalSeconds,
|
|
6362
|
+
// Search intentionally reuses the table-ingestion defaults and terminal
|
|
6363
|
+
// predicate; the status set is identical (a naming-drift smell, not a bug).
|
|
6364
|
+
defaultTimeoutSeconds: TABLE_INGESTION_WAIT_DEFAULT_TIMEOUT_SECONDS,
|
|
6365
|
+
defaultIntervalSeconds: TABLE_INGESTION_WAIT_DEFAULT_INTERVAL_SECONDS,
|
|
6366
|
+
isTerminal: isTerminalTableIngestionStatus,
|
|
6367
|
+
runKey: "searchRun",
|
|
6368
|
+
runIdKey: "searchRunId",
|
|
6369
|
+
timeoutCode: "search_run_wait_timeout",
|
|
6370
|
+
timeoutMessage: "Timed out waiting for search run to finish.",
|
|
6371
|
+
timeoutDetailIdKey: "search_run_id",
|
|
6372
|
+
}));
|
|
6373
|
+
}
|
|
6374
|
+
function waitForTableActionRun(runId, options) {
|
|
6375
|
+
return waitForCliRun(buildGetSurfaceRunWaitConfig({
|
|
6376
|
+
runId,
|
|
6377
|
+
endpoint: "/api/cli/table-action-runs",
|
|
6378
|
+
requestedTimeoutSeconds: options.timeoutSeconds,
|
|
6379
|
+
requestedIntervalSeconds: options.intervalSeconds,
|
|
6380
|
+
defaultTimeoutSeconds: TABLE_ACTION_RUN_WAIT_DEFAULT_TIMEOUT_SECONDS,
|
|
6381
|
+
defaultIntervalSeconds: TABLE_ACTION_RUN_WAIT_DEFAULT_INTERVAL_SECONDS,
|
|
6382
|
+
isTerminal: isTerminalTableActionRunStatus,
|
|
6383
|
+
runKey: "actionRun",
|
|
6384
|
+
runIdKey: "actionRunId",
|
|
6385
|
+
timeoutCode: "table_action_run_wait_timeout",
|
|
6386
|
+
timeoutMessage: "Timed out waiting for table action run to finish.",
|
|
6387
|
+
timeoutDetailIdKey: "action_run_id",
|
|
6388
|
+
}));
|
|
6389
|
+
}
|
|
6390
|
+
// Domain sync is paginated under the server's Cloudflare request budget; the
|
|
6391
|
+
// CLI re-POSTs the same body while the server reports a partial sweep with a
|
|
6392
|
+
// next page, then prints one aggregated summary.
|
|
6393
|
+
async function runDomainsSync(options) {
|
|
6394
|
+
const body = options.full ? { full: true } : {};
|
|
6395
|
+
let totalSynced = 0;
|
|
6396
|
+
let totalPagesFetched = 0;
|
|
6397
|
+
let totalRdapResolved = 0;
|
|
6398
|
+
let totalDnsChecked = 0;
|
|
6399
|
+
let sawRdapResolved = false;
|
|
6400
|
+
let sawDnsChecked = false;
|
|
6401
|
+
let requestCount = 0;
|
|
6402
|
+
for (;;) {
|
|
6403
|
+
requestCount += 1;
|
|
6404
|
+
const latest = await requestOxygen("/api/cli/domains/sync", {
|
|
6405
|
+
method: "POST",
|
|
6406
|
+
body,
|
|
6407
|
+
});
|
|
6408
|
+
// `full` means "restart from page 1" server-side; only the first request may
|
|
6409
|
+
// carry it. Continuations must drop it so the server advances the saved
|
|
6410
|
+
// cursor instead of re-sweeping the first pages every iteration.
|
|
6411
|
+
delete body.full;
|
|
6412
|
+
totalSynced += readCount(latest.synced);
|
|
6413
|
+
totalPagesFetched += readCount(latest.pages_fetched);
|
|
6414
|
+
if (typeof latest.rdap_resolved === "number") {
|
|
6415
|
+
sawRdapResolved = true;
|
|
6416
|
+
totalRdapResolved += readCount(latest.rdap_resolved);
|
|
5762
6417
|
}
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
details: {
|
|
5767
|
-
ingestion_run_id: runId,
|
|
5768
|
-
status: status ?? null,
|
|
5769
|
-
timeout_seconds: timeoutSeconds,
|
|
5770
|
-
polls,
|
|
5771
|
-
},
|
|
5772
|
-
exitCode: 1,
|
|
5773
|
-
});
|
|
6418
|
+
if (typeof latest.dns_checked === "number") {
|
|
6419
|
+
sawDnsChecked = true;
|
|
6420
|
+
totalDnsChecked += readCount(latest.dns_checked);
|
|
5774
6421
|
}
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
?? TABLE_INGESTION_WAIT_DEFAULT_TIMEOUT_SECONDS;
|
|
5781
|
-
const intervalSeconds = readPositiveInt(options.intervalSeconds)
|
|
5782
|
-
?? TABLE_INGESTION_WAIT_DEFAULT_INTERVAL_SECONDS;
|
|
5783
|
-
const startedAt = Date.now();
|
|
5784
|
-
const deadline = startedAt + timeoutSeconds * 1000;
|
|
5785
|
-
let polls = 0;
|
|
5786
|
-
while (true) {
|
|
5787
|
-
polls += 1;
|
|
5788
|
-
const latestRun = await requestOxygen(`/api/cli/search/runs/${encodeURIComponent(runId)}`);
|
|
5789
|
-
const status = readRecordString(latestRun, "status");
|
|
5790
|
-
if (isTerminalTableIngestionStatus(status)) {
|
|
6422
|
+
const hasNextPage = latest.next_page !== undefined && latest.next_page !== null;
|
|
6423
|
+
const shouldContinue = latest.partial === true
|
|
6424
|
+
&& hasNextPage
|
|
6425
|
+
&& requestCount <= DOMAINS_SYNC_MAX_CONTINUATIONS;
|
|
6426
|
+
if (!shouldContinue) {
|
|
5791
6427
|
return {
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
...(readRecordString(latestRun, "web_url") ? { web_url: readRecordString(latestRun, "web_url") } : {}),
|
|
6428
|
+
...latest,
|
|
6429
|
+
synced: totalSynced,
|
|
6430
|
+
pages_fetched: totalPagesFetched,
|
|
6431
|
+
...(sawRdapResolved ? { rdap_resolved: totalRdapResolved } : {}),
|
|
6432
|
+
...(sawDnsChecked ? { dns_checked: totalDnsChecked } : {}),
|
|
6433
|
+
requests: requestCount,
|
|
5799
6434
|
};
|
|
5800
6435
|
}
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
});
|
|
5812
|
-
}
|
|
5813
|
-
await sleep(Math.min(intervalSeconds * 1000, remainingMs));
|
|
6436
|
+
// A 429 mid-sync keeps completed pages and reports a pushback window.
|
|
6437
|
+
const retryAfterSeconds = readCount(latest.retry_after_seconds);
|
|
6438
|
+
if (retryAfterSeconds > 0)
|
|
6439
|
+
await sleep(retryAfterSeconds * 1000);
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
async function runDomainsBuy(domains, options) {
|
|
6443
|
+
const quoteId = readOption(options.quote);
|
|
6444
|
+
if (options.approved && !quoteId) {
|
|
6445
|
+
throw new Error("--quote <id> is required with --approved. Run the same command without --approved first to get the preview and its quote_id.");
|
|
5814
6446
|
}
|
|
6447
|
+
const data = await requestOxygen("/api/cli/domains/buy", {
|
|
6448
|
+
method: "POST",
|
|
6449
|
+
body: {
|
|
6450
|
+
domains,
|
|
6451
|
+
...(options.autoRenew ? { auto_renew: true } : {}),
|
|
6452
|
+
...(options.privacy === false ? { privacy: false } : {}),
|
|
6453
|
+
...(options.approved ? { approved: true, quote_id: quoteId } : {}),
|
|
6454
|
+
...(options.acceptPremium ? { accept_premium_pricing: true } : {}),
|
|
6455
|
+
},
|
|
6456
|
+
});
|
|
6457
|
+
if (!options.approved) {
|
|
6458
|
+
const previewQuoteId = readRecordString(data, "quote_id");
|
|
6459
|
+
return previewQuoteId
|
|
6460
|
+
? { ...data, rerun_command: domainsBuyRerunCommand(domains, previewQuoteId, options, data) }
|
|
6461
|
+
: data;
|
|
6462
|
+
}
|
|
6463
|
+
if (!options.wait)
|
|
6464
|
+
return data;
|
|
6465
|
+
const pendingDomains = Array.isArray(data.results)
|
|
6466
|
+
? data.results
|
|
6467
|
+
.filter((entry) => {
|
|
6468
|
+
const status = readRecordString(entry, "status");
|
|
6469
|
+
return status === "submitting" || status === "in_progress";
|
|
6470
|
+
})
|
|
6471
|
+
.map((entry) => readRecordString(entry, "domain"))
|
|
6472
|
+
.filter((entry) => entry !== null)
|
|
6473
|
+
: [];
|
|
6474
|
+
if (pendingDomains.length === 0)
|
|
6475
|
+
return data;
|
|
6476
|
+
const waitResults = [];
|
|
6477
|
+
for (const domain of pendingDomains) {
|
|
6478
|
+
waitResults.push(await waitForDomainRegistration(domain));
|
|
6479
|
+
}
|
|
6480
|
+
return { ...data, wait: { waited: pendingDomains.length, results: waitResults } };
|
|
6481
|
+
}
|
|
6482
|
+
function domainsBuyRerunCommand(domains, quoteId, options, data) {
|
|
6483
|
+
// The buy preview returns items and requires_accept_premium_pricing at the
|
|
6484
|
+
// TOP level of data (there is no preview wrapper); reading data.preview here
|
|
6485
|
+
// drops the premium ack flag and makes a copy-paste of the rerun command 422.
|
|
6486
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
6487
|
+
const needsPremiumAck = options.acceptPremium === true
|
|
6488
|
+
|| data.requires_accept_premium_pricing === true
|
|
6489
|
+
|| items.some((item) => readRecordString(item, "tier") === "premium");
|
|
6490
|
+
const flags = [
|
|
6491
|
+
"--approved",
|
|
6492
|
+
`--quote ${quoteId}`,
|
|
6493
|
+
...(needsPremiumAck ? ["--accept-premium"] : []),
|
|
6494
|
+
...(options.autoRenew ? ["--auto-renew"] : []),
|
|
6495
|
+
...(options.privacy === false ? ["--no-privacy"] : []),
|
|
6496
|
+
];
|
|
6497
|
+
return `${resolveCliBinaryName()} domains buy ${domains.join(" ")} ${flags.join(" ")}`;
|
|
6498
|
+
}
|
|
6499
|
+
// succeeded/failed are terminal; action_required stops the wait too because
|
|
6500
|
+
// only the user (in the Cloudflare dashboard) can move it forward.
|
|
6501
|
+
function isSettledDomainRegistrationStatus(status) {
|
|
6502
|
+
return status === "succeeded"
|
|
6503
|
+
|| status === "failed"
|
|
6504
|
+
|| status === "action_required";
|
|
5815
6505
|
}
|
|
5816
|
-
async function
|
|
5817
|
-
const timeoutSeconds = readPositiveInt(options.timeoutSeconds)
|
|
5818
|
-
?? TABLE_ACTION_RUN_WAIT_DEFAULT_TIMEOUT_SECONDS;
|
|
5819
|
-
const intervalSeconds = readPositiveInt(options.intervalSeconds)
|
|
5820
|
-
?? TABLE_ACTION_RUN_WAIT_DEFAULT_INTERVAL_SECONDS;
|
|
6506
|
+
async function waitForDomainRegistration(domain) {
|
|
5821
6507
|
const startedAt = Date.now();
|
|
5822
|
-
const deadline = startedAt +
|
|
6508
|
+
const deadline = startedAt + DOMAIN_REGISTRATION_WAIT_TIMEOUT_SECONDS * 1000;
|
|
5823
6509
|
let polls = 0;
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
6510
|
+
let lastStatus = null;
|
|
6511
|
+
let lastRegistration = null;
|
|
6512
|
+
for (;;) {
|
|
6513
|
+
let delayMs = DOMAIN_REGISTRATION_WAIT_INTERVAL_SECONDS * 1000;
|
|
6514
|
+
try {
|
|
6515
|
+
polls += 1;
|
|
6516
|
+
const latest = await requestOxygen(`/api/cli/domains/registrations/${encodeURIComponent(domain)}`);
|
|
6517
|
+
lastRegistration = readRecord(latest, "registration");
|
|
6518
|
+
lastStatus = readRecordString(lastRegistration, "status");
|
|
6519
|
+
if (isSettledDomainRegistrationStatus(lastStatus)) {
|
|
6520
|
+
if (lastStatus === "action_required") {
|
|
6521
|
+
process.stderr.write(`hint: ${domain} needs action in the Cloudflare dashboard before registration can finish.\n`);
|
|
6522
|
+
}
|
|
6523
|
+
return {
|
|
6524
|
+
domain,
|
|
6525
|
+
status: lastStatus,
|
|
6526
|
+
terminal: true,
|
|
6527
|
+
polls,
|
|
6528
|
+
elapsedMs: Date.now() - startedAt,
|
|
6529
|
+
...(lastRegistration ? { registration: lastRegistration } : {}),
|
|
6530
|
+
...(readRecordString(latest, "deepLink") ? { deepLink: readRecordString(latest, "deepLink") } : {}),
|
|
6531
|
+
};
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
catch (error) {
|
|
6535
|
+
// Honor the server's rate-limit pushback instead of failing the wait.
|
|
6536
|
+
const retryAfterSeconds = error instanceof OxygenError
|
|
6537
|
+
? readDetailsNumber(error.details, "retry_after_seconds")
|
|
6538
|
+
: null;
|
|
6539
|
+
if (retryAfterSeconds === null)
|
|
6540
|
+
throw error;
|
|
6541
|
+
delayMs = Math.max(delayMs, retryAfterSeconds * 1000);
|
|
6542
|
+
}
|
|
6543
|
+
const remainingMs = deadline - Date.now();
|
|
6544
|
+
if (remainingMs <= 0) {
|
|
6545
|
+
process.stderr.write(`hint: registration for ${domain} is still ${lastStatus ?? "pending"}; the background worker keeps polling — re-run \`${resolveCliBinaryName()} domains registration-status ${domain} --wait\` later.\n`);
|
|
5829
6546
|
return {
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
6547
|
+
domain,
|
|
6548
|
+
status: lastStatus,
|
|
6549
|
+
terminal: false,
|
|
6550
|
+
timed_out: true,
|
|
5834
6551
|
polls,
|
|
5835
6552
|
elapsedMs: Date.now() - startedAt,
|
|
5836
|
-
...(
|
|
6553
|
+
...(lastRegistration ? { registration: lastRegistration } : {}),
|
|
5837
6554
|
};
|
|
5838
6555
|
}
|
|
5839
|
-
|
|
5840
|
-
if (remainingMs <= 0) {
|
|
5841
|
-
throw new OxygenError("table_action_run_wait_timeout", "Timed out waiting for table action run to finish.", {
|
|
5842
|
-
details: {
|
|
5843
|
-
action_run_id: runId,
|
|
5844
|
-
status: status ?? null,
|
|
5845
|
-
timeout_seconds: timeoutSeconds,
|
|
5846
|
-
polls,
|
|
5847
|
-
},
|
|
5848
|
-
exitCode: 1,
|
|
5849
|
-
});
|
|
5850
|
-
}
|
|
5851
|
-
await sleep(Math.min(intervalSeconds * 1000, remainingMs));
|
|
6556
|
+
await sleep(Math.min(delayMs, remainingMs));
|
|
5852
6557
|
}
|
|
5853
6558
|
}
|
|
5854
6559
|
async function exportRows(table, options) {
|
|
@@ -5877,6 +6582,7 @@ async function exportRows(table, options) {
|
|
|
5877
6582
|
const TABLE_BUNDLE_SCHEMA_VERSION = 1;
|
|
5878
6583
|
const TABLE_BUNDLE_MAX_PAGE_SIZE = 1000;
|
|
5879
6584
|
const TABLE_BUNDLE_DEFAULT_PAGE_SIZE = 500;
|
|
6585
|
+
// skipcq: JS-R1005 — intentional branching across describe/query pagination, column-definition normalization, and output sink (stdout/file)
|
|
5880
6586
|
async function exportTableBundle(table, options) {
|
|
5881
6587
|
const pageSize = Math.min(readPositiveInt(options.pageSize) ?? TABLE_BUNDLE_DEFAULT_PAGE_SIZE, TABLE_BUNDLE_MAX_PAGE_SIZE);
|
|
5882
6588
|
// Pull the canonical schema (incl. enrichment/tool definitions and
|
|
@@ -6787,9 +7493,9 @@ async function handleProfilesCurrentAction(options) {
|
|
|
6787
7493
|
}
|
|
6788
7494
|
}
|
|
6789
7495
|
function shellQuote(value) {
|
|
6790
|
-
if (/^[A-Za-z0-9_
|
|
7496
|
+
if (/^[A-Za-z0-9_.\-:/@%+=]+$/.test(value))
|
|
6791
7497
|
return value;
|
|
6792
|
-
return `'${value.replace(/'/g,
|
|
7498
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
6793
7499
|
}
|
|
6794
7500
|
async function handleLogoutAction(options) {
|
|
6795
7501
|
try {
|
|
@@ -6815,7 +7521,7 @@ async function handleLogoutAction(options) {
|
|
|
6815
7521
|
process.exitCode = error instanceof OxygenError ? error.exitCode : 1;
|
|
6816
7522
|
}
|
|
6817
7523
|
}
|
|
6818
|
-
|
|
7524
|
+
function handleUpdateAction(options) {
|
|
6819
7525
|
try {
|
|
6820
7526
|
const result = updateCli(options);
|
|
6821
7527
|
if (options.json) {
|
|
@@ -7472,7 +8178,7 @@ function describeProfileSource(source) {
|
|
|
7472
8178
|
case "flag": return "from --profile flag";
|
|
7473
8179
|
case "env": return "from OXYGEN_PROFILE";
|
|
7474
8180
|
case "file": return "from stored active profile";
|
|
7475
|
-
|
|
8181
|
+
default: return "default fallback";
|
|
7476
8182
|
}
|
|
7477
8183
|
}
|
|
7478
8184
|
function formatLogoutSuccess(result) {
|
|
@@ -7534,8 +8240,8 @@ function renderBox(lines) {
|
|
|
7534
8240
|
return [border, ...body, border].join("\n");
|
|
7535
8241
|
}
|
|
7536
8242
|
function visibleLength(value) {
|
|
7537
|
-
// skipcq: JS-0004 — ESC (\x1b) is the ANSI CSI introducer; required to strip color codes
|
|
7538
|
-
return value.replace(/\x1b\[[0-9;]*m/
|
|
8243
|
+
// skipcq: JS-0004, JS-W1035 — ESC (\x1b) is the ANSI CSI introducer; the literal escape is required to strip color codes
|
|
8244
|
+
return value.replace(/\x1b\[[0-9;]*m/gu, "").length;
|
|
7539
8245
|
}
|
|
7540
8246
|
function ansi(enabled) {
|
|
7541
8247
|
const wrap = (open, close) => enabled
|
|
@@ -7593,14 +8299,6 @@ function buildFeedbackBody(options) {
|
|
|
7593
8299
|
}
|
|
7594
8300
|
return body;
|
|
7595
8301
|
}
|
|
7596
|
-
function splitCsvOption(value) {
|
|
7597
|
-
if (!value)
|
|
7598
|
-
return [];
|
|
7599
|
-
return value
|
|
7600
|
-
.split(",")
|
|
7601
|
-
.map((entry) => entry.trim())
|
|
7602
|
-
.filter((entry) => entry.length > 0);
|
|
7603
|
-
}
|
|
7604
8302
|
function buildSupportTicketBody(options) {
|
|
7605
8303
|
const body = { subject: readOption(options.subject) };
|
|
7606
8304
|
if (readOption(options.body))
|
|
@@ -7614,9 +8312,9 @@ function buildSupportTicketBody(options) {
|
|
|
7614
8312
|
context.operation = readOption(options.operation);
|
|
7615
8313
|
if (readOption(options.errorCode))
|
|
7616
8314
|
context.error_code = readOption(options.errorCode);
|
|
7617
|
-
const runIds =
|
|
7618
|
-
const tableIds =
|
|
7619
|
-
const deepLinks =
|
|
8315
|
+
const runIds = readCsvOption(options.runIds);
|
|
8316
|
+
const tableIds = readCsvOption(options.tableIds);
|
|
8317
|
+
const deepLinks = readCsvOption(options.deepLinks);
|
|
7620
8318
|
if (runIds.length)
|
|
7621
8319
|
context.run_ids = runIds;
|
|
7622
8320
|
if (tableIds.length)
|
|
@@ -7647,12 +8345,6 @@ function readCsvOption(value) {
|
|
|
7647
8345
|
.map((entry) => entry.trim())
|
|
7648
8346
|
.filter(Boolean);
|
|
7649
8347
|
}
|
|
7650
|
-
function splitCsv(value) {
|
|
7651
|
-
return value
|
|
7652
|
-
.split(",")
|
|
7653
|
-
.map((entry) => entry.trim())
|
|
7654
|
-
.filter(Boolean);
|
|
7655
|
-
}
|
|
7656
8348
|
// Assemble the optional campaign email binding from the --email-* flags. The
|
|
7657
8349
|
// content spec (--email-definition-file) is the author-provided email sequence
|
|
7658
8350
|
// that the API compiles to an Instantly campaign on start; provider/connection
|
|
@@ -7665,7 +8357,7 @@ function readCampaignEmailBinding(options) {
|
|
|
7665
8357
|
if (!provider && !connectionId && !definitionPath)
|
|
7666
8358
|
return undefined;
|
|
7667
8359
|
const definition = definitionPath
|
|
7668
|
-
?
|
|
8360
|
+
? readJsonFileValue(resolve(definitionPath), "--email-definition-file")
|
|
7669
8361
|
: undefined;
|
|
7670
8362
|
return {
|
|
7671
8363
|
...(provider ? { provider } : {}),
|
|
@@ -7685,7 +8377,7 @@ slug, options) {
|
|
|
7685
8377
|
if (filePath) {
|
|
7686
8378
|
const fs = await import("node:fs/promises");
|
|
7687
8379
|
const raw = await fs.readFile(filePath, "utf8");
|
|
7688
|
-
body.envelope =
|
|
8380
|
+
body.envelope = parseJsonValue(raw, "--file");
|
|
7689
8381
|
}
|
|
7690
8382
|
const fromUrl = readOption(options.fromUrl);
|
|
7691
8383
|
if (fromUrl) {
|
|
@@ -7700,12 +8392,7 @@ slug, options) {
|
|
|
7700
8392
|
}
|
|
7701
8393
|
const inputJson = readOption(options.inputJson);
|
|
7702
8394
|
if (inputJson) {
|
|
7703
|
-
|
|
7704
|
-
body.inputs = JSON.parse(inputJson);
|
|
7705
|
-
}
|
|
7706
|
-
catch {
|
|
7707
|
-
throw new Error("--input-json must be valid JSON.");
|
|
7708
|
-
}
|
|
8395
|
+
body.inputs = parseJsonValue(inputJson, "--input-json");
|
|
7709
8396
|
}
|
|
7710
8397
|
const tableRefEntries = Array.isArray(options.tableRef) ? options.tableRef : [];
|
|
7711
8398
|
if (tableRefEntries.length > 0) {
|
|
@@ -7839,6 +8526,7 @@ table, options) {
|
|
|
7839
8526
|
? { phone_waterfall_profile: readOption(options.phoneWaterfallProfile) }
|
|
7840
8527
|
: {}),
|
|
7841
8528
|
...(options.verifyPhone ? { verify_phone: true } : {}),
|
|
8529
|
+
...(options.allowPremiumLanes ? { allow_premium_lanes: true } : {}),
|
|
7842
8530
|
...(readOption(options.phoneVerificationCredentialMode)
|
|
7843
8531
|
? { phone_verification_credential_mode: readOption(options.phoneVerificationCredentialMode) }
|
|
7844
8532
|
: {}),
|