@oxygen-agent/cli 1.46.0 → 1.50.37
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 +13 -1
- package/dist/credentials.js +114 -43
- package/dist/index.js +371 -91
- package/dist/local-custom-http-column.js +1 -1
- package/dist/update.d.ts +31 -0
- package/dist/update.js +116 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +2 -1
- package/node_modules/@oxygen/shared/dist/billing.js +2 -1
- package/node_modules/@oxygen/shared/dist/cell-format.d.ts +60 -0
- package/node_modules/@oxygen/shared/dist/cell-format.js +278 -0
- package/node_modules/@oxygen/shared/dist/column-types.d.ts +2 -1
- package/node_modules/@oxygen/shared/dist/column-types.js +3 -2
- package/node_modules/@oxygen/shared/dist/file-import.js +1 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/index.js +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +1 -1
- 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/workflows/dist/index.d.ts +145 -143
- package/node_modules/@oxygen/workflows/dist/index.js +30 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
// skipcq: JS-0271 — bin entry source; build chmod+x on dist/index.js
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
@@ -8,7 +9,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
8
9
|
import { stdin as input, stdout as output } from "node:process";
|
|
9
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
11
|
import { Command } from "commander";
|
|
11
|
-
import { OXYGEN_VERSION, OxygenError, success, toFailure } from "@oxygen/shared";
|
|
12
|
+
import { formatCellForDisplay, OXYGEN_VERSION, OxygenError, success, toFailure } from "@oxygen/shared";
|
|
12
13
|
import { inferRowsFileFormat, normalizeRowsForNewTable, normalizeRowsFormat, parseRowsFileBuffer, } from "@oxygen/shared/file-import";
|
|
13
14
|
import { assertRecipeBundleSafe, assertWorkflowManifest, buildRecipeManifest, compileWorkflowDefinition, isAnyWorkflowManifest, isRecipeManifest, isWorkflowDefinition, isWorkflowManifest, } from "@oxygen/workflows";
|
|
14
15
|
import { isRecipeDefinition } from "@oxygen/recipe-sdk";
|
|
@@ -17,6 +18,7 @@ import { clearCredentials, defaultApiUrl, listCredentialProfiles, normalizeApiUr
|
|
|
17
18
|
import { requestOxygen } from "./http-client.js";
|
|
18
19
|
import { runLocalCustomHttpColumn } from "./local-custom-http-column.js";
|
|
19
20
|
import { addSessionOutput, addSessionStatus, getSessionUsage, startSession, updateSessionStep, } from "./session.js";
|
|
21
|
+
import { updateCli } from "./update.js";
|
|
20
22
|
const BROWSER_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
21
23
|
const OXYGEN_SPINNER_INTERVAL_MS = 90;
|
|
22
24
|
const OXYGEN_SPINNER_FRAMES = [
|
|
@@ -43,7 +45,6 @@ const TABLE_INGESTION_WAIT_DEFAULT_TIMEOUT_SECONDS = 600;
|
|
|
43
45
|
const TABLE_INGESTION_WAIT_DEFAULT_INTERVAL_SECONDS = 5;
|
|
44
46
|
const WORKFLOW_TAIL_DEFAULT_TIMEOUT_SECONDS = 600;
|
|
45
47
|
const WORKFLOW_TAIL_DEFAULT_INTERVAL_SECONDS = 2;
|
|
46
|
-
const DEFAULT_CLI_PACKAGE_SPEC = "@oxygen-agent/cli@latest";
|
|
47
48
|
const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
48
49
|
const RECIPE_ESBUILD_NODE_PATHS = [
|
|
49
50
|
resolve("node_modules"),
|
|
@@ -518,9 +519,9 @@ export function createProgram() {
|
|
|
518
519
|
await handleAsyncAction("tables import", options, async () => importRows(table, options));
|
|
519
520
|
}))
|
|
520
521
|
.addCommand(new Command("export")
|
|
521
|
-
.description("Export workspace table rows as JSON, JSONL, or
|
|
522
|
+
.description("Export workspace table rows as JSON, JSONL, CSV, or a human-readable table.")
|
|
522
523
|
.argument("<table>", "Table id or slug.")
|
|
523
|
-
.option("--format <format>", "json, jsonl, or
|
|
524
|
+
.option("--format <format>", "json, jsonl, csv, or table. Defaults to json. Use table for a typed, human-readable rendering with thousands grouping.")
|
|
524
525
|
.option("--output <path>", "Write export content to a file.")
|
|
525
526
|
.option("--limit <n>", "Maximum rows to export. Defaults to 100; hard cap is 1000.")
|
|
526
527
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -617,6 +618,17 @@ export function createProgram() {
|
|
|
617
618
|
method: "POST",
|
|
618
619
|
body: { table },
|
|
619
620
|
}));
|
|
621
|
+
}))
|
|
622
|
+
.addCommand(new Command("move")
|
|
623
|
+
.description("Move a workspace table to a different project.")
|
|
624
|
+
.argument("<table>", "Table id or slug.")
|
|
625
|
+
.requiredOption("--project <project>", "Destination project id or slug.")
|
|
626
|
+
.option("--json", "Print a JSON envelope.")
|
|
627
|
+
.action(async (table, options) => {
|
|
628
|
+
await handleAsyncAction("tables move", options, async () => requestOxygen("/api/cli/tables/move", {
|
|
629
|
+
method: "POST",
|
|
630
|
+
body: { table, project: options.project },
|
|
631
|
+
}));
|
|
620
632
|
}));
|
|
621
633
|
tablesCommand.addCommand(new Command("webhook")
|
|
622
634
|
.description("Create and manage direct table webhooks.")
|
|
@@ -721,6 +733,139 @@ export function createProgram() {
|
|
|
721
733
|
body: { id: assetId },
|
|
722
734
|
}));
|
|
723
735
|
})));
|
|
736
|
+
program
|
|
737
|
+
.command("templates")
|
|
738
|
+
.description("Reusable prompt templates layered into AI columns at run time.")
|
|
739
|
+
.addCommand(new Command("list")
|
|
740
|
+
.description("List prompt templates in the workspace.")
|
|
741
|
+
.option("--kind <kind>", "Filter by ai_column_system, scoring_rubric, or other.")
|
|
742
|
+
.option("--include-archived", "Include archived templates.")
|
|
743
|
+
.option("--json", "Print a JSON envelope.")
|
|
744
|
+
.action(async (options) => {
|
|
745
|
+
await handleAsyncAction("templates list", options, async () => {
|
|
746
|
+
const params = new URLSearchParams();
|
|
747
|
+
if (readOption(options.kind))
|
|
748
|
+
params.set("kind", readOption(options.kind));
|
|
749
|
+
if (options.includeArchived)
|
|
750
|
+
params.set("include_archived", "true");
|
|
751
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
752
|
+
return requestOxygen(`/api/cli/templates${qs}`);
|
|
753
|
+
});
|
|
754
|
+
}))
|
|
755
|
+
.addCommand(new Command("get")
|
|
756
|
+
.description("Read one prompt template by id or slug.")
|
|
757
|
+
.argument("<id_or_slug>", "Template UUID or slug.")
|
|
758
|
+
.option("--json", "Print a JSON envelope.")
|
|
759
|
+
.action(async (idOrSlug, options) => {
|
|
760
|
+
await handleAsyncAction("templates get", options, async () => requestOxygen("/api/cli/templates/get", {
|
|
761
|
+
method: "POST",
|
|
762
|
+
body: idOrSlug.includes("-") && idOrSlug.length >= 32
|
|
763
|
+
? { id: idOrSlug }
|
|
764
|
+
: { slug: idOrSlug },
|
|
765
|
+
}));
|
|
766
|
+
}))
|
|
767
|
+
.addCommand(new Command("upsert")
|
|
768
|
+
.description("Create or update a prompt template.")
|
|
769
|
+
.option("--id <id>", "Existing template UUID to update. Omit to create.")
|
|
770
|
+
.option("--slug <slug>", "Stable slug (kebab-case).")
|
|
771
|
+
.option("--name <name>", "Human-readable name.")
|
|
772
|
+
.option("--description <text>", "Short description.")
|
|
773
|
+
.option("--kind <kind>", "Template kind: ai_column_system, scoring_rubric, or other.")
|
|
774
|
+
.option("--body <text>", "Prompt body.")
|
|
775
|
+
.option("--body-file <path>", "Path to a file containing the prompt body.")
|
|
776
|
+
.option("--json", "Print a JSON envelope.")
|
|
777
|
+
.action(async (options) => {
|
|
778
|
+
await handleAsyncAction("templates upsert", options, async () => {
|
|
779
|
+
const body = {};
|
|
780
|
+
if (readOption(options.id))
|
|
781
|
+
body.id = readOption(options.id);
|
|
782
|
+
if (readOption(options.slug))
|
|
783
|
+
body.slug = readOption(options.slug);
|
|
784
|
+
if (readOption(options.name))
|
|
785
|
+
body.name = readOption(options.name);
|
|
786
|
+
if (readOption(options.description) !== undefined)
|
|
787
|
+
body.description = readOption(options.description);
|
|
788
|
+
if (readOption(options.kind))
|
|
789
|
+
body.kind = readOption(options.kind);
|
|
790
|
+
if (readOption(options.body))
|
|
791
|
+
body.body = readOption(options.body);
|
|
792
|
+
else if (readOption(options.bodyFile)) {
|
|
793
|
+
const path = readOption(options.bodyFile);
|
|
794
|
+
const fs = await import("node:fs/promises");
|
|
795
|
+
body.body = await fs.readFile(path, "utf8");
|
|
796
|
+
}
|
|
797
|
+
return requestOxygen("/api/cli/templates/upsert", { method: "POST", body });
|
|
798
|
+
});
|
|
799
|
+
}))
|
|
800
|
+
.addCommand(new Command("archive")
|
|
801
|
+
.description("Archive a prompt template. Seeded templates cannot be archived.")
|
|
802
|
+
.argument("<id>", "Template UUID.")
|
|
803
|
+
.option("--json", "Print a JSON envelope.")
|
|
804
|
+
.action(async (id, options) => {
|
|
805
|
+
await handleAsyncAction("templates archive", options, async () => requestOxygen("/api/cli/templates/archive", { method: "POST", body: { id } }));
|
|
806
|
+
}));
|
|
807
|
+
program
|
|
808
|
+
.command("reviews")
|
|
809
|
+
.description("Human-in-the-loop reviews for AI-generated outreach messages.")
|
|
810
|
+
.addCommand(new Command("list")
|
|
811
|
+
.description("List message reviews.")
|
|
812
|
+
.option("--status <status>", "Filter by pending, accepted, rejected, or superseded.")
|
|
813
|
+
.option("--table <table_id>", "Filter by table id.")
|
|
814
|
+
.option("--limit <n>", "Max rows to return (default 50, max 200).")
|
|
815
|
+
.option("--json", "Print a JSON envelope.")
|
|
816
|
+
.action(async (options) => {
|
|
817
|
+
await handleAsyncAction("reviews list", options, async () => {
|
|
818
|
+
const params = new URLSearchParams();
|
|
819
|
+
if (readOption(options.status))
|
|
820
|
+
params.set("status", readOption(options.status));
|
|
821
|
+
if (readOption(options.table))
|
|
822
|
+
params.set("table_id", readOption(options.table));
|
|
823
|
+
if (readOption(options.limit))
|
|
824
|
+
params.set("limit", readOption(options.limit));
|
|
825
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
826
|
+
return requestOxygen(`/api/cli/message-reviews${qs}`);
|
|
827
|
+
});
|
|
828
|
+
}))
|
|
829
|
+
.addCommand(new Command("next")
|
|
830
|
+
.description("Read the oldest pending review.")
|
|
831
|
+
.option("--table <table_id>", "Filter by table id.")
|
|
832
|
+
.option("--json", "Print a JSON envelope.")
|
|
833
|
+
.action(async (options) => {
|
|
834
|
+
await handleAsyncAction("reviews next", options, async () => {
|
|
835
|
+
const params = new URLSearchParams();
|
|
836
|
+
if (readOption(options.table))
|
|
837
|
+
params.set("table_id", readOption(options.table));
|
|
838
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
839
|
+
return requestOxygen(`/api/cli/message-reviews/next${qs}`);
|
|
840
|
+
});
|
|
841
|
+
}))
|
|
842
|
+
.addCommand(new Command("accept")
|
|
843
|
+
.description("Accept a pending message review.")
|
|
844
|
+
.argument("<review_id>", "Message review UUID.")
|
|
845
|
+
.option("--json", "Print a JSON envelope.")
|
|
846
|
+
.action(async (reviewId, options) => {
|
|
847
|
+
await handleAsyncAction("reviews accept", options, async () => requestOxygen("/api/cli/message-reviews/decide", {
|
|
848
|
+
method: "POST",
|
|
849
|
+
body: { id: reviewId, decision: "accept" },
|
|
850
|
+
}));
|
|
851
|
+
}))
|
|
852
|
+
.addCommand(new Command("reject")
|
|
853
|
+
.description("Reject a pending message review with optional highlights and auto-rerun.")
|
|
854
|
+
.argument("<review_id>", "Message review UUID.")
|
|
855
|
+
.option("--highlights-json <json>", "JSON array of {start,end,comment} highlight objects.")
|
|
856
|
+
.option("--auto-rerun", "Trigger a single-row rerun of the column after rejecting.")
|
|
857
|
+
.option("--json", "Print a JSON envelope.")
|
|
858
|
+
.action(async (reviewId, options) => {
|
|
859
|
+
await handleAsyncAction("reviews reject", options, async () => {
|
|
860
|
+
const body = { id: reviewId, decision: "reject" };
|
|
861
|
+
if (readOption(options.highlightsJson)) {
|
|
862
|
+
body.highlights = JSON.parse(readOption(options.highlightsJson));
|
|
863
|
+
}
|
|
864
|
+
if (options.autoRerun)
|
|
865
|
+
body.auto_rerun = true;
|
|
866
|
+
return requestOxygen("/api/cli/message-reviews/decide", { method: "POST", body });
|
|
867
|
+
});
|
|
868
|
+
}));
|
|
724
869
|
program
|
|
725
870
|
.command("columns")
|
|
726
871
|
.description("Workspace table column commands.")
|
|
@@ -825,6 +970,24 @@ export function createProgram() {
|
|
|
825
970
|
},
|
|
826
971
|
});
|
|
827
972
|
});
|
|
973
|
+
}))
|
|
974
|
+
.addCommand(new Command("rerun")
|
|
975
|
+
.description("Re-run a single AI column cell, optionally threading a prior message review's feedback into the prompt.")
|
|
976
|
+
.requiredOption("--table <table>", "Table id or slug.")
|
|
977
|
+
.requiredOption("--column <column>", "Column id or key.")
|
|
978
|
+
.requiredOption("--row <row_id>", "Row UUID.")
|
|
979
|
+
.option("--from-review-id <review_id>", "Prior message review whose feedback to thread into the regeneration prompt.")
|
|
980
|
+
.option("--json", "Print a JSON envelope.")
|
|
981
|
+
.action(async (options) => {
|
|
982
|
+
await handleAsyncAction("columns rerun", options, async () => requestOxygen("/api/cli/columns/rerun", {
|
|
983
|
+
method: "POST",
|
|
984
|
+
body: {
|
|
985
|
+
table: options.table,
|
|
986
|
+
column: options.column,
|
|
987
|
+
row_id: options.row,
|
|
988
|
+
...(readOption(options.fromReviewId) ? { from_review_id: readOption(options.fromReviewId) } : {}),
|
|
989
|
+
},
|
|
990
|
+
}));
|
|
828
991
|
}))
|
|
829
992
|
.addCommand(new Command("materialize")
|
|
830
993
|
.description("Materialize useful fields from a JSONB result column into target columns.")
|
|
@@ -1211,6 +1374,48 @@ export function createProgram() {
|
|
|
1211
1374
|
},
|
|
1212
1375
|
}));
|
|
1213
1376
|
}));
|
|
1377
|
+
program
|
|
1378
|
+
.command("companies")
|
|
1379
|
+
.description("Company prospecting and account enrichment workflows.")
|
|
1380
|
+
.addCommand(new Command("enrich")
|
|
1381
|
+
.description("Preview or run a company enrichment waterfall over an existing table.")
|
|
1382
|
+
.addCommand(new Command("preview")
|
|
1383
|
+
.description("Inspect missing company fields, provider routing, and credit estimates without provider calls.")
|
|
1384
|
+
.argument("<table>", "Table id or slug.")
|
|
1385
|
+
.option("--missing-fields <fields>", "Comma-separated fields to fill: domain,linkedin_url,headcount,industry,funding,technologies,hiring_signals,company_profile.")
|
|
1386
|
+
.option("--providers <providers>", "Comma-separated provider order pool. Defaults to blitzapi,crustdata,ai_ark,prospeo,leadmagic.")
|
|
1387
|
+
.option("--all", "Preview all rows.")
|
|
1388
|
+
.option("--limit <n>", "Preview a limited row scope.")
|
|
1389
|
+
.option("--row-ids <ids>", "Comma-separated row ids.")
|
|
1390
|
+
.option("--filter-json <json>", "Filter object or array for row selection.")
|
|
1391
|
+
.option("--selection-json <json>", "Raw table action selection JSON.")
|
|
1392
|
+
.option("--json", "Print a JSON envelope.")
|
|
1393
|
+
.action(async (table, options) => {
|
|
1394
|
+
await handleAsyncAction("companies enrich preview", options, async () => requestOxygen("/api/cli/company-enrichment/preview", {
|
|
1395
|
+
method: "POST",
|
|
1396
|
+
body: readCompaniesEnrichBody(table, options),
|
|
1397
|
+
}));
|
|
1398
|
+
}))
|
|
1399
|
+
.addCommand(new Command("run")
|
|
1400
|
+
.description("Queue a live company enrichment waterfall, or return a dry-run plan.")
|
|
1401
|
+
.argument("<table>", "Table id or slug.")
|
|
1402
|
+
.option("--missing-fields <fields>", "Comma-separated fields to fill.")
|
|
1403
|
+
.option("--providers <providers>", "Comma-separated provider pool.")
|
|
1404
|
+
.option("--mode <mode>", "dry_run or live. Defaults to live.")
|
|
1405
|
+
.option("--max-credits <n>", "Required credit ceiling for live runs.")
|
|
1406
|
+
.option("--all", "Run on all rows.")
|
|
1407
|
+
.option("--limit <n>", "Run on a limited row scope.")
|
|
1408
|
+
.option("--row-ids <ids>", "Comma-separated row ids.")
|
|
1409
|
+
.option("--filter-json <json>", "Filter object or array for row selection.")
|
|
1410
|
+
.option("--selection-json <json>", "Raw table action selection JSON.")
|
|
1411
|
+
.option("--force", "Re-run the waterfall audit column even when it already has a value.")
|
|
1412
|
+
.option("--json", "Print a JSON envelope.")
|
|
1413
|
+
.action(async (table, options) => {
|
|
1414
|
+
await handleAsyncAction("companies enrich run", options, async () => requestOxygen("/api/cli/company-enrichment/run", {
|
|
1415
|
+
method: "POST",
|
|
1416
|
+
body: readCompaniesEnrichBody(table, options),
|
|
1417
|
+
}));
|
|
1418
|
+
})));
|
|
1214
1419
|
program
|
|
1215
1420
|
.command("worker")
|
|
1216
1421
|
.description("Background worker commands.")
|
|
@@ -1644,8 +1849,9 @@ export function createProgram() {
|
|
|
1644
1849
|
.description("Configure provider events that can trigger workflows.")
|
|
1645
1850
|
.addCommand(new Command("list")
|
|
1646
1851
|
.description("List supported provider events and this org's enabled subscriptions.")
|
|
1647
|
-
.option("--source <source>", "Filter by event source, such as hubspot.")
|
|
1852
|
+
.option("--source <source>", "Filter by event source, such as hubspot or composio.gmail.")
|
|
1648
1853
|
.option("--event <event>", "Filter by event type, such as contact.created.")
|
|
1854
|
+
.option("--toolkit <id>", "Filter by toolkit / integration id, such as gmail.")
|
|
1649
1855
|
.option("--json", "Print a JSON envelope.")
|
|
1650
1856
|
.action(async (options) => {
|
|
1651
1857
|
await handleAsyncAction("integrations events list", options, async () => {
|
|
@@ -1654,25 +1860,47 @@ export function createProgram() {
|
|
|
1654
1860
|
query.set("source", readOption(options.source) ?? "");
|
|
1655
1861
|
if (readOption(options.event))
|
|
1656
1862
|
query.set("event", readOption(options.event) ?? "");
|
|
1863
|
+
if (readOption(options.toolkit))
|
|
1864
|
+
query.set("toolkit", readOption(options.toolkit) ?? "");
|
|
1657
1865
|
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
1658
1866
|
return requestOxygen(`/api/cli/integrations/events${suffix}`);
|
|
1659
1867
|
});
|
|
1660
1868
|
}))
|
|
1661
1869
|
.addCommand(new Command("enable")
|
|
1662
1870
|
.description("Enable a provider event for a connected integration account.")
|
|
1663
|
-
.requiredOption("--source <source>", "Event source, such as hubspot.")
|
|
1871
|
+
.requiredOption("--source <source>", "Event source, such as hubspot or composio.gmail.")
|
|
1664
1872
|
.requiredOption("--event <event>", "Event type, such as contact.created.")
|
|
1665
1873
|
.option("--connection-id <connection_id>", "Specific integration connection id. Defaults to the active default connection.")
|
|
1874
|
+
.option("--trigger-config <json>", "JSON object passed to the provider when registering the trigger (Composio triggers only).")
|
|
1666
1875
|
.option("--json", "Print a JSON envelope.")
|
|
1667
1876
|
.action(async (options) => {
|
|
1668
|
-
await handleAsyncAction("integrations events enable", options, async () =>
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1877
|
+
await handleAsyncAction("integrations events enable", options, async () => {
|
|
1878
|
+
const triggerConfigRaw = readOption(options.triggerConfig);
|
|
1879
|
+
let triggerConfig;
|
|
1880
|
+
if (triggerConfigRaw) {
|
|
1881
|
+
try {
|
|
1882
|
+
const parsed = JSON.parse(triggerConfigRaw);
|
|
1883
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1884
|
+
throw new Error("--trigger-config must be a JSON object.");
|
|
1885
|
+
}
|
|
1886
|
+
triggerConfig = parsed;
|
|
1887
|
+
}
|
|
1888
|
+
catch (error) {
|
|
1889
|
+
throw new Error(error instanceof Error
|
|
1890
|
+
? `Invalid --trigger-config: ${error.message}`
|
|
1891
|
+
: "Invalid --trigger-config");
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return requestOxygen("/api/cli/integrations/events/enable", {
|
|
1895
|
+
method: "POST",
|
|
1896
|
+
body: {
|
|
1897
|
+
source: readOption(options.source),
|
|
1898
|
+
event: readOption(options.event),
|
|
1899
|
+
...(readOption(options.connectionId) ? { connection_id: readOption(options.connectionId) } : {}),
|
|
1900
|
+
...(triggerConfig ? { trigger_config: triggerConfig } : {}),
|
|
1901
|
+
},
|
|
1902
|
+
});
|
|
1903
|
+
});
|
|
1676
1904
|
}))
|
|
1677
1905
|
.addCommand(new Command("disable")
|
|
1678
1906
|
.description("Disable a provider event for a connected integration account.")
|
|
@@ -2183,6 +2411,7 @@ function referencesRecipeSdk(source) {
|
|
|
2183
2411
|
// Escape Next static analysis (the CLI is bundled by tsc, but mirror the
|
|
2184
2412
|
// worker's escape so both load identically).
|
|
2185
2413
|
const dynamicRecipeImport = new Function("specifier", "return import(specifier);");
|
|
2414
|
+
// skipcq: JS-R1003
|
|
2186
2415
|
async function importRecipeModule(specifier) {
|
|
2187
2416
|
try {
|
|
2188
2417
|
return await dynamicRecipeImport(specifier);
|
|
@@ -2384,6 +2613,55 @@ function readTableRunSelection(options) {
|
|
|
2384
2613
|
exitCode: 1,
|
|
2385
2614
|
});
|
|
2386
2615
|
}
|
|
2616
|
+
function readCompaniesEnrichBody(table, options) {
|
|
2617
|
+
const body = { table };
|
|
2618
|
+
const fields = readCsvOption(options.missingFields);
|
|
2619
|
+
const providers = readCsvOption(options.providers);
|
|
2620
|
+
const selection = readCompaniesEnrichSelection(options);
|
|
2621
|
+
const mode = readOption(options.mode);
|
|
2622
|
+
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
2623
|
+
if (fields.length > 0)
|
|
2624
|
+
body.missing_fields = fields;
|
|
2625
|
+
if (providers.length > 0)
|
|
2626
|
+
body.providers = providers;
|
|
2627
|
+
if (selection)
|
|
2628
|
+
body.selection = selection;
|
|
2629
|
+
if (mode)
|
|
2630
|
+
body.mode = mode;
|
|
2631
|
+
if (maxCredits !== undefined)
|
|
2632
|
+
body.max_credits = maxCredits;
|
|
2633
|
+
if (options.force !== undefined)
|
|
2634
|
+
body.force = Boolean(options.force);
|
|
2635
|
+
return body;
|
|
2636
|
+
}
|
|
2637
|
+
function readCompaniesEnrichSelection(options) {
|
|
2638
|
+
const explicitSelection = readSelectionJsonOption(options.selectionJson);
|
|
2639
|
+
const hasAll = Boolean(options.all);
|
|
2640
|
+
const limit = readPositiveInt(options.limit);
|
|
2641
|
+
const rowIds = readCsvOption(options.rowIds);
|
|
2642
|
+
const filterSelection = readFilterSelectionOption(options.filterJson);
|
|
2643
|
+
const selectedModes = [
|
|
2644
|
+
Boolean(explicitSelection),
|
|
2645
|
+
hasAll,
|
|
2646
|
+
Boolean(limit),
|
|
2647
|
+
rowIds.length > 0,
|
|
2648
|
+
Boolean(filterSelection),
|
|
2649
|
+
].filter(Boolean).length;
|
|
2650
|
+
if (selectedModes > 1) {
|
|
2651
|
+
throw new OxygenError("invalid_company_enrichment", "Pass only one row scope option.", { exitCode: 1 });
|
|
2652
|
+
}
|
|
2653
|
+
if (explicitSelection)
|
|
2654
|
+
return explicitSelection;
|
|
2655
|
+
if (hasAll)
|
|
2656
|
+
return { mode: "all" };
|
|
2657
|
+
if (limit)
|
|
2658
|
+
return { mode: "limit", limit };
|
|
2659
|
+
if (rowIds.length > 0)
|
|
2660
|
+
return { mode: "row_ids", row_ids: rowIds };
|
|
2661
|
+
if (filterSelection)
|
|
2662
|
+
return filterSelection;
|
|
2663
|
+
return undefined;
|
|
2664
|
+
}
|
|
2387
2665
|
function readFilterSelectionOption(value) {
|
|
2388
2666
|
const filters = readFilterJsonOption(value);
|
|
2389
2667
|
return filters ? { mode: "filter", filters } : undefined;
|
|
@@ -2599,11 +2877,13 @@ async function prepareImportTarget(table, options, parsedRows) {
|
|
|
2599
2877
|
exitCode: 1,
|
|
2600
2878
|
});
|
|
2601
2879
|
}
|
|
2880
|
+
const createdWebUrl = readRecordString(created, "web_url")
|
|
2881
|
+
?? readRecordString(created, "deepLink");
|
|
2602
2882
|
return {
|
|
2603
2883
|
tableRef: createdSlug,
|
|
2604
2884
|
rows: normalized.rows,
|
|
2605
2885
|
createdTable: created,
|
|
2606
|
-
tableWebUrl: tableWebUrl(createdSlug),
|
|
2886
|
+
tableWebUrl: createdWebUrl ?? tableWebUrl(createdSlug),
|
|
2607
2887
|
upsertKey: normalizeCreatedTableUpsertKey(options.upsertKey, normalized.keyBySource),
|
|
2608
2888
|
};
|
|
2609
2889
|
}
|
|
@@ -2786,16 +3066,17 @@ async function exportRows(table, options) {
|
|
|
2786
3066
|
...(limit ? { limit } : {}),
|
|
2787
3067
|
},
|
|
2788
3068
|
});
|
|
2789
|
-
const
|
|
3069
|
+
const formatted = formatRows(result.rows, format, result.columns);
|
|
2790
3070
|
if (options.output) {
|
|
2791
|
-
writeFileSync(options.output, content);
|
|
3071
|
+
writeFileSync(options.output, formatted.content);
|
|
2792
3072
|
}
|
|
2793
3073
|
return {
|
|
2794
3074
|
table: result.table ?? null,
|
|
2795
3075
|
format,
|
|
2796
3076
|
rowCount: result.rows.length,
|
|
2797
3077
|
output: options.output ?? null,
|
|
2798
|
-
...(options.output ? {} : { content }),
|
|
3078
|
+
...(options.output ? {} : { content: formatted.content }),
|
|
3079
|
+
...(formatted.rescuedCount > 0 ? { rescuedNumericCells: formatted.rescuedCount } : {}),
|
|
2799
3080
|
};
|
|
2800
3081
|
}
|
|
2801
3082
|
async function readRowsFile(path, format, sheet) {
|
|
@@ -2808,18 +3089,26 @@ function normalizeCreatedTableUpsertKey(value, keyBySource) {
|
|
|
2808
3089
|
}
|
|
2809
3090
|
function normalizeExportRowsFormat(value) {
|
|
2810
3091
|
const normalized = value?.trim().toLowerCase() || "json";
|
|
2811
|
-
if (normalized === "json"
|
|
3092
|
+
if (normalized === "json"
|
|
3093
|
+
|| normalized === "jsonl"
|
|
3094
|
+
|| normalized === "csv"
|
|
3095
|
+
|| normalized === "table")
|
|
2812
3096
|
return normalized;
|
|
2813
|
-
throw new OxygenError("invalid_format", "Export format must be json, jsonl, or
|
|
3097
|
+
throw new OxygenError("invalid_format", "Export format must be json, jsonl, csv, or table.", {
|
|
2814
3098
|
details: { format: value },
|
|
2815
3099
|
exitCode: 1,
|
|
2816
3100
|
});
|
|
2817
3101
|
}
|
|
2818
3102
|
function formatRows(rows, format, columns) {
|
|
2819
|
-
if (format === "json")
|
|
2820
|
-
return `${JSON.stringify(rows, null, 2)}\n
|
|
2821
|
-
|
|
2822
|
-
|
|
3103
|
+
if (format === "json") {
|
|
3104
|
+
return { content: `${JSON.stringify(rows, null, 2)}\n`, rescuedCount: 0 };
|
|
3105
|
+
}
|
|
3106
|
+
if (format === "jsonl") {
|
|
3107
|
+
return {
|
|
3108
|
+
content: `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`,
|
|
3109
|
+
rescuedCount: 0,
|
|
3110
|
+
};
|
|
3111
|
+
} // skipcq: JS-0246
|
|
2823
3112
|
const keys = [
|
|
2824
3113
|
"_row_id",
|
|
2825
3114
|
"_created_at",
|
|
@@ -2827,10 +3116,57 @@ function formatRows(rows, format, columns) {
|
|
|
2827
3116
|
...(columns?.map((column) => column.key) ?? []),
|
|
2828
3117
|
...rows.flatMap((row) => Object.keys(row)),
|
|
2829
3118
|
].filter((key, index, all) => all.indexOf(key) === index && rows.some((row) => key in row));
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
3119
|
+
if (format === "csv") {
|
|
3120
|
+
return {
|
|
3121
|
+
content: [
|
|
3122
|
+
keys.map(escapeCsvField).join(","),
|
|
3123
|
+
...rows.map((row) => keys.map((key) => escapeCsvField(row[key])).join(",")),
|
|
3124
|
+
].join("\n") + "\n",
|
|
3125
|
+
rescuedCount: 0,
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
// "table": render with type-aware formatting and a Markdown-style frame.
|
|
3129
|
+
const columnByKey = new Map(columns?.map((c) => [c.key, c]) ?? []);
|
|
3130
|
+
let rescuedCount = 0;
|
|
3131
|
+
const headers = keys.map((key) => columnByKey.get(key)?.label ?? key);
|
|
3132
|
+
const formattedRows = rows.map((row) => keys.map((key) => {
|
|
3133
|
+
const column = columnByKey.get(key) ?? null;
|
|
3134
|
+
const formatted = formatCellForDisplay(row[key], column, {
|
|
3135
|
+
surface: "cli",
|
|
3136
|
+
onRescued: () => {
|
|
3137
|
+
rescuedCount += 1;
|
|
3138
|
+
},
|
|
3139
|
+
});
|
|
3140
|
+
// Pipe and any line-break char are the row/cell delimiters of the
|
|
3141
|
+
// Markdown frame — raw values containing them would corrupt the layout.
|
|
3142
|
+
// Match on the line-break class (not just `\r?\n`) so a standalone `\r`
|
|
3143
|
+
// doesn't slip through and split the row visually.
|
|
3144
|
+
return formatted.replace(/[\r\n]+/g, " ↵ ").replace(/\|/g, "\\|");
|
|
3145
|
+
}));
|
|
3146
|
+
const widths = headers.map((header, columnIndex) => {
|
|
3147
|
+
let max = header.length;
|
|
3148
|
+
for (const row of formattedRows) {
|
|
3149
|
+
const cell = row[columnIndex] ?? "";
|
|
3150
|
+
if (cell.length > max)
|
|
3151
|
+
max = cell.length;
|
|
3152
|
+
}
|
|
3153
|
+
return Math.min(max, 60);
|
|
3154
|
+
});
|
|
3155
|
+
const renderRow = (cells) => "| " + cells.map((cell, i) => clipCell(cell, widths[i]).padEnd(widths[i])).join(" | ") + " |";
|
|
3156
|
+
const separator = "|" + widths.map((w) => "-".repeat(w + 2)).join("|") + "|";
|
|
3157
|
+
const lines = [renderRow(headers), separator, ...formattedRows.map(renderRow)];
|
|
3158
|
+
if (rescuedCount > 0) {
|
|
3159
|
+
lines.push("");
|
|
3160
|
+
lines.push(`# Note: reformatted ${rescuedCount} cell${rescuedCount === 1 ? "" : "s"} that look numeric in text columns. Run \`oxygen columns retype --to numeric\` to make this permanent.`);
|
|
3161
|
+
}
|
|
3162
|
+
return { content: lines.join("\n") + "\n", rescuedCount };
|
|
3163
|
+
}
|
|
3164
|
+
function clipCell(value, width) {
|
|
3165
|
+
if (value.length <= width)
|
|
3166
|
+
return value;
|
|
3167
|
+
if (width <= 1)
|
|
3168
|
+
return value.slice(0, width);
|
|
3169
|
+
return value.slice(0, width - 1) + "…";
|
|
2834
3170
|
}
|
|
2835
3171
|
function escapeCsvField(value) {
|
|
2836
3172
|
const text = value === null || value === undefined
|
|
@@ -3030,64 +3366,6 @@ async function handleUpdateAction(options) {
|
|
|
3030
3366
|
process.exitCode = error instanceof OxygenError ? error.exitCode : 1;
|
|
3031
3367
|
}
|
|
3032
3368
|
}
|
|
3033
|
-
function detectCliInstallPrefix() {
|
|
3034
|
-
try {
|
|
3035
|
-
const path = fileURLToPath(import.meta.url);
|
|
3036
|
-
const suffixes = [
|
|
3037
|
-
"/lib/node_modules/@oxygen-agent/cli/dist/index.js",
|
|
3038
|
-
"/lib/node_modules/@oxygen/cli/dist/index.js",
|
|
3039
|
-
];
|
|
3040
|
-
for (const suffix of suffixes) {
|
|
3041
|
-
if (path.endsWith(suffix)) {
|
|
3042
|
-
return path.slice(0, -suffix.length);
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
catch {
|
|
3047
|
-
// Non-file URL (e.g. running from a bundler) — fall through.
|
|
3048
|
-
}
|
|
3049
|
-
return null;
|
|
3050
|
-
}
|
|
3051
|
-
function updateCli(options) {
|
|
3052
|
-
const packageSpec = readOption(options.package) ?? DEFAULT_CLI_PACKAGE_SPEC;
|
|
3053
|
-
const prefix = detectCliInstallPrefix();
|
|
3054
|
-
const args = prefix
|
|
3055
|
-
? ["install", "-g", "--prefix", prefix, packageSpec]
|
|
3056
|
-
: ["install", "-g", packageSpec];
|
|
3057
|
-
const command = ["npm", ...args].join(" ");
|
|
3058
|
-
if (options.dryRun) {
|
|
3059
|
-
return {
|
|
3060
|
-
current_version: OXYGEN_VERSION,
|
|
3061
|
-
package: packageSpec,
|
|
3062
|
-
command,
|
|
3063
|
-
dry_run: true,
|
|
3064
|
-
updated: false,
|
|
3065
|
-
};
|
|
3066
|
-
}
|
|
3067
|
-
const result = spawnSync("npm", args, {
|
|
3068
|
-
encoding: "utf8",
|
|
3069
|
-
stdio: options.json ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
3070
|
-
});
|
|
3071
|
-
if (result.error || result.status !== 0) {
|
|
3072
|
-
throw new OxygenError("cli_update_failed", "Unable to update the Oxygen CLI.", {
|
|
3073
|
-
details: {
|
|
3074
|
-
command,
|
|
3075
|
-
package: packageSpec,
|
|
3076
|
-
exit_code: result.status,
|
|
3077
|
-
reason: result.error instanceof Error ? result.error.message : null,
|
|
3078
|
-
stderr: typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim().slice(0, 4000) : null,
|
|
3079
|
-
},
|
|
3080
|
-
exitCode: 1,
|
|
3081
|
-
});
|
|
3082
|
-
}
|
|
3083
|
-
return {
|
|
3084
|
-
current_version: OXYGEN_VERSION,
|
|
3085
|
-
package: packageSpec,
|
|
3086
|
-
command,
|
|
3087
|
-
dry_run: false,
|
|
3088
|
-
updated: true,
|
|
3089
|
-
};
|
|
3090
|
-
}
|
|
3091
3369
|
function buildApiKeyCreateBody(options) {
|
|
3092
3370
|
const body = {};
|
|
3093
3371
|
const name = readOption(options.name);
|
|
@@ -3325,6 +3603,7 @@ function formatLoginSuccess(identity, credentials, profile) {
|
|
|
3325
3603
|
.update(`oxygen-cli:${credentials.token}`)
|
|
3326
3604
|
.digest("hex");
|
|
3327
3605
|
const c = ansi(output.isTTY === true && !process.env.NO_COLOR);
|
|
3606
|
+
// skipcq: JS-0820 — not a React component; rule misfire on array of tuples
|
|
3328
3607
|
const rows = [
|
|
3329
3608
|
["Account", email],
|
|
3330
3609
|
["Organization", org],
|
|
@@ -3401,7 +3680,7 @@ function formatProfileUseSuccess(profile) {
|
|
|
3401
3680
|
` ${c.dim("Fingerprint")} ${profile.token_fingerprint}`,
|
|
3402
3681
|
"",
|
|
3403
3682
|
].join("\n");
|
|
3404
|
-
}
|
|
3683
|
+
} // skipcq: JS-C1002
|
|
3405
3684
|
function formatLogoutSuccess(result) {
|
|
3406
3685
|
const c = ansi(output.isTTY === true && !process.env.NO_COLOR);
|
|
3407
3686
|
const removed = result.removedProfile
|
|
@@ -3412,7 +3691,7 @@ function formatLogoutSuccess(result) {
|
|
|
3412
3691
|
`${c.green("[OK]")} ${c.bold("CLI logged out")}`,
|
|
3413
3692
|
"",
|
|
3414
3693
|
` ${c.dim("Credentials")} ${removed}`,
|
|
3415
|
-
` ${c.dim("Profiles left")} ${String(result.remainingProfiles)}`,
|
|
3694
|
+
` ${c.dim("Profiles left")} ${String(result.remainingProfiles)}`, // skipcq: JS-C1002
|
|
3416
3695
|
"",
|
|
3417
3696
|
].join("\n");
|
|
3418
3697
|
}
|
|
@@ -3427,7 +3706,7 @@ function formatUpdateSuccess(result) {
|
|
|
3427
3706
|
` ${c.dim("Command")} ${result.command}`,
|
|
3428
3707
|
"",
|
|
3429
3708
|
].join("\n");
|
|
3430
|
-
}
|
|
3709
|
+
} // skipcq: JS-C1002
|
|
3431
3710
|
function renderBox(lines) {
|
|
3432
3711
|
const width = Math.max(...lines.map(visibleLength), 0);
|
|
3433
3712
|
const border = `+${"-".repeat(width + 2)}+`;
|
|
@@ -3435,6 +3714,7 @@ function renderBox(lines) {
|
|
|
3435
3714
|
return [border, ...body, border].join("\n");
|
|
3436
3715
|
}
|
|
3437
3716
|
function visibleLength(value) {
|
|
3717
|
+
// skipcq: JS-0004 — ESC (\x1b) is the ANSI CSI introducer; required to strip color codes
|
|
3438
3718
|
return value.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
3439
3719
|
}
|
|
3440
3720
|
function ansi(enabled) {
|
|
@@ -3442,7 +3722,7 @@ function ansi(enabled) {
|
|
|
3442
3722
|
? (text) => `\x1b[${open}m${text}\x1b[${close}m`
|
|
3443
3723
|
: (text) => text;
|
|
3444
3724
|
return {
|
|
3445
|
-
bold: wrap(1, 22),
|
|
3725
|
+
bold: wrap(1, 22), // skipcq: JS-0117 // skipcq: JS-W1035
|
|
3446
3726
|
dim: wrap(2, 22),
|
|
3447
3727
|
green: wrap(32, 39),
|
|
3448
3728
|
};
|
|
@@ -387,7 +387,7 @@ async function readLocalHttpResponseBody(response) {
|
|
|
387
387
|
if (!text)
|
|
388
388
|
return null;
|
|
389
389
|
const contentType = response.headers.get("content-type") ?? "";
|
|
390
|
-
const looksJson = contentType.toLowerCase().includes("json") || /^[\s\n\r]*[\[{]/.test(text);
|
|
390
|
+
const looksJson = contentType.toLowerCase().includes("json") || /^[\s\n\r]*[\[{]/.test(text); // skipcq: JS-0097
|
|
391
391
|
if (!looksJson)
|
|
392
392
|
return text;
|
|
393
393
|
try {
|