@oxygen-agent/cli 1.45.4 → 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.
Files changed (74) hide show
  1. package/README.md +13 -1
  2. package/dist/browser-login.d.ts +0 -1
  3. package/dist/browser-login.js +0 -1
  4. package/dist/credentials.d.ts +0 -1
  5. package/dist/credentials.js +114 -44
  6. package/dist/http-client.d.ts +1 -2
  7. package/dist/http-client.js +0 -1
  8. package/dist/index.d.ts +0 -1
  9. package/dist/index.js +436 -87
  10. package/dist/local-custom-http-column.d.ts +0 -1
  11. package/dist/local-custom-http-column.js +1 -2
  12. package/dist/session.d.ts +0 -1
  13. package/dist/session.js +0 -1
  14. package/dist/update.d.ts +31 -0
  15. package/dist/update.js +116 -0
  16. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +0 -1
  17. package/node_modules/@oxygen/recipe-sdk/dist/index.js +0 -1
  18. package/node_modules/@oxygen/shared/dist/billing.d.ts +2 -2
  19. package/node_modules/@oxygen/shared/dist/billing.js +2 -2
  20. package/node_modules/@oxygen/shared/dist/cell-format.d.ts +60 -0
  21. package/node_modules/@oxygen/shared/dist/cell-format.js +278 -0
  22. package/node_modules/@oxygen/shared/dist/column-types.d.ts +2 -2
  23. package/node_modules/@oxygen/shared/dist/column-types.js +3 -3
  24. package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +0 -1
  25. package/node_modules/@oxygen/shared/dist/credit-guidance.js +0 -1
  26. package/node_modules/@oxygen/shared/dist/file-import.d.ts +0 -1
  27. package/node_modules/@oxygen/shared/dist/file-import.js +1 -2
  28. package/node_modules/@oxygen/shared/dist/index.d.ts +1 -1
  29. package/node_modules/@oxygen/shared/dist/index.js +1 -1
  30. package/node_modules/@oxygen/shared/dist/log.d.ts +0 -1
  31. package/node_modules/@oxygen/shared/dist/log.js +14 -6
  32. package/node_modules/@oxygen/shared/dist/redaction.d.ts +0 -1
  33. package/node_modules/@oxygen/shared/dist/redaction.js +0 -1
  34. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +0 -1
  35. package/node_modules/@oxygen/shared/dist/telemetry.js +0 -1
  36. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -2
  37. package/node_modules/@oxygen/shared/dist/version.js +1 -2
  38. package/node_modules/@oxygen/workflows/dist/index.d.ts +145 -144
  39. package/node_modules/@oxygen/workflows/dist/index.js +30 -27
  40. package/package.json +6 -1
  41. package/dist/browser-login.d.ts.map +0 -1
  42. package/dist/browser-login.js.map +0 -1
  43. package/dist/credentials.d.ts.map +0 -1
  44. package/dist/credentials.js.map +0 -1
  45. package/dist/http-client.d.ts.map +0 -1
  46. package/dist/http-client.js.map +0 -1
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js.map +0 -1
  49. package/dist/local-custom-http-column.d.ts.map +0 -1
  50. package/dist/local-custom-http-column.js.map +0 -1
  51. package/dist/session.d.ts.map +0 -1
  52. package/dist/session.js.map +0 -1
  53. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts.map +0 -1
  54. package/node_modules/@oxygen/recipe-sdk/dist/index.js.map +0 -1
  55. package/node_modules/@oxygen/shared/dist/billing.d.ts.map +0 -1
  56. package/node_modules/@oxygen/shared/dist/billing.js.map +0 -1
  57. package/node_modules/@oxygen/shared/dist/column-types.d.ts.map +0 -1
  58. package/node_modules/@oxygen/shared/dist/column-types.js.map +0 -1
  59. package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts.map +0 -1
  60. package/node_modules/@oxygen/shared/dist/credit-guidance.js.map +0 -1
  61. package/node_modules/@oxygen/shared/dist/file-import.d.ts.map +0 -1
  62. package/node_modules/@oxygen/shared/dist/file-import.js.map +0 -1
  63. package/node_modules/@oxygen/shared/dist/index.d.ts.map +0 -1
  64. package/node_modules/@oxygen/shared/dist/index.js.map +0 -1
  65. package/node_modules/@oxygen/shared/dist/log.d.ts.map +0 -1
  66. package/node_modules/@oxygen/shared/dist/log.js.map +0 -1
  67. package/node_modules/@oxygen/shared/dist/redaction.d.ts.map +0 -1
  68. package/node_modules/@oxygen/shared/dist/redaction.js.map +0 -1
  69. package/node_modules/@oxygen/shared/dist/telemetry.d.ts.map +0 -1
  70. package/node_modules/@oxygen/shared/dist/telemetry.js.map +0 -1
  71. package/node_modules/@oxygen/shared/dist/version.d.ts.map +0 -1
  72. package/node_modules/@oxygen/shared/dist/version.js.map +0 -1
  73. package/node_modules/@oxygen/workflows/dist/index.d.ts.map +0 -1
  74. package/node_modules/@oxygen/workflows/dist/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { execFileSync, spawnSync } from "node:child_process";
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"),
@@ -227,6 +228,36 @@ export function createProgram() {
227
228
  .action(async (options) => {
228
229
  await handleUpdateAction(options);
229
230
  });
231
+ program
232
+ .command("api-keys")
233
+ .description("Manage Oxygen CLI API keys for the current organization.")
234
+ .addCommand(new Command("list")
235
+ .description("List active CLI API keys for the current user and organization.")
236
+ .option("--json", "Print a JSON envelope.")
237
+ .action(async (options) => {
238
+ await handleAsyncAction("api-keys list", options, async () => requestOxygen("/api/cli/api-keys"));
239
+ }))
240
+ .addCommand(new Command("create")
241
+ .description("Create a CLI API key. The token is shown once.")
242
+ .option("--name <name>", "Key name. Defaults to 'CLI key'.")
243
+ .option("--expires-at <iso>", "Expiration timestamp as ISO 8601.")
244
+ .option("--expires-in-days <days>", "Expire the key after this many days.")
245
+ .option("--json", "Print a JSON envelope.")
246
+ .action(async (options) => {
247
+ await handleAsyncAction("api-keys create", options, async () => requestOxygen("/api/cli/api-keys", {
248
+ method: "POST",
249
+ body: buildApiKeyCreateBody(options),
250
+ }));
251
+ }))
252
+ .addCommand(new Command("revoke")
253
+ .description("Revoke a CLI API key for the current user and organization.")
254
+ .argument("<key-id>", "CLI API key id.")
255
+ .option("--json", "Print a JSON envelope.")
256
+ .action(async (keyId, options) => {
257
+ await handleAsyncAction("api-keys revoke", options, async () => requestOxygen(`/api/cli/api-keys/${encodeURIComponent(keyId)}`, {
258
+ method: "DELETE",
259
+ }));
260
+ }));
230
261
  program
231
262
  .command("whoami")
232
263
  .description("Show the current Oxygen CLI identity.")
@@ -488,9 +519,9 @@ export function createProgram() {
488
519
  await handleAsyncAction("tables import", options, async () => importRows(table, options));
489
520
  }))
