@oxygen-agent/cli 1.1011.493 → 1.1011.600

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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/crm-file-read-commands.d.ts +6 -0
  3. package/dist/crm-file-read-commands.js +16 -0
  4. package/dist/feed-read-flags.d.ts +3 -0
  5. package/dist/feed-read-flags.js +9 -0
  6. package/dist/functions-commands.js +2 -1
  7. package/dist/help.js +18 -5
  8. package/dist/index.js +168 -107
  9. package/dist/sender-limits-flags.js +6 -4
  10. package/dist/settings-commands.js +11 -11
  11. package/dist/whatsapp-account-flags.d.ts +4 -0
  12. package/dist/whatsapp-account-flags.js +18 -0
  13. package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
  14. package/node_modules/@oxygen/shared/dist/billing.d.ts +41 -0
  15. package/node_modules/@oxygen/shared/dist/billing.js +56 -0
  16. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +21 -0
  17. package/node_modules/@oxygen/shared/dist/capability-discovery.js +251 -21
  18. package/node_modules/@oxygen/shared/dist/contracts/billing-owner-read.d.ts +6 -0
  19. package/node_modules/@oxygen/shared/dist/contracts/billing-owner-read.js +7 -0
  20. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
  21. package/node_modules/@oxygen/shared/dist/contracts/copilot-sessions.d.ts +11 -0
  22. package/node_modules/@oxygen/shared/dist/contracts/copilot-sessions.js +7 -0
  23. package/node_modules/@oxygen/shared/dist/contracts/crm-file-bytes.d.ts +33 -0
  24. package/node_modules/@oxygen/shared/dist/contracts/crm-file-bytes.js +12 -0
  25. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +6 -3
  26. package/node_modules/@oxygen/shared/dist/contracts/function-library-read.d.ts +14 -0
  27. package/node_modules/@oxygen/shared/dist/contracts/function-library-read.js +16 -0
  28. package/node_modules/@oxygen/shared/dist/contracts/inbox-drafts-read.d.ts +45 -0
  29. package/node_modules/@oxygen/shared/dist/contracts/inbox-drafts-read.js +21 -0
  30. package/node_modules/@oxygen/shared/dist/contracts/limits-read.d.ts +29 -0
  31. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +121 -0
  32. package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.js +1 -1
  33. package/node_modules/@oxygen/shared/dist/contracts/pipedrive-catalog.d.ts +18 -0
  34. package/node_modules/@oxygen/shared/dist/contracts/pipedrive-catalog.js +29 -0
  35. package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.d.ts +181 -0
  36. package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +62 -0
  37. package/node_modules/@oxygen/shared/dist/contracts/sender-limits.js +7 -5
  38. package/node_modules/@oxygen/shared/dist/contracts/sequence-list.d.ts +3 -0
  39. package/node_modules/@oxygen/shared/dist/contracts/sequence-list.js +8 -0
  40. package/node_modules/@oxygen/shared/dist/contracts/stored-read-window.d.ts +17 -0
  41. package/node_modules/@oxygen/shared/dist/contracts/stored-read-window.js +8 -0
  42. package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.d.ts +67 -0
  43. package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.js +19 -0
  44. package/node_modules/@oxygen/shared/dist/contracts/table-row-read.d.ts +30 -0
  45. package/node_modules/@oxygen/shared/dist/contracts/table-row-read.js +9 -0
  46. package/node_modules/@oxygen/shared/dist/contracts/whatsapp-reads.d.ts +111 -0
  47. package/node_modules/@oxygen/shared/dist/contracts/whatsapp-reads.js +33 -0
  48. package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.d.ts +6 -5
  49. package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.js +5 -5
  50. package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +4 -1
  51. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +17 -0
  52. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +26 -0
  53. package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +2 -32
  54. package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +5 -0
  55. package/node_modules/@oxygen/shared/dist/feature-gates.js +6 -0
  56. package/node_modules/@oxygen/shared/dist/first-month-offer-window.d.ts +34 -0
  57. package/node_modules/@oxygen/shared/dist/first-month-offer-window.js +41 -0
  58. package/node_modules/@oxygen/shared/dist/first-month-offer.d.ts +42 -0
  59. package/node_modules/@oxygen/shared/dist/first-month-offer.js +56 -0
  60. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +32 -1
  61. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +68 -4
  62. package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +6 -2
  63. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +6 -1
  64. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +11 -0
  65. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +11 -0
  66. package/node_modules/@oxygen/shared/dist/sql-error.js +13 -2
  67. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +3 -0
  68. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +17 -1
  69. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  70. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  71. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +11 -1
  72. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +29 -3
  73. package/package.json +1 -1
