@oxygen-agent/cli 1.272.83 → 1.275.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,4 +34,4 @@ oxygen update
34
34
 
35
35
  For product documentation, visit https://oxygen-agent.com/docs. For support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.272.83
37
+ Version: 1.275.0
package/dist/index.js CHANGED
@@ -1325,6 +1325,8 @@ export function createProgram() {
1325
1325
  .option("--table-ids <ids>", "Comma-separated table ids to attach.")
1326
1326
  .option("--deep-links <urls>", "Comma-separated https://oxygen-agent.com/... links.")
1327
1327
  .option("--slack-permalink <url>", "Slack message permalink (https://*.slack.com/...) to attach as context.")
1328
+ .option("--slack-channel-id <id>", "Slack channel id (e.g. C0123ABC456) the report came from.")
1329
+ .option("--customer-workspace <label>", "Customer workspace (org name/slug) a forwarded report concerns.")
1328
1330
  .option("--json", "Print a JSON envelope.")
1329
1331
  .action(async (options) => {
1330
1332
  await handleAsyncAction("support ticket create", options, () => requestOxygen("/api/cli/support/tickets", {
@@ -1370,6 +1372,32 @@ export function createProgram() {
1370
1372
  }))
1371
1373
  .addCommand(new Command("admin")
1372
1374
  .description("Staff-only support triage commands.")
1375
+ .addCommand(new Command("file")
1376
+ .description("File a support ticket into a customer's workspace on their behalf (staff only).")
1377
+ .requiredOption("--organization <organization>", "Target organization id or slug the ticket belongs to.")
1378
+ .requiredOption("--subject <subject>", "One-line summary of the customer's request.")
1379
+ .option("--body <body>", "The customer's request in their words, plus any staff notes.")
1380
+ .option("--severity <severity>", "low | normal | high. Defaults to normal.")
1381
+ .option("--category <category>", "Optional category label, e.g. bug, feature, question.")
1382
+ .option("--source <source>", "Where the ticket originated. Only slack is supported today.")
1383
+ .option("--operation <operation>", "The operation/tool involved, if known.")
1384
+ .option("--error-code <code>", "The error envelope code, if known.")
1385
+ .option("--run-ids <ids>", "Comma-separated run ids to attach.")
1386
+ .option("--table-ids <ids>", "Comma-separated table ids to attach.")
1387
+ .option("--deep-links <urls>", "Comma-separated https://oxygen-agent.com/... links.")
1388
+ .option("--slack-permalink <url>", "Slack message permalink (https://*.slack.com/...) to attach as context.")
1389
+ .option("--slack-channel-id <id>", "Slack channel id (e.g. C0123ABC456) the report came from.")
1390
+ .option("--customer-workspace <label>", "Customer workspace label when --org is not the customer's own org.")
1391
+ .option("--json", "Print a JSON envelope.")
1392
+ .action(async (options) => {
1393
+ await handleAsyncAction("support admin file", options, () => requestOxygen("/api/cli/admin/support/tickets", {
1394
+ method: "POST",
1395
+ body: {
1396
+ ...buildSupportTicketBody(options),
1397
+ organization: readOption(options.organization),
1398
+ },
1399
+ }));
1400
+ }))
1373
1401
  .addCommand(new Command("list")
1374
1402
  .description("List support tickets across all organizations (staff only).")
1375
1403
  .option("--status <status>", "Filter by status.")
@@ -4643,6 +4671,52 @@ export function createProgram() {
4643
4671
  ...(options.approve ? { approve: true } : {}),
4644
4672
  },
4645
4673
  }));
4674
+ }))
4675
+ .addCommand(new Command("register")
4676
+ .description("STAFF: register a manually-purchased egress IP (e.g. IPRoyal ISP-dedicated) into this environment's inventory. Secrets never pass here — --credential-ref names the Doppler family EGRESS_CRED_<REF>_USERNAME/_PASSWORD, set separately in Doppler. Register, then `egress assign`, then enable EGRESS_POOL_ENABLED (pool mode fails closed for unassigned mailboxes).")
4677
+ .requiredOption("--host <host>", "Proxy hostname or IP — bare, no scheme/port/credentials.")
4678
+ .requiredOption("--port <port>", "Proxy port (1-65535).")
4679
+ .requiredOption("--credential-ref <ref>", "Doppler credential-family name (A-Z, 0-9, _), e.g. IPROYAL.")
4680
+ .option("--vendor <vendor>", "Vendor slug for inventory provenance.", "iproyal")
4681
+ .option("--protocol <protocol>", "Proxy protocol: http (default), https, or socks5.")
4682
+ .option("--tier <tier>", "shared (default) or dedicated (requires --dedicated-org).")
4683
+ .option("--dedicated-org <orgId>", "Organization id a dedicated-tier IP is reserved for.")
4684
+ .option("--session-label <label>", "Optional vendor session/order label for provenance.")
4685
+ .option("--json", "Print a JSON envelope.")
4686
+ .action(async (options) => {
4687
+ await handleAsyncAction("egress register", options, () => requestOxygen("/api/cli/egress/register", {
4688
+ method: "POST",
4689
+ body: {
4690
+ host: readOption(options.host),
4691
+ port: Number(readOption(options.port)),
4692
+ credential_ref: readOption(options.credentialRef),
4693
+ vendor: readOption(options.vendor),
4694
+ ...(readOption(options.protocol) ? { protocol: readOption(options.protocol) } : {}),
4695
+ ...(readOption(options.tier) ? { tier: readOption(options.tier) } : {}),
4696
+ ...(readOption(options.dedicatedOrg) ? { dedicated_organization_id: readOption(options.dedicatedOrg) } : {}),
4697
+ ...(readOption(options.sessionLabel) ? { session_label: readOption(options.sessionLabel) } : {}),
4698
+ },
4699
+ }));
4700
+ }))
4701
+ .addCommand(new Command("assign")
4702
+ .description("Assign sending mailboxes to inventoried egress IPs — least-loaded across the active pool, or pinned with --ip. Existing open assignments are kept (stable for life; use rotate to re-home a burned IP). Admin/owner role; no spend, 0 credits.")
4703
+ .option("--mailboxes <list>", "Comma-separated mailbox ids or addresses to assign.")
4704
+ .option("--all", "Assign every sending mailbox in the pool that lacks an open assignment.")
4705
+ .option("--ip <egressIpId>", "Pin the targeted mailboxes to this egress IP id (must be active and org-visible).")
4706
+ .option("--json", "Print a JSON envelope.")
4707
+ .action(async (options) => {
4708
+ await handleAsyncAction("egress assign", options, () => {
4709
+ const list = readOption(options.mailboxes);
4710
+ const mailboxes = list ? list.split(",").map((entry) => entry.trim()).filter(Boolean) : [];
4711
+ return requestOxygen("/api/cli/egress/assign", {
4712
+ method: "POST",
4713
+ body: {
4714
+ ...(mailboxes.length > 0 ? { mailboxes } : {}),
4715
+ ...(options.all ? { all: true } : {}),
4716
+ ...(readOption(options.ip) ? { egress_ip_id: readOption(options.ip) } : {}),
4717
+ },
4718
+ });
4719
+ });
4646
4720
  })));
