@oxygen-agent/cli 1.1014.779 → 1.1014.951
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/command-manifest.js +10 -1
- package/dist/commands/admin.js +1 -1
- package/dist/commands/billing.js +13 -9
- package/dist/commands/columns.js +3 -3
- package/dist/commands/crm.js +2 -2
- package/dist/commands/deliverability.js +18 -10
- package/dist/commands/domains.js +15 -3
- package/dist/commands/mailboxes.js +22 -20
- package/dist/commands/managed-inboxes.js +1 -1
- package/dist/commands/sequences.js +34 -8
- package/dist/commands/voice.d.ts +21 -0
- package/dist/commands/voice.js +121 -6
- package/dist/customer-operation-commands.js +12 -2
- package/dist/index.js +6 -0
- package/dist/run-wait.js +10 -2
- package/node_modules/@oxygen/formula/package.json +8 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +22 -1
- package/node_modules/@oxygen/shared/dist/billing-money.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/billing-money.js +60 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/billing.js +9 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +2 -2
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +76 -0
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +4 -2
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +2 -2
- package/node_modules/@oxygen/shared/dist/contracts/guided-activation.d.ts +177 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +59 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +4 -3
- package/node_modules/@oxygen/shared/dist/copilot-access.js +2 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +8 -15
- package/node_modules/@oxygen/shared/dist/domain-authentication.d.ts +65 -0
- package/node_modules/@oxygen/shared/dist/domain-authentication.js +54 -0
- package/node_modules/@oxygen/shared/dist/email-infrastructure-policy.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/email-infrastructure-policy.js +13 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +155 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.fixture.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.fixture.js +21 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.js +38 -0
- package/node_modules/@oxygen/shared/dist/linkedin-connection-identity.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/linkedin-connection-identity.js +41 -0
- package/node_modules/@oxygen/shared/dist/managed-email-branding.js +9 -5
- package/node_modules/@oxygen/shared/dist/placement-history.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/placement-history.js +12 -0
- package/node_modules/@oxygen/shared/dist/placement-report.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/placement-report.js +34 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +1 -0
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +1 -0
- package/node_modules/@oxygen/shared/dist/sending-limits.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sending-limits.js +2 -10
- package/node_modules/@oxygen/shared/dist/teammates.js +2 -1
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +100 -2
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/voice-countries.d.ts +66 -0
- package/node_modules/@oxygen/shared/dist/voice-countries.js +75 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/command-manifest.js
CHANGED
|
@@ -105,6 +105,11 @@ const MUTATING_VERBS = new Set([
|
|
|
105
105
|
// status persists state and can pause the provider; reconnect replaces its
|
|
106
106
|
// connection and can restart provider history. Other status commands are reads.
|
|
107
107
|
const MUTATING_COMMANDS = new Set([
|
|
108
|
+
// Live DNS checks save observations; --cached explicitly reads saved evidence.
|
|
109
|
+
"domains dns",
|
|
110
|
+
// Sends the workspace's company documents to Twilio for a human review that
|
|
111
|
+
// cannot be edited once started. `submit` is its only leaf, so it is exact.
|
|
112
|
+
"voice verification submit",
|
|
108
113
|
// Approved runs write relation edges, Records fields or delete duplicate rows.
|
|
109
114
|
// Exact names: a `link` verb would also catch the read-only `tables link-suggest`.
|
|
110
115
|
"tables link",
|
|
@@ -165,6 +170,8 @@ const MUTATING_COMMANDS = new Set([
|
|
|
165
170
|
// mode, but a small number of approved operations are explicitly
|
|
166
171
|
// zero-credit. Keep those exceptions exact so discovery never invents spend.
|
|
167
172
|
const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
|
|
173
|
+
// --approved hands documents to the carrier's reviewers; no credits, no carrier fee.
|
|
174
|
+
"voice verification submit",
|
|
168
175
|
"domains dns apply",
|
|
169
176
|
// The compatibility --approved flag never bypasses canonical proposal review.
|
|
170
177
|
// Wiki revisions and canonical pin changes are free (Knowledge audit, 2026-10-06).
|
|
@@ -250,6 +257,8 @@ const READ_ONLY_COMMANDS = new Set([
|
|
|
250
257
|
// invocation still previews, so machine-readable discovery must not imply that
|
|
251
258
|
// calling the command without its confirmation flag writes anything.
|
|
252
259
|
const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
260
|
+
// A bare call validates and names what would be sent; --approved submits.
|
|
261
|
+
"voice verification submit",
|
|
253
262
|
"domains dns apply",
|
|
254
263
|
"mailboxes warmup enable",
|
|
255
264
|
"mailboxes warmup google",
|
|
@@ -280,7 +289,7 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
|
280
289
|
// audience and caps and writes nothing. Without this entry discovery would tell
|
|
281
290
|
// an agent that previewing the play arms it, and the preview would go unrun.
|
|
282
291
|
"linkedin intent autoenroll",
|
|
283
|
-
"mailboxes
|
|
292
|
+
"mailboxes deliverability auto-enroll",
|
|
284
293
|
"domains delete",
|
|
285
294
|
"mailboxes delete",
|
|
286
295
|
"mailboxes connect-oauth",
|
package/dist/commands/admin.js
CHANGED
|
@@ -46,7 +46,7 @@ export function registerAdminFamily(program, handleAsyncAction, binaryName) {
|
|
|
46
46
|
.command("admin")
|
|
47
47
|
.description("Staff-only commands.")
|
|
48
48
|
.addCommand(new Command("kpis")
|
|
49
|
-
.description("Show the company conversion funnel (visitors → signups → activation on any surface, web, CLI or MCP → paid, with step rates and agent connected beside it), self-reported signup-channel attribution, new and positive-paid MRR/ARR, and paid churn separated from trial loss. OXYGEN staff only; workspace admin role alone does not grant access.")
|
|
49
|
+
.description("Show the company conversion funnel (visitors → signups → activation on any surface, web, CLI or MCP → paid, with step rates, and agent connected then activated through it beside it), self-reported signup-channel attribution, new and positive-paid MRR/ARR, and paid churn separated from trial loss. OXYGEN staff only; workspace admin role alone does not grant access.")
|
|
50
50
|
.addOption(new Option("--range <range>", "Range for acquisition, the funnel's signup cohort, new revenue, and churn; current MRR/ARR and scheduled cancellations remain point-in-time.")
|
|
51
51
|
.choices(["7d", "30d", "90d"])
|
|
52
52
|
.default("30d"))
|
package/dist/commands/billing.js
CHANGED
|
@@ -107,7 +107,7 @@ export function registerBillingFamily(program, handleAsyncAction, billingDocsUrl
|
|
|
107
107
|
await handleAsyncAction("billing commitments", options, () => requestOxygen(`/api/cli/billing/commitments${options.view ? `?view=${encodeURIComponent(options.view)}` : ""}`));
|
|
108
108
|
}))
|
|
109
109
|
.addCommand(new Command("estimate")
|
|
110
|
-
.description("Quote prospective enrichment, senders, X/WhatsApp,
|
|
110
|
+
.description("Quote prospective enrichment, senders, X/WhatsApp, OXYGEN deliverability tests and an unsaved Workflow without provider calls, writes or reservations. Work-email figures are labelled conservative ceilings until matching measured populations exist; LinkedIn finder prices distinguish found results, misses and declared averages. Current workspace plan_fit uses stored spendable balance and balance-only months; --plan selects a hypothetical monthly allowance instead. Unknown/floor costs suppress complete plan recommendations. Workflow quotes include step floors, 30-day cadence and a separate declared ceiling; no execution authority. Own-key test vendor charges are separate. Prices are in force; opening the quote costs 0 credits. Example: oxygen billing estimate --leads 50 --enrich linkedin_url --whatsapp-accounts 2 --x-accounts 1 --json.")
|
|
111
111
|
.option("--leads <n>", "Leads you work through a month.")
|
|
112
112
|
.option("--enrich <id>", "An enrichment run on every lead, by id from `oxygen enrichment catalog --json` (work_email, mobile_phone, person_enrich, capability:verify_email …). Repeat for several.", collectRepeatable, [])
|
|
113
113
|
.option("--email-steps <n>", "Email steps per lead (only the email ones; LinkedIn steps go in --linkedin-steps).")
|
|
@@ -117,10 +117,14 @@ export function registerBillingFamily(program, handleAsyncAction, billingDocsUrl
|
|
|
117
117
|
.option("--linkedin-accounts <n>", "LinkedIn accounts you connect.")
|
|
118
118
|
.option("--x-accounts <n>", "X accounts; prospective scenario quantity.")
|
|
119
119
|
.option("--whatsapp-accounts <n>", "WhatsApp accounts; prospective scenario quantity.")
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
.
|
|
123
|
-
.
|
|
120
|
+
.addOption(new Option("--emailguard-inboxes <n>").hideHelp())
|
|
121
|
+
.addOption(new Option("--emailguard-tests-per-inbox <n>").hideHelp())
|
|
122
|
+
.addOption(new Option("--emailguard-byok-inboxes <n>").hideHelp())
|
|
123
|
+
.addOption(new Option("--emailguard-byok-tests-per-inbox <n>").hideHelp())
|
|
124
|
+
.option("--deliverability-inboxes <n>", "Managed OXYGEN deliverability inboxes; prospective scenario quantity.")
|
|
125
|
+
.option("--deliverability-tests-per-inbox <n>", "Managed tests per inbox per cycle; prospective scenario quantity.")
|
|
126
|
+
.option("--deliverability-byok-inboxes <n>", "Own-key OXYGEN deliverability inboxes; prospective scenario quantity.")
|
|
127
|
+
.option("--deliverability-byok-tests-per-inbox <n>", "Own-key tests per inbox per cycle; prospective scenario quantity.")
|
|
124
128
|
.option("--workflow-manifest <json>", "Unsaved oxygen-workflows-v2 draft JSON. Saves nothing.")
|
|
125
129
|
.option("--workflow-cron <cron>", "Five-field schedule for a 30-day step-cost floor; arms nothing.")
|
|
126
130
|
.option("--workflow-max-credits <n>", "Declared ceiling for comparison only; grants no approval or authority.")
|
|
@@ -139,10 +143,10 @@ export function registerBillingFamily(program, handleAsyncAction, billingDocsUrl
|
|
|
139
143
|
linkedin_accounts: readPositiveNumberOrZero(options.linkedinAccounts) ?? 0,
|
|
140
144
|
x_accounts: options.xAccounts === undefined ? 0 : Number(options.xAccounts),
|
|
141
145
|
whatsapp_accounts: options.whatsappAccounts === undefined ? 0 : Number(options.whatsappAccounts),
|
|
142
|
-
emailguard_inboxes: options.
|
|
143
|
-
emailguard_tests_per_inbox: options.
|
|
144
|
-
emailguard_byok_inboxes: options.
|
|
145
|
-
emailguard_byok_tests_per_inbox: options.
|
|
146
|
+
emailguard_inboxes: Number(options.deliverabilityInboxes ?? options.emailguardInboxes ?? 0),
|
|
147
|
+
emailguard_tests_per_inbox: Number(options.deliverabilityTestsPerInbox ?? options.emailguardTestsPerInbox ?? 0),
|
|
148
|
+
emailguard_byok_inboxes: Number(options.deliverabilityByokInboxes ?? options.emailguardByokInboxes ?? 0),
|
|
149
|
+
emailguard_byok_tests_per_inbox: Number(options.deliverabilityByokTestsPerInbox ?? options.emailguardByokTestsPerInbox ?? 0),
|
|
146
150
|
...(options.workflowManifest ? { workflow_manifest: parseJsonObject(options.workflowManifest) } : {}),
|
|
147
151
|
...(options.workflowCron ? { workflow_cron: options.workflowCron } : {}),
|
|
148
152
|
...(options.workflowMaxCredits === undefined ? {} : { workflow_max_credits: Number(options.workflowMaxCredits) }),
|
package/dist/commands/columns.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseKeyValuePairs, parseJsonObject, readNonNegativeInt, readPositiveInt, readRecordString } from "../cli-values.js";
|
|
2
2
|
import { readColumnDecisionFlags } from "../column-decision-options.js";
|
|
3
3
|
import { formatAiPromptPreviewNotice, formatColumnReferenceNotices, formatEnrichmentInputMappingNotice } from "../column-run-notices.js";
|
|
4
|
-
import { collectRepeatable, parseJsonArray, parseJsonValue, readCsvOption, readFileIfPresent, readFilterSelectionOption, readFormulaValuesOption, readJsonFileValue, readNonNegativeNumber,
|
|
4
|
+
import { collectRepeatable, parseJsonArray, parseJsonValue, readCsvOption, readFileIfPresent, readFilterSelectionOption, readFormulaValuesOption, readJsonFileValue, readNonNegativeNumber, tableRunsListPath, waitForTableActionRun } from "../command-support.js";
|
|
5
5
|
import { requestOxygen } from "../http-client.js";
|
|
6
6
|
import { runLocalCustomHttpColumn } from "../local-custom-http-column.js";
|
|
7
7
|
import { isRecord, readOption } from "../util.js";
|
|
@@ -830,7 +830,7 @@ export function registerColumnsFamily(program, handleAsyncAction) {
|
|
|
830
830
|
.addCommand(new Command("add")
|
|
831
831
|
.description("Add a nullable column, a catalog template (--prompt-key) or a multi-column enrichment bundle (--preset) to a workspace table. Writes the definition only — this never runs the column and never spends credits; use `columns run` for that, with --dry-run first to see the cost. For a page, posting or filing the row already links to, or a company fact Oxygen already knows how to research (founders, parent company, funding, cloud provider, offers demos, industry, NAICS, HQ, a LinkedIn or Crunchbase page), pick a template from `columns catalog` (--prompt-key) instead of writing a prompt; or read a URL column with --kind research --research-url. The guide is the oxygen-tables skill (`oxygen skills get oxygen-tables --json`). For ICP fit, use Classify Score: `columns add <table> --label 'ICP fit' --score 'low,medium,high' --prompt 'Assess {{company_name}}: {{description}}, {{employees}} employees' --grounding workspace` (0.075 credits/row). A formula column needs no run at all: it evaluates on read from its current inputs, so `tables query` shows its values immediately. A plain field is --type: text, number, checkbox, date, json, markdown, image, or a choice column — select, multi_select or status, with --options \"Lead,Contacted,Won\". To link another table row to row, use `oxygen tables relate <table> --to <other-table> --live`; to read a value out of one, --lookup-table.")
|
|
832
832
|
.argument("<table>", "Table id or slug.")
|
|
833
|
-
.option("--preset <preset>", "Add a pre-built column bundle instead of one column, which is what --prompt-key adds: a preset writes SEVERAL columns at once and needs no --input on a table whose identity column it can find, while --prompt-key adds one template column and binds each input you name. Presets: `person_enrich` (one LinkedIn profile lookup — Blitz first, Professional Network Data (Up2Data) only when Blitz has no match — then first/last/full name, current company and its LinkedIn URL, job title, start date, education, school, headline, bio, location and followers as free stored fields that fill when the lookup runs, followers only on rows Professional Network Data answers because Blitz publishes none; the person templates in `columns catalog --category people` all read its payload column), `company_enrich` (one company lookup from a domain or LinkedIn URL — Blitz first, Professional Network Data (Up2Data) only when Blitz has no match, about 1 credit a company — keeping the whole profile in its JSON column plus name, domain, LinkedIn URL, headcount, industry, description, founded year and HQ country as free stored fields; no field choices; binds to the person preset's current_company_linkedin_url automatically), `company_tech_stack` (the technologies a company runs, from its website and hiring), `company_funding` (total raised, latest round, number of rounds and investors), `company_revenue` (an annual revenue estimate), `company_hiring` (the roles a company is hiring for, with a count) — each of these four is one paid lookup column that fills its own columns, priced per row in `tools search --for-table` — plus `company_lookalikes` (one managed lookup from --input company_domain=website; keeps similar companies separate from the seed; dry-run for price) — or `domain_check` (1
|
|
833
|
+
.option("--preset <preset>", "Add a pre-built column bundle instead of one column, which is what --prompt-key adds: a preset writes SEVERAL columns at once and needs no --input on a table whose identity column it can find, while --prompt-key adds one template column and binds each input you name. Presets: `person_enrich` (one LinkedIn profile lookup — Blitz first, Professional Network Data (Up2Data) only when Blitz has no match — then first/last/full name, current company and its LinkedIn URL, job title, start date, education, school, headline, bio, location and followers as free stored fields that fill when the lookup runs, followers only on rows Professional Network Data answers because Blitz publishes none; the person templates in `columns catalog --category people` all read its payload column), `company_enrich` (one company lookup from a domain or LinkedIn URL — Blitz first, Professional Network Data (Up2Data) only when Blitz has no match, about 1 credit a company — keeping the whole profile in its JSON column plus name, domain, LinkedIn URL, headcount, industry, description, founded year and HQ country as free stored fields; no field choices; binds to the person preset's current_company_linkedin_url automatically), `company_tech_stack` (the technologies a company runs, from its website and hiring), `company_funding` (total raised, latest round, number of rounds and investors), `company_revenue` (an annual revenue estimate), `company_hiring` (the roles a company is hiring for, with a count) — each of these four is one paid lookup column that fills its own columns, priced per row in `tools search --for-table` — plus `company_lookalikes` (one managed lookup from --input company_domain=website; keeps similar companies separate from the seed; dry-run for price) — or `domain_check` (0.1 credits/row at list price for a live/redirect/parked/not_found/dead verdict on every domain, cached 30 days, plus a free label column to filter on). For a single cleaned or classified column, use a template from `columns catalog` instead — that is one column, not a bundle. Name the input column with --input: `url` for person_enrich, `domain` and/or `linkedin_url` for the company presets. Any column works whatever it is called; without --input Oxygen guesses from column names. Creating the columns is free; run them afterwards, --dry-run first.")
|
|
834
834
|
.option("--input <slot=column...>", "Bind a preset or template input to an exact column, e.g. --input url=pricing_url with --prompt-key, or --input domain=website --input linkedin_url=company_li with --preset. Repeatable. For a preset it is only needed when the automatic match is wrong or missing; for a template it names the column each declared input reads.", collectRepeatable, [])
|
|
835
835
|
// No preset takes a field list any more (founder, 2026-09-24). Hidden,
|
|
836
836
|
// not removed: an old script passing them gets the server's refusal,
|
|
@@ -1158,7 +1158,7 @@ export function registerColumnsFamily(program, handleAsyncAction) {
|
|
|
1158
1158
|
.action(async (table, column, options) => {
|
|
1159
1159
|
const limit = readPositiveInt(options.limit);
|
|
1160
1160
|
const localConcurrency = readPositiveInt(options.localConcurrency);
|
|
1161
|
-
const maxCredits =
|
|
1161
|
+
const maxCredits = readNonNegativeNumber(options.maxCredits);
|
|
1162
1162
|
const maxConcurrency = readPositiveInt(options.maxConcurrency);
|
|
1163
1163
|
const filterSelection = readFilterSelectionOption(options.filterJson);
|
|
1164
1164
|
const formulaValues = readFormulaValuesOption(options.formulaValues);
|
package/dist/commands/crm.js
CHANGED
|
@@ -906,7 +906,7 @@ export function registerCrmFamily(program, handleAsyncAction) {
|
|
|
906
906
|
})
|
|
907
907
|
.addCommand(new Command("create")
|
|
908
908
|
.description("Create or repair a custom CRM object. Defaults to dry-run.")
|
|
909
|
-
.requiredOption("--slug <slug>", "Object slug (snake_case), such as projects or tasks.")
|
|
909
|
+
.requiredOption("--slug <slug>", "Object slug (snake_case), such as projects or vendors. Slugs CRM already uses for its own objects and pages (companies, deals, tasks, dashboards, ...) are refused.")
|
|
910
910
|
.requiredOption("--display-name <name>", "Human-readable object name, such as Projects.")
|
|
911
911
|
.requiredOption("--columns-json <json>", 'JSON array. Every column requires key,label,data_type,semantic_type; camelCase aliases are accepted. Example: [{"key":"name","label":"Name","data_type":"text","semantic_type":"text","is_record_label":true}].')
|
|
912
912
|
.option("--identities-json <json>", "JSON array. Every identity requires column_key and normalization; camelCase columnKey is accepted. normalization is one of email_v1, domain_v1, linkedin_url_v1, exact_text_v1, uuid_v1 — the versioned CRM names, NOT the short exact/email/domain spellings --lookup-normalize takes. At most one identity may set is_primary.")
|
|
@@ -930,7 +930,7 @@ export function registerCrmFamily(program, handleAsyncAction) {
|
|
|
930
930
|
}))
|
|
931
931
|
.addCommand(new Command("delete")
|
|
932
932
|
.description("Delete a CUSTOM CRM object: removes the object and schedules its backing table for deletion (restorable for 14 days). Standard objects (companies, people, deals) are permanent and refused. Defaults to dry-run; --live requires --confirm. Re-run on an object a teammate already deleted, it reports alreadyArchived with the table's deletion state and purge date. Restoring that table returns its rows as an ordinary table; the object stays deleted. Purging it also removes the object's archived definition.")
|
|
933
|
-
.argument("<object>", "Custom CRM object slug, such as projects or
|
|
933
|
+
.argument("<object>", "Custom CRM object slug, such as projects or vendors.")
|
|
934
934
|
.option("--keep-table", "Deregister the object but keep its table as an ordinary workspace table (no delete timer). Ignored once the table is already scheduled.")
|
|
935
935
|
.option("--purge-after <days>", "Days until the backing table's physical purge. Defaults to 14; restorable via `tables restore` until then. Ignored once the table is already scheduled.")
|
|
936
936
|
.option("--dry-run", "Preview the object, rows, relationships, and edges the delete would take. Default.")
|
|
@@ -3,17 +3,17 @@ import { readOption } from "../util.js";
|
|
|
3
3
|
import { Command, CommanderError, Option } from "commander";
|
|
4
4
|
export function registerDeliverabilityFamily(program, handleAsyncAction) {
|
|
5
5
|
program.addCommand(new Command("deliverability")
|
|
6
|
-
.description("External email deliverability: fleet reputation health and DIRECTIONAL inbox-placement (spam) tests via
|
|
6
|
+
.description("External email deliverability: fleet reputation health and DIRECTIONAL inbox-placement (spam) tests via OXYGEN deliverability, Zapmail, or an explicitly configured OXYGEN placement evaluation pilot. Start with `placement-test list` and `placement-test get` to inspect existing results for 0 credits without creating or sending a test. For continuous OXYGEN deliverability account monitoring use `oxygen mailboxes deliverability connect`. Placement tests are approval-gated paid runs (managed bills Oxygen credits; own-key tests pay the in-force platform fee plus separate vendor charges — Zapmail bills your Zapmail wallet ~$2/test). OXYGEN placement evaluation is never auto-selected or generally available: it is a limited pilot with one 0-credit test for its configured eligible connected sender.")
|
|
7
7
|
.addCommand(new Command("placement-test")
|
|
8
8
|
.description("Inspect saved directional inbox-placement results with list/get (0 credits; no test email is sent). New tests use preview and approval through run/send.")
|
|
9
9
|
.addCommand(new Command("run")
|
|
10
|
-
.description("Create a placement test for one sending mailbox. Without --approved this returns a cost PREVIEW.
|
|
10
|
+
.description("Create a placement test for one sending mailbox. Without --approved this returns a cost PREVIEW. OXYGEN deliverability creation returns exact seeds + phrase but sends nothing; next run `placement-test send <id>` to preview and approve that external email. Zapmail owns its probe delivery and completes async (2-24h). OXYGEN placement evaluation requires explicit --provider oxygen_placement plus --subject and --body, and is available only for the eligible connected sender enabled for the limited pilot. Its approval creates AND sends one probe with no separate send step: preview first, then re-run the same mailbox, provider, subject, and body with --approved --max-credits 0 --plan <plan_hash>.")
|
|
11
11
|
.argument("<mailbox>", "Sending mailbox address to test (e.g. ada@send.acme.com).")
|
|
12
|
-
.addOption(new Option("--provider <provider>", "Health provider:
|
|
12
|
+
.addOption(new Option("--provider <provider>", "Health provider: oxygen_deliverability, zapmail, or oxygen_placement. Omit to auto-resolve OXYGEN deliverability/Zapmail. OXYGEN placement evaluation is never auto-selected and requires the sender enabled for the limited pilot.")
|
|
13
13
|
.argParser((value) => {
|
|
14
14
|
const provider = value === "sendkit" ? "oxygen_placement" : value;
|
|
15
|
-
if (!["emailguard", "zapmail", "oxygen_placement"].includes(provider)) {
|
|
16
|
-
throw new CommanderError(2, "commander.invalidArgument", "Choose
|
|
15
|
+
if (!["oxygen_deliverability", "emailguard", "zapmail", "oxygen_placement"].includes(provider)) {
|
|
16
|
+
throw new CommanderError(2, "commander.invalidArgument", "Choose oxygen_deliverability, zapmail, or oxygen_placement.");
|
|
17
17
|
}
|
|
18
18
|
return provider;
|
|
19
19
|
}))
|
|
@@ -52,7 +52,7 @@ export function registerDeliverabilityFamily(program, handleAsyncAction) {
|
|
|
52
52
|
}));
|
|
53
53
|
}))
|
|
54
54
|
.addCommand(new Command("send")
|
|
55
|
-
.description("Preview or send the exact
|
|
55
|
+
.description("Preview or send the exact OXYGEN deliverability seed message for a provider-created test. Preview returns sender, all recipients, exact content, and plan_hash. Live send requires --approved --plan <hash>, costs 0 additional credits, and sends one message only.")
|
|
56
56
|
.argument("<id>", "Oxygen placement-test id returned by `placement-test run`.")
|
|
57
57
|
.option("--allow-paused-probe", "Opt in to one diagnostic probe while preserving campaign pause; repeat at both create and send approvals. Never permits disabled senders.")
|
|
58
58
|
.option("--approved", "Send the exact previewed seed message (otherwise preview only).")
|
|
@@ -71,17 +71,25 @@ export function registerDeliverabilityFamily(program, handleAsyncAction) {
|
|
|
71
71
|
}));
|
|
72
72
|
}))
|
|
73
73
|
.addCommand(new Command("list")
|
|
74
|
-
.description("Inspect
|
|
74
|
+
.description("Inspect directional inbox-placement tests. Use --view summary for inventory, --status to filter, and pass next_cursor as --cursor until null. count is this page; total and status_counts cover all matches before the cursor. creditsUsed is the original charge; results.refund (summary: refund) records restoration. Plus `pricing`: OXYGEN deliverability monitoring credits per inbox-month, two included tests per inbox per cycle, and the managed extra-test price in force. Covered tests cost no extra credits. The allowance resets each UTC calendar month; unused tests do not roll over. Own-key tests pay the quoted OXYGEN platform fee separately from provider charges. The returned web link opens Accounts; use get for an exact result link. Costs 0 credits and never creates or sends a test; active tests may refresh their observations.")
|
|
75
75
|
.option("--limit <n>", "Max rows (default 50, cap 200).")
|
|
76
|
+
.option("--status <status>", "Filter: pending_send, sending, awaiting_results, completed, incomplete, failed.")
|
|
77
|
+
.option("--cursor <cursor>", "Continue from next_cursor with the same status filter.")
|
|
78
|
+
.option("--view <view>", "full (default) or summary for compact inventory.")
|
|
76
79
|
.option("--json", "Print a JSON envelope.")
|
|
77
80
|
.action(async (options) => {
|
|
78
81
|
await handleAsyncAction("deliverability placement-test list", options, () => {
|
|
79
|
-
const
|
|
80
|
-
|
|
82
|
+
const params = new URLSearchParams();
|
|
83
|
+
for (const key of ["limit", "status", "cursor", "view"]) {
|
|
84
|
+
const value = readOption(options[key]);
|
|
85
|
+
if (value)
|
|
86
|
+
params.set(key, value);
|
|
87
|
+
}
|
|
88
|
+
return requestOxygen(`/api/cli/deliverability/placement-tests${params.size ? `?${params}` : ""}`);
|
|
81
89
|
});
|
|
82
90
|
}))
|
|
83
91
|
.addCommand(new Command("get")
|
|
84
|
-
.description("Inspect one saved directional inbox-placement test by id: status, seed counts, per-provider results, and a web result link. Costs 0 credits and never creates or sends a test; active tests may refresh their observations. Zapmail results land async (2-24h); keep polling while status is awaiting_results.")
|
|
92
|
+
.description("Inspect one saved directional inbox-placement test by id: status, seed counts, per-provider results, and a web result link. creditsUsed is the original charge; results.refund records any restoration. Costs 0 credits and never creates or sends a test; active tests may refresh their observations. Zapmail results land async (2-24h); keep polling while status is awaiting_results.")
|
|
85
93
|
.argument("<id>", "Placement test id.")
|
|
86
94
|
.option("--json", "Print a JSON envelope.")
|
|
87
95
|
.action(async (id, options) => {
|
package/dist/commands/domains.js
CHANGED
|
@@ -290,11 +290,13 @@ export function registerDomainsFamily(program, handleAsyncAction) {
|
|
|
290
290
|
program.addCommand(new Command("domains")
|
|
291
291
|
.description("Cold-email domains: sync zones from your own Cloudflare account (BYOK), inspect age/warmup/DNS health, check availability and pricing, buy domains, and archive or delete domains with their inboxes (OXYGEN-bought domains included). Your own Cloudflare account bills purchases to its payment method; domains registered by OXYGEN cost credits at the vendor price. Preview `domains buy` before approval.")
|
|
292
292
|
.addCommand(new Command("list")
|
|
293
|
-
.description("
|
|
293
|
+
.description("Saved domain health, no external calls. Use --authentication for compact daily SPF/DKIM/DMARC/MX/DNS results across managed, Cloudflare and imported sending domains; --attention returns issues, unverified, stale and failed checks. Daily checks cover non-disabled sending mailboxes, including paused mailboxes; disabled/deleted mailboxes are excluded. Default output includes cached Cloudflare rows, managed domains and standalone registrations, mailbox/warmup rollups, sending volume and DNS health. summary.by_dns_state gives all zone-state counts in one read. Run domains sync to refresh the Cloudflare cache.")
|
|
294
|
+
.option("--authentication", "Only saved daily authentication summaries and coverage counts; q narrows domains. No provider calls.")
|
|
295
|
+
.option("--attention", "Only authentication domains needing attention; implies --authentication. No provider calls.")
|
|
294
296
|
.option("--status <status>", "Filter by zone status: unknown, initializing, pending, active, moved, or deleted.")
|
|
295
297
|
.option("--registrar <registrar>", "Filter by registrar name.")
|
|
296
298
|
.option("--q <text>", "Filter by domain substring.")
|
|
297
|
-
.option("--dns <state>", "Filter
|
|
299
|
+
.option("--dns <state>", "Filter cached zone DNS only: ok, issues, not_checked, check_failed, check_incomplete, no_zone. summary.by_dns_state gives all counts; --attention filters daily authentication instead.")
|
|
298
300
|
.option("--expiring", "Only non-deleted domains expiring within 60 days.")
|
|
299
301
|
.option("--has-mailboxes <bool>", "true → only domains with sending mailboxes; false → only domains without.")
|
|
300
302
|
.option("--include-archived", "Include archived domains (hidden by default).")
|
|
@@ -303,6 +305,10 @@ export function registerDomainsFamily(program, handleAsyncAction) {
|
|
|
303
305
|
.action(async (options) => {
|
|
304
306
|
await handleAsyncAction("domains list", options, () => {
|
|
305
307
|
const params = new URLSearchParams();
|
|
308
|
+
if (options.authentication || options.attention)
|
|
309
|
+
params.set("authentication", "true");
|
|
310
|
+
if (options.attention)
|
|
311
|
+
params.set("attention", "true");
|
|
306
312
|
const status = readOption(options.status);
|
|
307
313
|
if (status)
|
|
308
314
|
params.set("status", status);
|
|
@@ -431,12 +437,14 @@ export function registerDomainsFamily(program, handleAsyncAction) {
|
|
|
431
437
|
await handleAsyncAction("domains get", options, () => requestOxygen(`/api/cli/domains/${encodeURIComponent(domain)}`));
|
|
432
438
|
}))
|
|
433
439
|
.addCommand(new Command("dns")
|
|
434
|
-
.description("
|
|
440
|
+
.description("Read domain DNS health, 0 credits. --cached reads saved daily authentication evidence without external calls; --authentication checks public SPF/DKIM/DMARC/MX/nameservers and saves the observation. Otherwise check the domain's live records: Cloudflare persists its verdict; managed domains contact their provider; external domains query public DNS. DNS records are never changed by this command. Add --deep for content-level checks (SPF DNS-lookup budget, DMARC enforcement, MX provider) plus an external DNS-over-HTTPS nameserver-authority check. An Oxygen-managed domain reads its vendor-held records and verdict instead (--deep ignored). A domain hosted outside Oxygen that you send from returns rail=external: the records public DNS resolves — the mail set, delegation, policy records and web root — with health.enumerable=false, because public DNS cannot list a zone (a DKIM key is probed at common selectors, so dkim=unknown means unproven, never missing). `dns plan`/`dns apply` edit your own Cloudflare zones only. DNS editing is unavailable for Oxygen-bought domains; keep their nameservers and contact support.")
|
|
435
441
|
.argument("[domain]", "Domain name, such as acme.com.")
|
|
436
442
|
// NOT --provider: commander parses parent options greedily by default,
|
|
437
443
|
// so a --provider here would swallow the `dns plan`/`dns apply`
|
|
438
444
|
// subcommands' required --provider flag.
|
|
439
445
|
.option("--mx-provider <provider>", "With --deep: report whether the live MX matches this mailbox provider (google or microsoft).")
|
|
446
|
+
.option("--cached", "Read saved authentication evidence and monitoring eligibility; no DNS/provider calls or observation writes.")
|
|
447
|
+
.option("--authentication", "Save one free public-DNS check. Does not enable daily monitoring or paid services; no DNS changes.")
|
|
440
448
|
.option("--deep", "Add content-level DNS analysis (SPF lookup budget, DMARC enforcement, MX provider) and an external DoH nameserver-authority check.")
|
|
441
449
|
.option("--json", "Print a JSON envelope.")
|
|
442
450
|
.action(async (domain, options) => {
|
|
@@ -444,6 +452,10 @@ export function registerDomainsFamily(program, handleAsyncAction) {
|
|
|
444
452
|
throw new Error("Provide a domain (oxygen domains dns <domain>) or use a subcommand: `dns plan` / `dns apply`.");
|
|
445
453
|
}
|
|
446
454
|
const query = new URLSearchParams();
|
|
455
|
+
if (options.cached)
|
|
456
|
+
query.set("cached", "true");
|
|
457
|
+
if (options.authentication)
|
|
458
|
+
query.set("authentication", "true");
|
|
447
459
|
if (options.deep)
|
|
448
460
|
query.set("deep", "true");
|
|
449
461
|
if (options.mxProvider)
|
|
@@ -404,9 +404,9 @@ async function readMailboxImportFile(path) {
|
|
|
404
404
|
}
|
|
405
405
|
export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
406
406
|
program.addCommand(new Command("mailboxes")
|
|
407
|
-
.description("Connect customer-owned Gmail/Microsoft 365 inboxes with `oxygen mailboxes connect-oauth --vendor composio` for mail history in Unibox and business activity in CRM; read and control progress with `oxygen mailboxes sync`. Also operate the native email sending pool: register/refresh Google/Microsoft mailboxes (including Microsoft SMTP transport and secure local credential-file transfer), pause, disable or delete inboxes, connect
|
|
407
|
+
.description("Connect customer-owned Gmail/Microsoft 365 inboxes with `oxygen mailboxes connect-oauth --vendor composio` for mail history in Unibox and business activity in CRM; read and control progress with `oxygen mailboxes sync`. Also operate the native email sending pool: register/refresh Google/Microsoft mailboxes (including Microsoft SMTP transport and secure local credential-file transfer), pause, disable or delete inboxes, connect OXYGEN deliverability monitoring, and inspect/control OXYGEN Warm-up. Fresh managed InboxKit Google/Microsoft/Azure orders activate exact-scope warm-up automatically after provisioning under their approved default-on add-on. Google reuses the managed credential just in time; Microsoft/Azure uses native export. Standalone warm-up plans and credit caps are for BYOK/imported mailboxes, managed opt-outs, or later separate enrollment. OXYGEN Warm-up never owns campaign dispatch. Every inbox here always belongs to a sender profile (the person it sends as) — list, create, and attach those with `oxygen senders profiles`. Safe import preflight: run `oxygen mailboxes compatibility --catalog-only --json`, then `oxygen mailboxes import --file <path> --validate-only --json` (add the credential source flags shown by import help when the file contains credentials).")
|
|
408
408
|
.addCommand(new Command("google-workspace")
|
|
409
|
-
.description("Authorize a Workspace administrator for Gmail audit/usage, domains, users, groups, verification and Postmaster data. Separate from individual mailbox consent and paid warm-up/
|
|
409
|
+
.description("Authorize a Workspace administrator for Gmail audit/usage, domains, users, groups, verification and Postmaster data. Separate from individual mailbox consent and paid warm-up/OXYGEN deliverability enrollment.")
|
|
410
410
|
.addCommand(new Command("connect")
|
|
411
411
|
.description("Preview requested Google admin permissions; --approved creates an expiring consent link. Complete authorization in Google as a Workspace administrator.")
|
|
412
412
|
.option("--approved", "Create the administrator consent link after reviewing permissions.")
|
|
@@ -500,7 +500,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
500
500
|
writeMailboxPoolOverview(result, { filtered: suffix.length > 0, filterFlags });
|
|
501
501
|
}))
|
|
502
502
|
.addCommand(new Command("get")
|
|
503
|
-
.description("Get one sending mailbox's configuration/readiness detail (provider, status, daily cap, warmup state, auth mode, and source — managed vs bring-your-own) plus a one-row pool summary. effective_cap comes first: today's campaign limit (effective_daily_cap), the ramp-up mode and its ramp_schedule. This is not sent/replied/bounced performance; use `oxygen sequences analytics` for native Sequence attribution by mailbox/domain. An ineligible native-send transport includes transport_reason + transport_hint; it does not by itself block OXYGEN Warm-up or
|
|
503
|
+
.description("Get one sending mailbox's configuration/readiness detail (provider, status, daily cap, warmup state, auth mode, and source — managed vs bring-your-own) plus a one-row pool summary. effective_cap comes first: today's campaign limit (effective_daily_cap), the ramp-up mode and its ramp_schedule. This is not sent/replied/bounced performance; use `oxygen sequences analytics` for native Sequence attribution by mailbox/domain. An ineligible native-send transport includes transport_reason + transport_hint; it does not by itself block OXYGEN Warm-up or OXYGEN deliverability monitoring. <mailbox> accepts a mailbox id or email address.")
|
|
504
504
|
.argument("<mailbox>", "Mailbox id or email address.")
|
|
505
505
|
.option("--json", "Print a JSON envelope.")
|
|
506
506
|
.action(async (mailbox, options) => {
|
|
@@ -548,7 +548,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
548
548
|
await handleAsyncAction("mailboxes health", options, () => requestOxygen("/api/cli/mailboxes/health"));
|
|
549
549
|
}))
|
|
550
550
|
.addCommand(new Command("compatibility")
|
|
551
|
-
.description("Read-only compatibility report per mailbox — select with --mailboxes <address,…> (omit for the whole pool; filter here, not in a script): generic origin class, real infrastructure tier (including InboxKit Azure), OXYGEN native-send connection quality, OXYGEN Warm-up path,
|
|
551
|
+
.description("Read-only compatibility report per mailbox — select with --mailboxes <address,…> (omit for the whole pool; filter here, not in a script): generic origin class, real infrastructure tier (including InboxKit Azure), OXYGEN native-send connection quality, OXYGEN Warm-up path, OXYGEN deliverability monitoring path, and exact next actions. Read automatic_repair for native send and warmup_automatic_repair for an existing warm-up seat: mode=automatic means OXYGEN owns the next bounded retry. Native send distinguishes destination-bound OAuth, provider-managed API transport, admin delegation, and authorization_required. Use --catalog-only for the compact workspace-independent import contract. JSON data.compatibility contains checked rows; data.import_methods and data.import_fields describe accepted transfer inputs; data.provider_matrix is the current public product matrix; data.non_transferable_auth names credentials that must be reconnected; data.summary rolls up states; data.web_url opens the pool. Downstream states distinguish credential_required, consent_required, and vendor_blocked. Never decrypts a credential or calls a downstream provider.")
|
|
552
552
|
.option("--mailboxes <list>", "Comma-separated mailbox ids or addresses. Omit for the whole pool.")
|
|
553
553
|
.option("--catalog-only", "Return only the bounded import-method, field, auth-boundary, and public provider catalogs; do not read or return workspace mailbox rows.")
|
|
554
554
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -570,7 +570,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
570
570
|
});
|
|
571
571
|
}))
|
|
572
572
|
.addCommand(new Command("import")
|
|
573
|
-
.description("Register (or refresh) sending mailboxes in bulk. Use --validate-only with a local file for a non-mutating, no-network preflight; without it, import is a 0-credit state mutation whose upsert is idempotent by mailbox address. CSV/JSON/JSONL/XLSX identity files from any vendor use --file plus optional --vendor. Compatible Google app-password exports use --from credentials --vendor <source>. Connected Zapmail inventories use --from zapmail. In credential files, app_password is Google-only; Microsoft password fields are discarded locally. OAuth grants, MFA/TOTP seeds, and delegation keys never transfer from customer files: fresh managed InboxKit Google orders reuse the vendor-held credential just in time for automatic warmup, while fresh managed Microsoft/Azure orders use native InboxKit Sequencer export; opted-out/separate managed enrollment uses standalone `warmup enable`; eligible non-InboxKit Microsoft warmup uses the Outlook OAuth fallback (`mailboxes warmup microsoft`);
|
|
573
|
+
.description("Register (or refresh) sending mailboxes in bulk. Use --validate-only with a local file for a non-mutating, no-network preflight; without it, import is a 0-credit state mutation whose upsert is idempotent by mailbox address. CSV/JSON/JSONL/XLSX identity files from any vendor use --file plus optional --vendor. Compatible Google app-password exports use --from credentials --vendor <source>. Connected Zapmail inventories use --from zapmail. In credential files, app_password is Google-only; Microsoft password fields are discarded locally. OAuth grants, MFA/TOTP seeds, and delegation keys never transfer from customer files: fresh managed InboxKit Google orders reuse the vendor-held credential just in time for automatic warmup, while fresh managed Microsoft/Azure orders use native InboxKit Sequencer export; opted-out/separate managed enrollment uses standalone `warmup enable`; eligible non-InboxKit Microsoft warmup uses the Outlook OAuth fallback (`mailboxes warmup microsoft`); OXYGEN deliverability remains vendor_blocked for Microsoft. Google app passwords supplied through an authorized credential import are sent only in the request body, encrypted server-side, and never returned. Every imported inbox is attached to a sender profile: --sender <id> names the person, a row's first/last or display name finds or creates them, otherwise OXYGEN creates a profile named from the address for you to correct.")
|
|
574
574
|
.addHelpText("after", [
|
|
575
575
|
"",
|
|
576
576
|
"Ordinary identity file contract (CSV / JSON / JSONL / XLSX):",
|
|
@@ -698,7 +698,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
698
698
|
.addCommand(new Command("cap")
|
|
699
699
|
.description("View and adjust a single mailbox's daily send cap (throttle a problem inbox without pausing it). The campaign ramp-up that climbs to it is `mailboxes ramp set`.")
|
|
700
700
|
.addCommand(new Command("set")
|
|
701
|
-
.description("Set one sending mailbox's daily send cap (sends per day; new mailboxes get 25). --daily-cap must be a positive whole number; `oxygen limits show` reports the maximum in force. The campaign ramp-up (standard 5, 10, then 20/day over the first nine sending days) climbs to this cap; customize or turn it off with `mailboxes ramp set`. Optional provider warm-up is configured with `mailboxes warmup config`. Consumes no Oxygen credits. <mailbox> accepts a mailbox id or email address.")
|
|
701
|
+
.description("Set one sending mailbox's daily send cap (sends per day; new mailboxes get 25). --daily-cap must be a positive whole number; `oxygen limits show` reports the maximum in force. The campaign ramp-up (standard 5, 10, then 20/day over the first nine sending days) climbs to this cap; customize or turn it off with `mailboxes ramp set`. Entra stays capped at 2 campaign sends/day even with its ramp off. Optional provider warm-up is configured with `mailboxes warmup config`. Consumes no Oxygen credits. <mailbox> accepts a mailbox id or email address.")
|
|
702
702
|
.argument("<mailbox>", "Mailbox id or email address.")
|
|
703
703
|
.requiredOption("--daily-cap <n>", "New daily send cap (a positive whole number of sends per day).")
|
|
704
704
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -802,7 +802,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
802
802
|
});
|
|
803
803
|
}))
|
|
804
804
|
.addCommand(new Command("sync")
|
|
805
|
-
.description("Read or control full Inbox, Sent and archive import for Gmail or Microsoft 365. History sync and CRM analysis are included in the mailbox
|
|
805
|
+
.description("Read or control full Inbox, Sent and archive import for Gmail or Microsoft 365. History sync and CRM analysis are included in the monthly mailbox connection (100 credits standard; 25 for Entra). Spam, trash and drafts are excluded. Without --action, this is a free progress read; controls preview until --approved. Disconnect keeps mail and CRM records, stops sync/sends and future mailbox commitment renewals, with no current-period refund. Paid seats and add-ons retain their own billing contracts.")
|
|
806
806
|
.argument("<mailbox>", "Mailbox ID or exact email address.")
|
|
807
807
|
.option("--action <action>", "start, pause, resume or disconnect; omit to read progress.")
|
|
808
808
|
.option("--approved", "Apply the reviewed control; default is preview.")
|
|
@@ -814,12 +814,13 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
814
814
|
: requestOxygen(`/api/cli/mailboxes/sync?${new URLSearchParams({ mailbox }).toString()}`));
|
|
815
815
|
}))
|
|
816
816
|
.addCommand(new Command("connect-oauth")
|
|
817
|
-
.description("Connect Gmail/Microsoft 365 with --vendor composio to import all mail into Unibox and business activity into CRM. The
|
|
817
|
+
.description("Connect Gmail/Microsoft 365 with --vendor composio to import all mail into Unibox and business activity into CRM. The recurring rate is 100 credits per standard mailbox-month or 25 for Entra, including history, sync and analysis; previews cost 0, with no extra execution fee. Before consent, inspect current monthly connection prices and first-debit timing with billing commitments --view connection-prices --json. Quote a standard mailbox count with billing estimate --mailboxes <n> --json; the managed preview shows the listed rate; an unavailable managed Gmail connection directs you to OXYGEN support, and an unavailable managed Microsoft connection offers OXYGEN's own sign-in (--vendor oxygen --authorization-mode individual) as an explicit, sending-only choice. Managed OAuth requires provider rollout readiness and exact registered addresses, max 10. Use mailboxes sync for progress and pause/resume/disconnect. Composio uses individual owner consent only. Native --vendor oxygen retains its existing polling; full history/CRM requires managed reconnection. Warm-up retains its existing eligibility and pricing. Preview by default; pass --approved only after reviewing exact candidates. --vendor oxygen requires an exact address list and never discovers a domain directory. For --vendor oxygen only, Microsoft supports --authorization-mode tenant (one administrator approval, up to 500 exact selected addresses, recommended for dedicated sending tenants) or individual (one account sign-in per mailbox, max 10 per run, recommended for ordinary company/personal mailboxes). For Microsoft's built-in roles, tenant mode requires a Global Administrator or Privileged Role Administrator because it requests the Microsoft Graph application Mail.Send and Mail.Read permissions; Application Administrator and Cloud Application Administrator cannot consent to Microsoft Graph application roles. Microsoft's application grant is tenant-wide at the provider; OXYGEN—not Microsoft—enforces the selected-address boundary at verification and send time unless the tenant separately configures Exchange Application RBAC. For non-managed Microsoft mailboxes, native-send approval does not authorize OXYGEN Warm-up: every mailbox still needs its own `mailboxes warmup microsoft` Outlook consent before standalone enrollment. Connecting authorization does not change whether a mailbox is active or disabled. Google uses individual OAuth; --google-smtp with vendor=oxygen explicitly previews full-mail SMTP OAuth consent. Source tokens, passwords, authenticator seeds, and one-time codes never transfer. Managed authorization paths remain available through their vendor options.")
|
|
818
818
|
.addHelpText("after", "\nDocs: https://oxygen-agent.com/docs/providers/mailboxes\n")
|
|
819
819
|
.option("--provider <provider>", "Mailbox provider: google or microsoft.")
|
|
820
820
|
.option("--vendor <vendor>", "Authorization path: composio for customer-owned Gmail/Microsoft 365 with managed consent, history and CRM; oxygen for native/tenant consent; zapmail (default) or inboxkit for provisioned inboxes.")
|
|
821
821
|
.option("--mailboxes <list>", "Comma-separated exact mailbox addresses. Required for vendor=composio (max 10) or vendor=oxygen (tenant mode max 500; individual mode max 10).")
|
|
822
822
|
.option("--authorization-mode <mode>", "vendor=oxygen only: auto, tenant, or individual. Tenant is Microsoft-only and never scans the directory.")
|
|
823
|
+
.option("--google-smtp", "oxygen + google only: request full-mail OAuth access for SMTP XOAUTH2. Preview scopes before approval.")
|
|
823
824
|
.option("--domains <list>", "Comma-separated sending domains to limit an inboxkit run to. Omit to cover every domain in the pool.")
|
|
824
825
|
.option("--no-canary", "inboxkit only: fan out to every eligible mailbox on a domain that has never connected one, instead of firing a single canary first.")
|
|
825
826
|
.option("--connection <id>", "Zapmail connection id. Defaults to the org's active Zapmail connection.")
|
|
@@ -855,6 +856,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
855
856
|
method: "POST",
|
|
856
857
|
body: {
|
|
857
858
|
provider,
|
|
859
|
+
...(options.googleSmtp === true ? { google_smtp: true } : {}),
|
|
858
860
|
// Every key stays absent unless it was asked for, so a run
|
|
859
861
|
// without the new flags is the request this command has always
|
|
860
862
|
// sent — the server reads an absent vendor as zapmail.
|
|
@@ -878,17 +880,17 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
878
880
|
.action(async (options) => {
|
|
879
881
|
await handleAsyncAction("mailboxes oauth-health", options, () => requestOxygen("/api/cli/mailboxes/oauth-health"));
|
|
880
882
|
}))
|
|
881
|
-
.addCommand(new Command("
|
|
882
|
-
.description("Assess every mailbox origin for
|
|
883
|
+
.addCommand(new Command("deliverability")
|
|
884
|
+
.description("Assess every mailbox origin for OXYGEN deliverability and connect credential-capable Google Workspace mailboxes from managed, Zapmail, or compatible encrypted external imports. Manual/native Google rows report credential_required; Microsoft 365/Azure reports vendor_blocked. Preview never materializes a password; approved execution fetches it just in time and never prints it.")
|
|
883
885
|
.addCommand(new Command("connect")
|
|
884
|
-
.description("Preview or connect selected mailboxes to
|
|
886
|
+
.description("Preview or connect selected mailboxes to OXYGEN deliverability. Managed mode is 100 credits/inbox-month, with two placement tests per inbox per cycle included; an extra managed test costs 66 credits. The preview shows the price in force. Own-key monitoring has no monthly Oxygen fee; placement tests pay the in-force platform fee and vendor charges separately. Google Workspace requires a real app password, fetched only after approval. Microsoft 365/Azure is explicitly vendor-blocked because OXYGEN deliverability exposes neither Microsoft OAuth nor tenant consent; Oxygen never downgrades it to password auth. Starts with a 0-credit, no-provider-write preview. Account connection sends no placement probe.")
|
|
885
887
|
.option("--mailboxes <list>", "Comma-separated mailbox ids or addresses. Omit for the whole pool.")
|
|
886
|
-
.option("--approved", "Perform the external
|
|
888
|
+
.option("--approved", "Perform the external OXYGEN deliverability account write.")
|
|
887
889
|
.option("--plan <hash>", "Exact hash from the fresh preview (required with --approved).")
|
|
888
890
|
.option("--max-credits <n>", "Hard first-cycle credit cap from the preview (required with --approved, including 0 for BYOK).")
|
|
889
891
|
.option("--json", "Print a JSON envelope.")
|
|
890
892
|
.action(async (options) => {
|
|
891
|
-
await handleAsyncAction("mailboxes
|
|
893
|
+
await handleAsyncAction("mailboxes deliverability connect", options, () => {
|
|
892
894
|
const mailboxes = readCsvOption(options.mailboxes);
|
|
893
895
|
const planHash = readOption(options.plan);
|
|
894
896
|
const maxCredits = options.approved
|
|
@@ -914,13 +916,13 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
914
916
|
});
|
|
915
917
|
}))
|
|
916
918
|
.addCommand(new Command("disconnect")
|
|
917
|
-
.description("Preview or remove selected mailboxes from
|
|
919
|
+
.description("Preview or remove selected mailboxes from OXYGEN deliverability and cancel their recurring monitoring subscription. Remote absence is confirmed before billing stops.")
|
|
918
920
|
.option("--mailboxes <list>", "Comma-separated mailbox ids or addresses. Omit for the whole pool.")
|
|
919
|
-
.option("--approved", "Perform the external
|
|
921
|
+
.option("--approved", "Perform the external OXYGEN deliverability account deletion and cancel billing.")
|
|
920
922
|
.option("--plan <hash>", "Exact hash from the fresh preview (required with --approved).")
|
|
921
923
|
.option("--json", "Print a JSON envelope.")
|
|
922
924
|
.action(async (options) => {
|
|
923
|
-
await handleAsyncAction("mailboxes
|
|
925
|
+
await handleAsyncAction("mailboxes deliverability disconnect", options, () => {
|
|
924
926
|
const mailboxes = readCsvOption(options.mailboxes);
|
|
925
927
|
const planHash = readOption(options.plan);
|
|
926
928
|
if (options.approved && !planHash) {
|
|
@@ -937,7 +939,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
937
939
|
});
|
|
938
940
|
}))
|
|
939
941
|
.addCommand(new Command("auto-enroll")
|
|
940
|
-
.description("Standing permission to connect NEW eligible mailboxes to
|
|
942
|
+
.description("Standing permission to connect NEW eligible mailboxes to OXYGEN deliverability automatically, so monitoring keeps up with the fleet instead of needing a manual connect per inbox. Enrolment is a RECURRING monthly charge per inbox, so arming is approval-gated and always bounded by a per-cycle credit ceiling. Without --arm/--disarm/--run this reports the current state (0 credits).")
|
|
941
943
|
.option("--arm", "Arm the standing permission. Requires --max-credits; previews unless --approved.")
|
|
942
944
|
.option("--disarm", "Turn it off. Existing subscriptions are untouched. 0 credits, no approval needed.")
|
|
943
945
|
.option("--run", "Enrol one bounded batch now.")
|
|
@@ -946,7 +948,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
946
948
|
.option("--approved", "Actually arm (otherwise --arm returns a preview).")
|
|
947
949
|
.option("--json", "Print a JSON envelope.")
|
|
948
950
|
.action(async (options) => {
|
|
949
|
-
await handleAsyncAction("mailboxes
|
|
951
|
+
await handleAsyncAction("mailboxes deliverability auto-enroll", options, () => {
|
|
950
952
|
const path = "/api/cli/mailboxes/emailguard/auto-enroll";
|
|
951
953
|
if (options.disarm) {
|
|
952
954
|
return requestOxygen(path, {
|
|
@@ -981,7 +983,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
981
983
|
});
|
|
982
984
|
})))
|
|
983
985
|
.addCommand(new Command("delegation")
|
|
984
|
-
.description("Google sending-domain delegation only — not Microsoft OAuth and not a credential-file handoff to OXYGEN Warm-up or
|
|
986
|
+
.description("Google sending-domain delegation only — not Microsoft OAuth and not a credential-file handoff to OXYGEN Warm-up or OXYGEN deliverability. Reports covered domains plus the exact client id/scopes; --probe mints a real token per mailbox. Read-only, sends no mail, 0 Oxygen credits.")
|
|
985
987
|
.option("--domain <domain>", "Only report this sending domain.")
|
|
986
988
|
.option("--probe", "Verify each delegated domain by minting a real delegated token per mailbox. Makes one Google token call per mailbox; still 0 Oxygen credits.")
|
|
987
989
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -998,7 +1000,7 @@ export function registerMailboxesFamily(program, handleAsyncAction) {
|
|
|
998
1000
|
});
|
|
999
1001
|
}))
|
|
1000
1002
|
.addCommand(new Command("warmup")
|
|
1001
|
-
.description("OXYGEN Warm-up for the sending pool — the weeks of low-volume conversational mail that make a new inbox look trusted before you cold-send from it. It costs 100 credits per warming inbox per month ($1), or nothing once it is included in
|
|
1003
|
+
.description("OXYGEN Warm-up for the sending pool — the weeks of low-volume conversational mail that make a new inbox look trusted before you cold-send from it. It costs 100 credits per warming inbox per month ($1), or nothing once it is included in the mailbox connection (100 credits standard; 25 for Entra, with warm-up included); the preview shows which applies. Fresh managed InboxKit Google/Microsoft/Azure orders with the default-on warm-up add-on activate automatically after provisioning under the approved order quote; do not run a second `warmup enable`. Google reuses the InboxKit-held credential only during activation and never stores or returns it; Microsoft/Azure uses exact native export. Standalone preview → approval is for BYOK/imported mailboxes, a managed opt-out, or later separate enrollment. InboxKit auto-export stays off for Microsoft/Azure because Oxygen submits only the exact authorized UIDs, and any non-cancelled InboxKit warm-up blocks either handoff so one mailbox cannot warm twice. OXYGEN Warm-up never owns campaign dispatch: native OXYGEN Sequences still own campaigns and sends. Eligible non-InboxKit Microsoft mailboxes use the separate `mailboxes warmup microsoft` OAuth fallback. Inboxes already warming on the retired TrulyInbox rail keep reporting and are wound down there with `warmup disable`; they are never silently moved to the current managed rail. On existing active paid seats, Google credential repair uses its own enabled-by-default switch. When the separate worker recovery mode is enforce (the unset default), Microsoft OAuth or tenant-consent seats get a credential-free vendor reactivation, and stalled dispatch or DNS readiness is rechecked. Shadow reports candidates in those enabled lanes without their vendor writes; off skips those lanes. Google credential repair can still run in shadow/off. Correct bad DNS records yourself; an active unpaused paid seat in enforce mode needs no manual resume after that correction. OXYGEN never invents consent, resumes a human or safety pause, starts a new paid enrollment, or blindly repeats an ambiguous provider write. Check `warmup status` and `mailboxes list` for the exact next action.")
|
|
1002
1004
|
.addHelpText("after", "\nDocs: https://oxygen-agent.com/docs/providers/mailboxes\n")
|
|
1003
1005
|
.addCommand(new Command("enable")
|
|
1004
1006
|
.description("Optional provider warm-up defaults to 1/day, +1/day, max 10/day (dedicated-tenant profiles stay stricter); see `mailboxes warmup config` for the effective plan and unsupported OXYGEN Warm-up reply-rate control. This is separate from the 5/10/20 campaign ramp. STANDALONE ENROLLMENT: enroll BYOK/imported mailboxes, a managed order that opted out, or mailboxes being added to warmup later. Fresh managed InboxKit Google/Microsoft/Azure orders whose approved quote included warmup activate automatically after provisioning and do not need this command. Targets the whole pool unless --mailboxes is given; one synchronous preview/approval is capped at 220 mailboxes, so split larger pools into explicit batches. Without --approved this is a 0-credit, no-provider-write priced preview: nothing is handed off, enrolled, or billed. The price is 100 credits per warming mailbox-month, or 0 once warm-up is included in the mailbox's 100-credit monthly connection (2026-09 repricing; the preview's included_in_connection says which, and its max_credits is then 0); the preview returns the exact first-cycle ceiling. Preview one inbox with `oxygen mailboxes warmup enable --mailboxes sender@example.com --json`. Execute by echoing its --plan and --max-credits with --approved. Gmail connected through Composio uses that connection after approval of standard mailbox access (send, read and organize), without a second Google consent. The preview names this access and disabling warm-up revokes it. Existing vendor-consent seats are preserved. Eligible InboxKit-managed Google targets use the InboxKit-held credential just in time without storing or returning it; Microsoft/Azure targets use native InboxKit Sequencer export with exact UIDs and auto-export off. Any non-cancelled InboxKit warmup must be cancelled before either handoff (pausing is not enough). Only eligible non-InboxKit Microsoft mailboxes need `mailboxes warmup microsoft`. OXYGEN Warm-up retains no campaign authority — OXYGEN Sequences own enrollment and dispatch. New enrollments only ever land on the managed OXYGEN rail; the retired TrulyInbox rail is refused here and only accepts teardown.")
|
|
@@ -138,7 +138,7 @@ export function registerManagedInboxesFamily(program, handleAsyncAction) {
|
|
|
138
138
|
await handleAsyncAction("managed-inboxes registrant", options, () => requestOxygen("/api/cli/managed-inboxes/registrant"));
|
|
139
139
|
}))
|
|
140
140
|
.addCommand(new Command("subscribe")
|
|
141
|
-
.description("Buy managed monthly inboxes on a new domain or your existing Cloudflare domain. Use --existing-domain for Cloudflare: the read-only preview shows ownership, occupancy, DNS scope and blockers, with zero registration charge. --max-per-domain saves a stricter limit with the approved order, including existing addresses; later additions honor it. Preview does not save the limit or change workspace data. WITHOUT --approved this prints a priced PREVIEW with a quote_id (nothing ordered, nothing charged); re-run with --approved --quote <id> to place the order. OXYGEN Warm-up defaults on for Google, Microsoft, and Azure when available: preview and approved responses carry `warmup_activation` with the exact `mailboxes` and `scope=warmup_only`; it is null when warmup is disabled or unavailable. For InboxKit, Google carries `transport=inboxkit_managed_google_handoff`; Microsoft/Azure carries `transport=inboxkit_sequencer_export`. Both carry `mailbox_credentials_required=false` and `native_send_oauth_separate=true`, so managed warm-up needs no customer-supplied OAuth or mailbox password. Google retrieves the existing managed credential only during activation and never persists or returns it; Microsoft/Azure uses native export. Native sending authorization is separate and may still be required before OXYGEN can send. Verify each exact address with `mailboxes oauth-health --json`; InboxKit's unattended Google domain approval usually lands within minutes and the worker keeps retrying, so a waiting row is not a reason to ask the customer to sign in. This order authorizes automatic OXYGEN Warm-up activation after provisioning, so no second `mailboxes warmup enable` approval is needed. The preview shows the price in force: from the 2026-09 repricing date a Google Workspace or Microsoft 365 inbox is one flat mailbox line plus the 100-credit mailbox connection, warm-up included, which `connection_monthly_credits` shows and your balance reserves once the inbox connects (400 credits a month all-in at the committed price), and an Azure tenant is one flat charge for its domain (4,000 a month) plus each inbox's connection; `managed-inboxes list --json` reports the prices in force under `prices`. Domains are priced from the vendor's LIVE domain price, and any price below vendor cost is refused. A domain you bought on its own through Oxygen (`domains buy`) takes its first inboxes here too: the preview says `domain_source: owned`, nothing is registered again and `one_off_credits` is 0 (Google or
|
|
141
|
+
.description("Buy managed monthly inboxes on a new domain or your existing Cloudflare domain. Use --existing-domain for Cloudflare: the read-only preview shows ownership, occupancy, DNS scope and blockers, with zero registration charge. --max-per-domain saves a stricter limit with the approved order, including existing addresses; later additions honor it. Preview does not save the limit or change workspace data. WITHOUT --approved this prints a priced PREVIEW with a quote_id (nothing ordered, nothing charged); re-run with --approved --quote <id> to place the order. OXYGEN Warm-up defaults on for Google, Microsoft, and Azure when available: preview and approved responses carry `warmup_activation` with the exact `mailboxes` and `scope=warmup_only`; it is null when warmup is disabled or unavailable. For InboxKit, Google carries `transport=inboxkit_managed_google_handoff`; Microsoft/Azure carries `transport=inboxkit_sequencer_export`. Both carry `mailbox_credentials_required=false` and `native_send_oauth_separate=true`, so managed warm-up needs no customer-supplied OAuth or mailbox password. Google retrieves the existing managed credential only during activation and never persists or returns it; Microsoft/Azure uses native export. Native sending authorization is separate and may still be required before OXYGEN can send. Verify each exact address with `mailboxes oauth-health --json`; InboxKit's unattended Google domain approval usually lands within minutes and the worker keeps retrying, so a waiting row is not a reason to ask the customer to sign in. This order authorizes automatic OXYGEN Warm-up activation after provisioning, so no second `mailboxes warmup enable` approval is needed. The preview shows the price in force: from the 2026-09 repricing date a Google Workspace or Microsoft 365 inbox is one flat mailbox line plus the 100-credit mailbox connection, warm-up included, which `connection_monthly_credits` shows and your balance reserves once the inbox connects (400 credits a month all-in at the committed price), and an Azure/Entra tenant is one flat hosting charge for its domain (4,000 credits/$40 a month) plus each inbox's 25-credit ($0.25) Sequencer connection with warm-up included: 6,500 credits/$65 monthly for 100 inboxes; `managed-inboxes list --json` reports the prices in force under `prices`. Domains are priced from the vendor's LIVE domain price, and any price below vendor cost is refused. A domain you bought on its own through Oxygen (`domains buy`) takes its first inboxes here too: the preview says `domain_source: owned`, nothing is registered again and `one_off_credits` is 0 (Google, Microsoft or Azure; omit --years). To add mailboxes to a domain that already has inboxes use `managed-inboxes add-inboxes`.")
|
|
142
142
|
.argument("[domain]", "Sending domain to register + host the mailboxes (e.g. send.acme.com), or one you already bought through Oxygen. May also be passed as --domain.")
|
|
143
143
|
.option("--domain <domain>", "Sending domain (alternative to the positional argument).")
|
|
144
144
|
.requiredOption("--provider <provider>", "Mailbox PLATFORM: google, microsoft, or azure. (google/microsoft cap at 5 mailboxes per domain; azure allows 100.)")
|