@@ -3,12 +3,14 @@
3
3
  // MCP tool and the route use too. This overlay only keeps the flag names and
4
4
  // help text the command shipped with before the contract existed.
5
5
  import { OxygenError } from "@oxygen/shared/cli-result";
6
- import { LINKEDIN_SENDER_LIMIT_DEFAULTS, LINKEDIN_SENDER_LIMIT_MAXIMUMS, } from "@oxygen/shared/linkedin-sequences";
6
+ import { LINKEDIN_SENDER_LIMIT_DEFAULTS, LINKEDIN_SENDER_LIMIT_MAXIMUMS, linkedInSenderLimitCautionLevel, } from "@oxygen/shared/linkedin-sequences";
7
7
  import { SENDER_LIMITS_PATCH_CONTRACT } from "@oxygen/shared/contracts/sender-limits";
8
8
  import { readContractFlags } from "./contract-flags.js";
9
9
  /** "<text> (default N, max M)." for a LinkedIn sender limit flag, from the enforced constants. */
10
10
  function linkedInLimitHelp(key, text) {
11
- return `${text} (default ${LINKEDIN_SENDER_LIMIT_DEFAULTS[key]}, max ${LINKEDIN_SENDER_LIMIT_MAXIMUMS[key]}).`;
11
+ const caution = linkedInSenderLimitCautionLevel(key);
12
+ const risk = caution === undefined ? "" : ` Above ${caution} raises the risk of a LinkedIn restriction.`;
13
+ return `${text} (default ${LINKEDIN_SENDER_LIMIT_DEFAULTS[key]}, max ${LINKEDIN_SENDER_LIMIT_MAXIMUMS[key]}).${risk}`;
12
14
  }