490
521
  .addCommand(new Command("export")
491
- .description("Export workspace table rows as JSON, JSONL, or CSV.")
522
+ .description("Export workspace table rows as JSON, JSONL, CSV, or a human-readable table.")
492
523
  .argument("<table>", "Table id or slug.")
493
- .option("--format <format>", "json, jsonl, or csv. Defaults to json.")
524
+ .option("--format <format>", "json, jsonl, csv, or table. Defaults to json. Use table for a typed, human-readable rendering with thousands grouping.")
494
525
  .option("--output <path>", "Write export content to a file.")
495
526
  .option("--limit <n>", "Maximum rows to export. Defaults to 100; hard cap is 1000.")
496
527
  .option("--json", "Print a JSON envelope.")
@@ -587,6 +618,17 @@ export function createProgram() {
587
618
  method: "POST",
588
619
  body: { table },
589
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
+ }));
590
632
  }));
591
633
  tablesCommand.addCommand(new Command("webhook")
592
634
  .description("Create and manage direct table webhooks.")
@@ -691,6 +733,139 @@ export function createProgram() {
691
733
  body: { id: assetId },
692
734
  }));
693
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
+ }));
694
869
  program
695
870
  .command("columns")
696
871
  .description("Workspace table column commands.")
@@ -795,6 +970,24 @@ export function createProgram() {
795
970
  },
