@oxygen-agent/cli 1.162.10 → 1.177.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/http-client.js +2 -78
- package/dist/index.js +1312 -527
- package/dist/run-wait.d.ts +23 -0
- package/dist/run-wait.js +57 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +0 -5
- 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 +27 -0
- package/node_modules/@oxygen/shared/dist/cli-envelope.js +102 -0
- 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 +3 -39
- package/node_modules/@oxygen/shared/dist/index.js +3 -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/redaction.js +45 -4
- 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/node_modules/@oxygen/workflows/dist/index.d.ts +12 -11
- package/node_modules/@oxygen/workflows/dist/index.js +58 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,14 +14,51 @@ import { inferImportColumnLabels, inferRowsFileFormat, normalizeImportColumnKey,
|
|
|
14
14
|
import { assertRecipeBundleSafe, assertWorkflowManifest, buildRecipeManifest, compileWorkflowDefinition, isAnyWorkflowManifest, isRecipeManifest, isWorkflowDefinition, isWorkflowManifest, } from "@oxygen/workflows";
|
|
15
15
|
import { isRecipeDefinition } from "@oxygen/recipe-sdk";
|
|
16
16
|
import { createBrowserLoginSession, openBrowser } from "./browser-login.js";
|
|
17
|
-
import { clearCredentials, defaultApiUrl, listCredentialProfiles, normalizeApiUrl, pickProfileNameForIdentity, pickProfileNameForUserSession, resolveActiveProfile, saveCredentials, switchCredentialProfile, updateActiveOrganizationForProfile, } from "./credentials.js";
|
|
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"),
|
|
@@ -83,22 +128,47 @@ async function handleAsyncAction(command, options, action) {
|
|
|
83
128
|
process.exitCode = error instanceof OxygenError ? error.exitCode : 1;
|
|
84
129
|
}
|
|
85
130
|
}
|
|
86
|
-
// Paid
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
131
|
+
// Paid live runs are refused server-side with typed spend-gate errors
|
|
132
|
+
// (max_credits_required, approval_required, spend_cap_required,
|
|
133
|
+
// spend_cap_too_low). Surface each as a one-line stderr hint with the concrete
|
|
134
|
+
// re-run flags so users don't have to dig values out of the JSON envelope
|
|
135
|
+
// (stderr keeps --json stdout machine-clean).
|
|
90
136
|
function writeMaxCreditsHint(error) {
|
|
91
|
-
if (!(error instanceof OxygenError)
|
|
92
|
-
return;
|
|
93
|
-
const recommended = readRecommendedMaxCredits(error.details);
|
|
94
|
-
if (recommended === null)
|
|
137
|
+
if (!(error instanceof OxygenError))
|
|
95
138
|
return;
|
|
96
|
-
|
|
139
|
+
switch (error.code) {
|
|
140
|
+
case "max_credits_required": {
|
|
141
|
+
const recommended = readDetailsNumber(error.details, "recommended_max_credits");
|
|
142
|
+
if (recommended === null)
|
|
143
|
+
return;
|
|
144
|
+
process.stderr.write(`hint: re-run with --max-credits ${recommended} to approve the spend cap\n`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
case "approval_required": {
|
|
148
|
+
const estimated = readDetailsNumber(error.details, "estimated_credits");
|
|
149
|
+
process.stderr.write(`hint: inspect the dry run, then re-run with --approved --max-credits <n>${estimated !== null ? ` (estimated ~${estimated} credits)` : ""}\n`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
case "spend_cap_required": {
|
|
153
|
+
const estimated = readDetailsNumber(error.details, "estimated_credits");
|
|
154
|
+
process.stderr.write(`hint: re-run with --max-credits ${estimated !== null ? estimated : "<n>"} to cap the spend\n`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
case "spend_cap_too_low": {
|
|
158
|
+
const estimated = readDetailsNumber(error.details, "estimated_credits")
|
|
159
|
+
?? readDetailsNumber(error.details, "estimated_max_credits");
|
|
160
|
+
if (estimated === null)
|
|
161
|
+
return;
|
|
162
|
+
process.stderr.write(`hint: the estimate is ${estimated} credits; re-run with --max-credits ${estimated} or higher\n`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
}
|
|
97
167
|
}
|
|
98
|
-
function
|
|
168
|
+
function readDetailsNumber(details, key) {
|
|
99
169
|
if (!details || typeof details !== "object" || Array.isArray(details))
|
|
100
170
|
return null;
|
|
101
|
-
const value = details
|
|
171
|
+
const value = details[key];
|
|
102
172
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
103
173
|
}
|
|
104
174
|
function parseJsonObject(value) {
|
|
@@ -137,6 +207,33 @@ function parseJsonArray(value) {
|
|
|
137
207
|
}
|
|
138
208
|
return parsed;
|
|
139
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
|
+
}
|
|
140
237
|
async function readDeleteRowIdsOption(options) {
|
|
141
238
|
const rowIdsJson = readOption(options.rowIdsJson);
|
|
142
239
|
const rowIdsFile = readOption(options.rowIdsFile);
|
|
@@ -259,6 +356,54 @@ function resolveComposioRunMode(options) {
|
|
|
259
356
|
}
|
|
260
357
|
return "dry_run";
|
|
261
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
|
+
}
|
|
262
407
|
function readSpecFileBody(path) {
|
|
263
408
|
const text = readFileSync(resolve(path), "utf8");
|
|
264
409
|
try {
|
|
@@ -271,6 +416,88 @@ function readSpecFileBody(path) {
|
|
|
271
416
|
throw error;
|
|
272
417
|
}
|
|
273
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
|
+
}
|
|
274
501
|
export function createProgram() {
|
|
275
502
|
const program = new Command();
|
|
276
503
|
const binaryName = resolveCliBinaryName();
|
|
@@ -371,8 +598,8 @@ export function createProgram() {
|
|
|
371
598
|
.option("--package <npm_spec>", "Override the npm package spec.")
|
|
372
599
|
.option("--dry-run", "Print the update command without running it.")
|
|
373
600
|
.option("--json", "Print a JSON envelope.")
|
|
374
|
-
.action(
|
|
375
|
-
|
|
601
|
+
.action((options) => {
|
|
602
|
+
handleUpdateAction(options);
|
|
376
603
|
});
|
|
377
604
|
program
|
|
378
605
|
.command("api-keys")
|
|
@@ -381,7 +608,7 @@ export function createProgram() {
|
|
|
381
608
|
.description("List active CLI API keys for the current user and organization.")
|
|
382
609
|
.option("--json", "Print a JSON envelope.")
|
|
383
610
|
.action(async (options) => {
|
|
384
|
-
await handleAsyncAction("api-keys list", options,
|
|
611
|
+
await handleAsyncAction("api-keys list", options, () => requestOxygen("/api/cli/api-keys"));
|
|
385
612
|
}))
|
|
386
613
|
.addCommand(new Command("create")
|
|
387
614
|
.description("Create a CLI API key. The token is shown once.")
|
|
@@ -390,7 +617,7 @@ export function createProgram() {
|
|
|
390
617
|
.option("--expires-in-days <days>", "Expire the key after this many days.")
|
|
391
618
|
.option("--json", "Print a JSON envelope.")
|
|
392
619
|
.action(async (options) => {
|
|
393
|
-
await handleAsyncAction("api-keys create", options,
|
|
620
|
+
await handleAsyncAction("api-keys create", options, () => requestOxygen("/api/cli/api-keys", {
|
|
394
621
|
method: "POST",
|
|
395
622
|
body: buildApiKeyCreateBody(options),
|
|
396
623
|
}));
|
|
@@ -400,7 +627,7 @@ export function createProgram() {
|
|
|
400
627
|
.argument("<key-id>", "CLI API key id.")
|
|
401
628
|
.option("--json", "Print a JSON envelope.")
|
|
402
629
|
.action(async (keyId, options) => {
|
|
403
|
-
await handleAsyncAction("api-keys revoke", options,
|
|
630
|
+
await handleAsyncAction("api-keys revoke", options, () => requestOxygen(`/api/cli/api-keys/${encodeURIComponent(keyId)}`, {
|
|
404
631
|
method: "DELETE",
|
|
405
632
|
}));
|
|
406
633
|
}));
|
|
@@ -429,13 +656,26 @@ export function createProgram() {
|
|
|
429
656
|
}));
|
|
430
657
|
program
|
|
431
658
|
.command("status")
|
|
432
|
-
.description("Compare the local Oxygen CLI version against
|
|
659
|
+
.description("Compare the local Oxygen CLI version against the active profile's deployed Oxygen API.")
|
|
433
660
|
.option("--json", "Print a JSON envelope.")
|
|
434
661
|
.action(async (options) => {
|
|
435
662
|
await handleAsyncAction("status", options, async () => {
|
|
436
|
-
|
|
663
|
+
// Resolve the API URL exactly like authenticated commands do: the
|
|
664
|
+
// active profile (honoring --profile / OXYGEN_PROFILE / OXYGEN_API_URL
|
|
665
|
+
// overrides), falling back to the default prod URL only when no
|
|
666
|
+
// credentials are stored. Without this, requestOxygen's
|
|
667
|
+
// requireAuth:false path skips loadCredentials() and always hits prod,
|
|
668
|
+
// so `status` reports a different deployment than every other command.
|
|
669
|
+
const credentials = await loadCredentials();
|
|
670
|
+
const apiUrl = credentials?.apiUrl ?? defaultApiUrl();
|
|
671
|
+
const server = await requestOxygen("/api/health", {
|
|
672
|
+
credentials: credentials ?? { token: "", apiUrl },
|
|
673
|
+
requireAuth: false,
|
|
674
|
+
enforceMinimumCliVersion: false,
|
|
675
|
+
});
|
|
437
676
|
return {
|
|
438
677
|
client_version: OXYGEN_VERSION,
|
|
678
|
+
api_url: apiUrl,
|
|
439
679
|
server_version: server.server_version,
|
|
440
680
|
minimum_cli_version: server.minimum_cli_version ?? null,
|
|
441
681
|
sha: server.sha,
|
|
@@ -451,7 +691,7 @@ export function createProgram() {
|
|
|
451
691
|
.description("List organizations available to the current CLI identity.")
|
|
452
692
|
.option("--json", "Print a JSON envelope.")
|
|
453
693
|
.action(async (options) => {
|
|
454
|
-
await handleAsyncAction("orgs list", options,
|
|
694
|
+
await handleAsyncAction("orgs list", options, () => requestOxygen("/api/cli/orgs"));
|
|
455
695
|
}))
|
|
456
696
|
.addCommand(new Command("use")
|
|
457
697
|
.description("Select the active organization for this CLI profile.")
|
|
@@ -604,20 +844,20 @@ export function createProgram() {
|
|
|
604
844
|
.description("Show the current organization's tenant database status. Staff can pass global --org to inspect another org.")
|
|
605
845
|
.option("--json", "Print a JSON envelope.")
|
|
606
846
|
.action(async (options) => {
|
|
607
|
-
await handleAsyncAction("db status", options,
|
|
847
|
+
await handleAsyncAction("db status", options, () => requestOxygen("/api/cli/db/status"));
|
|
608
848
|
}))
|
|
609
849
|
.addCommand(new Command("provision")
|
|
610
850
|
.description("Provision a managed Neon tenant database for the current organization. Staff can pass global --org to repair another org.")
|
|
611
851
|
.option("--json", "Print a JSON envelope.")
|
|
612
852
|
.action(async (options) => {
|
|
613
|
-
await handleAsyncAction("db provision", options,
|
|
853
|
+
await handleAsyncAction("db provision", options, () => requestOxygen("/api/cli/db/provision", { method: "POST", body: {} }));
|
|
614
854
|
}))
|
|
615
855
|
.addCommand(new Command("migrate")
|
|
616
856
|
.description("Apply pending tenant database migrations for the current organization.")
|
|
617
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.")
|
|
618
858
|
.option("--json", "Print a JSON envelope.")
|
|
619
859
|
.action(async (options) => {
|
|
620
|
-
await handleAsyncAction("db migrate", options,
|
|
860
|
+
await handleAsyncAction("db migrate", options, () => requestOxygen("/api/cli/db/migrate", {
|
|
621
861
|
method: "POST",
|
|
622
862
|
body: options.rotateCredentials ? { rotate_credentials: true } : {},
|
|
623
863
|
}));
|
|
@@ -628,7 +868,7 @@ export function createProgram() {
|
|
|
628
868
|
.option("--dry-run", "List pending tenants without applying migrations.")
|
|
629
869
|
.option("--json", "Print a JSON envelope.")
|
|
630
870
|
.action(async (options) => {
|
|
631
|
-
await handleAsyncAction("db migrate-all", options,
|
|
871
|
+
await handleAsyncAction("db migrate-all", options, () => {
|
|
632
872
|
const limit = readPositiveInt(options.limit);
|
|
633
873
|
return requestOxygen("/api/cli/db/migrate-all", {
|
|
634
874
|
method: "POST",
|
|
@@ -704,14 +944,14 @@ export function createProgram() {
|
|
|
704
944
|
.description("Show tenant database cost controls and reconciliation status.")
|
|
705
945
|
.option("--json", "Print a JSON envelope.")
|
|
706
946
|
.action(async (options) => {
|
|
707
|
-
await handleAsyncAction("db cost-policy", options,
|
|
947
|
+
await handleAsyncAction("db cost-policy", options, () => requestOxygen("/api/cli/db/cost-policy"));
|
|
708
948
|
}))
|
|
709
949
|
.addCommand(new Command("reconcile-cost-policy")
|
|
710
950
|
.description("Apply tenant database cost controls to the managed Neon endpoint.")
|
|
711
951
|
.option("--suspend-idle", "Request immediate Neon compute suspension when the tenant is idle.")
|
|
712
952
|
.option("--json", "Print a JSON envelope.")
|
|
713
953
|
.action(async (options) => {
|
|
714
|
-
await handleAsyncAction("db reconcile-cost-policy", options,
|
|
954
|
+
await handleAsyncAction("db reconcile-cost-policy", options, () => requestOxygen("/api/cli/db/reconcile-cost-policy", {
|
|
715
955
|
method: "POST",
|
|
716
956
|
body: {
|
|
717
957
|
...(options.suspendIdle ? { suspend_idle: true } : {}),
|
|
@@ -723,7 +963,7 @@ export function createProgram() {
|
|
|
723
963
|
.requiredOption("--database-url <url>", "Owner connection string for the tenant Postgres database.")
|
|
724
964
|
.option("--json", "Print a JSON envelope.")
|
|
725
965
|
.action(async (options) => {
|
|
726
|
-
await handleAsyncAction("db attach", options,
|
|
966
|
+
await handleAsyncAction("db attach", options, () => requestOxygen("/api/cli/db/attach", {
|
|
727
967
|
method: "POST",
|
|
728
968
|
body: { database_url: options.databaseUrl },
|
|
729
969
|
}));
|
|
@@ -734,7 +974,7 @@ export function createProgram() {
|
|
|
734
974
|
.option("--max-rows <n>", "Maximum rows to return. Defaults to 100; hard cap is 1000.")
|
|
735
975
|
.option("--json", "Print a JSON envelope.")
|
|
736
976
|
.action(async (options) => {
|
|
737
|
-
await handleAsyncAction("db query", options,
|
|
977
|
+
await handleAsyncAction("db query", options, () => {
|
|
738
978
|
const maxRows = readPositiveInt(options.maxRows);
|
|
739
979
|
return requestOxygen("/api/cli/db/query", {
|
|
740
980
|
method: "POST",
|
|
@@ -752,18 +992,87 @@ export function createProgram() {
|
|
|
752
992
|
.description("List table projects in the current tenant database.")
|
|
753
993
|
.option("--json", "Print a JSON envelope.")
|
|
754
994
|
.action(async (options) => {
|
|
755
|
-
await handleAsyncAction("projects list", options,
|
|
995
|
+
await handleAsyncAction("projects list", options, () => requestOxygen("/api/cli/projects"));
|
|
756
996
|
}))
|
|
757
997
|
.addCommand(new Command("create")
|
|
758
998
|
.description("Create a schema-backed table project.")
|
|
759
999
|
.argument("<name>", "Display name for the project.")
|
|
760
1000
|
.option("--json", "Print a JSON envelope.")
|
|
761
1001
|
.action(async (name, options) => {
|
|
762
|
-
await handleAsyncAction("projects create", options,
|
|
1002
|
+
await handleAsyncAction("projects create", options, () => requestOxygen("/api/cli/projects", {
|
|
763
1003
|
method: "POST",
|
|
764
1004
|
body: { name },
|
|
765
1005
|
}));
|
|
766
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
|
+
}));
|
|
767
1076
|
const tablesCommand = program
|
|
768
1077
|
.command("tables")
|
|
769
1078
|
.description("Tenant workspace table commands.")
|
|
@@ -774,7 +1083,7 @@ export function createProgram() {
|
|
|
774
1083
|
.option("--project <project>", "Project id or slug. Defaults to General.")
|
|
775
1084
|
.option("--json", "Print a JSON envelope.")
|
|
776
1085
|
.action(async (name, options) => {
|
|
777
|
-
await handleAsyncAction("tables create", options,
|
|
1086
|
+
await handleAsyncAction("tables create", options, () => requestOxygen("/api/cli/tables", {
|
|
778
1087
|
method: "POST",
|
|
779
1088
|
body: {
|
|
780
1089
|
name,
|
|
@@ -790,7 +1099,7 @@ export function createProgram() {
|
|
|
790
1099
|
.option("--schema-only", "Duplicate columns and definitions without copying row values.")
|
|
791
1100
|
.option("--json", "Print a JSON envelope.")
|
|
792
1101
|
.action(async (table, options) => {
|
|
793
|
-
await handleAsyncAction("tables duplicate", options,
|
|
1102
|
+
await handleAsyncAction("tables duplicate", options, () => requestOxygen("/api/cli/tables/duplicate", {
|
|
794
1103
|
method: "POST",
|
|
795
1104
|
body: {
|
|
796
1105
|
table,
|
|
@@ -805,7 +1114,7 @@ export function createProgram() {
|
|
|
805
1114
|
.requiredOption("--rows-json <json>", "JSON array of row objects keyed by column key.")
|
|
806
1115
|
.option("--json", "Print a JSON envelope.")
|
|
807
1116
|
.action(async (table, options) => {
|
|
808
|
-
await handleAsyncAction("tables insert", options,
|
|
1117
|
+
await handleAsyncAction("tables insert", options, () => requestOxygen("/api/cli/tables/rows", {
|
|
809
1118
|
method: "POST",
|
|
810
1119
|
body: {
|
|
811
1120
|
table,
|
|
@@ -820,7 +1129,7 @@ export function createProgram() {
|
|
|
820
1129
|
.requiredOption("--values-json <json>", "JSON object of values keyed by column key.")
|
|
821
1130
|
.option("--json", "Print a JSON envelope.")
|
|
822
1131
|
.action(async (table, rowId, options) => {
|
|
823
|
-
await handleAsyncAction("tables update", options,
|
|
1132
|
+
await handleAsyncAction("tables update", options, () => requestOxygen("/api/cli/tables/rows/update", {
|
|
824
1133
|
method: "POST",
|
|
825
1134
|
body: {
|
|
826
1135
|
table,
|
|
@@ -835,7 +1144,7 @@ export function createProgram() {
|
|
|
835
1144
|
.argument("<row_id>", "Workspace row UUID.")
|
|
836
1145
|
.option("--json", "Print a JSON envelope.")
|
|
837
1146
|
.action(async (table, rowId, options) => {
|
|
838
|
-
await handleAsyncAction("tables delete-row", options,
|
|
1147
|
+
await handleAsyncAction("tables delete-row", options, () => requestOxygen("/api/cli/tables/rows/delete", {
|
|
839
1148
|
method: "POST",
|
|
840
1149
|
body: {
|
|
841
1150
|
table,
|
|
@@ -873,7 +1182,7 @@ export function createProgram() {
|
|
|
873
1182
|
.option("--dry-run", "Preview inserts, updates, duplicate keys, and field conflicts without writing rows.")
|
|
874
1183
|
.option("--json", "Print a JSON envelope.")
|
|
875
1184
|
.action(async (table, options) => {
|
|
876
|
-
await handleAsyncAction("tables upsert", options,
|
|
1185
|
+
await handleAsyncAction("tables upsert", options, () => requestOxygen("/api/cli/tables/rows/upsert", {
|
|
877
1186
|
method: "POST",
|
|
878
1187
|
body: {
|
|
879
1188
|
table,
|
|
@@ -899,7 +1208,7 @@ export function createProgram() {
|
|
|
899
1208
|
.option("--max-concurrency <n>", "Maximum concurrent import chunks for background mode. Defaults to 5.")
|
|
900
1209
|
.option("--json", "Print a JSON envelope.")
|
|
901
1210
|
.action(async (table, options) => {
|
|
902
|
-
await handleAsyncAction("tables import", options,
|
|
1211
|
+
await handleAsyncAction("tables import", options, () => importRows(table, options));
|
|
903
1212
|
}))
|
|
904
1213
|
.addCommand(new Command("export")
|
|
905
1214
|
.description("Export workspace table rows as JSON, JSONL, CSV, or a human-readable table.")
|
|
@@ -909,7 +1218,7 @@ export function createProgram() {
|
|
|
909
1218
|
.option("--limit <n>", "Maximum rows to export. Defaults to 100; hard cap is 1000.")
|
|
910
1219
|
.option("--json", "Print a JSON envelope.")
|
|
911
1220
|
.action(async (table, options) => {
|
|
912
|
-
await handleAsyncAction("tables export", options,
|
|
1221
|
+
await handleAsyncAction("tables export", options, () => exportRows(table, options));
|
|
913
1222
|
}))
|
|
914
1223
|
.addCommand(new Command("export-bundle")
|
|
915
1224
|
.description("Export a workspace table as a portable bundle (schema + every row) for cross-environment / cross-org copies.")
|
|
@@ -918,7 +1227,7 @@ export function createProgram() {
|
|
|
918
1227
|
.option("--page-size <n>", "Rows per cursor-paginated request. Defaults to 500; hard cap is 1000.")
|
|
919
1228
|
.option("--json", "Print a JSON envelope (omit row payload — use --output to keep the rows).")
|
|
920
1229
|
.action(async (table, options) => {
|
|
921
|
-
await handleAsyncAction("tables export-bundle", options,
|
|
1230
|
+
await handleAsyncAction("tables export-bundle", options, () => exportTableBundle(table, options));
|
|
922
1231
|
}))
|
|
923
1232
|
.addCommand(new Command("import-bundle")
|
|
924
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.")
|
|
@@ -930,14 +1239,14 @@ export function createProgram() {
|
|
|
930
1239
|
.option("--batch-size <n>", "Rows per write request. Defaults to 500; paid orgs may use up to 5000.")
|
|
931
1240
|
.option("--json", "Print a JSON envelope.")
|
|
932
1241
|
.action(async (options) => {
|
|
933
|
-
await handleAsyncAction("tables import-bundle", options,
|
|
1242
|
+
await handleAsyncAction("tables import-bundle", options, () => importTableBundle(options));
|
|
934
1243
|
}))
|
|
935
1244
|
.addCommand(new Command("list")
|
|
936
1245
|
.description("List workspace tables in the current tenant database.")
|
|
937
1246
|
.option("--project <project>", "Project id or slug to filter by.")
|
|
938
1247
|
.option("--json", "Print a JSON envelope.")
|
|
939
1248
|
.action(async (options) => {
|
|
940
|
-
await handleAsyncAction("tables list", options,
|
|
1249
|
+
await handleAsyncAction("tables list", options, () => requestOxygen(readOption(options.project)
|
|
941
1250
|
? `/api/cli/tables?project=${encodeURIComponent(readOption(options.project))}`
|
|
942
1251
|
: "/api/cli/tables"));
|
|
943
1252
|
}))
|
|
@@ -947,12 +1256,20 @@ export function createProgram() {
|
|
|
947
1256
|
.option("--limit <n>", "Maximum rows to return. Defaults to 100; hard cap is 1000.")
|
|
948
1257
|
.option("--cursor <cursor>", "Pagination cursor returned by a previous query.")
|
|
949
1258
|
.option("--fields <columns>", "Comma-separated column keys or ids to include.")
|
|
950
|
-
.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).")
|
|
951
1263
|
.option("--json", "Print a JSON envelope.")
|
|
952
1264
|
.action(async (table, options) => {
|
|
953
|
-
await handleAsyncAction("tables query", options,
|
|
1265
|
+
await handleAsyncAction("tables query", options, () => {
|
|
954
1266
|
const limit = readPositiveInt(options.limit);
|
|
955
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
|
+
}
|
|
956
1273
|
return requestOxygen("/api/cli/tables/query", {
|
|
957
1274
|
method: "POST",
|
|
958
1275
|
body: {
|
|
@@ -961,6 +1278,9 @@ export function createProgram() {
|
|
|
961
1278
|
...(readOption(options.cursor) ? { cursor: readOption(options.cursor) } : {}),
|
|
962
1279
|
...(readOption(options.fields) ? { fields: readCsvOption(options.fields) } : {}),
|
|
963
1280
|
...(filters ? { filters } : {}),
|
|
1281
|
+
...(filterTree ? { filterTree } : {}),
|
|
1282
|
+
...(sorts ? { sorts } : {}),
|
|
1283
|
+
...(options.systemFields === false ? { include_system_fields: false } : {}),
|
|
964
1284
|
},
|
|
965
1285
|
});
|
|
966
1286
|
});
|
|
@@ -996,13 +1316,27 @@ export function createProgram() {
|
|
|
996
1316
|
.option("--include-archived", "Include archived columns.")
|
|
997
1317
|
.option("--json", "Print a JSON envelope.")
|
|
998
1318
|
.action(async (table, options) => {
|
|
999
|
-
await handleAsyncAction("tables describe", options,
|
|
1319
|
+
await handleAsyncAction("tables describe", options, () => requestOxygen("/api/cli/tables/describe", {
|
|
1000
1320
|
method: "POST",
|
|
1001
1321
|
body: {
|
|
1002
1322
|
table,
|
|
1003
1323
|
...(options.includeArchived ? { include_archived: true } : {}),
|
|
1004
1324
|
},
|
|
1005
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
|
+
});
|
|
1006
1340
|
}))
|
|
1007
1341
|
.addCommand(new Command("rename")
|
|
1008
1342
|
.description("Rename a workspace table display name and slug.")
|
|
@@ -1010,7 +1344,7 @@ export function createProgram() {
|
|
|
1010
1344
|
.requiredOption("--name <name>", "New table display name.")
|
|
1011
1345
|
.option("--json", "Print a JSON envelope.")
|
|
1012
1346
|
.action(async (table, options) => {
|
|
1013
|
-
await handleAsyncAction("tables rename", options,
|
|
1347
|
+
await handleAsyncAction("tables rename", options, () => requestOxygen("/api/cli/tables/rename", {
|
|
1014
1348
|
method: "POST",
|
|
1015
1349
|
body: { table, name: options.name },
|
|
1016
1350
|
}));
|
|
@@ -1020,7 +1354,7 @@ export function createProgram() {
|
|
|
1020
1354
|
.argument("<table>", "Table id or slug.")
|
|
1021
1355
|
.option("--json", "Print a JSON envelope.")
|
|
1022
1356
|
.action(async (table, options) => {
|
|
1023
|
-
await handleAsyncAction("tables archive", options,
|
|
1357
|
+
await handleAsyncAction("tables archive", options, () => requestOxygen("/api/cli/tables/archive", {
|
|
1024
1358
|
method: "POST",
|
|
1025
1359
|
body: { table },
|
|
1026
1360
|
}));
|
|
@@ -1031,7 +1365,7 @@ export function createProgram() {
|
|
|
1031
1365
|
.requiredOption("--project <project>", "Destination project id or slug.")
|
|
1032
1366
|
.option("--json", "Print a JSON envelope.")
|
|
1033
1367
|
.action(async (table, options) => {
|
|
1034
|
-
await handleAsyncAction("tables move", options,
|
|
1368
|
+
await handleAsyncAction("tables move", options, () => requestOxygen("/api/cli/tables/move", {
|
|
1035
1369
|
method: "POST",
|
|
1036
1370
|
body: { table, project: options.project },
|
|
1037
1371
|
}));
|
|
@@ -1042,7 +1376,7 @@ export function createProgram() {
|
|
|
1042
1376
|
.option("--max-items <n>", "Max pending cache rows to poll in this run. Defaults to 1000.")
|
|
1043
1377
|
.option("--json", "Print a JSON envelope.")
|
|
1044
1378
|
.action(async (table, options) => {
|
|
1045
|
-
await handleAsyncAction("tables recover-pending", options,
|
|
1379
|
+
await handleAsyncAction("tables recover-pending", options, () => {
|
|
1046
1380
|
const maxItems = options.maxItems ? Number.parseInt(options.maxItems, 10) : undefined;
|
|
1047
1381
|
return requestOxygen("/api/cli/tables/recover-pending", {
|
|
1048
1382
|
method: "POST",
|
|
@@ -1158,6 +1492,69 @@ export function createProgram() {
|
|
|
1158
1492
|
autoRunStatus: options.autoRunStatus,
|
|
1159
1493
|
limit: options.limit,
|
|
1160
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" })))));
|
|
1161
1558
|
program
|
|
1162
1559
|
.command("context")
|
|
1163
1560
|
.description("Workspace-level GTM context commands.")
|
|
@@ -1172,7 +1569,7 @@ export function createProgram() {
|
|
|
1172
1569
|
.option("--require-ready", "Exit with a conflict error when required context sections are missing.")
|
|
1173
1570
|
.option("--json", "Print a JSON envelope.")
|
|
1174
1571
|
.action(async (options) => {
|
|
1175
|
-
await handleAsyncAction("context resolve", options,
|
|
1572
|
+
await handleAsyncAction("context resolve", options, () => requestOxygen("/api/cli/context/resolve", {
|
|
1176
1573
|
method: "POST",
|
|
1177
1574
|
body: buildContextResolveBody(options),
|
|
1178
1575
|
}));
|
|
@@ -1183,7 +1580,7 @@ export function createProgram() {
|
|
|
1183
1580
|
.description("Read the current workspace company profile.")
|
|
1184
1581
|
.option("--json", "Print a JSON envelope.")
|
|
1185
1582
|
.action(async (options) => {
|
|
1186
|
-
await handleAsyncAction("context profile get", options,
|
|
1583
|
+
await handleAsyncAction("context profile get", options, () => requestOxygen("/api/cli/context/profile"));
|
|
1187
1584
|
}))
|
|
1188
1585
|
.addCommand(new Command("update")
|
|
1189
1586
|
.description("Merge one or more profile sections into workspace GTM memory.")
|
|
@@ -1191,7 +1588,7 @@ export function createProgram() {
|
|
|
1191
1588
|
.option("--summary <text>", "Optional concise summary for the profile.")
|
|
1192
1589
|
.option("--json", "Print a JSON envelope.")
|
|
1193
1590
|
.action(async (options) => {
|
|
1194
|
-
await handleAsyncAction("context profile update", options,
|
|
1591
|
+
await handleAsyncAction("context profile update", options, () => requestOxygen("/api/cli/context/profile/update", {
|
|
1195
1592
|
method: "POST",
|
|
1196
1593
|
body: {
|
|
1197
1594
|
data: parseJsonObject(options.dataJson ?? "{}"),
|
|
@@ -1209,14 +1606,14 @@ export function createProgram() {
|
|
|
1209
1606
|
.option("--include-archived", "Include archived assets when no status filter is set.")
|
|
1210
1607
|
.option("--json", "Print a JSON envelope.")
|
|
1211
1608
|
.action(async (options) => {
|
|
1212
|
-
await handleAsyncAction("context assets list", options,
|
|
1609
|
+
await handleAsyncAction("context assets list", options, () => requestOxygen(`/api/cli/context/assets${contextAssetsQuery(options)}`));
|
|
1213
1610
|
}))
|
|
1214
1611
|
.addCommand(new Command("get")
|
|
1215
1612
|
.description("Read one context asset.")
|
|
1216
1613
|
.argument("<asset_id>", "Context asset UUID.")
|
|
1217
1614
|
.option("--json", "Print a JSON envelope.")
|
|
1218
1615
|
.action(async (assetId, options) => {
|
|
1219
|
-
await handleAsyncAction("context asset get", options,
|
|
1616
|
+
await handleAsyncAction("context asset get", options, () => requestOxygen("/api/cli/context/assets/get", {
|
|
1220
1617
|
method: "POST",
|
|
1221
1618
|
body: { id: assetId },
|
|
1222
1619
|
}));
|
|
@@ -1234,7 +1631,7 @@ export function createProgram() {
|
|
|
1234
1631
|
.option("--asset-json <json>", "Full asset JSON object. CLI flags override matching fields.")
|
|
1235
1632
|
.option("--json", "Print a JSON envelope.")
|
|
1236
1633
|
.action(async (options) => {
|
|
1237
|
-
await handleAsyncAction("context asset upsert", options,
|
|
1634
|
+
await handleAsyncAction("context asset upsert", options, () => requestOxygen("/api/cli/context/assets/upsert", {
|
|
1238
1635
|
method: "POST",
|
|
1239
1636
|
body: buildContextAssetUpsertBody(options),
|
|
1240
1637
|
}));
|
|
@@ -1244,7 +1641,7 @@ export function createProgram() {
|
|
|
1244
1641
|
.argument("<asset_id>", "Context asset UUID.")
|
|
1245
1642
|
.option("--json", "Print a JSON envelope.")
|
|
1246
1643
|
.action(async (assetId, options) => {
|
|
1247
|
-
await handleAsyncAction("context asset archive", options,
|
|
1644
|
+
await handleAsyncAction("context asset archive", options, () => requestOxygen("/api/cli/context/assets/archive", {
|
|
1248
1645
|
method: "POST",
|
|
1249
1646
|
body: { id: assetId },
|
|
1250
1647
|
}));
|
|
@@ -1298,10 +1695,10 @@ export function createProgram() {
|
|
|
1298
1695
|
const body = { workflow_id: options.workflow };
|
|
1299
1696
|
const tables = readOption(options.tables);
|
|
1300
1697
|
if (tables)
|
|
1301
|
-
body.table_ids =
|
|
1698
|
+
body.table_ids = readCsvOption(tables);
|
|
1302
1699
|
const prompts = readOption(options.prompts);
|
|
1303
1700
|
if (prompts)
|
|
1304
|
-
body.prompt_slugs =
|
|
1701
|
+
body.prompt_slugs = readCsvOption(prompts);
|
|
1305
1702
|
const blueprintId = readOption(options.blueprintId);
|
|
1306
1703
|
if (blueprintId)
|
|
1307
1704
|
body.blueprint_id = blueprintId;
|
|
@@ -1313,7 +1710,7 @@ export function createProgram() {
|
|
|
1313
1710
|
body.blueprint_summary = blueprintSummary;
|
|
1314
1711
|
const blueprintTags = readOption(options.blueprintTags);
|
|
1315
1712
|
if (blueprintTags)
|
|
1316
|
-
body.blueprint_tags =
|
|
1713
|
+
body.blueprint_tags = readCsvOption(blueprintTags);
|
|
1317
1714
|
const result = await requestOxygen("/api/cli/blueprints/export", {
|
|
1318
1715
|
method: "POST",
|
|
1319
1716
|
body,
|
|
@@ -1377,7 +1774,7 @@ export function createProgram() {
|
|
|
1377
1774
|
throw new Error("--file is required for blueprints save");
|
|
1378
1775
|
const fs = await import("node:fs/promises");
|
|
1379
1776
|
const raw = await fs.readFile(filePath, "utf8");
|
|
1380
|
-
const envelope =
|
|
1777
|
+
const envelope = parseJsonValue(raw, "--file");
|
|
1381
1778
|
const body = { envelope };
|
|
1382
1779
|
const slug = readOption(options.slug);
|
|
1383
1780
|
if (slug)
|
|
@@ -1480,154 +1877,8 @@ export function createProgram() {
|
|
|
1480
1877
|
return requestOxygen(`/api/blueprints/marketplace${qs}`, { requireAuth: false });
|
|
1481
1878
|
});
|
|
1482
1879
|
}));
|
|
1483
|
-
program
|
|
1484
|
-
|
|
1485
|
-
.description("Reusable prompt templates layered into AI columns at run time.")
|
|
1486
|
-
.addCommand(new Command("list")
|
|
1487
|
-
.description("List prompt templates in the workspace.")
|
|
1488
|
-
.option("--kind <kind>", "Filter by ai_column_system, scoring_rubric, or other.")
|
|
1489
|
-
.option("--include-archived", "Include archived templates.")
|
|
1490
|
-
.option("--json", "Print a JSON envelope.")
|
|
1491
|
-
.action(async (options) => {
|
|
1492
|
-
await handleAsyncAction("prompts list", options, () => {
|
|
1493
|
-
const params = new URLSearchParams();
|
|
1494
|
-
const kind = readOption(options.kind);
|
|
1495
|
-
if (kind)
|
|
1496
|
-
params.set("kind", kind);
|
|
1497
|
-
if (options.includeArchived)
|
|
1498
|
-
params.set("include_archived", "true");
|
|
1499
|
-
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1500
|
-
return requestOxygen(`/api/cli/templates${qs}`);
|
|
1501
|
-
});
|
|
1502
|
-
}))
|
|
1503
|
-
.addCommand(new Command("get")
|
|
1504
|
-
.description("Read one prompt template by id or slug.")
|
|
1505
|
-
.argument("<id_or_slug>", "Template UUID or slug.")
|
|
1506
|
-
.option("--json", "Print a JSON envelope.")
|
|
1507
|
-
.action(async (idOrSlug, options) => {
|
|
1508
|
-
await handleAsyncAction("prompts get", options, () => requestOxygen("/api/cli/templates/get", {
|
|
1509
|
-
method: "POST",
|
|
1510
|
-
body: idOrSlug.includes("-") && idOrSlug.length >= 32
|
|
1511
|
-
? { id: idOrSlug }
|
|
1512
|
-
: { slug: idOrSlug },
|
|
1513
|
-
}));
|
|
1514
|
-
}))
|
|
1515
|
-
.addCommand(new Command("upsert")
|
|
1516
|
-
.description("Create or update a prompt template.")
|
|
1517
|
-
.option("--id <id>", "Existing template UUID to update. Omit to create.")
|
|
1518
|
-
.option("--slug <slug>", "Stable slug (kebab-case).")
|
|
1519
|
-
.option("--name <name>", "Human-readable name.")
|
|
1520
|
-
.option("--description <text>", "Short description.")
|
|
1521
|
-
.option("--kind <kind>", "Template kind: ai_column_system, scoring_rubric, or other.")
|
|
1522
|
-
.option("--body <text>", "Prompt body.")
|
|
1523
|
-
.option("--body-file <path>", "Path to a file containing the prompt body.")
|
|
1524
|
-
.option("--json", "Print a JSON envelope.")
|
|
1525
|
-
.action(async (options) => {
|
|
1526
|
-
await handleAsyncAction("prompts upsert", options, async () => {
|
|
1527
|
-
const body = {};
|
|
1528
|
-
if (readOption(options.id))
|
|
1529
|
-
body.id = readOption(options.id);
|
|
1530
|
-
if (readOption(options.slug))
|
|
1531
|
-
body.slug = readOption(options.slug);
|
|
1532
|
-
if (readOption(options.name))
|
|
1533
|
-
body.name = readOption(options.name);
|
|
1534
|
-
if (readOption(options.description) !== undefined)
|
|
1535
|
-
body.description = readOption(options.description);
|
|
1536
|
-
if (readOption(options.kind))
|
|
1537
|
-
body.kind = readOption(options.kind);
|
|
1538
|
-
if (readOption(options.body))
|
|
1539
|
-
body.body = readOption(options.body);
|
|
1540
|
-
else {
|
|
1541
|
-
const path = readOption(options.bodyFile);
|
|
1542
|
-
if (path) {
|
|
1543
|
-
const fs = await import("node:fs/promises");
|
|
1544
|
-
body.body = await fs.readFile(path, "utf8");
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
return requestOxygen("/api/cli/templates/upsert", { method: "POST", body });
|
|
1548
|
-
});
|
|
1549
|
-
}))
|
|
1550
|
-
.addCommand(new Command("archive")
|
|
1551
|
-
.description("Archive a prompt template. Seeded templates cannot be archived.")
|
|
1552
|
-
.argument("<id>", "Template UUID.")
|
|
1553
|
-
.option("--json", "Print a JSON envelope.")
|
|
1554
|
-
.action(async (id, options) => {
|
|
1555
|
-
await handleAsyncAction("prompts archive", options, () => requestOxygen("/api/cli/templates/archive", { method: "POST", body: { id } }));
|
|
1556
|
-
}));
|
|
1557
|
-
program
|
|
1558
|
-
.command("templates")
|
|
1559
|
-
.description("Deprecated alias for 'oxygen prompts'. Will be removed in a future release.")
|
|
1560
|
-
.addCommand(new Command("list")
|
|
1561
|
-
.description("List prompt templates in the workspace.")
|
|
1562
|
-
.option("--kind <kind>", "Filter by ai_column_system, scoring_rubric, or other.")
|
|
1563
|
-
.option("--include-archived", "Include archived templates.")
|
|
1564
|
-
.option("--json", "Print a JSON envelope.")
|
|
1565
|
-
.action(async (options) => {
|
|
1566
|
-
await handleAsyncAction("templates list", options, () => {
|
|
1567
|
-
const params = new URLSearchParams();
|
|
1568
|
-
const kind = readOption(options.kind);
|
|
1569
|
-
if (kind)
|
|
1570
|
-
params.set("kind", kind);
|
|
1571
|
-
if (options.includeArchived)
|
|
1572
|
-
params.set("include_archived", "true");
|
|
1573
|
-
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1574
|
-
return requestOxygen(`/api/cli/templates${qs}`);
|
|
1575
|
-
});
|
|
1576
|
-
}))
|
|
1577
|
-
.addCommand(new Command("get")
|
|
1578
|
-
.description("Read one prompt template by id or slug.")
|
|
1579
|
-
.argument("<id_or_slug>", "Template UUID or slug.")
|
|
1580
|
-
.option("--json", "Print a JSON envelope.")
|
|
1581
|
-
.action(async (idOrSlug, options) => {
|
|
1582
|
-
await handleAsyncAction("templates get", options, async () => requestOxygen("/api/cli/templates/get", {
|
|
1583
|
-
method: "POST",
|
|
1584
|
-
body: idOrSlug.includes("-") && idOrSlug.length >= 32
|
|
1585
|
-
? { id: idOrSlug }
|
|
1586
|
-
: { slug: idOrSlug },
|
|
1587
|
-
}));
|
|
1588
|
-
}))
|
|
1589
|
-
.addCommand(new Command("upsert")
|
|
1590
|
-
.description("Create or update a prompt template.")
|
|
1591
|
-
.option("--id <id>", "Existing template UUID to update. Omit to create.")
|
|
1592
|
-
.option("--slug <slug>", "Stable slug (kebab-case).")
|
|
1593
|
-
.option("--name <name>", "Human-readable name.")
|
|
1594
|
-
.option("--description <text>", "Short description.")
|
|
1595
|
-
.option("--kind <kind>", "Template kind: ai_column_system, scoring_rubric, or other.")
|
|
1596
|
-
.option("--body <text>", "Prompt body.")
|
|
1597
|
-
.option("--body-file <path>", "Path to a file containing the prompt body.")
|
|
1598
|
-
.option("--json", "Print a JSON envelope.")
|
|
1599
|
-
.action(async (options) => {
|
|
1600
|
-
await handleAsyncAction("templates upsert", options, async () => {
|
|
1601
|
-
const body = {};
|
|
1602
|
-
if (readOption(options.id))
|
|
1603
|
-
body.id = readOption(options.id);
|
|
1604
|
-
if (readOption(options.slug))
|
|
1605
|
-
body.slug = readOption(options.slug);
|
|
1606
|
-
if (readOption(options.name))
|
|
1607
|
-
body.name = readOption(options.name);
|
|
1608
|
-
if (readOption(options.description) !== undefined)
|
|
1609
|
-
body.description = readOption(options.description);
|
|
1610
|
-
if (readOption(options.kind))
|
|
1611
|
-
body.kind = readOption(options.kind);
|
|
1612
|
-
if (readOption(options.body))
|
|
1613
|
-
body.body = readOption(options.body);
|
|
1614
|
-
else {
|
|
1615
|
-
const path = readOption(options.bodyFile);
|
|
1616
|
-
if (path) {
|
|
1617
|
-
const fs = await import("node:fs/promises");
|
|
1618
|
-
body.body = await fs.readFile(path, "utf8");
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
return requestOxygen("/api/cli/templates/upsert", { method: "POST", body });
|
|
1622
|
-
});
|
|
1623
|
-
}))
|
|
1624
|
-
.addCommand(new Command("archive")
|
|
1625
|
-
.description("Archive a prompt template. Seeded templates cannot be archived.")
|
|
1626
|
-
.argument("<id>", "Template UUID.")
|
|
1627
|
-
.option("--json", "Print a JSON envelope.")
|
|
1628
|
-
.action(async (id, options) => {
|
|
1629
|
-
await handleAsyncAction("templates archive", options, async () => requestOxygen("/api/cli/templates/archive", { method: "POST", body: { id } }));
|
|
1630
|
-
}));
|
|
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."));
|
|
1631
1882
|
program
|
|
1632
1883
|
.command("reviews")
|
|
1633
1884
|
.description("Human-in-the-loop reviews for AI-generated outreach messages.")
|
|
@@ -1672,7 +1923,7 @@ export function createProgram() {
|
|
|
1672
1923
|
.argument("<review_id>", "Message review UUID.")
|
|
1673
1924
|
.option("--json", "Print a JSON envelope.")
|
|
1674
1925
|
.action(async (reviewId, options) => {
|
|
1675
|
-
await handleAsyncAction("reviews accept", options,
|
|
1926
|
+
await handleAsyncAction("reviews accept", options, () => requestOxygen("/api/cli/message-reviews/decide", {
|
|
1676
1927
|
method: "POST",
|
|
1677
1928
|
body: { id: reviewId, decision: "accept" },
|
|
1678
1929
|
}));
|
|
@@ -1688,7 +1939,7 @@ export function createProgram() {
|
|
|
1688
1939
|
const body = { id: reviewId, decision: "reject" };
|
|
1689
1940
|
const highlightsJson = readOption(options.highlightsJson);
|
|
1690
1941
|
if (highlightsJson) {
|
|
1691
|
-
body.highlights =
|
|
1942
|
+
body.highlights = parseJsonValue(highlightsJson, "--highlights-json");
|
|
1692
1943
|
}
|
|
1693
1944
|
if (options.autoRerun)
|
|
1694
1945
|
body.auto_rerun = true;
|
|
@@ -1709,7 +1960,14 @@ export function createProgram() {
|
|
|
1709
1960
|
.option("--definition-json <json>", "Optional JSON object with column definition metadata.")
|
|
1710
1961
|
.option("--prompt-key <key>", "OXYGEN prompt-library key (e.g. email_draft_v1). Materializes prompt + output_schema and forces kind=ai.")
|
|
1711
1962
|
.option("--input-mapping <json>", "Required with --prompt-key. JSON object mapping prompt input names to column or literal refs.")
|
|
1712
|
-
.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
|
|
1713
1971
|
.action(async (table, options) => {
|
|
1714
1972
|
if (!options.promptKey && !options.label) {
|
|
1715
1973
|
throw new OxygenError("invalid_request", "--label is required.", { exitCode: 1 });
|
|
@@ -1730,12 +1988,20 @@ export function createProgram() {
|
|
|
1730
1988
|
column.semantic_type = options.semanticType;
|
|
1731
1989
|
if (options.definitionJson)
|
|
1732
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
|
+
}
|
|
1733
1999
|
const body = { table, column };
|
|
1734
2000
|
if (options.promptKey)
|
|
1735
2001
|
body.prompt_key = options.promptKey;
|
|
1736
2002
|
if (options.inputMapping)
|
|
1737
2003
|
body.input_mapping = parseJsonObject(options.inputMapping);
|
|
1738
|
-
await handleAsyncAction("columns add", options,
|
|
2004
|
+
await handleAsyncAction("columns add", options, () => requestOxygen("/api/cli/tables/columns", {
|
|
1739
2005
|
method: "POST",
|
|
1740
2006
|
body,
|
|
1741
2007
|
}));
|
|
@@ -1755,6 +2021,7 @@ export function createProgram() {
|
|
|
1755
2021
|
.option("--max-concurrency <n>", "Maximum concurrent row items for a background run. Defaults to 250 for AI columns and 50 otherwise.")
|
|
1756
2022
|
.option("--local", "Run a custom HTTP column in this CLI process so env-var secrets stay local.")
|
|
1757
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.")
|
|
1758
2025
|
.option("--json", "Print a JSON envelope.")
|
|
1759
2026
|
.action(async (table, column, options) => {
|
|
1760
2027
|
const limit = readPositiveInt(options.limit);
|
|
@@ -1778,7 +2045,8 @@ export function createProgram() {
|
|
|
1778
2045
|
exitCode: 1,
|
|
1779
2046
|
});
|
|
1780
2047
|
}
|
|
1781
|
-
|
|
2048
|
+
// skipcq: JS-R1005 — intentional branching for local/background/filter column-run modes
|
|
2049
|
+
await handleAsyncAction("columns run", options, () => {
|
|
1782
2050
|
if (options.local) {
|
|
1783
2051
|
if (options.background) {
|
|
1784
2052
|
throw new OxygenError("invalid_column_run", "Pass either --local or --background, not both.", {
|
|
@@ -1809,6 +2077,7 @@ export function createProgram() {
|
|
|
1809
2077
|
...(options.background ? { background: true } : {}),
|
|
1810
2078
|
...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
|
|
1811
2079
|
...(maxConcurrency ? { max_concurrency: maxConcurrency } : {}),
|
|
2080
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
1812
2081
|
};
|
|
1813
2082
|
return requestColumnsRun(body, table, {
|
|
1814
2083
|
background: Boolean(options.background),
|
|
@@ -1821,15 +2090,17 @@ export function createProgram() {
|
|
|
1821
2090
|
.requiredOption("--column <column>", "Column id or key.")
|
|
1822
2091
|
.requiredOption("--row <row_id>", "Row UUID.")
|
|
1823
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.")
|
|
1824
2094
|
.option("--json", "Print a JSON envelope.")
|
|
1825
2095
|
.action(async (options) => {
|
|
1826
|
-
await handleAsyncAction("columns rerun", options,
|
|
2096
|
+
await handleAsyncAction("columns rerun", options, () => requestOxygen("/api/cli/columns/rerun", {
|
|
1827
2097
|
method: "POST",
|
|
1828
2098
|
body: {
|
|
1829
2099
|
table: options.table,
|
|
1830
2100
|
column: options.column,
|
|
1831
2101
|
row_id: options.row,
|
|
1832
2102
|
...(readOption(options.fromReviewId) ? { from_review_id: readOption(options.fromReviewId) } : {}),
|
|
2103
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
1833
2104
|
},
|
|
1834
2105
|
}));
|
|
1835
2106
|
}))
|
|
@@ -1844,7 +2115,7 @@ export function createProgram() {
|
|
|
1844
2115
|
.action(async (table, sourceColumn, options) => {
|
|
1845
2116
|
const mappings = options.mappingsJson ? parseJsonArray(options.mappingsJson) : undefined;
|
|
1846
2117
|
const preset = readOption(options.preset);
|
|
1847
|
-
await handleAsyncAction("columns materialize", options,
|
|
2118
|
+
await handleAsyncAction("columns materialize", options, () => requestOxygen("/api/cli/tables/columns/materialize", {
|
|
1848
2119
|
method: "POST",
|
|
1849
2120
|
body: {
|
|
1850
2121
|
table,
|
|
@@ -1863,7 +2134,7 @@ export function createProgram() {
|
|
|
1863
2134
|
.option("--label <label>", "Optional new display label.")
|
|
1864
2135
|
.option("--json", "Print a JSON envelope.")
|
|
1865
2136
|
.action(async (table, column, options) => {
|
|
1866
|
-
await handleAsyncAction("columns rename", options,
|
|
2137
|
+
await handleAsyncAction("columns rename", options, () => requestOxygen("/api/cli/tables/columns/rename", {
|
|
1867
2138
|
method: "POST",
|
|
1868
2139
|
body: {
|
|
1869
2140
|
table,
|
|
@@ -1880,9 +2151,26 @@ export function createProgram() {
|
|
|
1880
2151
|
.option("--label <label>", "New display label.")
|
|
1881
2152
|
.option("--semantic-type <type>", "New semantic type.")
|
|
1882
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.")
|
|
1883
2160
|
.option("--dry-run", "Return the would-be merged definition without writing.")
|
|
1884
2161
|
.option("--json", "Print a JSON envelope.")
|
|
1885
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
|
+
}
|
|
1886
2174
|
await handleAsyncAction("columns update", options, () => requestOxygen("/api/cli/tables/columns/update", {
|
|
1887
2175
|
method: "POST",
|
|
1888
2176
|
body: {
|
|
@@ -1890,7 +2178,7 @@ export function createProgram() {
|
|
|
1890
2178
|
column,
|
|
1891
2179
|
...(readOption(options.label) ? { label: readOption(options.label) } : {}),
|
|
1892
2180
|
...(readOption(options.semanticType) ? { semantic_type: readOption(options.semanticType) } : {}),
|
|
1893
|
-
...(
|
|
2181
|
+
...(definition ? { definition } : {}),
|
|
1894
2182
|
...(options.dryRun ? { dry_run: true } : {}),
|
|
1895
2183
|
},
|
|
1896
2184
|
}));
|
|
@@ -1903,7 +2191,7 @@ export function createProgram() {
|
|
|
1903
2191
|
.option("--dry-run", "Preview the conversion (row counts and samples) without writing.")
|
|
1904
2192
|
.option("--json", "Print a JSON envelope.")
|
|
1905
2193
|
.action(async (table, column, options) => {
|
|
1906
|
-
await handleAsyncAction("columns retype", options,
|
|
2194
|
+
await handleAsyncAction("columns retype", options, () => requestOxygen("/api/cli/tables/columns/retype", {
|
|
1907
2195
|
method: "POST",
|
|
1908
2196
|
body: {
|
|
1909
2197
|
table,
|
|
@@ -1919,7 +2207,7 @@ export function createProgram() {
|
|
|
1919
2207
|
.argument("<column>", "Column id or key.")
|
|
1920
2208
|
.option("--json", "Print a JSON envelope.")
|
|
1921
2209
|
.action(async (table, column, options) => {
|
|
1922
|
-
await handleAsyncAction("columns archive", options,
|
|
2210
|
+
await handleAsyncAction("columns archive", options, () => requestOxygen("/api/cli/tables/columns/archive", {
|
|
1923
2211
|
method: "POST",
|
|
1924
2212
|
body: { table, column },
|
|
1925
2213
|
}));
|
|
@@ -1938,7 +2226,7 @@ export function createProgram() {
|
|
|
1938
2226
|
.option("--dry-run", "Preview the provider action column without creating it.")
|
|
1939
2227
|
.option("--json", "Print a JSON envelope.")
|
|
1940
2228
|
.action(async (table, options) => {
|
|
1941
|
-
await handleAsyncAction("action-column add-provider", options,
|
|
2229
|
+
await handleAsyncAction("action-column add-provider", options, () => {
|
|
1942
2230
|
const tableId = readOption(table) ?? readOption(options.table);
|
|
1943
2231
|
if (!tableId) {
|
|
1944
2232
|
throw new OxygenError("invalid_request", "Pass a table argument or --table <table>.", {
|
|
@@ -1983,7 +2271,7 @@ export function createProgram() {
|
|
|
1983
2271
|
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
1984
2272
|
const maxConcurrency = readPositiveInt(options.maxConcurrency);
|
|
1985
2273
|
const chainSteps = readChainStepsOption(options.thenJson);
|
|
1986
|
-
await handleAsyncAction("table-runs create", options,
|
|
2274
|
+
await handleAsyncAction("table-runs create", options, () => requestOxygen("/api/cli/table-action-runs", {
|
|
1987
2275
|
method: "POST",
|
|
1988
2276
|
body: {
|
|
1989
2277
|
table,
|
|
@@ -2012,7 +2300,7 @@ export function createProgram() {
|
|
|
2012
2300
|
.argument("<run_id>", "Table action run UUID or parent workspace run UUID.")
|
|
2013
2301
|
.option("--json", "Print a JSON envelope.")
|
|
2014
2302
|
.action(async (runId, options) => {
|
|
2015
|
-
await handleAsyncAction("table-runs get", options,
|
|
2303
|
+
await handleAsyncAction("table-runs get", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}`));
|
|
2016
2304
|
}))
|
|
2017
2305
|
.addCommand(new Command("items")
|
|
2018
2306
|
.description("List row items for a durable table action run.")
|
|
@@ -2021,7 +2309,7 @@ export function createProgram() {
|
|
|
2021
2309
|
.option("--limit <n>", "Maximum items to return. Defaults to 100.")
|
|
2022
2310
|
.option("--json", "Print a JSON envelope.")
|
|
2023
2311
|
.action(async (runId, options) => {
|
|
2024
|
-
await handleAsyncAction("table-runs items", options,
|
|
2312
|
+
await handleAsyncAction("table-runs items", options, () => {
|
|
2025
2313
|
const query = new URLSearchParams();
|
|
2026
2314
|
if (readOption(options.status))
|
|
2027
2315
|
query.set("status", readOption(options.status) ?? "");
|
|
@@ -2039,21 +2327,21 @@ export function createProgram() {
|
|
|
2039
2327
|
.option("--interval-seconds <n>", "Polling interval. Defaults to 5.")
|
|
2040
2328
|
.option("--json", "Print a JSON envelope.")
|
|
2041
2329
|
.action(async (runId, options) => {
|
|
2042
|
-
await handleAsyncAction("table-runs wait", options,
|
|
2330
|
+
await handleAsyncAction("table-runs wait", options, () => waitForTableActionRun(runId, options));
|
|
2043
2331
|
}))
|
|
2044
2332
|
.addCommand(new Command("provider-summary")
|
|
2045
2333
|
.description("Summarize provider attempts, upstream request events, and credit capture/release for a table action run.")
|
|
2046
2334
|
.argument("<run_id>", "Table action run UUID.")
|
|
2047
2335
|
.option("--json", "Print a JSON envelope.")
|
|
2048
2336
|
.action(async (runId, options) => {
|
|
2049
|
-
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`));
|
|
2050
2338
|
}))
|
|
2051
2339
|
.addCommand(new Command("cancel")
|
|
2052
2340
|
.description("Request cancellation for a durable table action run.")
|
|
2053
2341
|
.argument("<run_id>", "Table action run UUID or parent workspace run UUID.")
|
|
2054
2342
|
.option("--json", "Print a JSON envelope.")
|
|
2055
2343
|
.action(async (runId, options) => {
|
|
2056
|
-
await handleAsyncAction("table-runs cancel", options,
|
|
2344
|
+
await handleAsyncAction("table-runs cancel", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/cancel`, {
|
|
2057
2345
|
method: "POST",
|
|
2058
2346
|
body: {},
|
|
2059
2347
|
}));
|
|
@@ -2063,7 +2351,7 @@ export function createProgram() {
|
|
|
2063
2351
|
.argument("<run_id>", "Table action run UUID.")
|
|
2064
2352
|
.option("--json", "Print a JSON envelope.")
|
|
2065
2353
|
.action(async (runId, options) => {
|
|
2066
|
-
await handleAsyncAction("table-runs pause", options,
|
|
2354
|
+
await handleAsyncAction("table-runs pause", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/pause`, {
|
|
2067
2355
|
method: "POST",
|
|
2068
2356
|
body: {},
|
|
2069
2357
|
}));
|
|
@@ -2073,7 +2361,7 @@ export function createProgram() {
|
|
|
2073
2361
|
.argument("<run_id>", "Table action run UUID.")
|
|
2074
2362
|
.option("--json", "Print a JSON envelope.")
|
|
2075
2363
|
.action(async (runId, options) => {
|
|
2076
|
-
await handleAsyncAction("table-runs resume", options,
|
|
2364
|
+
await handleAsyncAction("table-runs resume", options, () => requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/resume`, {
|
|
2077
2365
|
method: "POST",
|
|
2078
2366
|
body: {},
|
|
2079
2367
|
}));
|
|
@@ -2083,7 +2371,7 @@ export function createProgram() {
|
|
|
2083
2371
|
.argument("<run_id>", "Table action run UUID.")
|
|
2084
2372
|
.option("--json", "Print a JSON envelope.")
|
|
2085
2373
|
.action(async (runId, options) => {
|
|
2086
|
-
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`, {
|
|
2087
2375
|
method: "POST",
|
|
2088
2376
|
body: {},
|
|
2089
2377
|
}));
|
|
@@ -2108,7 +2396,7 @@ export function createProgram() {
|
|
|
2108
2396
|
.option("--metadata-json <json>", "Optional metadata object to attach to the run.")
|
|
2109
2397
|
.option("--json", "Print a JSON envelope.")
|
|
2110
2398
|
.action(async (table, options) => {
|
|
2111
|
-
await handleAsyncAction("table-ingestions create-tool-page", options,
|
|
2399
|
+
await handleAsyncAction("table-ingestions create-tool-page", options, () => {
|
|
2112
2400
|
const toolId = readOption(options.tool) ?? readOption(options.toolId);
|
|
2113
2401
|
if (!toolId) {
|
|
2114
2402
|
throw new OxygenError("invalid_table_ingestion", "Pass --tool or --tool-id.", {
|
|
@@ -2155,7 +2443,7 @@ export function createProgram() {
|
|
|
2155
2443
|
.argument("<run_id>", "Table ingestion run UUID.")
|
|
2156
2444
|
.option("--json", "Print a JSON envelope.")
|
|
2157
2445
|
.action(async (runId, options) => {
|
|
2158
|
-
await handleAsyncAction("table-ingestions get", options,
|
|
2446
|
+
await handleAsyncAction("table-ingestions get", options, () => requestOxygen(`/api/cli/table-ingestion-runs/${encodeURIComponent(runId)}`));
|
|
2159
2447
|
}))
|
|
2160
2448
|
.addCommand(new Command("items")
|
|
2161
2449
|
.description("List items for a durable table ingestion run.")
|
|
@@ -2164,7 +2452,7 @@ export function createProgram() {
|
|
|
2164
2452
|
.option("--limit <n>", "Maximum items to return. Defaults to 100.")
|
|
2165
2453
|
.option("--json", "Print a JSON envelope.")
|
|
2166
2454
|
.action(async (runId, options) => {
|
|
2167
|
-
await handleAsyncAction("table-ingestions items", options,
|
|
2455
|
+
await handleAsyncAction("table-ingestions items", options, () => {
|
|
2168
2456
|
const query = new URLSearchParams();
|
|
2169
2457
|
if (readOption(options.status))
|
|
2170
2458
|
query.set("status", readOption(options.status) ?? "");
|
|
@@ -2182,14 +2470,14 @@ export function createProgram() {
|
|
|
2182
2470
|
.option("--interval-seconds <n>", "Polling interval. Defaults to 5.")
|
|
2183
2471
|
.option("--json", "Print a JSON envelope.")
|
|
2184
2472
|
.action(async (runId, options) => {
|
|
2185
|
-
await handleAsyncAction("table-ingestions wait", options,
|
|
2473
|
+
await handleAsyncAction("table-ingestions wait", options, () => waitForTableIngestionRun(runId, options));
|
|
2186
2474
|
}))
|
|
2187
2475
|
.addCommand(new Command("cancel")
|
|
2188
2476
|
.description("Request cancellation for a durable table ingestion run.")
|
|
2189
2477
|
.argument("<run_id>", "Table ingestion run UUID.")
|
|
2190
2478
|
.option("--json", "Print a JSON envelope.")
|
|
2191
2479
|
.action(async (runId, options) => {
|
|
2192
|
-
await handleAsyncAction("table-ingestions cancel", options,
|
|
2480
|
+
await handleAsyncAction("table-ingestions cancel", options, () => requestOxygen(`/api/cli/table-ingestion-runs/${encodeURIComponent(runId)}/cancel`, {
|
|
2193
2481
|
method: "POST",
|
|
2194
2482
|
body: {},
|
|
2195
2483
|
}));
|
|
@@ -2199,7 +2487,7 @@ export function createProgram() {
|
|
|
2199
2487
|
.argument("<run_id>", "Table ingestion run UUID.")
|
|
2200
2488
|
.option("--json", "Print a JSON envelope.")
|
|
2201
2489
|
.action(async (runId, options) => {
|
|
2202
|
-
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`, {
|
|
2203
2491
|
method: "POST",
|
|
2204
2492
|
body: {},
|
|
2205
2493
|
}));
|
|
@@ -2234,7 +2522,7 @@ export function createProgram() {
|
|
|
2234
2522
|
.option("--materialize-preview", "Create a preview table for the plan. Defaults to no table side effect.")
|
|
2235
2523
|
.option("--json", "Print a JSON envelope.")
|
|
2236
2524
|
.action(async (options) => {
|
|
2237
|
-
await handleAsyncAction("lead-sourcing plan", options,
|
|
2525
|
+
await handleAsyncAction("lead-sourcing plan", options, () => requestOxygen("/api/cli/lead-sourcing/plan", {
|
|
2238
2526
|
method: "POST",
|
|
2239
2527
|
body: {
|
|
2240
2528
|
prompt: readFileIfPresent(options.prompt),
|
|
@@ -2248,7 +2536,7 @@ export function createProgram() {
|
|
|
2248
2536
|
.requiredOption("--spec <file>", "JSON LeadSourcingSpec file, or a text prompt file to compile into a spec.")
|
|
2249
2537
|
.option("--json", "Print a JSON envelope.")
|
|
2250
2538
|
.action(async (table, options) => {
|
|
2251
|
-
await handleAsyncAction("lead-sourcing audit", options,
|
|
2539
|
+
await handleAsyncAction("lead-sourcing audit", options, () => requestOxygen("/api/cli/lead-sourcing/audit", {
|
|
2252
2540
|
method: "POST",
|
|
2253
2541
|
body: {
|
|
2254
2542
|
table,
|
|
@@ -2262,7 +2550,7 @@ export function createProgram() {
|
|
|
2262
2550
|
.addCommand(new Command("plan")
|
|
2263
2551
|
.description("Plan an Oxygen search or scrape route before running provider jobs.")
|
|
2264
2552
|
.requiredOption("--goal <file|text>", "Search/scrape goal text, or a local file path containing the goal.")
|
|
2265
|
-
.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'.")
|
|
2266
2554
|
.option("--target-count <n>", "Optional target row count.")
|
|
2267
2555
|
.option("--geography <text>", "Optional geography hint.")
|
|
2268
2556
|
.option("--known-urls <urls>", "Comma-separated known URLs for scrape routes.")
|
|
@@ -2271,6 +2559,7 @@ export function createProgram() {
|
|
|
2271
2559
|
.action(async (options) => {
|
|
2272
2560
|
await handleAsyncAction("search plan", options, () => {
|
|
2273
2561
|
assertNotCompanySearchKind(options.kind);
|
|
2562
|
+
assertNotPeopleSearchKind(options.kind);
|
|
2274
2563
|
return requestOxygen("/api/cli/search/plan", {
|
|
2275
2564
|
method: "POST",
|
|
2276
2565
|
body: {
|
|
@@ -2385,7 +2674,7 @@ export function createProgram() {
|
|
|
2385
2674
|
.description("Compile a company-search prompt into ordered provider routes without provider calls.")
|
|
2386
2675
|
.requiredOption("--prompt <text-or-file>", "Company-search prompt, or a path to a prompt file.")
|
|
2387
2676
|
.option("--target-count <n>", "Desired company count for routing and estimates.")
|
|
2388
|
-
.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.")
|
|
2389
2678
|
.option("--filters-json <json-or-file>", "CompanySearchFilters JSON inline or a @file/path; wins over individual flags per top-level filter path.")
|
|
2390
2679
|
.option("--industries <csv>", "Comma-separated industries to include.")
|
|
2391
2680
|
.option("--exclude-industries <csv>", "Comma-separated industries to exclude.")
|
|
@@ -2398,7 +2687,7 @@ export function createProgram() {
|
|
|
2398
2687
|
.option("--revenue <range>", "Annual revenue (USD) range: 1000000-20000000, 1000000+, or -5000000.")
|
|
2399
2688
|
.option("--founded <range>", "Founded year range: 2015-2024, 2020+, or -2010.")
|
|
2400
2689
|
.option("--lookalike <csv>", "Comma-separated lookalike company domains.")
|
|
2401
|
-
.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).")
|
|
2402
2691
|
.option("--materialize-preview", "Create a preview table with route rows.")
|
|
2403
2692
|
.option("--json", "Print a JSON envelope.")
|
|
2404
2693
|
.action(async (options) => {
|
|
@@ -2432,7 +2721,7 @@ export function createProgram() {
|
|
|
2432
2721
|
.option("--revenue <range>", "Annual revenue (USD) range when planning from --prompt.")
|
|
2433
2722
|
.option("--founded <range>", "Founded year range when planning from --prompt.")
|
|
2434
2723
|
.option("--lookalike <csv>", "Comma-separated lookalike company domains when planning from --prompt.")
|
|
2435
|
-
.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).")
|
|
2436
2725
|
.option("--approved", "Required for live runs after inspecting dry-run output.")
|
|
2437
2726
|
.option("--json", "Print a JSON envelope.")
|
|
2438
2727
|
.action(async (options) => {
|
|
@@ -2455,7 +2744,7 @@ export function createProgram() {
|
|
|
2455
2744
|
.option("--selection-json <json>", "Raw table action selection JSON.")
|
|
2456
2745
|
.option("--json", "Print a JSON envelope.")
|
|
2457
2746
|
.action(async (table, options) => {
|
|
2458
|
-
await handleAsyncAction("companies enrich preview", options,
|
|
2747
|
+
await handleAsyncAction("companies enrich preview", options, () => requestOxygen("/api/cli/company-enrichment/preview", {
|
|
2459
2748
|
method: "POST",
|
|
2460
2749
|
body: readCompaniesEnrichBody(table, options),
|
|
2461
2750
|
}));
|
|
@@ -2475,7 +2764,7 @@ export function createProgram() {
|
|
|
2475
2764
|
.option("--force", "Re-run the waterfall audit column even when it already has a value.")
|
|
2476
2765
|
.option("--json", "Print a JSON envelope.")
|
|
2477
2766
|
.action(async (table, options) => {
|
|
2478
|
-
await handleAsyncAction("companies enrich run", options,
|
|
2767
|
+
await handleAsyncAction("companies enrich run", options, () => requestOxygen("/api/cli/company-enrichment/run", {
|
|
2479
2768
|
method: "POST",
|
|
2480
2769
|
body: readCompaniesEnrichBody(table, options),
|
|
2481
2770
|
}));
|
|
@@ -2504,7 +2793,8 @@ export function createProgram() {
|
|
|
2504
2793
|
.option("--require-email", "Only return people with a work email available (provider-dependent).")
|
|
2505
2794
|
.option("--require-phone", "Only return people with a phone/mobile available (provider-dependent).")
|
|
2506
2795
|
.option("--max-per-company <n>", "Cap on contacts per company for account-anchored searches.")
|
|
2507
|
-
.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).")
|
|
2508
2798
|
.option("--materialize-preview", "Create a preview table with route rows.")
|
|
2509
2799
|
.option("--json", "Print a JSON envelope.")
|
|
2510
2800
|
.action(async (options) => {
|
|
@@ -2540,7 +2830,8 @@ export function createProgram() {
|
|
|
2540
2830
|
.option("--require-email", "Only return people with a work email available when planning from --prompt.")
|
|
2541
2831
|
.option("--require-phone", "Only return people with a phone available when planning from --prompt.")
|
|
2542
2832
|
.option("--max-per-company <n>", "Cap on contacts per company when planning from --prompt.")
|
|
2543
|
-
.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.")
|
|
2544
2835
|
.option("--approved", "Required for live runs after inspecting dry-run output.")
|
|
2545
2836
|
.option("--json", "Print a JSON envelope.")
|
|
2546
2837
|
.action(async (options) => {
|
|
@@ -2556,15 +2847,15 @@ export function createProgram() {
|
|
|
2556
2847
|
.description("Show background action and ingestion queue health.")
|
|
2557
2848
|
.option("--json", "Print a JSON envelope.")
|
|
2558
2849
|
.action(async (options) => {
|
|
2559
|
-
await handleAsyncAction("worker queue-stats", options,
|
|
2850
|
+
await handleAsyncAction("worker queue-stats", options, () => requestOxygen("/api/cli/worker/queue-stats"));
|
|
2560
2851
|
}))
|
|
2561
2852
|
.addCommand(new Command("failures")
|
|
2562
2853
|
.description("List failed background action and ingestion items.")
|
|
2563
|
-
.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.")
|
|
2564
2855
|
.option("--limit <n>", "Maximum failed items per queue. Defaults to 25; server cap is 100.")
|
|
2565
2856
|
.option("--json", "Print a JSON envelope.")
|
|
2566
2857
|
.action(async (options) => {
|
|
2567
|
-
await handleAsyncAction("worker failures", options,
|
|
2858
|
+
await handleAsyncAction("worker failures", options, () => {
|
|
2568
2859
|
const query = new URLSearchParams();
|
|
2569
2860
|
if (readOption(options.queue))
|
|
2570
2861
|
query.set("queue", readOption(options.queue) ?? "");
|
|
@@ -2579,7 +2870,7 @@ export function createProgram() {
|
|
|
2579
2870
|
.description("Repair stale background action, ingestion, and workflow queue state.")
|
|
2580
2871
|
.option("--json", "Print a JSON envelope.")
|
|
2581
2872
|
.action(async (options) => {
|
|
2582
|
-
await handleAsyncAction("worker repair", options,
|
|
2873
|
+
await handleAsyncAction("worker repair", options, () => requestOxygen("/api/cli/worker/repair", {
|
|
2583
2874
|
method: "POST",
|
|
2584
2875
|
body: {},
|
|
2585
2876
|
}));
|
|
@@ -2594,22 +2885,13 @@ export function createProgram() {
|
|
|
2594
2885
|
.option("--recipe-timeout-ms <n>", "Compatibility option; ignored by the Postgres worker.")
|
|
2595
2886
|
.option("--json", "Print a JSON envelope.")
|
|
2596
2887
|
.action(async (options) => {
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
const recipeTimeoutMs = readPositiveInt(options.recipeTimeoutMs);
|
|
2603
|
-
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", {
|
|
2604
2893
|
method: "POST",
|
|
2605
|
-
body: {
|
|
2606
|
-
...(claimLimit ? { claim_limit: claimLimit } : {}),
|
|
2607
|
-
...(concurrency ? { concurrency } : {}),
|
|
2608
|
-
...(enrichmentConcurrency ? { enrichment_concurrency: enrichmentConcurrency } : {}),
|
|
2609
|
-
...(leaseSeconds ? { lease_seconds: leaseSeconds } : {}),
|
|
2610
|
-
...(providerTimeoutMs ? { provider_timeout_ms: providerTimeoutMs } : {}),
|
|
2611
|
-
...(recipeTimeoutMs ? { recipe_timeout_ms: recipeTimeoutMs } : {}),
|
|
2612
|
-
},
|
|
2894
|
+
body: {},
|
|
2613
2895
|
}));
|
|
2614
2896
|
}));
|
|
2615
2897
|
program
|
|
@@ -2619,7 +2901,7 @@ export function createProgram() {
|
|
|
2619
2901
|
.description("Show the current plan and managed credit balance.")
|
|
2620
2902
|
.option("--json", "Print a JSON envelope.")
|
|
2621
2903
|
.action(async (options) => {
|
|
2622
|
-
await handleAsyncAction("billing balance", options,
|
|
2904
|
+
await handleAsyncAction("billing balance", options, () => requestOxygen("/api/cli/billing/balance"));
|
|
2623
2905
|
}))
|
|
2624
2906
|
.addCommand(new Command("usage")
|
|
2625
2907
|
.description("Show credit ledger events.")
|
|
@@ -2637,7 +2919,7 @@ export function createProgram() {
|
|
|
2637
2919
|
.option("--nonzero", "Only include events that changed available or reserved credits.")
|
|
2638
2920
|
.option("--json", "Print a JSON envelope.")
|
|
2639
2921
|
.action(async (options) => {
|
|
2640
|
-
await handleAsyncAction("billing usage", options,
|
|
2922
|
+
await handleAsyncAction("billing usage", options, () => {
|
|
2641
2923
|
const params = buildBillingLedgerParams(options);
|
|
2642
2924
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
2643
2925
|
return requestOxygen(`/api/cli/billing/usage${suffix}`);
|
|
@@ -2659,7 +2941,7 @@ export function createProgram() {
|
|
|
2659
2941
|
.option("--nonzero", "Only include events that changed available or reserved credits. Default for audit except BYOK filters.")
|
|
2660
2942
|
.option("--json", "Print a JSON envelope.")
|
|
2661
2943
|
.action(async (options) => {
|
|
2662
|
-
await handleAsyncAction("billing audit", options,
|
|
2944
|
+
await handleAsyncAction("billing audit", options, () => {
|
|
2663
2945
|
const params = buildBillingLedgerParams(options);
|
|
2664
2946
|
if (readOption(options.groupBy))
|
|
2665
2947
|
params.set("group_by", readOption(options.groupBy));
|
|
@@ -2676,7 +2958,7 @@ export function createProgram() {
|
|
|
2676
2958
|
.option("--description <text>", "Ledger description for the admin grant.")
|
|
2677
2959
|
.option("--json", "Print a JSON envelope.")
|
|
2678
2960
|
.action(async (options) => {
|
|
2679
|
-
await handleAsyncAction("billing grant", options,
|
|
2961
|
+
await handleAsyncAction("billing grant", options, () => requestOxygen("/api/cli/billing/grant", {
|
|
2680
2962
|
method: "POST",
|
|
2681
2963
|
body: {
|
|
2682
2964
|
credits: readPositiveNumber(options.credits),
|
|
@@ -2688,6 +2970,54 @@ export function createProgram() {
|
|
|
2688
2970
|
},
|
|
2689
2971
|
}));
|
|
2690
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
|
+
}));
|
|
2691
3021
|
program
|
|
2692
3022
|
.command("admin")
|
|
2693
3023
|
.description("Staff-only commands.")
|
|
@@ -2697,7 +3027,7 @@ export function createProgram() {
|
|
|
2697
3027
|
.option("--credential-mode <mode>", "managed (default; real COGS), byok (customer-supplied credentials), or all.", "managed")
|
|
2698
3028
|
.option("--json", "Print a JSON envelope.")
|
|
2699
3029
|
.action(async (options) => {
|
|
2700
|
-
await handleAsyncAction("admin costs", options,
|
|
3030
|
+
await handleAsyncAction("admin costs", options, () => {
|
|
2701
3031
|
const params = new URLSearchParams();
|
|
2702
3032
|
const top = readPositiveInt(options.top);
|
|
2703
3033
|
if (top)
|
|
@@ -2765,7 +3095,7 @@ export function createProgram() {
|
|
|
2765
3095
|
.option("--limit <n>", "Maximum events to return. Defaults to 50.")
|
|
2766
3096
|
.option("--json", "Print a JSON envelope.")
|
|
2767
3097
|
.action(async (options) => {
|
|
2768
|
-
await handleAsyncAction("observability events", options,
|
|
3098
|
+
await handleAsyncAction("observability events", options, () => {
|
|
2769
3099
|
const params = new URLSearchParams();
|
|
2770
3100
|
const status = readOption(options.status);
|
|
2771
3101
|
const traceId = readOption(options.traceId);
|
|
@@ -2791,7 +3121,7 @@ export function createProgram() {
|
|
|
2791
3121
|
.option("--limit <n>", "Maximum runs to return. Defaults to 50.")
|
|
2792
3122
|
.option("--json", "Print a JSON envelope.")
|
|
2793
3123
|
.action(async (options) => {
|
|
2794
|
-
await handleAsyncAction("runs list", options,
|
|
3124
|
+
await handleAsyncAction("runs list", options, () => {
|
|
2795
3125
|
const limit = readPositiveInt(options.limit);
|
|
2796
3126
|
return requestOxygen(`/api/cli/runs${limit ? `?limit=${limit}` : ""}`);
|
|
2797
3127
|
});
|
|
@@ -2801,7 +3131,7 @@ export function createProgram() {
|
|
|
2801
3131
|
.argument("<run_id>", "Run UUID.")
|
|
2802
3132
|
.option("--json", "Print a JSON envelope.")
|
|
2803
3133
|
.action(async (runId, options) => {
|
|
2804
|
-
await handleAsyncAction("runs get", options,
|
|
3134
|
+
await handleAsyncAction("runs get", options, () => requestOxygen("/api/cli/runs/get", {
|
|
2805
3135
|
method: "POST",
|
|
2806
3136
|
body: { run_id: runId },
|
|
2807
3137
|
}));
|
|
@@ -2816,7 +3146,7 @@ export function createProgram() {
|
|
|
2816
3146
|
.option("--limit <n>", "Maximum changes to return. Defaults to 50.")
|
|
2817
3147
|
.option("--json", "Print a JSON envelope.")
|
|
2818
3148
|
.action(async (table, rowId, options) => {
|
|
2819
|
-
await handleAsyncAction("rows history", options,
|
|
3149
|
+
await handleAsyncAction("rows history", options, () => {
|
|
2820
3150
|
const limit = readPositiveInt(options.limit);
|
|
2821
3151
|
return requestOxygen("/api/cli/tables/rows/history", {
|
|
2822
3152
|
method: "POST",
|
|
@@ -2839,7 +3169,7 @@ export function createProgram() {
|
|
|
2839
3169
|
.option("--history-limit <n>", "Maximum recent cell changes to include. Defaults to 10; cap 50.")
|
|
2840
3170
|
.option("--json", "Print a JSON envelope.")
|
|
2841
3171
|
.action(async (table, rowId, column, options) => {
|
|
2842
|
-
await handleAsyncAction("cells inspect", options,
|
|
3172
|
+
await handleAsyncAction("cells inspect", options, () => {
|
|
2843
3173
|
const historyLimit = readPositiveInt(options.historyLimit);
|
|
2844
3174
|
return requestOxygen("/api/cli/tables/cells/inspect", {
|
|
2845
3175
|
method: "POST",
|
|
@@ -2860,7 +3190,7 @@ export function createProgram() {
|
|
|
2860
3190
|
.option("--limit <n>", "Maximum changes to return. Defaults to 50.")
|
|
2861
3191
|
.option("--json", "Print a JSON envelope.")
|
|
2862
3192
|
.action(async (table, rowId, column, options) => {
|
|
2863
|
-
await handleAsyncAction("cells history", options,
|
|
3193
|
+
await handleAsyncAction("cells history", options, () => {
|
|
2864
3194
|
const limit = readPositiveInt(options.limit);
|
|
2865
3195
|
return requestOxygen("/api/cli/tables/cells/history", {
|
|
2866
3196
|
method: "POST",
|
|
@@ -2885,6 +3215,7 @@ export function createProgram() {
|
|
|
2885
3215
|
.option("--session-id <id>", "Session id to update. Defaults to the current session.")
|
|
2886
3216
|
.option("--json", "Print a JSON envelope.")
|
|
2887
3217
|
.action(async (options) => {
|
|
3218
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous session helpers to satisfy handleAsyncAction's Promise<unknown> action
|
|
2888
3219
|
await handleAsyncAction("session start", options, async () => {
|
|
2889
3220
|
const updateIndex = readNonNegativeInt(options.update);
|
|
2890
3221
|
if (updateIndex !== undefined) {
|
|
@@ -2912,6 +3243,7 @@ export function createProgram() {
|
|
|
2912
3243
|
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
2913
3244
|
.option("--json", "Print a JSON envelope.")
|
|
2914
3245
|
.action(async (options) => {
|
|
3246
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous addSessionStatus helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
2915
3247
|
await handleAsyncAction("session status", options, async () => addSessionStatus({
|
|
2916
3248
|
sessionId: readOption(options.sessionId),
|
|
2917
3249
|
message: options.message,
|
|
@@ -2928,6 +3260,7 @@ export function createProgram() {
|
|
|
2928
3260
|
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
2929
3261
|
.option("--json", "Print a JSON envelope.")
|
|
2930
3262
|
.action(async (options) => {
|
|
3263
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous addSessionOutput helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
2931
3264
|
await handleAsyncAction("session output", options, async () => addSessionOutput({
|
|
2932
3265
|
sessionId: readOption(options.sessionId),
|
|
2933
3266
|
csv: readOption(options.csv),
|
|
@@ -2942,6 +3275,7 @@ export function createProgram() {
|
|
|
2942
3275
|
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
2943
3276
|
.option("--json", "Print a JSON envelope.")
|
|
2944
3277
|
.action(async (options) => {
|
|
3278
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous getSessionUsage helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
2945
3279
|
await handleAsyncAction("session usage", options, async () => getSessionUsage({ sessionId: readOption(options.sessionId) }));
|
|
2946
3280
|
}));
|
|
2947
3281
|
program
|
|
@@ -3011,7 +3345,7 @@ export function createProgram() {
|
|
|
3011
3345
|
.argument("<tool_id>", "Tool id.")
|
|
3012
3346
|
.option("--json", "Print a JSON envelope.")
|
|
3013
3347
|
.action(async (toolId, options) => {
|
|
3014
|
-
await handleAsyncAction("tools get", options,
|
|
3348
|
+
await handleAsyncAction("tools get", options, () => requestOxygen(`/api/cli/tools/${encodeURIComponent(toolId)}`));
|
|
3015
3349
|
}))
|
|
3016
3350
|
.addCommand(new Command("enums")
|
|
3017
3351
|
.description("Provider enum catalogs for fields that accept normalized values.")
|
|
@@ -3062,8 +3396,11 @@ export function createProgram() {
|
|
|
3062
3396
|
.option("--return <mode>", "Legacy response shape: raw, compact, or summary. Defaults to raw.")
|
|
3063
3397
|
.option("--return-mode <mode>", "Response shape: raw, compact, or summary. Prefer summary for large search responses.")
|
|
3064
3398
|
.option("--oxygen-cursor <cursor>", "Short Oxygen cursor returned as oxygen_next_cursor by a previous tool run.")
|
|
3399
|
+
.option("--max-credits <n>", "Required credit ceiling for live runs of paid tools.")
|
|
3400
|
+
.option("--approved", "Required for live runs of paid tools after inspecting dry-run output.")
|
|
3065
3401
|
.option("--json", "Print a JSON envelope.")
|
|
3066
3402
|
.action(async (toolId, options) => {
|
|
3403
|
+
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
3067
3404
|
await handleAsyncAction("tools run", options, () => requestOxygen("/api/cli/tools/run", {
|
|
3068
3405
|
method: "POST",
|
|
3069
3406
|
body: {
|
|
@@ -3078,6 +3415,8 @@ export function createProgram() {
|
|
|
3078
3415
|
...(readOption(options["return"]) ? { return: readOption(options["return"]) } : {}),
|
|
3079
3416
|
...(readOption(options.returnMode) ? { return_mode: readOption(options.returnMode) } : {}),
|
|
3080
3417
|
...(readOption(options.oxygenCursor) ? { oxygen_cursor: readOption(options.oxygenCursor) } : {}),
|
|
3418
|
+
...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
|
|
3419
|
+
...(options.approved ? { approved: true } : {}),
|
|
3081
3420
|
},
|
|
3082
3421
|
}));
|
|
3083
3422
|
}));
|
|
@@ -3104,6 +3443,7 @@ export function createProgram() {
|
|
|
3104
3443
|
.option("--email-pattern-validation <mode>", "Work-email pattern pre-step: leadmagic_valid_only or disabled.")
|
|
3105
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%).")
|
|
3106
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.")
|
|
3107
3447
|
.option("--phone-verification-credential-mode <mode>", "ClearoutPhone credential mode for phone verification: managed or user_api_key.")
|
|
3108
3448
|
.option("--limit <n>", "Rows to estimate. Defaults to 10.")
|
|
3109
3449
|
.option("--all", "Estimate all rows.")
|
|
@@ -3112,7 +3452,7 @@ export function createProgram() {
|
|
|
3112
3452
|
.option("--only-missing", "Estimate rows missing the capability's normalized output when no explicit selection is passed.")
|
|
3113
3453
|
.option("--json", "Print a JSON envelope.")
|
|
3114
3454
|
.action(async (table, options) => {
|
|
3115
|
-
await handleAsyncAction("enrich-column preview", options,
|
|
3455
|
+
await handleAsyncAction("enrich-column preview", options, () => requestOxygen("/api/cli/enrich-column/preview", {
|
|
3116
3456
|
method: "POST",
|
|
3117
3457
|
body: buildEnrichColumnBody(table, options),
|
|
3118
3458
|
}));
|
|
@@ -3138,6 +3478,7 @@ export function createProgram() {
|
|
|
3138
3478
|
.option("--email-pattern-validation <mode>", "Work-email pattern pre-step: leadmagic_valid_only or disabled.")
|
|
3139
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%).")
|
|
3140
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.")
|
|
3141
3482
|
.option("--phone-verification-credential-mode <mode>", "ClearoutPhone credential mode for phone verification: managed or user_api_key.")
|
|
3142
3483
|
.option("--limit <n>", "Rows to queue.")
|
|
3143
3484
|
.option("--all", "Queue all rows.")
|
|
@@ -3149,7 +3490,7 @@ export function createProgram() {
|
|
|
3149
3490
|
.option("--json", "Print a JSON envelope.")
|
|
3150
3491
|
.action(async (table, options) => {
|
|
3151
3492
|
const maxConcurrency = readPositiveInt(options.maxConcurrency);
|
|
3152
|
-
await handleAsyncAction("enrich-column run", options,
|
|
3493
|
+
await handleAsyncAction("enrich-column run", options, () => requestOxygen("/api/cli/enrich-column/run", {
|
|
3153
3494
|
method: "POST",
|
|
3154
3495
|
body: {
|
|
3155
3496
|
...buildEnrichColumnBody(table, options),
|
|
@@ -3159,6 +3500,62 @@ export function createProgram() {
|
|
|
3159
3500
|
},
|
|
3160
3501
|
}));
|
|
3161
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
|
+
}));
|
|
3162
3559
|
program
|
|
3163
3560
|
.command("enrichment")
|
|
3164
3561
|
.description("High-level enrichment column definition helpers.")
|
|
@@ -3190,7 +3587,7 @@ export function createProgram() {
|
|
|
3190
3587
|
.option("--toolkit <id>", "Filter by toolkit / integration id, such as gmail.")
|
|
3191
3588
|
.option("--json", "Print a JSON envelope.")
|
|
3192
3589
|
.action(async (options) => {
|
|
3193
|
-
await handleAsyncAction("integrations events list", options,
|
|
3590
|
+
await handleAsyncAction("integrations events list", options, () => {
|
|
3194
3591
|
const query = new URLSearchParams();
|
|
3195
3592
|
if (readOption(options.source))
|
|
3196
3593
|
query.set("source", readOption(options.source) ?? "");
|
|
@@ -3210,22 +3607,17 @@ export function createProgram() {
|
|
|
3210
3607
|
.option("--trigger-config <json>", "JSON object passed to the provider when registering the trigger (Composio triggers only).")
|
|
3211
3608
|
.option("--json", "Print a JSON envelope.")
|
|
3212
3609
|
.action(async (options) => {
|
|
3213
|
-
await handleAsyncAction("integrations events enable", options,
|
|
3610
|
+
await handleAsyncAction("integrations events enable", options, () => {
|
|
3214
3611
|
const triggerConfigRaw = readOption(options.triggerConfig);
|
|
3215
3612
|
let triggerConfig;
|
|
3216
3613
|
if (triggerConfigRaw) {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
}
|
|
3222
|
-
triggerConfig = parsed;
|
|
3223
|
-
}
|
|
3224
|
-
catch (error) {
|
|
3225
|
-
throw new Error(error instanceof Error
|
|
3226
|
-
? `Invalid --trigger-config: ${error.message}`
|
|
3227
|
-
: "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
|
+
});
|
|
3228
3619
|
}
|
|
3620
|
+
triggerConfig = parsed;
|
|
3229
3621
|
}
|
|
3230
3622
|
return requestOxygen("/api/cli/integrations/events/enable", {
|
|
3231
3623
|
method: "POST",
|
|
@@ -3245,7 +3637,7 @@ export function createProgram() {
|
|
|
3245
3637
|
.option("--connection-id <connection_id>", "Specific integration connection id. Defaults to the active default connection.")
|
|
3246
3638
|
.option("--json", "Print a JSON envelope.")
|
|
3247
3639
|
.action(async (options) => {
|
|
3248
|
-
await handleAsyncAction("integrations events disable", options,
|
|
3640
|
+
await handleAsyncAction("integrations events disable", options, () => requestOxygen("/api/cli/integrations/events/disable", {
|
|
3249
3641
|
method: "POST",
|
|
3250
3642
|
body: {
|
|
3251
3643
|
source: readOption(options.source),
|
|
@@ -3285,21 +3677,24 @@ export function createProgram() {
|
|
|
3285
3677
|
.description("List supported integrations and this org's connections.")
|
|
3286
3678
|
.option("--json", "Print a JSON envelope.")
|
|
3287
3679
|
.action(async (options) => {
|
|
3288
|
-
await handleAsyncAction("integrations list", options,
|
|
3680
|
+
await handleAsyncAction("integrations list", options, () => requestOxygen("/api/cli/integrations/composio/list"));
|
|
3289
3681
|
}))
|
|
3290
3682
|
.addCommand(new Command("connect")
|
|
3291
3683
|
.description("Connect an integration. OAuth toolkits return a redirect URL; API-key integrations accept --api-key.")
|
|
3292
3684
|
.argument("<integration_id>", "Integration id, such as 'slack' or 'serpapi'.")
|
|
3293
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).")
|
|
3294
3687
|
.option("--json", "Print a JSON envelope.")
|
|
3295
3688
|
.action(async (integrationId, options) => {
|
|
3296
|
-
await handleAsyncAction("integrations connect", options,
|
|
3689
|
+
await handleAsyncAction("integrations connect", options, () => {
|
|
3297
3690
|
const apiKey = readOption(options.apiKey)?.trim();
|
|
3691
|
+
const accountId = readOption(options.accountId);
|
|
3298
3692
|
return requestOxygen("/api/cli/integrations/connect", {
|
|
3299
3693
|
method: "POST",
|
|
3300
3694
|
body: {
|
|
3301
3695
|
integration_id: integrationId,
|
|
3302
3696
|
...(apiKey ? { api_key: apiKey } : {}),
|
|
3697
|
+
...(accountId ? { account_id: accountId } : {}),
|
|
3303
3698
|
},
|
|
3304
3699
|
});
|
|
3305
3700
|
});
|
|
@@ -3324,7 +3719,7 @@ export function createProgram() {
|
|
|
3324
3719
|
.argument("<integration_id>", "Integration id, such as 'slack'.")
|
|
3325
3720
|
.option("--json", "Print a JSON envelope.")
|
|
3326
3721
|
.action(async (integrationId, options) => {
|
|
3327
|
-
await handleAsyncAction("integrations actions", options,
|
|
3722
|
+
await handleAsyncAction("integrations actions", options, () => {
|
|
3328
3723
|
const params = new URLSearchParams({ integration_id: integrationId });
|
|
3329
3724
|
return requestOxygen(`/api/cli/integrations/composio/actions?${params.toString()}`);
|
|
3330
3725
|
});
|
|
@@ -3339,7 +3734,7 @@ export function createProgram() {
|
|
|
3339
3734
|
.option("--mode <mode>", "'live' or 'dry_run'. Overridden by --live/--dry-run if provided.")
|
|
3340
3735
|
.option("--json", "Print a JSON envelope.")
|
|
3341
3736
|
.action(async (integrationId, actionSlug, options) => {
|
|
3342
|
-
await handleAsyncAction("integrations run", options,
|
|
3737
|
+
await handleAsyncAction("integrations run", options, () => {
|
|
3343
3738
|
const args = readOption(options.input)
|
|
3344
3739
|
? parseJsonObject(readOption(options.input))
|
|
3345
3740
|
: {};
|
|
@@ -3394,7 +3789,7 @@ export function createProgram() {
|
|
|
3394
3789
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
3395
3790
|
.option("--json", "Print a JSON envelope.")
|
|
3396
3791
|
.action(async (id, options) => {
|
|
3397
|
-
await handleAsyncAction("senders get", options,
|
|
3792
|
+
await handleAsyncAction("senders get", options, () => requestOxygen(`/api/cli/senders/${encodeURIComponent(id)}`));
|
|
3398
3793
|
}))
|
|
3399
3794
|
.addCommand(new Command("sync")
|
|
3400
3795
|
.description("Refresh LinkedIn account state (status, profile) from Unipile. Pass --connection-id to sync one account, or omit it to sync all.")
|
|
@@ -3416,7 +3811,7 @@ export function createProgram() {
|
|
|
3416
3811
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
3417
3812
|
.option("--json", "Print a JSON envelope.")
|
|
3418
3813
|
.action(async (id, options) => {
|
|
3419
|
-
await handleAsyncAction("senders disconnect", options,
|
|
3814
|
+
await handleAsyncAction("senders disconnect", options, () => requestOxygen(`/api/cli/senders/${encodeURIComponent(id)}/disconnect`, {
|
|
3420
3815
|
method: "POST",
|
|
3421
3816
|
}));
|
|
3422
3817
|
}))
|
|
@@ -3428,7 +3823,7 @@ export function createProgram() {
|
|
|
3428
3823
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
3429
3824
|
.option("--json", "Print a JSON envelope.")
|
|
3430
3825
|
.action(async (id, options) => {
|
|
3431
|
-
await handleAsyncAction("senders limits get", options,
|
|
3826
|
+
await handleAsyncAction("senders limits get", options, () => requestOxygen(`/api/cli/senders/${encodeURIComponent(id)}/limits`));
|
|
3432
3827
|
}))
|
|
3433
3828
|
.addCommand(new Command("set")
|
|
3434
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.")
|
|
@@ -3460,10 +3855,11 @@ export function createProgram() {
|
|
|
3460
3855
|
});
|
|
3461
3856
|
}))));
|
|
3462
3857
|
program.addCommand(new Command("inbox")
|
|
3463
|
-
.description("
|
|
3858
|
+
.description("Unified inbox (unibox): LinkedIn conversations and (--channel email) the fleet's email conversations synced from Zapmail Zapbox. Scan, read threads, and reply.")
|
|
3464
3859
|
.addCommand(new Command("list")
|
|
3465
|
-
.description("List
|
|
3466
|
-
.option("--
|
|
3860
|
+
.description("List conversations across all connected accounts, newest first. --channel email lists the Zapbox-synced email inbox.")
|
|
3861
|
+
.option("--channel <channel>", "Inbox channel: linkedin (default) or email.")
|
|
3862
|
+
.option("--account <id>", "LinkedIn only: filter to one sender account (sender id, connection id, or Unipile account id).")
|
|
3467
3863
|
.option("--unread", "Only show conversations with unread messages.")
|
|
3468
3864
|
.option("--search <text>", "Filter by attendee name or last-message text.")
|
|
3469
3865
|
.option("--include-archived", "Include archived conversations.")
|
|
@@ -3472,6 +3868,9 @@ export function createProgram() {
|
|
|
3472
3868
|
.action(async (options) => {
|
|
3473
3869
|
await handleAsyncAction("inbox list", options, () => {
|
|
3474
3870
|
const params = new URLSearchParams();
|
|
3871
|
+
const channel = readOption(options.channel);
|
|
3872
|
+
if (channel)
|
|
3873
|
+
params.set("channel", channel);
|
|
3475
3874
|
const account = readOption(options.account);
|
|
3476
3875
|
if (account)
|
|
3477
3876
|
params.set("account", account);
|
|
@@ -3490,13 +3889,17 @@ export function createProgram() {
|
|
|
3490
3889
|
});
|
|
3491
3890
|
}))
|
|
3492
3891
|
.addCommand(new Command("get")
|
|
3493
|
-
.description("Get one conversation with its full message thread. <conversation> accepts a conversation id
|
|
3494
|
-
.argument("<conversation>", "Conversation id
|
|
3892
|
+
.description("Get one conversation with its full message thread. <conversation> accepts a conversation id, Unipile chat id, or (email) Zapbox thread id.")
|
|
3893
|
+
.argument("<conversation>", "Conversation id, Unipile chat id, or Zapbox thread id.")
|
|
3894
|
+
.option("--channel <channel>", "Inbox channel: linkedin (default) or email.")
|
|
3495
3895
|
.option("--message-limit <n>", "Maximum messages to return (1-500). Defaults to 100.")
|
|
3496
3896
|
.option("--json", "Print a JSON envelope.")
|
|
3497
3897
|
.action(async (conversation, options) => {
|
|
3498
3898
|
await handleAsyncAction("inbox get", options, () => {
|
|
3499
3899
|
const params = new URLSearchParams();
|
|
3900
|
+
const channel = readOption(options.channel);
|
|
3901
|
+
if (channel)
|
|
3902
|
+
params.set("channel", channel);
|
|
3500
3903
|
const messageLimit = readOption(options.messageLimit);
|
|
3501
3904
|
if (messageLimit)
|
|
3502
3905
|
params.set("message_limit", messageLimit);
|
|
@@ -3505,19 +3908,24 @@ export function createProgram() {
|
|
|
3505
3908
|
});
|
|
3506
3909
|
}))
|
|
3507
3910
|
.addCommand(new Command("send")
|
|
3508
|
-
.description("Reply into a LinkedIn conversation. Sends a real
|
|
3509
|
-
.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.")
|
|
3510
3913
|
.requiredOption("--text <message>", "Reply text to send.")
|
|
3914
|
+
.option("--channel <channel>", "Inbox channel: linkedin (default) or email.")
|
|
3511
3915
|
.option("--approved", "Approve and send the message. Without this flag, returns a preview only.")
|
|
3512
3916
|
.option("--json", "Print a JSON envelope.")
|
|
3513
3917
|
.action(async (conversation, options) => {
|
|
3514
|
-
await handleAsyncAction("inbox send", options, () =>
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
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
|
+
});
|
|
3521
3929
|
}))
|
|
3522
3930
|
.addCommand(new Command("mark-read")
|
|
3523
3931
|
.description("Mark a conversation and all its messages as read.")
|
|
@@ -3527,15 +3935,40 @@ export function createProgram() {
|
|
|
3527
3935
|
await handleAsyncAction("inbox mark-read", options, () => requestOxygen(`/api/cli/inbox/${encodeURIComponent(conversation)}/read`, {
|
|
3528
3936
|
method: "POST",
|
|
3529
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
|
+
}));
|
|
3530
3959
|
}))
|
|
3531
3960
|
.addCommand(new Command("sync")
|
|
3532
3961
|
.description("Force a backstop inbox sync from Unipile for all active sender accounts (pulls recent chats + messages into the unibox).")
|
|
3962
|
+
.option("--account <id>", "Force-sync one sender account (sender id, connection id, or Unipile account id).")
|
|
3533
3963
|
.option("--chat-limit <n>", "Maximum chats to sync per account. Defaults to 30.")
|
|
3534
3964
|
.option("--message-limit <n>", "Maximum messages to sync per chat. Defaults to 20.")
|
|
3535
3965
|
.option("--json", "Print a JSON envelope.")
|
|
3536
3966
|
.action(async (options) => {
|
|
3537
3967
|
await handleAsyncAction("inbox sync", options, () => {
|
|
3538
3968
|
const body = {};
|
|
3969
|
+
const account = readOption(options.account);
|
|
3970
|
+
if (account)
|
|
3971
|
+
body.account = account;
|
|
3539
3972
|
const chatLimit = readOption(options.chatLimit);
|
|
3540
3973
|
if (chatLimit)
|
|
3541
3974
|
body.chat_limit = Number(chatLimit);
|
|
@@ -3606,8 +4039,7 @@ export function createProgram() {
|
|
|
3606
4039
|
const stepsPath = readOption(options.stepsFile);
|
|
3607
4040
|
if (!stepsPath)
|
|
3608
4041
|
throw new Error("--steps-file is required.");
|
|
3609
|
-
const
|
|
3610
|
-
const definition = JSON.parse(raw);
|
|
4042
|
+
const definition = readJsonFileValue(resolve(stepsPath), "--steps-file");
|
|
3611
4043
|
const channels = readCsvOption(options.channels);
|
|
3612
4044
|
const senders = readCsvOption(options.senders);
|
|
3613
4045
|
const email = readCampaignEmailBinding(options);
|
|
@@ -3649,7 +4081,7 @@ export function createProgram() {
|
|
|
3649
4081
|
body.name = name;
|
|
3650
4082
|
const stepsPath = readOption(options.stepsFile);
|
|
3651
4083
|
if (stepsPath) {
|
|
3652
|
-
body.definition =
|
|
4084
|
+
body.definition = readJsonFileValue(resolve(stepsPath), "--steps-file");
|
|
3653
4085
|
}
|
|
3654
4086
|
const channels = readCsvOption(options.channels);
|
|
3655
4087
|
if (channels.length > 0)
|
|
@@ -3694,7 +4126,7 @@ export function createProgram() {
|
|
|
3694
4126
|
const leadsPath = readOption(options.leadsFile);
|
|
3695
4127
|
if (!leadsPath)
|
|
3696
4128
|
throw new Error("--leads-file is required.");
|
|
3697
|
-
const parsed =
|
|
4129
|
+
const parsed = readJsonFileValue(resolve(leadsPath), "--leads-file");
|
|
3698
4130
|
return requestOxygen(`/api/cli/sequences/${encodeURIComponent(sequence)}/enroll`, {
|
|
3699
4131
|
method: "POST",
|
|
3700
4132
|
body: { leads: parsed.leads ?? [] },
|
|
@@ -3774,6 +4206,35 @@ export function createProgram() {
|
|
|
3774
4206
|
.action(async (sequence, options) => {
|
|
3775
4207
|
await handleAsyncAction("sequences stats", options, () => requestOxygen(`/api/cli/sequences/${encodeURIComponent(sequence)}/stats`));
|
|
3776
4208
|
})));
|
|
4209
|
+
program.addCommand(new Command("email")
|
|
4210
|
+
.description("Ad-hoc email from the org's sending fleet: one-off sends with no sequence, enrollment, or contact sync. Zapbox-connected mailboxes send through Zapmail's API (no Google/Microsoft consent); BYOK — 0 Oxygen credits.")
|
|
4211
|
+
.addCommand(new Command("send")
|
|
4212
|
+
.description("Send ONE email right now. Without --approved, returns a preview naming the exact mailbox + transport. --from pins a mailbox; otherwise the pool's LRU rotation picks one.")
|
|
4213
|
+
.requiredOption("--to <email>", "Recipient email address (exactly one).")
|
|
4214
|
+
.requiredOption("--subject <text>", "Subject line.")
|
|
4215
|
+
.option("--body <text>", "Plain-text body (sent verbatim, no templating).")
|
|
4216
|
+
.option("--body-file <path>", "Read the body from a file instead of --body.")
|
|
4217
|
+
.option("--from <mailbox>", "Sending mailbox (address or id). Defaults to the pool rotation.")
|
|
4218
|
+
.option("--approved", "Approve and send. Without this flag, returns a preview only.")
|
|
4219
|
+
.option("--json", "Print a JSON envelope.")
|
|
4220
|
+
.action(async (options) => {
|
|
4221
|
+
await handleAsyncAction("email send", options, async () => {
|
|
4222
|
+
const bodyText = readOption(options.body) ?? (options.bodyFile ? readFileSync(resolve(options.bodyFile), "utf8") : undefined);
|
|
4223
|
+
if (!bodyText || !bodyText.trim()) {
|
|
4224
|
+
throw new Error("Provide --body or --body-file.");
|
|
4225
|
+
}
|
|
4226
|
+
return requestOxygen("/api/cli/email/send", {
|
|
4227
|
+
method: "POST",
|
|
4228
|
+
body: {
|
|
4229
|
+
to: readOption(options.to),
|
|
4230
|
+
subject: readOption(options.subject),
|
|
4231
|
+
body: bodyText,
|
|
4232
|
+
...(readOption(options.from) ? { from: readOption(options.from) } : {}),
|
|
4233
|
+
...(options.approved ? { approved: true } : {}),
|
|
4234
|
+
},
|
|
4235
|
+
});
|
|
4236
|
+
});
|
|
4237
|
+
})));
|
|
3777
4238
|
program.addCommand(new Command("mailboxes")
|
|
3778
4239
|
.description("Native email sending pool: register/refresh Google/Microsoft mailboxes a campaign rotates over, pause/disable inboxes, and delegate warmup to Instantly (BYOK — Instantly bills your account, 0 Oxygen credits).")
|
|
3779
4240
|
.addCommand(new Command("list")
|
|
@@ -3795,21 +4256,29 @@ export function createProgram() {
|
|
|
3795
4256
|
.option("--file <path>", "Path to a JSON file: { \"mailboxes\": [{ email_address, provider, workspace_external_id? }] }.")
|
|
3796
4257
|
.option("--from <source>", "Import source: 'zapmail' to pull the connected Zapmail workspace's mailboxes.")
|
|
3797
4258
|
.option("--connection <id>", "Zapmail connection id (--from zapmail). Defaults to the org's active Zapmail connection.")
|
|
4259
|
+
.option("--provider <provider>", "Zapmail pool to pull (--from zapmail): google or microsoft. Zapmail's mailbox list is provider-scoped, so the Microsoft pool is only reachable with --provider microsoft; Microsoft mailboxes get their Entra tenant id stamped on import.")
|
|
3798
4260
|
.option("--json", "Print a JSON envelope.")
|
|
3799
4261
|
.action(async (options) => {
|
|
3800
4262
|
await handleAsyncAction("mailboxes import", options, () => {
|
|
3801
4263
|
const from = readOption(options.from);
|
|
3802
4264
|
const filePath = readOption(options.file);
|
|
3803
4265
|
const connection = readOption(options.connection);
|
|
4266
|
+
const provider = readOption(options.provider);
|
|
3804
4267
|
if (from === "zapmail") {
|
|
3805
4268
|
return requestOxygen("/api/cli/mailboxes", {
|
|
3806
4269
|
method: "POST",
|
|
3807
|
-
body: {
|
|
4270
|
+
body: {
|
|
4271
|
+
source: "zapmail",
|
|
4272
|
+
...(connection ? { connection_id: connection } : {}),
|
|
4273
|
+
...(provider ? { service_provider: provider } : {}),
|
|
4274
|
+
},
|
|
3808
4275
|
});
|
|
3809
4276
|
}
|
|
4277
|
+
if (provider)
|
|
4278
|
+
throw new Error("--provider only applies with --from zapmail (inline files carry a per-mailbox provider).");
|
|
3810
4279
|
if (!filePath)
|
|
3811
4280
|
throw new Error("Provide --file <path> (a { \"mailboxes\": [...] } JSON file) or --from zapmail.");
|
|
3812
|
-
const parsed =
|
|
4281
|
+
const parsed = readJsonFileValue(resolve(filePath), "--file");
|
|
3813
4282
|
return requestOxygen("/api/cli/mailboxes", {
|
|
3814
4283
|
method: "POST",
|
|
3815
4284
|
body: { mailboxes: parsed.mailboxes ?? [] },
|
|
@@ -3908,68 +4377,177 @@ export function createProgram() {
|
|
|
3908
4377
|
return requestOxygen(`/api/cli/mailboxes/warmup/status${suffix ? `?${suffix}` : ""}`);
|
|
3909
4378
|
});
|
|
3910
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
|
+
})));
|
|
3911
4489
|
program
|
|
3912
4490
|
.command("workflows")
|
|
3913
4491
|
.description("Durable workflow automation commands.")
|
|
3914
4492
|
.addCommand(new Command("templates")
|
|
3915
|
-
.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).")
|
|
3916
4494
|
.addCommand(new Command("search")
|
|
3917
|
-
.description("
|
|
4495
|
+
.description("Deprecated alias for `oxygen blueprints list`. Searches Oxygen blueprints (seed templates plus saved blueprints).")
|
|
3918
4496
|
.argument("[query]", "Search text.")
|
|
3919
|
-
.option("--tag <tag>", "Filter by
|
|
4497
|
+
.option("--tag <tag>", "Filter by blueprint tag.")
|
|
3920
4498
|
.option("--json", "Print a JSON envelope.")
|
|
3921
4499
|
.action(async (query, options) => {
|
|
3922
|
-
await handleAsyncAction("workflows templates search", options,
|
|
4500
|
+
await handleAsyncAction("workflows templates search", options, () => {
|
|
3923
4501
|
const params = new URLSearchParams();
|
|
3924
4502
|
params.set("query", query ?? "");
|
|
3925
4503
|
if (readOption(options.tag))
|
|
3926
4504
|
params.set("tag", readOption(options.tag) ?? "");
|
|
3927
|
-
return requestOxygen(`/api/cli/
|
|
4505
|
+
return requestOxygen(`/api/cli/blueprints?${params.toString()}`);
|
|
3928
4506
|
});
|
|
3929
4507
|
}))
|
|
3930
4508
|
.addCommand(new Command("describe")
|
|
3931
|
-
.description("
|
|
3932
|
-
.argument("<template_id>", "
|
|
4509
|
+
.description("Deprecated alias for `oxygen blueprints describe`.")
|
|
4510
|
+
.argument("<template_id>", "Blueprint slug (formerly workflow template id).")
|
|
3933
4511
|
.option("--json", "Print a JSON envelope.")
|
|
3934
4512
|
.action(async (templateId, options) => {
|
|
3935
|
-
await handleAsyncAction("workflows templates describe", options,
|
|
4513
|
+
await handleAsyncAction("workflows templates describe", options, () => requestOxygen("/api/cli/blueprints/get", {
|
|
3936
4514
|
method: "POST",
|
|
3937
|
-
body: {
|
|
4515
|
+
body: { slug: templateId },
|
|
3938
4516
|
}));
|
|
3939
4517
|
}))
|
|
3940
4518
|
.addCommand(new Command("preflight")
|
|
3941
|
-
.description("
|
|
3942
|
-
.argument("<template_id>", "
|
|
3943
|
-
.requiredOption("--input-json <json>", "
|
|
3944
|
-
.option("--mode <mode>", "
|
|
3945
|
-
.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.")
|
|
3946
4524
|
.option("--json", "Print a JSON envelope.")
|
|
3947
4525
|
.action(async (templateId, options) => {
|
|
3948
|
-
await handleAsyncAction("workflows templates preflight", options,
|
|
4526
|
+
await handleAsyncAction("workflows templates preflight", options, () => requestOxygen("/api/cli/blueprints/preflight", {
|
|
3949
4527
|
method: "POST",
|
|
3950
|
-
body:
|
|
4528
|
+
body: workflowTemplateBlueprintBody(templateId, options),
|
|
3951
4529
|
}));
|
|
3952
4530
|
}))
|
|
3953
4531
|
.addCommand(new Command("apply")
|
|
3954
|
-
.description("
|
|
3955
|
-
.argument("<template_id>", "
|
|
3956
|
-
.requiredOption("--input-json <json>", "
|
|
3957
|
-
.option("--workflow-id <workflow_id>", "
|
|
3958
|
-
.option("--workflow-name <workflow_name>", "
|
|
3959
|
-
.option("--mode <mode>", "
|
|
3960
|
-
.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.")
|
|
3961
4539
|
.option("--include-bundle", "Include durable recipe bundles in JSON output.")
|
|
3962
4540
|
.option("--json", "Print a JSON envelope.")
|
|
3963
4541
|
.action(async (templateId, options) => {
|
|
3964
|
-
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", {
|
|
3965
4543
|
method: "POST",
|
|
3966
|
-
body:
|
|
4544
|
+
body: workflowTemplateBlueprintBody(templateId, options, true),
|
|
3967
4545
|
}), options));
|
|
3968
4546
|
}))
|
|
3969
4547
|
.addCommand(new Command("run")
|
|
3970
|
-
.description("
|
|
3971
|
-
.argument("<template_id>", "
|
|
3972
|
-
.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.")
|
|
3973
4551
|
.requiredOption("--mode <mode>", "Execution mode: smoke_test, dry_run, or live.")
|
|
3974
4552
|
.option("--workflow-id <workflow_id>", "Workflow id to create or update.")
|
|
3975
4553
|
.option("--workflow-name <workflow_name>", "Workflow display name.")
|
|
@@ -3988,7 +4566,7 @@ export function createProgram() {
|
|
|
3988
4566
|
.option("--subject <subject>", "Schema subject: all, apply, call, event, trigger, or manifest.")
|
|
3989
4567
|
.option("--json", "Print a JSON envelope.")
|
|
3990
4568
|
.action(async (options) => {
|
|
3991
|
-
await handleAsyncAction("workflows schema", options,
|
|
4569
|
+
await handleAsyncAction("workflows schema", options, () => requestOxygen(`/api/cli/workflows/schema?subject=${encodeURIComponent(options.subject ?? "all")}`));
|
|
3992
4570
|
}))
|
|
3993
4571
|
.addCommand(new Command("lint")
|
|
3994
4572
|
.description("Compile and lint a workflow file without saving it.")
|
|
@@ -4021,7 +4599,7 @@ export function createProgram() {
|
|
|
4021
4599
|
.description("List workflow automations.")
|
|
4022
4600
|
.option("--json", "Print a JSON envelope.")
|
|
4023
4601
|
.action(async (options) => {
|
|
4024
|
-
await handleAsyncAction("workflows list", options,
|
|
4602
|
+
await handleAsyncAction("workflows list", options, () => requestOxygen("/api/cli/workflows"));
|
|
4025
4603
|
}))
|
|
4026
4604
|
.addCommand(new Command("get")
|
|
4027
4605
|
.description("Get one workflow automation.")
|
|
@@ -4064,9 +4642,12 @@ export function createProgram() {
|
|
|
4064
4642
|
.option("--input-json <json>", "Workflow input object. Defaults to {}.")
|
|
4065
4643
|
.requiredOption("--mode <mode>", "Execution mode: live, dry-run, or smoke-test.")
|
|
4066
4644
|
.option("--idempotency-key <key>", "Optional idempotency key.")
|
|
4645
|
+
.option("--max-credits <n>", "Required credit ceiling for live calls.")
|
|
4646
|
+
.option("--approved", "Required for live calls after inspecting a dry run.")
|
|
4067
4647
|
.option("--include-bundle", "Include durable recipe bundles in JSON output.")
|
|
4068
4648
|
.option("--json", "Print a JSON envelope.")
|
|
4069
4649
|
.action(async (workflowArg, options) => {
|
|
4650
|
+
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
4070
4651
|
await handleAsyncAction("workflows call", options, async () => prepareWorkflowCliOutput(await requestOxygen("/api/cli/workflows/call", {
|
|
4071
4652
|
method: "POST",
|
|
4072
4653
|
body: {
|
|
@@ -4076,6 +4657,8 @@ export function createProgram() {
|
|
|
4076
4657
|
input: options.inputJson ? parseJsonObject(options.inputJson) : {},
|
|
4077
4658
|
...(readOption(options.mode) ? { mode: readOption(options.mode) } : {}),
|
|
4078
4659
|
...(readOption(options.idempotencyKey) ? { idempotency_key: readOption(options.idempotencyKey) } : {}),
|
|
4660
|
+
...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
|
|
4661
|
+
...(options.approved ? { approved: true } : {}),
|
|
4079
4662
|
},
|
|
4080
4663
|
}), options));
|
|
4081
4664
|
}))
|
|
@@ -4171,14 +4754,14 @@ export function createProgram() {
|
|
|
4171
4754
|
.option("--include-bundle", "Include durable recipe bundles in JSON output.")
|
|
4172
4755
|
.option("--json", "Print a JSON envelope.")
|
|
4173
4756
|
.action(async (runId, options) => {
|
|
4174
|
-
await handleAsyncAction("workflows tail", options, async () => prepareWorkflowCliOutput(await
|
|
4757
|
+
await handleAsyncAction("workflows tail", options, async () => prepareWorkflowCliOutput(await waitForWorkflowRun(runId, options), options));
|
|
4175
4758
|
}))
|
|
4176
4759
|
.addCommand(new Command("cancel")
|
|
4177
4760
|
.description("Cancel a queued or running workflow run.")
|
|
4178
4761
|
.argument("<run_id>", "Workflow run UUID.")
|
|
4179
4762
|
.option("--json", "Print a JSON envelope.")
|
|
4180
4763
|
.action(async (runId, options) => {
|
|
4181
|
-
await handleAsyncAction("workflows cancel", options,
|
|
4764
|
+
await handleAsyncAction("workflows cancel", options, () => requestOxygen("/api/cli/workflows/cancel", {
|
|
4182
4765
|
method: "POST",
|
|
4183
4766
|
body: { run_id: runId },
|
|
4184
4767
|
}));
|
|
@@ -4188,7 +4771,7 @@ export function createProgram() {
|
|
|
4188
4771
|
.argument("<workflow>", "Workflow id, slug, or name.")
|
|
4189
4772
|
.option("--json", "Print a JSON envelope.")
|
|
4190
4773
|
.action(async (workflow, options) => {
|
|
4191
|
-
await handleAsyncAction("workflows enable", options,
|
|
4774
|
+
await handleAsyncAction("workflows enable", options, () => requestOxygen("/api/cli/workflows/enable", {
|
|
4192
4775
|
method: "POST",
|
|
4193
4776
|
body: { workflow },
|
|
4194
4777
|
}));
|
|
@@ -4198,7 +4781,7 @@ export function createProgram() {
|
|
|
4198
4781
|
.argument("<workflow>", "Workflow id, slug, or name.")
|
|
4199
4782
|
.option("--json", "Print a JSON envelope.")
|
|
4200
4783
|
.action(async (workflow, options) => {
|
|
4201
|
-
await handleAsyncAction("workflows disable", options,
|
|
4784
|
+
await handleAsyncAction("workflows disable", options, () => requestOxygen("/api/cli/workflows/disable", {
|
|
4202
4785
|
method: "POST",
|
|
4203
4786
|
body: { workflow },
|
|
4204
4787
|
}));
|
|
@@ -4211,14 +4794,14 @@ export function createProgram() {
|
|
|
4211
4794
|
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
4212
4795
|
.option("--json", "Print a JSON envelope.")
|
|
4213
4796
|
.action(async (options) => {
|
|
4214
|
-
await handleAsyncAction("skills list", options,
|
|
4797
|
+
await handleAsyncAction("skills list", options, () => listAgentSkills(options));
|
|
4215
4798
|
}))
|
|
4216
4799
|
.addCommand(new Command("doctor")
|
|
4217
4800
|
.description("Check Oxygen skill index reachability and local installer prerequisites.")
|
|
4218
4801
|
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
4219
4802
|
.option("--json", "Print a JSON envelope.")
|
|
4220
4803
|
.action(async (options) => {
|
|
4221
|
-
await handleAsyncAction("skills doctor", options,
|
|
4804
|
+
await handleAsyncAction("skills doctor", options, () => doctorAgentSkills(options));
|
|
4222
4805
|
}))
|
|
4223
4806
|
.addCommand(new Command("install")
|
|
4224
4807
|
.description("Install Oxygen agent skills into local agent skill directories.")
|
|
@@ -4229,6 +4812,7 @@ export function createProgram() {
|
|
|
4229
4812
|
.option("--copy", "Copy skill files instead of symlinking when supported by npx skills. Default on Windows, where symlinks need Developer Mode or admin.")
|
|
4230
4813
|
.option("--json", "Print a JSON envelope.")
|
|
4231
4814
|
.action(async (options) => {
|
|
4815
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous installAgentSkills helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
4232
4816
|
await handleAsyncAction("skills install", options, async () => installAgentSkills(options));
|
|
4233
4817
|
}));
|
|
4234
4818
|
return program;
|
|
@@ -4349,8 +4933,8 @@ function referencesRecipeSdk(source) {
|
|
|
4349
4933
|
}
|
|
4350
4934
|
// Escape Next static analysis (the CLI is bundled by tsc, but mirror the
|
|
4351
4935
|
// worker's escape so both load identically).
|
|
4936
|
+
// skipcq: JS-R1003 — intentional dynamic-import escape mirrored from the worker; not arbitrary code execution
|
|
4352
4937
|
const dynamicRecipeImport = new Function("specifier", "return import(specifier);");
|
|
4353
|
-
// skipcq: JS-R1003
|
|
4354
4938
|
async function importRecipeModule(specifier) {
|
|
4355
4939
|
try {
|
|
4356
4940
|
return await dynamicRecipeImport(specifier);
|
|
@@ -4497,66 +5081,64 @@ export function toolStep(input) {
|
|
|
4497
5081
|
}
|
|
4498
5082
|
`;
|
|
4499
5083
|
}
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
let
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
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");
|
|
4522
5112
|
return {
|
|
4523
5113
|
run,
|
|
4524
5114
|
workflowRunId: readRecordString(run, "id") ?? runId,
|
|
4525
5115
|
status,
|
|
4526
5116
|
terminal: true,
|
|
4527
5117
|
polls,
|
|
4528
|
-
elapsedMs
|
|
5118
|
+
elapsedMs,
|
|
4529
5119
|
...(workflowUrl ? { workflowUrl } : {}),
|
|
4530
5120
|
...(runUrl ? { runUrl } : {}),
|
|
4531
5121
|
...(webUrl ? { web_url: webUrl } : {}),
|
|
4532
5122
|
...(deepLink ? { deepLink } : {}),
|
|
4533
5123
|
};
|
|
4534
|
-
}
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
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;
|
|
4538
5130
|
const queuedWithoutWorker = status === "queued"
|
|
4539
5131
|
&& worker !== null
|
|
4540
5132
|
&& worker.active === null
|
|
4541
5133
|
&& worker.lastClaim === null;
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
worker_status: "queued_no_worker",
|
|
4551
|
-
guidance: "No worker has claimed this workflow run. Check `oxygen worker queue-stats --json` for Postgres-backed worker queue health.",
|
|
4552
|
-
}
|
|
4553
|
-
: {}),
|
|
4554
|
-
},
|
|
4555
|
-
exitCode: 1,
|
|
4556
|
-
});
|
|
4557
|
-
}
|
|
4558
|
-
await sleep(Math.min(intervalSeconds * 1000, remainingMs));
|
|
4559
|
-
}
|
|
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
|
+
});
|
|
4560
5142
|
}
|
|
4561
5143
|
function workflowTemplateActionBody(templateId, options) {
|
|
4562
5144
|
const inputJson = readOption(options.inputJson);
|
|
@@ -4571,6 +5153,28 @@ function workflowTemplateActionBody(templateId, options) {
|
|
|
4571
5153
|
...(options.approved ? { approved: true } : {}),
|
|
4572
5154
|
};
|
|
4573
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
|
+
}
|
|
4574
5178
|
function prepareWorkflowCliOutput(value, options) {
|
|
4575
5179
|
const normalized = normalizeWorkflowRunErrors(value);
|
|
4576
5180
|
return (options.includeBundle ? normalized : stripWorkflowBundles(normalized));
|
|
@@ -4727,6 +5331,34 @@ function readTableRunSelection(options) {
|
|
|
4727
5331
|
exitCode: 1,
|
|
4728
5332
|
});
|
|
4729
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
|
+
}
|
|
4730
5362
|
function tableRunsListPath(options) {
|
|
4731
5363
|
const table = readOption(options.table);
|
|
4732
5364
|
if (!table) {
|
|
@@ -4926,7 +5558,7 @@ function readCompanySearchFilters(options) {
|
|
|
4926
5558
|
const fundingStages = readCsvOption(options.fundingStages);
|
|
4927
5559
|
if (fundingStages.length > 0)
|
|
4928
5560
|
filters.funding = { stages: fundingStages };
|
|
4929
|
-
const technologies = readIncludeExclude(options.technologies
|
|
5561
|
+
const technologies = readIncludeExclude(options.technologies);
|
|
4930
5562
|
if (technologies)
|
|
4931
5563
|
filters.technologies = technologies;
|
|
4932
5564
|
const revenue = readRangeOption(options.revenue, "--revenue");
|
|
@@ -5015,6 +5647,22 @@ function assertNotCompanySearchKind(kind) {
|
|
|
5015
5647
|
});
|
|
5016
5648
|
}
|
|
5017
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
|
+
}
|
|
5018
5666
|
function readCompanySearchPlanJson(value) {
|
|
5019
5667
|
const parsed = parseJsonObject(readFileIfPresent(value));
|
|
5020
5668
|
const data = parsed.data;
|
|
@@ -5030,7 +5678,8 @@ function readPeopleSearchPlanBody(options) {
|
|
|
5030
5678
|
...(targetCount !== undefined ? { target_count: targetCount } : {}),
|
|
5031
5679
|
...(options.sourceIntent ? { source_intent: options.sourceIntent } : {}),
|
|
5032
5680
|
...(filters ? { filters } : {}),
|
|
5033
|
-
|
|
5681
|
+
// Free sizing is on by default; only --no-estimate (options.estimate === false) opts out.
|
|
5682
|
+
estimate: options.estimate !== false,
|
|
5034
5683
|
...(options.materializePreview ? { materialize_preview: true } : {}),
|
|
5035
5684
|
};
|
|
5036
5685
|
}
|
|
@@ -5057,7 +5706,8 @@ function readPeopleSearchRunBody(options) {
|
|
|
5057
5706
|
...(targetCount !== undefined ? { target_count: targetCount } : {}),
|
|
5058
5707
|
...(options.sourceIntent ? { source_intent: options.sourceIntent } : {}),
|
|
5059
5708
|
...(filters ? { filters } : {}),
|
|
5060
|
-
|
|
5709
|
+
// Free sizing is on by default when planning from a prompt; --no-estimate opts out.
|
|
5710
|
+
...(prompt ? { estimate: options.estimate !== false } : {}),
|
|
5061
5711
|
...(options.approved ? { approved: true } : {}),
|
|
5062
5712
|
};
|
|
5063
5713
|
}
|
|
@@ -5210,6 +5860,26 @@ function readFilterJsonOption(value) {
|
|
|
5210
5860
|
}
|
|
5211
5861
|
return filters;
|
|
5212
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
|
+
}
|
|
5213
5883
|
function parseStringArray(value) {
|
|
5214
5884
|
const parsed = parseJsonArray(value);
|
|
5215
5885
|
const labels = parsed
|
|
@@ -5236,6 +5906,7 @@ function normalizeSessionStepStatus(value) {
|
|
|
5236
5906
|
exitCode: 1,
|
|
5237
5907
|
});
|
|
5238
5908
|
}
|
|
5909
|
+
// skipcq: JS-R1005 — intentional branching over file format, create/upsert, and sync/background import modes
|
|
5239
5910
|
async function importRows(table, options) {
|
|
5240
5911
|
const format = normalizeRowsFormat(options.format, inferRowsFileFormat(options.file));
|
|
5241
5912
|
const parsedRows = await readRowsFile(options.file, format, options.sheet);
|
|
@@ -5640,118 +6311,249 @@ function emitQueueWaitStderrNote(queueWait) {
|
|
|
5640
6311
|
if (note)
|
|
5641
6312
|
process.stderr.write(`note: ${note}\n`);
|
|
5642
6313
|
}
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
polls
|
|
5653
|
-
|
|
5654
|
-
const status = readRecordString(latestRun, "status");
|
|
5655
|
-
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");
|
|
5656
6325
|
return {
|
|
5657
|
-
|
|
5658
|
-
|
|
6326
|
+
[params.runKey]: run,
|
|
6327
|
+
[params.runIdKey]: readRecordString(run, "id") ?? params.runId,
|
|
5659
6328
|
status,
|
|
5660
6329
|
terminal: true,
|
|
5661
6330
|
polls,
|
|
5662
|
-
elapsedMs
|
|
5663
|
-
...(
|
|
6331
|
+
elapsedMs,
|
|
6332
|
+
...(webUrl ? { web_url: webUrl } : {}),
|
|
5664
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);
|
|
5665
6417
|
}
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
details: {
|
|
5670
|
-
ingestion_run_id: runId,
|
|
5671
|
-
status: status ?? null,
|
|
5672
|
-
timeout_seconds: timeoutSeconds,
|
|
5673
|
-
polls,
|
|
5674
|
-
},
|
|
5675
|
-
exitCode: 1,
|
|
5676
|
-
});
|
|
6418
|
+
if (typeof latest.dns_checked === "number") {
|
|
6419
|
+
sawDnsChecked = true;
|
|
6420
|
+
totalDnsChecked += readCount(latest.dns_checked);
|
|
5677
6421
|
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
?? TABLE_INGESTION_WAIT_DEFAULT_TIMEOUT_SECONDS;
|
|
5684
|
-
const intervalSeconds = readPositiveInt(options.intervalSeconds)
|
|
5685
|
-
?? TABLE_INGESTION_WAIT_DEFAULT_INTERVAL_SECONDS;
|
|
5686
|
-
const startedAt = Date.now();
|
|
5687
|
-
const deadline = startedAt + timeoutSeconds * 1000;
|
|
5688
|
-
let polls = 0;
|
|
5689
|
-
while (true) {
|
|
5690
|
-
polls += 1;
|
|
5691
|
-
const latestRun = await requestOxygen(`/api/cli/search/runs/${encodeURIComponent(runId)}`);
|
|
5692
|
-
const status = readRecordString(latestRun, "status");
|
|
5693
|
-
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) {
|
|
5694
6427
|
return {
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
...(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,
|
|
5702
6434
|
};
|
|
5703
6435
|
}
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
});
|
|
5715
|
-
}
|
|
5716
|
-
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.");
|
|
5717
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";
|
|
5718
6505
|
}
|
|
5719
|
-
async function
|
|
5720
|
-
const timeoutSeconds = readPositiveInt(options.timeoutSeconds)
|
|
5721
|
-
?? TABLE_ACTION_RUN_WAIT_DEFAULT_TIMEOUT_SECONDS;
|
|
5722
|
-
const intervalSeconds = readPositiveInt(options.intervalSeconds)
|
|
5723
|
-
?? TABLE_ACTION_RUN_WAIT_DEFAULT_INTERVAL_SECONDS;
|
|
6506
|
+
async function waitForDomainRegistration(domain) {
|
|
5724
6507
|
const startedAt = Date.now();
|
|
5725
|
-
const deadline = startedAt +
|
|
6508
|
+
const deadline = startedAt + DOMAIN_REGISTRATION_WAIT_TIMEOUT_SECONDS * 1000;
|
|
5726
6509
|
let polls = 0;
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
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`);
|
|
5732
6546
|
return {
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
6547
|
+
domain,
|
|
6548
|
+
status: lastStatus,
|
|
6549
|
+
terminal: false,
|
|
6550
|
+
timed_out: true,
|
|
5737
6551
|
polls,
|
|
5738
6552
|
elapsedMs: Date.now() - startedAt,
|
|
5739
|
-
...(
|
|
6553
|
+
...(lastRegistration ? { registration: lastRegistration } : {}),
|
|
5740
6554
|
};
|
|
5741
6555
|
}
|
|
5742
|
-
|
|
5743
|
-
if (remainingMs <= 0) {
|
|
5744
|
-
throw new OxygenError("table_action_run_wait_timeout", "Timed out waiting for table action run to finish.", {
|
|
5745
|
-
details: {
|
|
5746
|
-
action_run_id: runId,
|
|
5747
|
-
status: status ?? null,
|
|
5748
|
-
timeout_seconds: timeoutSeconds,
|
|
5749
|
-
polls,
|
|
5750
|
-
},
|
|
5751
|
-
exitCode: 1,
|
|
5752
|
-
});
|
|
5753
|
-
}
|
|
5754
|
-
await sleep(Math.min(intervalSeconds * 1000, remainingMs));
|
|
6556
|
+
await sleep(Math.min(delayMs, remainingMs));
|
|
5755
6557
|
}
|
|
5756
6558
|
}
|
|
5757
6559
|
async function exportRows(table, options) {
|
|
@@ -5780,6 +6582,7 @@ async function exportRows(table, options) {
|
|
|
5780
6582
|
const TABLE_BUNDLE_SCHEMA_VERSION = 1;
|
|
5781
6583
|
const TABLE_BUNDLE_MAX_PAGE_SIZE = 1000;
|
|
5782
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)
|
|
5783
6586
|
async function exportTableBundle(table, options) {
|
|
5784
6587
|
const pageSize = Math.min(readPositiveInt(options.pageSize) ?? TABLE_BUNDLE_DEFAULT_PAGE_SIZE, TABLE_BUNDLE_MAX_PAGE_SIZE);
|
|
5785
6588
|
// Pull the canonical schema (incl. enrichment/tool definitions and
|
|
@@ -6690,9 +7493,9 @@ async function handleProfilesCurrentAction(options) {
|
|
|
6690
7493
|
}
|
|
6691
7494
|
}
|
|
6692
7495
|
function shellQuote(value) {
|
|
6693
|
-
if (/^[A-Za-z0-9_
|
|
7496
|
+
if (/^[A-Za-z0-9_.\-:/@%+=]+$/.test(value))
|
|
6694
7497
|
return value;
|
|
6695
|
-
return `'${value.replace(/'/g,
|
|
7498
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
6696
7499
|
}
|
|
6697
7500
|
async function handleLogoutAction(options) {
|
|
6698
7501
|
try {
|
|
@@ -6718,7 +7521,7 @@ async function handleLogoutAction(options) {
|
|
|
6718
7521
|
process.exitCode = error instanceof OxygenError ? error.exitCode : 1;
|
|
6719
7522
|
}
|
|
6720
7523
|
}
|
|
6721
|
-
|
|
7524
|
+
function handleUpdateAction(options) {
|
|
6722
7525
|
try {
|
|
6723
7526
|
const result = updateCli(options);
|
|
6724
7527
|
if (options.json) {
|
|
@@ -7375,7 +8178,7 @@ function describeProfileSource(source) {
|
|
|
7375
8178
|
case "flag": return "from --profile flag";
|
|
7376
8179
|
case "env": return "from OXYGEN_PROFILE";
|
|
7377
8180
|
case "file": return "from stored active profile";
|
|
7378
|
-
|
|
8181
|
+
default: return "default fallback";
|
|
7379
8182
|
}
|
|
7380
8183
|
}
|
|
7381
8184
|
function formatLogoutSuccess(result) {
|
|
@@ -7437,8 +8240,8 @@ function renderBox(lines) {
|
|
|
7437
8240
|
return [border, ...body, border].join("\n");
|
|
7438
8241
|
}
|
|
7439
8242
|
function visibleLength(value) {
|
|
7440
|
-
// skipcq: JS-0004 — ESC (\x1b) is the ANSI CSI introducer; required to strip color codes
|
|
7441
|
-
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;
|
|
7442
8245
|
}
|
|
7443
8246
|
function ansi(enabled) {
|
|
7444
8247
|
const wrap = (open, close) => enabled
|
|
@@ -7496,14 +8299,6 @@ function buildFeedbackBody(options) {
|
|
|
7496
8299
|
}
|
|
7497
8300
|
return body;
|
|
7498
8301
|
}
|
|
7499
|
-
function splitCsvOption(value) {
|
|
7500
|
-
if (!value)
|
|
7501
|
-
return [];
|
|
7502
|
-
return value
|
|
7503
|
-
.split(",")
|
|
7504
|
-
.map((entry) => entry.trim())
|
|
7505
|
-
.filter((entry) => entry.length > 0);
|
|
7506
|
-
}
|
|
7507
8302
|
function buildSupportTicketBody(options) {
|
|
7508
8303
|
const body = { subject: readOption(options.subject) };
|
|
7509
8304
|
if (readOption(options.body))
|
|
@@ -7517,9 +8312,9 @@ function buildSupportTicketBody(options) {
|
|
|
7517
8312
|
context.operation = readOption(options.operation);
|
|
7518
8313
|
if (readOption(options.errorCode))
|
|
7519
8314
|
context.error_code = readOption(options.errorCode);
|
|
7520
|
-
const runIds =
|
|
7521
|
-
const tableIds =
|
|
7522
|
-
const deepLinks =
|
|
8315
|
+
const runIds = readCsvOption(options.runIds);
|
|
8316
|
+
const tableIds = readCsvOption(options.tableIds);
|
|
8317
|
+
const deepLinks = readCsvOption(options.deepLinks);
|
|
7523
8318
|
if (runIds.length)
|
|
7524
8319
|
context.run_ids = runIds;
|
|
7525
8320
|
if (tableIds.length)
|
|
@@ -7550,12 +8345,6 @@ function readCsvOption(value) {
|
|
|
7550
8345
|
.map((entry) => entry.trim())
|
|
7551
8346
|
.filter(Boolean);
|
|
7552
8347
|
}
|
|
7553
|
-
function splitCsv(value) {
|
|
7554
|
-
return value
|
|
7555
|
-
.split(",")
|
|
7556
|
-
.map((entry) => entry.trim())
|
|
7557
|
-
.filter(Boolean);
|
|
7558
|
-
}
|
|
7559
8348
|
// Assemble the optional campaign email binding from the --email-* flags. The
|
|
7560
8349
|
// content spec (--email-definition-file) is the author-provided email sequence
|
|
7561
8350
|
// that the API compiles to an Instantly campaign on start; provider/connection
|
|
@@ -7568,7 +8357,7 @@ function readCampaignEmailBinding(options) {
|
|
|
7568
8357
|
if (!provider && !connectionId && !definitionPath)
|
|
7569
8358
|
return undefined;
|
|
7570
8359
|
const definition = definitionPath
|
|
7571
|
-
?
|
|
8360
|
+
? readJsonFileValue(resolve(definitionPath), "--email-definition-file")
|
|
7572
8361
|
: undefined;
|
|
7573
8362
|
return {
|
|
7574
8363
|
...(provider ? { provider } : {}),
|
|
@@ -7588,7 +8377,7 @@ slug, options) {
|
|
|
7588
8377
|
if (filePath) {
|
|
7589
8378
|
const fs = await import("node:fs/promises");
|
|
7590
8379
|
const raw = await fs.readFile(filePath, "utf8");
|
|
7591
|
-
body.envelope =
|
|
8380
|
+
body.envelope = parseJsonValue(raw, "--file");
|
|
7592
8381
|
}
|
|
7593
8382
|
const fromUrl = readOption(options.fromUrl);
|
|
7594
8383
|
if (fromUrl) {
|
|
@@ -7603,12 +8392,7 @@ slug, options) {
|
|
|
7603
8392
|
}
|
|
7604
8393
|
const inputJson = readOption(options.inputJson);
|
|
7605
8394
|
if (inputJson) {
|
|
7606
|
-
|
|
7607
|
-
body.inputs = JSON.parse(inputJson);
|
|
7608
|
-
}
|
|
7609
|
-
catch {
|
|
7610
|
-
throw new Error("--input-json must be valid JSON.");
|
|
7611
|
-
}
|
|
8395
|
+
body.inputs = parseJsonValue(inputJson, "--input-json");
|
|
7612
8396
|
}
|
|
7613
8397
|
const tableRefEntries = Array.isArray(options.tableRef) ? options.tableRef : [];
|
|
7614
8398
|
if (tableRefEntries.length > 0) {
|
|
@@ -7742,6 +8526,7 @@ table, options) {
|
|
|
7742
8526
|
? { phone_waterfall_profile: readOption(options.phoneWaterfallProfile) }
|
|
7743
8527
|
: {}),
|
|
7744
8528
|
...(options.verifyPhone ? { verify_phone: true } : {}),
|
|
8529
|
+
...(options.allowPremiumLanes ? { allow_premium_lanes: true } : {}),
|
|
7745
8530
|
...(readOption(options.phoneVerificationCredentialMode)
|
|
7746
8531
|
? { phone_verification_credential_mode: readOption(options.phoneVerificationCredentialMode) }
|
|
7747
8532
|
: {}),
|