13
15
  const LIMIT_FLAG_HELP = {
14
16
  invites_per_day: "Daily LinkedIn connection invites cap",
@@ -31,8 +33,8 @@ const LIMIT_FLAG_HELP = {
31
33
  sales_nav_search_results_per_day: "Daily cap on Sales Navigator search result rows fetched",
32
34
  api_reads_per_day: "Daily cap on all other LinkedIn API reads",
33
35
  total_reads_per_day: "Daily cap across all read units",
34
- min_action_spacing_seconds: "Minimum seconds between actions",
35
- action_spacing_jitter_seconds: "Random jitter seconds added to action spacing",
36
+ min_action_spacing_seconds: "Minimum seconds between automated actions. Campaigns also spread each day's limit across the working hours (20 invites in 07:00-22:00 go out 30-45 minutes apart), so this is a floor",
37
+ action_spacing_jitter_seconds: "Random jitter seconds added to action spacing; a spread campaign gap adds up to half of itself",
36
38
  interactive_min_spacing_seconds: "Minimum seconds between human-sent Unibox actions",
37
39
  interactive_spacing_jitter_seconds: "Random jitter seconds added to human-sent Unibox action spacing",
38
40
  interactive_sends_per_hour: "Hourly cap on human-sent Unibox actions",
@@ -1,6 +1,6 @@
1
- // `oxygen settings`: workspace settings every member reads and admins change.
2
- // Its family today is `settings pages`, the web app's sidebar pages a workspace
3
- // hides for everyone (Settings → Customise). The set command's flags come from
1
+ // `oxygen settings`: your own settings in a workspace. Its family today is
2
+ // `settings pages`, the web app's sidebar pages you hide for yourself there
3
+ // (Settings → Customise; per user since 2026-10-02). The set command's flags come from
4
4
  // the shared contract that also generates `oxygen_settings_pages_update` and
5
5
  // validates the route, so the CLI and MCP cannot disagree about what can be set.
6
6
  import { Command } from "commander";
@@ -12,7 +12,7 @@ const list = WORKSPACE_PAGES_GET_OPERATION;
12
12
  const update = WORKSPACE_PAGES_UPDATE_OPERATION;
13
13
  export const WORKSPACE_PAGES_FLAG_OVERLAY = {
14
14
  hide: {
15
- help: "Pages to hide from the sidebar for everyone, comma-separated, each by its path (/unibox) or name (Unibox) from `settings pages list`. Hiding a section hides its sub-pages.",
15
+ help: "Pages to hide from your own sidebar, comma-separated, each by its path (/unibox) or name (Unibox) from `settings pages list`. Hiding a section hides its sub-pages.",
16
16
  valueName: "<pages>",
17
17
  },
18
18
  show: {
@@ -32,20 +32,20 @@ function names(value) {
32
32
  export function buildWorkspacePagesBody(options) {
33
33
  const body = readContractFlags(options, update.contract, WORKSPACE_PAGES_FLAG_OVERLAY);
34
34
  if (!names(body.hide) && !names(body.show) && body.show_all !== true) {
35
- throw new OxygenError("invalid_request", "Nothing to change: pass --hide <pages> to hide pages for everyone, --show <pages> to show hidden ones again, or --show-all. `settings pages list` names every page.", { exitCode: 1 });
35
+ throw new OxygenError("invalid_request", "Nothing to change: pass --hide <pages> to hide pages from your sidebar, --show <pages> to show hidden ones again, or --show-all. `settings pages list` names every page.", { exitCode: 1 });
36
36
  }
37
37
  return body;
38
38
  }
39
39
  export function registerSettingsCommands(program, handle) {
40
40
  const settings = program
41
41
  .command("settings")
42
- .description("Workspace settings: what every member of this workspace sees. "
43
- + "`settings pages` is Settings → Customise in the web app: hide or show pages in the sidebar navigation (the left menu) for everyone. "
44
- + "`settings pages list` shows each page and whether it is hidden; an admin or owner changes it with `settings pages set --hide <pages>`, `--show <pages>` or `--show-all`. "
45
- + "Nothing is deleted: a hidden page stays reachable by URL. Free.");
42
+ .description("Settings in this workspace, each with its own scope. "
43
+ + "`settings pages` is Settings → Customise in the web app and yours alone: hide or show pages in your own sidebar navigation (the left menu); teammates keep theirs. "
44
+ + "`settings pages list` shows each page and whether you hid it; change it with `settings pages set --hide <pages>`, `--show <pages>` or `--show-all`. Nothing is deleted: a hidden page stays reachable by URL. "
45
+ + "`settings beta-features` is workspace-wide: early-access features for everyone, changed by admins and owners. Free.");
46
46
  const pages = new Command("pages")
47
- .description("The web app's sidebar pages this workspace hides for every member (Settings → Customise): page visibility in the navigation menu. "
48
- + "`list` shows every page with its section and whether it is hidden or visible; `set` hides or shows pages (admins and owners only). "
47
+ .description("The web app's sidebar pages you hide for yourself in this workspace (Settings → Customise): page visibility in your navigation menu; teammates keep their own. "
48
+ + "`list` shows every page with its section and whether it is hidden or visible; `set` hides or shows pages. "
49
49
  + "Hiding a section hides its sub-pages. Presentation only: nothing is deleted, and a hidden page stays reachable by URL and deep-link. Every response carries its `web_url`.");
50
50
  pages.addCommand(new Command(list.cli.path.at(-1) ?? "list")
51
51
  .description(list.cli.summary)
@@ -0,0 +1,4 @@
1
+ import { type ContractFlagOverlay } from "./contract-flags.js";
2
+ /** Preserve the shipped status/tag coercion and --no-usage spelling. */
3
+ export declare const WHATSAPP_ACCOUNT_FLAGS: ContractFlagOverlay;
4
+ export declare function whatsappAccountQuery(options: Record<string, unknown>): URLSearchParams;
@@ -0,0 +1,18 @@
1
+ import { WHATSAPP_ACCOUNTS_READ_CONTRACT, whatsappReadQueryParams } from "@oxygen/shared/contracts/whatsapp-reads";
2
+ import { readContractFlags } from "./contract-flags.js";
3
+ /** Preserve the shipped status/tag coercion and --no-usage spelling. */
4
+ export const WHATSAPP_ACCOUNT_FLAGS = {
5
+ status: { valueName: "<status>", parse: raw => raw.trim() },
6
+ include_usage: { flag: "usage", negateHelp: "Skip today's per-account usage counts." },
7
+ tag: { valueName: "<tags>", parse: raw => raw.split(",").map(value => value.trim()).filter(Boolean).join(",") },
8
+ limit: { valueName: "<n>" }, offset: { valueName: "<n>" }, max_chars: { valueName: "<n>" },
9
+ };
10
+ export function whatsappAccountQuery(options) {
11
+ const input = readContractFlags(options, WHATSAPP_ACCOUNTS_READ_CONTRACT, WHATSAPP_ACCOUNT_FLAGS);
12
+ input.include_usage ??= true;
13
+ if (!input.status)
14
+ delete input.status;
15
+ if (!input.tag)
16
+ delete input.tag;
17
+ return whatsappReadQueryParams(input);
18
+ }
@@ -512,7 +512,7 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
512
512
  {
513
513
  name: "sponsor",
514
514
  kind: "boolean",
515
- description: "Sponsor each creator's LinkedIn account when they accept: your workspace pays its monthly credit reservation (shown in the preview), from when they connect LinkedIn. With --link-only, preview first, then create the links with --approved --preview-token.",
515
+ description: "Sponsor each creator's LinkedIn account when they accept: your workspace pays its monthly credit reservation (shown in the preview), from when they connect LinkedIn; nothing is charged if they never accept or connect. With --link-only, the first call previews; repeat it with --approved --preview-token <that preview's token> to create the links.",
516
516
  required: false,
517
517
  },
518
518
  {
@@ -1005,7 +1005,7 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
1005
1005
  {
1006
1006
  action: "sponsorship_preview",
1007
1007
  command: "sponsorship preview",
1008
- description: "Preview sponsoring creators as a signed-in master admin: funding is card (seat plus 10,000-credit grant) or credits (one LinkedIn reservation per creator). Org API keys cannot quote.",
1008
+ description: "Preview sponsoring creators who already joined, as a signed-in master admin: funding is card (seat plus 10,000-credit grant) or credits (one LinkedIn reservation per creator). Org API keys cannot quote. To sponsor creators who have not joined yet, invite them with creators invite --sponsor.",
1009
1009
  fields: [
1010
1010
  {
1011
1011
  name: "program_id",
@@ -532,6 +532,47 @@ export declare const ANNUAL_BILLING_MONTHS_CHARGED = 10;
532
532
  export declare function annualPlanPriceCents(monthlyPriceCents: number): number;
533
533
  /** How often a plan Price bills. Credits are granted monthly on both. */
534
534
  export type PlanBillingInterval = "month" | "year";
535
+ /** Commitment and payment frequency are separate: annual commitments bill monthly. */
536
+ export type PlanCommitmentTerm = "month" | "year";
537
+ export declare const ANNUAL_MONTHLY_TERMS_VERSION = "2026-10-02";
538
+ export declare function annualMonthlyPriceCents(monthlyPriceCents: number): number;
539
+ /** Read-only comparison; annual totals reflect twelve rounded monthly payments. */
540
+ export declare function purchasablePlanComparison(annualMonthlyAvailable: boolean): {
541
+ plan_key: "oxygen_199" | "oxygen_1999" | "oxygen_49" | "oxygen_499" | "oxygen_99" | "oxygen_999";
542
+ currency: string;
543
+ monthly_credits: number;
544
+ volume_bonus_percent: number;
545
+ monthly: {
546
+ payment_cents: number;
547
+ billing_interval: string;
548
+ commitment_months: number;
549
+ };
550
+ yearly: {
551
+ payment_cents: number;
552
+ billing_interval: string;
553
+ commitment_months: number;
554
+ total_payment_cents: number;
555
+ saving_cents: number;
556
+ prices_before_tax: boolean;
557
+ } | null;
558
+ }[];
559
+ export declare const ANNUAL_MONTHLY_PLAN_TERMS: {
560
+ readonly terms_version: "2026-10-02";
561
+ readonly commitment_months: 12;
562
+ readonly billing_interval: "month";
563
+ readonly credits_granted: "monthly_after_payment";
564
+ readonly renewal: "automatically_for_another_12_months";
565
+ readonly cancellation: "Stops annual renewal; monthly payments continue through the current term.";
566
+ readonly upgrades: "Immediate with proration; the current annual term end stays the same.";
567
+ readonly switch_from_monthly: "Starts at the next monthly billing boundary. The first discounted monthly payment starts the 12-month commitment; the current plan and credits stay until then.";
568
+ readonly downgrades_or_switch_to_monthly: "Take effect at the annual term end.";
569
+ };
570
+ export declare function readPlanCommitmentTerm(value: unknown): PlanCommitmentTerm | null;
571
+ /** Anchor arithmetic preserves Jan 31 and Feb 29 rather than drifting on clamps. */
572
+ export declare function annualCommitmentWindow(anchorSeconds: number, nowSeconds: number): {
573
+ start: number;
574
+ end: number;
575
+ };
535
576
  export declare const PLAN_BILLING_INTERVALS: readonly PlanBillingInterval[];
536
577
  /**
537
578
  * A billing interval as a customer or an agent types it. `month`/`monthly` and
@@ -1059,6 +1059,62 @@ export const ANNUAL_BILLING_MONTHS_CHARGED = 10;
1059
1059
  export function annualPlanPriceCents(monthlyPriceCents) {
1060
1060
  return monthlyPriceCents * ANNUAL_BILLING_MONTHS_CHARGED;
1061
1061
  }
1062
+ export const ANNUAL_MONTHLY_TERMS_VERSION = "2026-10-02";
1063
+ export function annualMonthlyPriceCents(monthlyPriceCents) {
1064
+ return Math.round(annualPlanPriceCents(monthlyPriceCents) / 12);
1065
+ }
1066
+ /** Read-only comparison; annual totals reflect twelve rounded monthly payments. */
1067
+ export function purchasablePlanComparison(annualMonthlyAvailable) {
1068
+ return PURCHASABLE_PLAN_KEYS.map((key) => {
1069
+ const plan = resolveBasePricingPlan(key);
1070
+ const monthly = plan.monthlyPriceCents;
1071
+ const annualPayment = annualMonthlyPriceCents(monthly);
1072
+ return {
1073
+ plan_key: key,
1074
+ currency: "usd",
1075
+ monthly_credits: plan.monthlyCredits,
1076
+ volume_bonus_percent: planVolumeBonusPercent(key),
1077
+ monthly: { payment_cents: monthly, billing_interval: "month", commitment_months: 1 },
1078
+ yearly: annualMonthlyAvailable ? {
1079
+ payment_cents: annualPayment,
1080
+ billing_interval: "month",
1081
+ commitment_months: 12,
1082
+ total_payment_cents: annualPayment * 12,
1083
+ saving_cents: monthly * 12 - annualPayment * 12,
1084
+ prices_before_tax: true,
1085
+ } : null,
1086
+ };
1087
+ });
1088
+ }
1089
+ export const ANNUAL_MONTHLY_PLAN_TERMS = {
1090
+ terms_version: ANNUAL_MONTHLY_TERMS_VERSION,
1091
+ commitment_months: 12,
1092
+ billing_interval: "month",
1093
+ credits_granted: "monthly_after_payment",
1094
+ renewal: "automatically_for_another_12_months",
1095
+ cancellation: "Stops annual renewal; monthly payments continue through the current term.",
1096
+ upgrades: "Immediate with proration; the current annual term end stays the same.",
1097
+ switch_from_monthly: "Starts at the next monthly billing boundary. The first discounted monthly payment starts the 12-month commitment; the current plan and credits stay until then.",
1098
+ downgrades_or_switch_to_monthly: "Take effect at the annual term end.",
1099
+ };
1100
+ export function readPlanCommitmentTerm(value) {
1101
+ if (value === undefined || value === null || value === "")
1102
+ return null;
1103
+ return parsePlanBillingInterval(value);
1104
+ }
1105
+ /** Anchor arithmetic preserves Jan 31 and Feb 29 rather than drifting on clamps. */
1106
+ export function annualCommitmentWindow(anchorSeconds, nowSeconds) {
1107
+ const anchor = new Date(anchorSeconds * 1000);
1108
+ const boundary = (years) => {
1109
+ const year = anchor.getUTCFullYear() + years;
1110
+ const day = Math.min(anchor.getUTCDate(), new Date(Date.UTC(year, anchor.getUTCMonth() + 1, 0)).getUTCDate());
1111
+ return Date.UTC(year, anchor.getUTCMonth(), day, anchor.getUTCHours(), anchor.getUTCMinutes(), anchor.getUTCSeconds()) / 1000;
1112
+ };
1113
+ let years = Math.max(0, new Date(nowSeconds * 1000).getUTCFullYear() - anchor.getUTCFullYear());
1114
+ if (boundary(years) > nowSeconds)
1115
+ years = Math.max(0, years - 1);
1116
+ return { start: boundary(years), end: boundary(years + 1) };
1117
+ }
1062
1118
  export const PLAN_BILLING_INTERVALS = ["month", "year"];
1063
1119
  /**
1064
1120
  * A billing interval as a customer or an agent types it. `month`/`monthly` and
@@ -216,6 +216,26 @@ export declare const CAPABILITY_ROUTE_MCP_CALLS: {
216
216
  family: string;
217
217
  };
218
218
  };
219
+ rowFanOut: () => {
220
+ tool: string;
221
+ arguments: {
222
+ source: string;
223
+ target: string;
224
+ flatten: {
225
+ column: string;
226
+ path: string;
227
+ };
228
+ mapping: {
229
+ target: string;
230
+ source: {
231
+ type: string;
232
+ key: string;
233
+ };
234
+ path: string;
235
+ }[];
236
+ dry_run: boolean;
237
+ };
238
+ };
219
239
  linkedinKeywordPosts: () => {
220
240
  tool: string;
221
241
  arguments: {
@@ -245,3 +265,4 @@ export declare const LOCAL_BUSINESS_SCRAPE_COMMAND = "oxygen companies search pr
245
265
  * column through `columns add`, which the route recommends next.
246
266
  */
247
267
  export declare const ROW_CLASSIFICATION_COMMAND = "oxygen columns catalog --family classification";
268
+ export declare const ROW_FAN_OUT_COMMAND = "oxygen tables send <source> <target> --flatten <column> --flatten-path <path> --mapping '[{\"target\":\"<column>\",\"source\":{\"type\":\"column\",\"key\":\"$element\"},\"path\":\"<field>\"}]' --dry-run";