796
971
  });
797
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
+ }));
798
991
  }))
799
992
  .addCommand(new Command("materialize")
800
993
  .description("Materialize useful fields from a JSONB result column into target columns.")
@@ -1181,6 +1374,48 @@ export function createProgram() {
1181
1374
  },
1182
1375
  }));
1183
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
+ })));
1184
1419
  program
1185
1420
  .command("worker")
1186
1421
  .description("Background worker commands.")
@@ -1614,8 +1849,9 @@ export function createProgram() {
1614
1849
  .description("Configure provider events that can trigger workflows.")
1615
1850
  .addCommand(new Command("list")
1616
1851
  .description("List supported provider events and this org's enabled subscriptions.")
1617
- .option("--source <source>", "Filter by event source, such as hubspot.")
1852
+ .option("--source <source>", "Filter by event source, such as hubspot or composio.gmail.")
1618
1853
  .option("--event <event>", "Filter by event type, such as contact.created.")
1854
+ .option("--toolkit <id>", "Filter by toolkit / integration id, such as gmail.")
1619
1855
  .option("--json", "Print a JSON envelope.")
1620
1856
  .action(async (options) => {
1621
1857
  await handleAsyncAction("integrations events list", options, async () => {
@@ -1624,25 +1860,47 @@ export function createProgram() {
1624
1860
  query.set("source", readOption(options.source) ?? "");
1625
1861
  if (readOption(options.event))
1626
1862
  query.set("event", readOption(options.event) ?? "");
1863
+ if (readOption(options.toolkit))
1864
+ query.set("toolkit", readOption(options.toolkit) ?? "");
1627
1865
  const suffix = query.toString() ? `?${query.toString()}` : "";
1628
1866
  return requestOxygen(`/api/cli/integrations/events${suffix}`);
1629
1867
  });
1630
1868
  }))
1631
1869
  .addCommand(new Command("enable")
1632
1870
  .description("Enable a provider event for a connected integration account.")
1633
- .requiredOption("--source <source>", "Event source, such as hubspot.")
1871
+ .requiredOption("--source <source>", "Event source, such as hubspot or composio.gmail.")
1634
1872
  .requiredOption("--event <event>", "Event type, such as contact.created.")
1635
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).")
1636
1875
  .option("--json", "Print a JSON envelope.")
1637
1876
  .action(async (options) => {
1638
- await handleAsyncAction("integrations events enable", options, async () => requestOxygen("/api/cli/integrations/events/enable", {
1639
- method: "POST",
1640
- body: {
1641
- source: readOption(options.source),
1642
- event: readOption(options.event),
1643
- ...(readOption(options.connectionId) ? { connection_id: readOption(options.connectionId) } : {}),
1644
- },
1645
- }));
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
+ });
1646
1904
  }))
