@oxygen-agent/cli 1.691.5 → 1.692.5

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.691.5
37
+ Version: 1.692.5
@@ -46,6 +46,13 @@ const MUTATING_VERBS = new Set([
46
46
  "unbind", "unsubscribe", "update", "upload", "upsert", "use", "warm-send", "warmup",
47
47
  "withdraw", "write",
48
48
  ]);
49
+ // `--approved` usually identifies a paid or externally mutating execution
50
+ // mode, but a small number of destructive local operations are explicitly
51
+ // zero-credit. Keep those exceptions exact so discovery never invents spend.
52
+ const ZERO_CREDIT_APPROVAL_COMMANDS = new Set(["mailboxes delete"]);
53
+ // These commands preview when the execution approval is omitted even though
54
+ // their gate is named `--approved` rather than `--live`.
55
+ const APPROVAL_PREVIEW_COMMANDS = new Set(["mailboxes delete"]);
49
56
  export function buildCommandManifest(program, binaryName) {
50
57
  const commands = [];
51
58
  for (const child of program.commands) {
@@ -172,8 +179,9 @@ function toManifestEntry(command, path) {
172
179
  const flagStrings = options.map((option) => option.flags);
173
180
  const leafVerb = path[path.length - 1] ?? "";
174
181
  const verbPrefix = leafVerb.split("-")[0] ?? leafVerb;
182
+ const commandName = path.join(" ");
175
183
  return {
176
- name: path.join(" "),
184
+ name: commandName,
177
185
  group: path[0] ?? "",
178
186
  description: command.description(),
179
187
  arguments: command.registeredArguments.map((argument) => ({
@@ -186,20 +194,22 @@ function toManifestEntry(command, path) {
186
194
  description: option.description,
187
195
  required: option.mandatory,
188
196
  })),
189
- spends_credits: flagStrings.some((flags) => flags.includes("--approved") ||
190
- flags.includes("--max-credits") ||
191
- // Copilot's explicit session inference-budget approval flag.
192
- flags.includes("--budget-credits")) ||
193
- // `copilot send` bills managed inference (5x actual model cost) inside the
194
- // session budget approved at `copilot start`, and `copilot approve`
195
- // dispatches the gated paid/external capability and requeues the paused
196
- // turn (waking the worker for more 5x-billed inference) — no per-command
197
- // cap flag exists for either, so the flag heuristic alone would misreport
198
- // them as free.
199
- (path[0] === "copilot" &&
200
- (leafVerb === "send" || leafVerb === "approve")),
197
+ spends_credits: !ZERO_CREDIT_APPROVAL_COMMANDS.has(commandName) &&
198
+ (flagStrings.some((flags) => flags.includes("--approved") ||
199
+ flags.includes("--max-credits") ||
200
+ // Copilot's explicit session inference-budget approval flag.
201
+ flags.includes("--budget-credits")) ||
202
+ // `copilot send` bills managed inference (5x actual model cost) inside the
203
+ // session budget approved at `copilot start`, and `copilot approve`
204
+ // dispatches the gated paid/external capability and requeues the paused
205
+ // turn (waking the worker for more 5x-billed inference) — no per-command
206
+ // cap flag exists for either, so the flag heuristic alone would misreport
207
+ // them as free.
208
+ (path[0] === "copilot" &&
209
+ (leafVerb === "send" || leafVerb === "approve"))),
201
210
  mutates: MUTATING_VERBS.has(leafVerb) || MUTATING_VERBS.has(verbPrefix),
202
- preview_by_default: flagStrings.some((flags) => flags.includes("--live")),
211
+ preview_by_default: flagStrings.some((flags) => flags.includes("--live")) ||
212
+ APPROVAL_PREVIEW_COMMANDS.has(commandName),
203
213
  json_supported: flagStrings.some((flags) => flags.includes("--json")),
204
214
  hidden: isHiddenCommand(command),
205
215
  };
package/dist/index.js CHANGED
@@ -12704,6 +12704,35 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
12704
12704
  .option("--json", "Print a JSON envelope.")
12705
12705
  .action(async (mailbox, options) => {
12706
12706
  await handleAsyncAction("mailboxes get", options, () => requestOxygen(`/api/cli/mailboxes/${encodeURIComponent(mailbox)}`));
12707
+ }))
12708
+ .addCommand(new Command("delete")
12709
+ .description("Preview or approve removal of exact mailboxes from Oxygen at 0 credits. Deletion immediately removes them from sending but never deletes the underlying Google Workspace or Microsoft 365 accounts. It preserves conversation/message/delivery history and stops the 1,000-credit mailbox commitment for future renewals; the current period is not refunded. Listed active/paused sequences keep their status but lose these senders and are not automatically paused. Managed mailboxes and live warmup/monitoring add-ons fail closed with their exact address-scoped teardown steps.")
12710
+ .requiredOption("--mailboxes <list>", "Comma-separated mailbox ids or addresses (maximum 500).")
12711
+ .option("--approved", "Execute the fresh preview. Requires --plan-hash and --confirmation.")
12712
+ .option("--plan-hash <hash>", "Fresh preview plan_hash.")
12713
+ .option("--confirmation <phrase>", "Exact confirmation_phrase returned by the fresh preview.")
12714
+ .option("--json", "Print a JSON envelope.")
12715
+ .action(async (options) => {
12716
+ await handleAsyncAction("mailboxes delete", options, () => {
12717
+ const mailboxes = readCsvOption(options.mailboxes);
12718
+ if (mailboxes.length === 0) {
12719
+ throw new Error("--mailboxes must contain at least one mailbox id or address.");
12720
+ }
12721
+ const planHash = readOption(options.planHash);
12722
+ const confirmation = readOption(options.confirmation);
12723
+ if (options.approved === true && (!planHash || !confirmation)) {
12724
+ throw new Error("--approved requires --plan-hash and --confirmation from a fresh deletion preview.");
12725
+ }
12726
+ return requestOxygen("/api/cli/mailboxes/delete", {
12727
+ method: "POST",
12728
+ body: {
12729
+ mailboxes,
12730
+ ...(options.approved === true ? { approved: true } : {}),
12731
+ ...(planHash ? { plan_hash: planHash } : {}),
12732
+ ...(confirmation ? { confirmation } : {}),
12733
+ },
12734
+ });
12735
+ });
12707
12736
  }))
12708
12737
  .addCommand(new Command("health")
12709
12738
  .description("Fleet email-health: the sending pool rolled up by external deliverability reputation (healthy/degraded/critical/unknown), per-mailbox scores, connected health providers, and DIRECTIONAL recommendations. Pure read — 0 credits; never pauses a mailbox.")
@@ -12746,7 +12775,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
12746
12775
  "Credential file contract:",
12747
12776
  ' JSON: {"mailboxes":[{"email_address":"ada@send-acme.com","provider":"google","app_password":"<Google mailbox app password>"}]}',
12748
12777
  " Only Google app passwords enter the encrypted seven-day transfer vault. Microsoft rows remain identity-only. Generic SMTP passwords and OAuth/MFA/delegation secrets are rejected.",
12749
- " Validation: add --validate-only to parse the complete real file and return safe aggregate counts without authentication, a network request, or a workspace write. Any parse, shape, provider, platform, tenant, secret-policy, or duplicate-conflict error rejects the entire file before the first mailbox write and names mailboxes[index]; validation-only never writes. A later import infrastructure failure may interrupt the upsert; re-run the same file because import is idempotent by address.",
12778
+ " Validation: add --validate-only to parse the complete real file and return safe aggregate counts plus the provider-specific exact-account OAuth review plan without authentication, a network request, or a workspace write. The plan groups only supplied addresses into reviews of at most 10; it never discovers a domain, and the later online preview may skip existing grants. Any parse, shape, provider, platform, tenant, secret-policy, or duplicate-conflict error rejects the entire file before the first mailbox write and names mailboxes[index]; validation-only never writes. A later import infrastructure failure may interrupt the upsert; re-run the same file because import is idempotent by address.",
12750
12779
  " Docs: https://oxygen-agent.com/docs/providers/mailbox-compatibility",
12751
12780
  " Skill: oxygen-email-infra (`oxygen skills install --skill oxygen-email-infra`).",
12752
12781
  "",
@@ -512,6 +512,12 @@ function normalizeIntent(query) {
512
512
  return query.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
513
513
  }
514
514
  function explicitCapabilityIntent(query) {
515
+ if (isMailboxDeleteIntent(query)) {
516
+ return ROUTE_BY_ID.get("sending-infrastructure") ?? null;
517
+ }
518
+ if (isMailboxOnboardingIntent(query)) {
519
+ return ROUTE_BY_ID.get("sending-infrastructure") ?? null;
520
+ }
515
521
  if (isHostedWorkflowIntent(query))
516
522
  return ROUTE_BY_PRIMITIVE.get("workflows") ?? null;
517
523
  if (isOwnedPostCommentIntent(query))
@@ -603,6 +609,36 @@ function highestScoringRoute(query) {
603
609
  return best?.card ?? null;
604
610
  }
605
611
  function recommendationsFor(card, query) {
612
+ if (card.id === "sending-infrastructure" && isMailboxDeleteIntent(query)) {
613
+ return {
614
+ tools: [
615
+ "oxygen_mailboxes_delete",
616
+ "oxygen_mailboxes_compatibility",
617
+ "oxygen_mailboxes_list",
618
+ ],
619
+ commands: [
620
+ "mailboxes delete",
621
+ "mailboxes compatibility",
622
+ "mailboxes list",
623
+ ],
624
+ };
625
+ }
626
+ if (card.id === "sending-infrastructure" && isMailboxOnboardingIntent(query)) {
627
+ return {
628
+ tools: [
629
+ "oxygen_mailboxes_compatibility",
630
+ "oxygen_mailboxes_import",
631
+ "oxygen_mailboxes_connect_oauth",
632
+ "oxygen_mailboxes_oauth_health",
633
+ ],
634
+ commands: [
635
+ "mailboxes compatibility",
636
+ "mailboxes import",
637
+ "mailboxes connect-oauth",
638
+ "mailboxes oauth-health",
639
+ ],
640
+ };
641
+ }
606
642
  if (card.primitive === "posts" && isOwnedPostCommentIntent(query)) {
607
643
  return {
608
644
  tools: ["oxygen_publishing_comments", "oxygen_publishing_analytics"],
@@ -782,6 +818,18 @@ function recommendationsFor(card, query) {
782
818
  }
783
819
  return { tools: [...card.gatewayTools], commands: [...card.gatewayCommands] };
784
820
  }
821
+ function isMailboxOnboardingIntent(query) {
822
+ const operation = /\b(import|migrat\w*|onboard|register|upload|bring|connect existing|add existing)\b/.test(query);
823
+ const mailboxScope = /\b(mailbox(?:es)?|inbox(?:es)?|sender accounts?|email accounts?|sending pool)\b/.test(query);
824
+ return operation && mailboxScope;
825
+ }
826
+ function isMailboxDeleteIntent(query) {
827
+ const mailboxScope = /\b(mailbox(?:es)?|inbox(?:es)?|sender accounts?|email accounts?)\b/.test(query);
828
+ const removal = /\b(delete|remove)\b/.test(query)
829
+ || /\bdisconnect\b.{0,24}\b(mailbox(?:es)?|inbox(?:es)?|sender accounts?|email accounts?)\b/.test(query);
830
+ const scopedDetach = /\b(remove|disconnect)\b.{0,48}\b(mailbox(?:es)?|inbox(?:es)?|sender accounts?|email accounts?)\b.{0,24}\b(?:from|in|on)\b.{0,24}\b(sequence|campaign|cadence|sender profile)\b/.test(query);
831
+ return mailboxScope && removal && !scopedDetach;
832
+ }
785
833
  function isNetNewLinkedInInitiation(query) {
786
834
  const mentionsLinkedIn = /\blinkedin\b|\bdm\b/.test(query);
787
835
  const startsConversation = /\b(send|message|dm|contact|reach out|initiate|start)\b/.test(query)
@@ -21,6 +21,7 @@ export type MailboxImportValidationSummary = {
21
21
  infrastructure_platforms: Record<string, number>;
22
22
  credential_rows: number;
23
23
  identity_only_rows: number;
24
+ oauth_review_plan: MailboxOAuthReviewPlan;
24
25
  limits: {
25
26
  max_rows: number;
26
27
  max_file_bytes: number;
@@ -31,8 +32,26 @@ export type MailboxImportValidationSummary = {
31
32
  credits_used: 0;
32
33
  next_action: string;
33
34
  };
35
+ export type MailboxOAuthProviderReviewPlan = {
36
+ mailboxes: number;
37
+ batches: number;
38
+ batch_sizes: number[];
39
+ };
40
+ export type MailboxOAuthReviewPlan = {
41
+ authorization: "exact_account_oauth";
42
+ scope: "validated_addresses";
43
+ directory_discovery: false;
44
+ existing_grants_checked: false;
45
+ batch_size: number;
46
+ total_batches: number;
47
+ providers: {
48
+ google: MailboxOAuthProviderReviewPlan;
49
+ microsoft: MailboxOAuthProviderReviewPlan;
50
+ };
51
+ };
34
52
  export declare const MAILBOX_IMPORT_FILE_MAX_BYTES: number;
35
53
  export declare const MAILBOX_IMPORT_ROW_LIMIT = 500;
54
+ export declare const MAILBOX_OAUTH_REVIEW_BATCH_SIZE = 10;
36
55
  /**
37
56
  * Infer the bounded mailbox-import format without node:path so browser and CLI
38
57
  * preflight use the same extension contract.
@@ -55,4 +74,11 @@ export declare function summarizeMailboxImportValidation(mailboxes: readonly Nor
55
74
  source: MailboxImportValidationSource;
56
75
  sourceProvider: string | null;
57
76
  }): MailboxImportValidationSummary;
77
+ /**
78
+ * Build the deterministic, provider-specific review queue for exact-account
79
+ * OAuth. This is safe during local file validation: it uses only normalized
80
+ * provider counts, performs no directory discovery, and does not claim to know
81
+ * whether a workspace already holds a usable grant for an address.
82
+ */
83
+ export declare function planMailboxOAuthReviews(mailboxes: readonly Pick<NormalizedMailboxImportRow, "provider">[]): MailboxOAuthReviewPlan;
58
84
  export declare function normalizeMailboxImportVendor(raw: string | null | undefined, from: string | null | undefined): string | null;
@@ -1,6 +1,7 @@
1
1
  import { OxygenError } from "./cli-result.js";
2
2
  export const MAILBOX_IMPORT_FILE_MAX_BYTES = 5 * 1024 * 1024;
3
3
  export const MAILBOX_IMPORT_ROW_LIMIT = 500;
4
+ export const MAILBOX_OAUTH_REVIEW_BATCH_SIZE = 10;
4
5
  const MAILBOX_EMAIL_HEADERS = new Set([
5
6
  "address",
6
7
  "email",
@@ -194,6 +195,7 @@ export function summarizeMailboxImportValidation(mailboxes, input) {
194
195
  infrastructure_platforms: infrastructurePlatforms,
195
196
  credential_rows: credentialRows,
196
197
  identity_only_rows: mailboxes.length - credentialRows,
198
+ oauth_review_plan: planMailboxOAuthReviews(mailboxes),
197
199
  limits: {
198
200
  max_rows: MAILBOX_IMPORT_ROW_LIMIT,
199
201
  max_file_bytes: MAILBOX_IMPORT_FILE_MAX_BYTES,
@@ -205,6 +207,40 @@ export function summarizeMailboxImportValidation(mailboxes, input) {
205
207
  next_action: "Submit this reviewed file to register the mailboxes.",
206
208
  };
207
209
  }
210
+ /**
211
+ * Build the deterministic, provider-specific review queue for exact-account
212
+ * OAuth. This is safe during local file validation: it uses only normalized
213
+ * provider counts, performs no directory discovery, and does not claim to know
214
+ * whether a workspace already holds a usable grant for an address.
215
+ */
216
+ export function planMailboxOAuthReviews(mailboxes) {
217
+ const counts = { google: 0, microsoft: 0 };
218
+ for (const mailbox of mailboxes)
219
+ counts[mailbox.provider] += 1;
220
+ const google = planProviderOAuthReviews(counts.google);
221
+ const microsoft = planProviderOAuthReviews(counts.microsoft);
222
+ return {
223
+ authorization: "exact_account_oauth",
224
+ scope: "validated_addresses",
225
+ directory_discovery: false,
226
+ existing_grants_checked: false,
227
+ batch_size: MAILBOX_OAUTH_REVIEW_BATCH_SIZE,
228
+ total_batches: google.batches + microsoft.batches,
229
+ providers: { google, microsoft },
230
+ };
231
+ }
232
+ function planProviderOAuthReviews(mailboxes) {
233
+ const fullBatches = Math.floor(mailboxes / MAILBOX_OAUTH_REVIEW_BATCH_SIZE);
234
+ const remainder = mailboxes % MAILBOX_OAUTH_REVIEW_BATCH_SIZE;
235
+ const batchSizes = Array.from({ length: fullBatches }, () => MAILBOX_OAUTH_REVIEW_BATCH_SIZE);
236
+ if (remainder > 0)
237
+ batchSizes.push(remainder);
238
+ return {
239
+ mailboxes,
240
+ batches: batchSizes.length,
241
+ batch_sizes: batchSizes,
242
+ };
243
+ }
208
244
  export function normalizeMailboxImportVendor(raw, from) {
209
245
  if (from === "hypertide") {
210
246
  if (raw && raw.trim().toLowerCase() !== "hypertide") {
@@ -1,3 +1,3 @@
1
- export declare const OXYGEN_VERSION = "1.691.5";
1
+ export declare const OXYGEN_VERSION = "1.692.5";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
3
  export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.691.5";
1
+ export const OXYGEN_VERSION = "1.692.5";
2
2
  // The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
3
3
  // operational route. Raising it hard-rejects every older CLI from the entire
4
4
  // product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.691.5",
3
+ "version": "1.692.5",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",