4647
4721
  program
4648
4722
  .command("directory")
@@ -7405,31 +7479,35 @@ export function createProgram() {
7405
7479
  await handleAsyncAction("mailboxes oauth-health", options, () => requestOxygen("/api/cli/mailboxes/oauth-health"));
7406
7480
  }))
7407
7481
  .addCommand(new Command("warmup")
7408
- .description("Mailbox warmup via Instantly (BYOK — Instantly bills your account, 0 Oxygen credits).")
7482
+ .description("Mailbox warmup via Instantly — BYOK over your connected Instantly account, or --billing-mode managed via the OXYGEN-managed Instantly workspace (both 0 Oxygen credits).")
7409
7483
  .addCommand(new Command("enable")
7410
- .description("Enable Instantly warmup for sending mailboxes and stamp each mailbox's warmup state. Targets the whole pool unless --mailboxes is given.")
7484
+ .description("Enable Instantly warmup for sending mailboxes and stamp each mailbox's warmup state. Targets the whole pool unless --mailboxes is given. Omit --billing-mode for automatic: BYOK when your Instantly is connected, else managed when enabled on the deployment.")
7411
7485
  .option("--mailboxes <list>", "Comma-separated mailbox ids or addresses to warm. Omit to warm the whole pool.")
7412
- .option("--connection <id>", "Instantly connection id. Defaults to the org's active Instantly connection.")
7486
+ .option("--connection <id>", "Instantly connection id (forces BYOK). Defaults to the org's active Instantly connection.")
7487
+ .option("--billing-mode <mode>", "byok = your Instantly connection; managed = the OXYGEN-managed Instantly workspace.")
7413
7488
  .option("--dry-run", "Simulate without calling Instantly (mailboxes marked pending).")
7414
7489
  .option("--json", "Print a JSON envelope.")
7415
7490
  .action(async (options) => {
7416
7491
  await handleAsyncAction("mailboxes warmup enable", options, () => {
7417
7492
  const mailboxes = readCsvOption(options.mailboxes);
7418
7493
  const connection = readOption(options.connection);
7494
+ const billingMode = readOption(options.billingMode);
7419
7495
  return requestOxygen("/api/cli/mailboxes/warmup", {
7420
7496
  method: "POST",
7421
7497
  body: {
7422
7498
  ...(mailboxes.length > 0 ? { mailboxes } : {}),
7423
7499
  ...(connection ? { connection_id: connection } : {}),
7500
+ ...(billingMode ? { billing_mode: billingMode } : {}),
7424
7501
  ...(options.dryRun ? { dry_run: true } : {}),
7425
7502
  },
7426
7503
  });
7427
7504
  });
7428
7505
  }))
7429
7506
  .addCommand(new Command("status")
7430
- .description("Sync Instantly's warmup analytics into the pool, updating each mailbox's warmup state. Targets the whole pool unless --mailboxes is given.")
7507
+ .description("Sync Instantly's warmup analytics into the pool, updating each mailbox's warmup state. Targets the whole pool unless --mailboxes is given. Omit --billing-mode for automatic: BYOK when connected, else managed when enabled.")
7431
7508
  .option("--mailboxes <list>", "Comma-separated mailbox ids or addresses to sync. Omit to sync the whole pool.")
7432
- .option("--connection <id>", "Instantly connection id. Defaults to the org's active Instantly connection.")
7509
+ .option("--connection <id>", "Instantly connection id (forces BYOK). Defaults to the org's active Instantly connection.")
7510
+ .option("--billing-mode <mode>", "byok = your Instantly connection; managed = the OXYGEN-managed Instantly workspace.")
7433
7511
  .option("--dry-run", "Skip the Instantly call (mailboxes marked pending).")
7434
7512
  .option("--json", "Print a JSON envelope.")
7435
7513
  .action(async (options) => {
@@ -7441,6 +7519,9 @@ export function createProgram() {
7441
7519
  const connection = readOption(options.connection);
7442
7520
  if (connection)
7443
7521
  params.set("connection_id", connection);
7522
+ const billingMode = readOption(options.billingMode);
7523
+ if (billingMode)
7524
+ params.set("billing_mode", billingMode);
7444
7525
  if (options.dryRun)
7445
7526
  params.set("dry_run", "true");
7446
7527
  const suffix = params.toString();
@@ -7497,14 +7578,15 @@ export function createProgram() {
7497
7578
  await handleAsyncAction("mailboxes order-status", options, () => requestOxygen(`/api/cli/mailboxes/orders/${encodeURIComponent(order)}`));
7498
7579
  })));
7499
7580
  program.addCommand(new Command("deliverability")
7500
- .description("External email deliverability: fleet reputation health and DIRECTIONAL inbox-placement (spam) tests. Placement tests are approval-gated paid runs (managed = 2 credits, BYOK = 0); fail closed with 409 when no health provider is connected.")
7581
+ .description("External email deliverability: fleet reputation health and DIRECTIONAL inbox-placement (spam) tests via EmailGuard or Zapmail. Placement tests are approval-gated paid runs (managed bills Oxygen credits; BYOK = 0 Oxygen credits — Zapmail BYOK bills your Zapmail wallet ~$2/test); fail closed with 409 when no health provider is connected.")
7501
7582
  .addCommand(new Command("placement-test")
7502
7583
  .description("Directional inbox-placement (spam) tests: run, list, and get. Results estimate inbox-vs-spam landing from seed inboxes.")
7503
7584
  .addCommand(new Command("run")
7504
- .description("Start a placement test for one sending mailbox. Without --approved this returns a PREVIEW (billing mode + credits_required). Re-run with --approved and --max-credits >= credits_required to send.")
7585
+ .description("Start a placement test for one sending mailbox. Without --approved this returns a PREVIEW (billing mode + credits_required). Re-run with --approved and --max-credits >= credits_required to send. Zapmail runs test Zapmail-hosted mailboxes ONLY, compose their own probe (subject/body ignored), and complete async (2-24h).")
7505
7586
  .argument("<mailbox>", "Sending mailbox address to test (e.g. ada@send.acme.com).")
7506
- .option("--subject <subject>", "Optional subject for the seed message.")
7507
- .option("--body <text>", "Optional plain-text body for the seed message.")
7587
+ .option("--provider <provider>", "Health provider: emailguard or zapmail. Omit to auto-resolve (Zapmail-hosted mailboxes route to zapmail, else emailguard).")
7588
+ .option("--subject <subject>", "Optional subject for the seed message (zapmail ignores it beyond labeling the test).")
7589
+ .option("--body <text>", "Optional plain-text body for the seed message (ignored by zapmail).")
7508
7590
  .option("--approved", "Actually run the test (otherwise a preview is returned).")
7509
7591
  .option("--max-credits <n>", "Credit cap the caller accepts (must be >= credits_required for managed runs).")
7510
7592
  .option("--json", "Print a JSON envelope.")
@@ -7513,6 +7595,7 @@ export function createProgram() {
7513
7595
  method: "POST",
7514
7596
  body: {
7515
7597
  mailbox,
7598
+ ...(readOption(options.provider) ? { provider: readOption(options.provider) } : {}),
7516
7599
  ...(readOption(options.subject) ? { subject: readOption(options.subject) } : {}),
7517
7600
  ...(readOption(options.body) ? { body_text: readOption(options.body) } : {}),
7518
7601
  ...(options.approved ? { approved: true } : {}),
@@ -7521,7 +7604,7 @@ export function createProgram() {
7521
7604
  }));
7522
7605
  }))
7523
7606
  .addCommand(new Command("list")
7524
- .description("List recent directional inbox-placement tests with status, billing mode, seed set, score, and credits used.")
7607
+ .description("List recent directional inbox-placement tests with status, health provider, billing mode, seed set, score, and credits used.")
7525
7608
  .option("--limit <n>", "Max rows (default 50, cap 200).")
7526
7609
  .option("--json", "Print a JSON envelope.")
7527
7610
  .action(async (options) => {
@@ -7531,7 +7614,7 @@ export function createProgram() {
7531
7614
  });
7532
7615
  }))
7533
7616
  .addCommand(new Command("get")
7534
- .description("Get one directional inbox-placement test by id — status, seed inboxes, per-provider results, and score.")
7617
+ .description("Get one directional inbox-placement test by id — status, health provider, seed inboxes, per-provider results, and score. Zapmail results land async (2-24h); keep polling while status is awaiting_results.")
7535
7618
  .argument("<id>", "Placement test id.")
7536
7619
  .option("--json", "Print a JSON envelope.")
7537
7620
  .action(async (id, options) => {
@@ -12176,6 +12259,10 @@ function buildSupportTicketBody(options) {
12176
12259
  context.error_code = readOption(options.errorCode);
12177
12260
  if (readOption(options.slackPermalink))
12178
12261
  context.slack_permalink = readOption(options.slackPermalink);
12262
+ if (readOption(options.slackChannelId))
12263
+ context.slack_channel_id = readOption(options.slackChannelId);
12264
+ if (readOption(options.customerWorkspace))
12265
+ context.customer_workspace = readOption(options.customerWorkspace);
12179
12266
  const runIds = readCsvOption(options.runIds);
12180
12267
  const tableIds = readCsvOption(options.tableIds);
12181
12268
  const deepLinks = readCsvOption(options.deepLinks);
@@ -119,7 +119,7 @@ export declare const BASE_PRICING_PLANS: {
119
119
  readonly description: "Custom procurement, invoicing, limits, and rollout support.";
120
120
  readonly ctaLabel: "Talk to us";
121
121
  readonly contactHref: "https://cal.com/tim-scheuer-mxbib9/45";
122
- readonly features: readonly ["Custom monthly usage pool", "Custom automation action allowance", "Phone number enrichment", "Custom AI-key and provider routing policies", "SSO, invoicing, and procurement support"];
122
+ readonly features: readonly ["Everything in Team", "Custom usage limits", "Volume pricing", "Custom invoicing and billing", "Dedicated support"];
123
123
  };
124
124
  };
125
125
  export declare const PUBLIC_PLAN_ORDER: readonly PlanTier[];
@@ -126,11 +126,11 @@ export const BASE_PRICING_PLANS = {
126
126
  ctaLabel: "Talk to us",
127
127
  contactHref: CONTACT_SALES_URL,
128
128
  features: [
129
- "Custom monthly usage pool",
130
- "Custom automation action allowance",
131
- "Phone number enrichment",
132
- "Custom AI-key and provider routing policies",
133
- "SSO, invoicing, and procurement support",
129
+ "Everything in Team",
130
+ "Custom usage limits",
131
+ "Volume pricing",
132
+ "Custom invoicing and billing",
133
+ "Dedicated support",
134
134
  ],
135
135
  },
136
136
  };
@@ -1,2 +1,2 @@
1
- export declare const OXYGEN_VERSION = "1.272.83";
1
+ export declare const OXYGEN_VERSION = "1.275.0";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.272.83";
1
+ export const OXYGEN_VERSION = "1.275.0";
2
2
  // Bump this only when deployed CLI/API contracts require a newer CLI.
3
3
  // 1.181.0: paid table action runs and background columns run require
4
4
  // approved=true in addition to max_credits; older CLIs cannot send the flag.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.272.83",
3
+ "version": "1.275.0",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",