1647
1905
  .addCommand(new Command("disable")
1648
1906
  .description("Disable a provider event for a connected integration account.")
@@ -2153,6 +2411,7 @@ function referencesRecipeSdk(source) {
2153
2411
  // Escape Next static analysis (the CLI is bundled by tsc, but mirror the
2154
2412
  // worker's escape so both load identically).
2155
2413
  const dynamicRecipeImport = new Function("specifier", "return import(specifier);");
2414
+ // skipcq: JS-R1003
2156
2415
  async function importRecipeModule(specifier) {
2157
2416
  try {
2158
2417
  return await dynamicRecipeImport(specifier);
@@ -2354,6 +2613,55 @@ function readTableRunSelection(options) {
2354
2613
  exitCode: 1,
2355
2614
  });
2356
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
+ }
2357
2665
  function readFilterSelectionOption(value) {
2358
2666
  const filters = readFilterJsonOption(value);
2359
2667
  return filters ? { mode: "filter", filters } : undefined;
@@ -2569,11 +2877,13 @@ async function prepareImportTarget(table, options, parsedRows) {
2569
2877
  exitCode: 1,
2570
2878
  });
2571
2879
  }
2880
+ const createdWebUrl = readRecordString(created, "web_url")
2881
+ ?? readRecordString(created, "deepLink");
2572
2882
  return {
2573
2883
  tableRef: createdSlug,
2574
2884
  rows: normalized.rows,
2575
2885
  createdTable: created,
2576
- tableWebUrl: tableWebUrl(createdSlug),
2886
+ tableWebUrl: createdWebUrl ?? tableWebUrl(createdSlug),
2577
2887
  upsertKey: normalizeCreatedTableUpsertKey(options.upsertKey, normalized.keyBySource),
2578
2888
  };
2579
2889
  }
@@ -2756,16 +3066,17 @@ async function exportRows(table, options) {
2756
3066
  ...(limit ? { limit } : {}),
2757
3067
  },
2758
3068
  });
2759
- const content = formatRows(result.rows, format, result.columns);
3069
+ const formatted = formatRows(result.rows, format, result.columns);
2760
3070
  if (options.output) {
2761
- writeFileSync(options.output, content);
3071
+ writeFileSync(options.output, formatted.content);
2762
3072
  }
2763
3073
  return {
2764
3074
  table: result.table ?? null,
2765
3075
  format,
2766
3076
  rowCount: result.rows.length,
2767
3077
  output: options.output ?? null,
2768
- ...(options.output ? {} : { content }),
3078
+ ...(options.output ? {} : { content: formatted.content }),
3079
+ ...(formatted.rescuedCount > 0 ? { rescuedNumericCells: formatted.rescuedCount } : {}),
2769
3080
  };
2770
3081
  }
2771
3082
  async function readRowsFile(path, format, sheet) {
@@ -2778,18 +3089,26 @@ function normalizeCreatedTableUpsertKey(value, keyBySource) {
2778
3089
  }
2779
3090
  function normalizeExportRowsFormat(value) {
2780
3091
  const normalized = value?.trim().toLowerCase() || "json";
2781
- if (normalized === "json" || normalized === "jsonl" || normalized === "csv")
3092
+ if (normalized === "json"
3093
+ || normalized === "jsonl"
3094
+ || normalized === "csv"
3095
+ || normalized === "table")
2782
3096
  return normalized;
2783
- throw new OxygenError("invalid_format", "Export format must be json, jsonl, or csv.", {
3097
+ throw new OxygenError("invalid_format", "Export format must be json, jsonl, csv, or table.", {
2784
3098
  details: { format: value },
2785
3099
  exitCode: 1,
2786
3100
  });
2787
3101
  }
2788
3102
  function formatRows(rows, format, columns) {
2789
- if (format === "json")
2790
- return `${JSON.stringify(rows, null, 2)}\n`;
2791
- if (format === "jsonl")
2792
- return `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`;
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
2793
3112
  const keys = [
2794
3113
  "_row_id",
2795
3114
  "_created_at",
@@ -2797,10 +3116,57 @@ function formatRows(rows, format, columns) {
2797
3116
  ...(columns?.map((column) => column.key) ?? []),
2798
3117
  ...rows.flatMap((row) => Object.keys(row)),
2799
3118
  ].filter((key, index, all) => all.indexOf(key) === index && rows.some((row) => key in row));
2800
- return [
2801
- keys.map(escapeCsvField).join(","),
2802
- ...rows.map((row) => keys.map((key) => escapeCsvField(row[key])).join(",")),
2803
- ].join("\n") + "\n";
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) + "…";
2804
3170
  }
2805
3171
  function escapeCsvField(value) {
2806
3172
  const text = value === null || value === undefined
@@ -3000,64 +3366,46 @@ async function handleUpdateAction(options) {
3000
3366
  process.exitCode = error instanceof OxygenError ? error.exitCode : 1;
3001
3367
  }
3002
3368
  }
3003
- function detectCliInstallPrefix() {
3004
- try {
3005
- const path = fileURLToPath(import.meta.url);
3006
- const suffixes = [
3007
- "/lib/node_modules/@oxygen-agent/cli/dist/index.js",
3008
- "/lib/node_modules/@oxygen/cli/dist/index.js",
3009
- ];
3010
- for (const suffix of suffixes) {
3011
- if (path.endsWith(suffix)) {
3012
- return path.slice(0, -suffix.length);
3013
- }
3369
+ function buildApiKeyCreateBody(options) {
3370
+ const body = {};
3371
+ const name = readOption(options.name);
3372
+ if (name)
3373
+ body.name = name;
3374
+ const expiresAt = resolveApiKeyExpiresAt(options);
3375
+ if (expiresAt)
3376
+ body.expires_at = expiresAt;
3377
+ return body;
3378
+ }
3379
+ function resolveApiKeyExpiresAt(options) {
3380
+ const expiresAt = readOption(options.expiresAt);
3381
+ const expiresInDays = readOption(options.expiresInDays);
3382
+ if (expiresAt && expiresInDays) {
3383
+ throw new OxygenError("conflicting_flags", "Pass either --expires-at or --expires-in-days, not both.", {
3384
+ exitCode: 1,
3385
+ });
3386
+ }
3387
+ if (expiresAt) {
3388
+ const date = new Date(expiresAt);
3389
+ if (!Number.isFinite(date.getTime())) {
3390
+ throw new OxygenError("invalid_expires_at", "--expires-at must be a valid ISO 8601 timestamp.", {
3391
+ details: { expires_at: expiresAt },
3392
+ exitCode: 1,
3393
+ });
3014
3394
  }
3395
+ return date.toISOString();
3015
3396
  }
3016
- catch {
3017
- // Non-file URL (e.g. running from a bundler) — fall through.
3397
+ if (expiresInDays) {
3398
+ const days = readPositiveInt(expiresInDays);
3399
+ if (!days) {
3400
+ throw new OxygenError("invalid_number", "--expires-in-days must be a positive integer.", {
3401
+ details: { value: expiresInDays },
3402
+ exitCode: 1,
3403
+ });
3404
+ }
3405
+ return new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
3018
3406
  }
3019
3407
  return null;
3020
3408
  }
3021
- function updateCli(options) {
3022
- const packageSpec = readOption(options.package) ?? DEFAULT_CLI_PACKAGE_SPEC;
3023
- const prefix = detectCliInstallPrefix();
3024
- const args = prefix
3025
- ? ["install", "-g", "--prefix", prefix, packageSpec]
3026
- : ["install", "-g", packageSpec];
3027
- const command = ["npm", ...args].join(" ");
3028
- if (options.dryRun) {
3029
- return {
3030
- current_version: OXYGEN_VERSION,
3031
- package: packageSpec,
3032
- command,
3033
- dry_run: true,
3034
- updated: false,
3035
- };
3036
- }
3037
- const result = spawnSync("npm", args, {
3038
- encoding: "utf8",
3039
- stdio: options.json ? ["ignore", "pipe", "pipe"] : "inherit",
3040
- });
3041
- if (result.error || result.status !== 0) {
3042
- throw new OxygenError("cli_update_failed", "Unable to update the Oxygen CLI.", {
3043
- details: {
3044
- command,
3045
- package: packageSpec,
3046
- exit_code: result.status,
3047
- reason: result.error instanceof Error ? result.error.message : null,
3048
- stderr: typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim().slice(0, 4000) : null,
3049
- },
3050
- exitCode: 1,
3051
- });
3052
- }
3053
- return {
3054
- current_version: OXYGEN_VERSION,
3055
- package: packageSpec,
3056
- command,
3057
- dry_run: false,
3058
- updated: true,
3059
- };
3060
- }
3061
3409
  async function login(options) {
3062
3410
  const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
3063
3411
  const token = readOption(options.token) ?? await promptForToken({
@@ -3255,6 +3603,7 @@ function formatLoginSuccess(identity, credentials, profile) {
3255
3603
  .update(`oxygen-cli:${credentials.token}`)
3256
3604
  .digest("hex");
3257
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
3258
3607
  const rows = [
3259
3608
  ["Account", email],
3260
3609
  ["Organization", org],
@@ -3331,7 +3680,7 @@ function formatProfileUseSuccess(profile) {
3331
3680
  ` ${c.dim("Fingerprint")} ${profile.token_fingerprint}`,
3332
3681
  "",
3333
3682
  ].join("\n");
3334
- }
3683
+ } // skipcq: JS-C1002
3335
3684
  function formatLogoutSuccess(result) {
3336
3685
  const c = ansi(output.isTTY === true && !process.env.NO_COLOR);
3337
3686
  const removed = result.removedProfile
@@ -3342,7 +3691,7 @@ function formatLogoutSuccess(result) {
3342
3691
  `${c.green("[OK]")} ${c.bold("CLI logged out")}`,
3343
3692
  "",
3344
3693
  ` ${c.dim("Credentials")} ${removed}`,
3345
- ` ${c.dim("Profiles left")} ${String(result.remainingProfiles)}`,
3694
+ ` ${c.dim("Profiles left")} ${String(result.remainingProfiles)}`, // skipcq: JS-C1002
3346
3695
  "",
3347
3696
  ].join("\n");
3348
3697
  }
@@ -3357,7 +3706,7 @@ function formatUpdateSuccess(result) {
3357
3706
  ` ${c.dim("Command")} ${result.command}`,
3358
3707
  "",
3359
3708
  ].join("\n");
3360
- }
3709
+ } // skipcq: JS-C1002
3361
3710
  function renderBox(lines) {
3362
3711
  const width = Math.max(...lines.map(visibleLength), 0);
3363
3712
  const border = `+${"-".repeat(width + 2)}+`;
@@ -3365,6 +3714,7 @@ function renderBox(lines) {
3365
3714
  return [border, ...body, border].join("\n");
3366
3715
  }
3367
3716
  function visibleLength(value) {
3717
+ // skipcq: JS-0004 — ESC (\x1b) is the ANSI CSI introducer; required to strip color codes
3368
3718
  return value.replace(/\x1b\[[0-9;]*m/g, "").length;
3369
3719
  }
3370
3720
  function ansi(enabled) {
@@ -3372,7 +3722,7 @@ function ansi(enabled) {
3372
3722
  ? (text) => `\x1b[${open}m${text}\x1b[${close}m`
3373
3723
  : (text) => text;
3374
3724
  return {
3375
- bold: wrap(1, 22),
3725
+ bold: wrap(1, 22), // skipcq: JS-0117 // skipcq: JS-W1035
3376
3726
  dim: wrap(2, 22),
3377
3727
  green: wrap(32, 39),
3378
3728
  };
@@ -3520,4 +3870,3 @@ function muteTokenEcho() {
3520
3870
  }
3521
3871
  }
3522
3872
  await createProgram().parseAsync(process.argv);
3523
- //# sourceMappingURL=index.js.map