@oxygen-agent/cli 1.1014.1600 → 1.1014.1696

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +4 -1
  3. package/dist/commands/custom-integrations.js +13 -0
  4. package/dist/commands/inbox.js +1 -1
  5. package/dist/commands/integrations.js +5 -1
  6. package/dist/commands/mailboxes.js +26 -6
  7. package/dist/commands/managed-inboxes.js +2 -2
  8. package/dist/commands/senders.js +4 -4
  9. package/dist/credential-storage/store.d.ts +10 -0
  10. package/dist/credential-storage/store.js +282 -0
  11. package/dist/credentials.d.ts +1 -3
  12. package/dist/credentials.js +3 -275
  13. package/dist/mailbox-fleet-output.js +11 -2
  14. package/dist/workspace-placement-commands.js +3 -2
  15. package/node_modules/@oxygen/shared/dist/capability-discovery.js +63 -9
  16. package/node_modules/@oxygen/shared/dist/clay-migration-auth-state.d.ts +7 -1
  17. package/node_modules/@oxygen/shared/dist/clay-migration-auth-state.js +6 -5
  18. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +1 -0
  19. package/node_modules/@oxygen/shared/dist/contracts/clay-migration.d.ts +39 -1
  20. package/node_modules/@oxygen/shared/dist/contracts/clay-migration.js +32 -4
  21. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +2 -1
  22. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +1 -1
  23. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +1 -1
  24. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +2 -2
  25. package/node_modules/@oxygen/shared/dist/email-classification-scope.d.ts +35 -0
  26. package/node_modules/@oxygen/shared/dist/email-classification-scope.js +40 -0
  27. package/node_modules/@oxygen/shared/dist/native-chat.d.ts +9 -0
  28. package/node_modules/@oxygen/shared/dist/native-chat.js +12 -1
  29. package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +9 -2
  30. package/node_modules/@oxygen/shared/dist/onboarding-research.js +9 -2
  31. package/node_modules/@oxygen/shared/dist/recipes.d.ts +2 -0
  32. package/node_modules/@oxygen/shared/dist/recipes.js +13 -0
  33. package/node_modules/@oxygen/shared/dist/sending-domain-rollout.d.ts +7 -0
  34. package/node_modules/@oxygen/shared/dist/sending-domain-rollout.js +28 -0
  35. package/node_modules/@oxygen/shared/dist/tool-call-arguments.d.ts +24 -0
  36. package/node_modules/@oxygen/shared/dist/tool-call-arguments.js +68 -0
  37. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  38. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  39. package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +3 -0
  40. package/node_modules/@oxygen/shared/dist/workspace-placement.js +11 -0
  41. package/package.json +1 -1
@@ -1,16 +1,10 @@
1
- import { execFile, spawn } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, chmodSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { dirname, join } from "node:path";
5
- import { promisify } from "node:util";
1
+ import { createCredentialStorage } from "./credential-storage/store.js";
6
2
  import { OxygenError } from "@oxygen/shared/cli-result";
7
3
  import { sameApiOrigin } from "./runtime.js";
8
- const execFileAsync = promisify(execFile);
9
- const SERVICE_NAME = "oxygen-cli";
10
- const ACCOUNT_NAME = "default";
4
+ export { resolveConfigDir } from "./credential-storage/store.js";
5
+ const { readSystemCredentialPayload, writeSystemCredentialPayload, deleteSystemCredentialPayload } = createCredentialStorage();
11
6
  const DEFAULT_API_URL = "https://oxygen-agent.com";
12
7
  const DEFAULT_PROFILE_NAME = "default";
13
- const WINDOWS_CREDENTIAL_PAYLOAD_ENV = "OXYGEN_CLI_WINDOWS_CREDENTIAL_PAYLOAD";
14
8
  function readEnvApiUrl(env) {
15
9
  const apiUrl = env.OXYGEN_API_URL?.trim();
16
10
  return apiUrl ? normalizeApiUrl(apiUrl) : null;
@@ -347,38 +341,6 @@ async function readSystemCredentialDocument(env) {
347
341
  return null;
348
342
  return parseCredentialDocument(payload);
349
343
  }
350
- async function readSystemCredentialPayload(env) {
351
- const store = credentialStore(env);
352
- if (store === "keychain")
353
- return readMacOSKeychainPayload(env);
354
- if (store === "credential-manager")
355
- return readWindowsCredentialManagerPayload(env);
356
- return readFileCredentialPayload(env);
357
- }
358
- async function writeSystemCredentialPayload(payload, env) {
359
- const store = credentialStore(env);
360
- if (store === "keychain") {
361
- await writeMacOSKeychainPayload(payload, env);
362
- return;
363
- }
364
- if (store === "credential-manager") {
365
- await writeWindowsCredentialManagerPayload(payload, env);
366
- return;
367
- }
368
- writeFileCredentialPayload(payload, env);
369
- }
370
- async function deleteSystemCredentialPayload(env) {
371
- const store = credentialStore(env);
372
- if (store === "keychain") {
373
- await deleteMacOSKeychain(env);
374
- return;
375
- }
376
- if (store === "credential-manager") {
377
- await deleteWindowsCredentialManager(env);
378
- return;
379
- }
380
- deleteFileCredentials(env);
381
- }
382
344
  async function writeCredentialDocument(input, env) {
383
345
  const activeCredentials = input.profiles[input.activeProfile];
384
346
  if (!activeCredentials) {
@@ -397,240 +359,6 @@ async function writeCredentialDocument(input, env) {
397
359
  document.identity = activeCredentials.identity;
398
360
  await writeSystemCredentialPayload(serializeCredentialDocument(document), env);
399
361
  }
400
- function credentialStore(env) {
401
- const forced = env.OXYGEN_CREDENTIAL_STORE?.trim().toLowerCase();
402
- if (forced === "file")
403
- return "file";
404
- if (forced === "keychain")
405
- return "keychain";
406
- if (forced === "credential-manager")
407
- return "credential-manager";
408
- if (process.platform === "darwin")
409
- return "keychain";
410
- if (process.platform === "win32")
411
- return "credential-manager";
412
- return "file";
413
- }
414
- /** The CLI's per-user config directory; `OXYGEN_CONFIG_DIR` overrides it. */
415
- export function resolveConfigDir(env = process.env) {
416
- return env.OXYGEN_CONFIG_DIR?.trim()
417
- || (process.platform === "win32"
418
- ? join(env.APPDATA || join(homedir(), "AppData", "Roaming"), "oxygen")
419
- : join(homedir(), ".config", "oxygen"));
420
- }
421
- function credentialsPath(env) {
422
- return join(resolveConfigDir(env), "credentials.json");
423
- }
424
- function readFileCredentialPayload(env) {
425
- const path = credentialsPath(env);
426
- if (!existsSync(path))
427
- return null;
428
- return readFileSync(path, "utf8");
429
- }
430
- function writeFileCredentialPayload(payload, env) {
431
- const path = credentialsPath(env);
432
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
433
- const tempPath = `${path}.${process.pid}.tmp`;
434
- writeFileSync(tempPath, payload, { mode: 0o600 });
435
- chmodSync(tempPath, 0o600);
436
- renameSync(tempPath, path);
437
- chmodSync(path, 0o600);
438
- }
439
- function deleteFileCredentials(env) {
440
- const path = credentialsPath(env);
441
- rmSync(path, { force: true });
442
- }
443
- async function readMacOSKeychainPayload(env) {
444
- try {
445
- const { stdout } = await execFileAsync(macOSSecurityCommand(env), [
446
- "find-generic-password",
447
- "-a",
448
- ACCOUNT_NAME,
449
- "-s",
450
- SERVICE_NAME,
451
- "-w",
452
- ], { env });
453
- return stdout;
454
- }
455
- catch {
456
- return null;
457
- }
458
- }
459
- // `security -i` reads commands with a line buffer that traditionally caps at
460
- // 4096 bytes. Beyond that, the input is silently truncated, which would
461
- // corrupt the stored credential and break re-auth. Reject early with a clear
462
- // error so a user with too many profiles gets actionable feedback instead of
463
- // a partial-write that only surfaces on the next CLI invocation.
464
- const MACOS_SECURITY_LINE_LIMIT_BYTES = 4096;
465
- async function writeMacOSKeychainPayload(payload, env) {
466
- // `security add-generic-password -w PAYLOAD` would put the token in argv
467
- // (visible to any same-UID `ps`). Use `security -i` instead: the subcommand
468
- // is written on stdin, so the only argv `ps` ever sees is `security -i`.
469
- const command = macOSSecurityCommand(env);
470
- const escaped = payload.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
471
- const line = `add-generic-password -a "${ACCOUNT_NAME}" -s "${SERVICE_NAME}" -w "${escaped}" -U\n`;
472
- const lineBytes = Buffer.byteLength(line, "utf8");
473
- if (lineBytes >= MACOS_SECURITY_LINE_LIMIT_BYTES) {
474
- throw credentialStoreError("write", "macOS Keychain", {
475
- code: "payload_too_large",
476
- stderr: `Credential document is ${lineBytes} bytes; \`security -i\` truncates lines at ${MACOS_SECURITY_LINE_LIMIT_BYTES} bytes. Reduce stored profiles (oxygen logout <profile>) and retry.`,
477
- });
478
- }
479
- await new Promise((resolve, reject) => {
480
- const child = spawn(command, ["-i"], { env, stdio: ["pipe", "pipe", "pipe"] });
481
- let stderr = "";
482
- child.stderr.on("data", (chunk) => {
483
- stderr += chunk.toString("utf8");
484
- });
485
- child.once("error", (error) => {
486
- reject(credentialStoreError("write", "macOS Keychain", error));
487
- });
488
- child.once("close", (code, signal) => {
489
- const trimmedStderr = stderr.trim();
490
- if (code === 0 && !trimmedStderr) {
491
- resolve();
492
- return;
493
- }
494
- reject(credentialStoreError("write", "macOS Keychain", { code, signal, stderr: trimmedStderr }));
495
- });
496
- child.stdin.end(line);
497
- });
498
- }
499
- async function deleteMacOSKeychain(env) {
500
- await execFileAsync(macOSSecurityCommand(env), [
501
- "delete-generic-password",
502
- "-a",
503
- ACCOUNT_NAME,
504
- "-s",
505
- SERVICE_NAME,
506
- ], { env }).catch(() => undefined);
507
- }
508
- async function readWindowsCredentialManagerPayload(env) {
509
- try {
510
- const { stdout } = await runPowerShell(`${windowsCredentialManagerScript()}
511
- $ptr = [IntPtr]::Zero
512
- try {
513
- if (-not [OxygenCredMan]::CredRead("${SERVICE_NAME}", 1, 0, [ref]$ptr)) { exit 2 }
514
- $cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][OxygenCredMan+CREDENTIAL])
515
- $secret = [Runtime.InteropServices.Marshal]::PtrToStringUni($cred.CredentialBlob, [int]($cred.CredentialBlobSize / 2))
516
- [Console]::Out.Write($secret)
517
- } finally {
518
- if ($ptr -ne [IntPtr]::Zero) { [OxygenCredMan]::CredFree($ptr) }
519
- }
520
- `, env);
521
- return stdout;
522
- }
523
- catch {
524
- return null;
525
- }
526
- }
527
- async function writeWindowsCredentialManagerPayload(value, env) {
528
- const payload = Buffer.from(value, "utf16le").toString("base64");
529
- try {
530
- await runPowerShell(`${windowsCredentialManagerScript()}
531
- $payload = $env:${WINDOWS_CREDENTIAL_PAYLOAD_ENV}
532
- if ([string]::IsNullOrWhiteSpace($payload)) { exit 1 }
533
- $secret = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($payload))
534
- $bytes = [Text.Encoding]::Unicode.GetBytes($secret)
535
- $blob = [Runtime.InteropServices.Marshal]::StringToCoTaskMemUni($secret)
536
- try {
537
- $cred = New-Object OxygenCredMan+CREDENTIAL
538
- $cred.Type = 1
539
- $cred.TargetName = "${SERVICE_NAME}"
540
- $cred.UserName = "${ACCOUNT_NAME}"
541
- $cred.CredentialBlob = $blob
542
- $cred.CredentialBlobSize = $bytes.Length
543
- $cred.Persist = 2
544
- if (-not [OxygenCredMan]::CredWrite([ref]$cred, 0)) { exit 1 }
545
- } finally {
546
- [Runtime.InteropServices.Marshal]::FreeCoTaskMem($blob)
547
- }
548
- `, {
549
- ...env,
550
- [WINDOWS_CREDENTIAL_PAYLOAD_ENV]: payload,
551
- });
552
- }
553
- catch (error) {
554
- throw credentialStoreError("write", "Windows Credential Manager", error);
555
- }
556
- }
557
- async function deleteWindowsCredentialManager(env) {
558
- await runPowerShell(`${windowsCredentialManagerScript()}
559
- [void][OxygenCredMan]::CredDelete("${SERVICE_NAME}", 1, 0)
560
- `, env).catch(() => undefined);
561
- }
562
- function runPowerShell(script, env) {
563
- const encodedCommand = Buffer.from(script, "utf16le").toString("base64");
564
- return execFileAsync(windowsPowerShellCommand(env), [
565
- "-NoProfile",
566
- "-NonInteractive",
567
- "-ExecutionPolicy",
568
- "Bypass",
569
- "-EncodedCommand",
570
- encodedCommand,
571
- ], { env });
572
- }
573
- function credentialStoreError(operation, store, error) {
574
- const child = error;
575
- const details = {
576
- operation,
577
- store,
578
- };
579
- if (typeof child.code === "number") {
580
- details.exit_code = child.code;
581
- }
582
- else if (typeof child.code === "string") {
583
- details.reason = child.code;
584
- }
585
- if (typeof child.signal === "string")
586
- details.signal = child.signal;
587
- if (typeof child.stderr === "string" && child.stderr.trim()) {
588
- details.stderr = sanitizeCredentialErrorText(child.stderr.trim()).slice(0, 4000);
589
- }
590
- return new OxygenError("credential_store_failed", `Unable to ${operation} Oxygen CLI credentials in ${store}.`, { details, exitCode: 1 });
591
- }
592
- function sanitizeCredentialErrorText(value) {
593
- return value.replace(/\boxy_(?:live|user)_[A-Za-z0-9_-]+\b/g, "[redacted_token]");
594
- }
595
- function macOSSecurityCommand(env) {
596
- return env.OXYGEN_SECURITY_COMMAND?.trim() || "security";
597
- }
598
- function windowsPowerShellCommand(env) {
599
- return env.OXYGEN_POWERSHELL_COMMAND?.trim() || "powershell.exe";
600
- }
601
- function windowsCredentialManagerScript() {
602
- return `
603
- Add-Type @"
604
- using System;
605
- using System.Runtime.InteropServices;
606
- public static class OxygenCredMan {
607
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
608
- public struct CREDENTIAL {
609
- public UInt32 Flags;
610
- public UInt32 Type;
611
- public string TargetName;
612
- public string Comment;
613
- public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
614
- public UInt32 CredentialBlobSize;
615
- public IntPtr CredentialBlob;
616
- public UInt32 Persist;
617
- public UInt32 AttributeCount;
618
- public IntPtr Attributes;
619
- public string TargetAlias;
620
- public string UserName;
621
- }
622
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
623
- public static extern bool CredWrite([In] ref CREDENTIAL userCredential, UInt32 flags);
624
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
625
- public static extern bool CredRead(string target, UInt32 type, UInt32 reservedFlag, out IntPtr credentialPtr);
626
- [DllImport("advapi32.dll", SetLastError = true)]
627
- public static extern bool CredDelete(string target, UInt32 type, UInt32 flags);
628
- [DllImport("advapi32.dll", SetLastError = true)]
629
- public static extern void CredFree([In] IntPtr cred);
630
- }
631
- "@
632
- `;
633
- }
634
362
  function serializeCredentialDocument(document) {
635
363
  return JSON.stringify(document);
636
364
  }
@@ -29,9 +29,9 @@ export function formatMailboxFleetHealth(data) {
29
29
  `Warm-up stop causes: ${counts(warmup.byCause)}`,
30
30
  "Enrollment and next actions: oxygen mailboxes list --json (warmupTruth).",
31
31
  "",
32
- "Domains (worst first):",
32
+ ...(Array.isArray(data.domain_context) ? ["Selected inbox domains (whole-domain totals):"] : ["Domains (worst first):"]),
33
33
  ];
34
- const domains = Array.isArray(data.domains) ? data.domains.map(record) : [];
34
+ const domains = Array.isArray(data.domain_context) ? data.domain_context.map(record) : Array.isArray(data.domains) ? data.domains.map(record) : [];
35
35
  for (const domain of domains)
36
36
  lines.push(` ${text(domain.domain)} · ${count(domain.mailboxes)} inboxes · provider rejections ${count(domain.providerRejections7d)} (Google ${count(domain.googleRejections7d)}) · hard bounces ${count(domain.hardBounces7d)} / ${count(domain.coldSends7d)} sequence sends · youngest warm-up day ${count(domain.warmupDayMin)}`);
37
37
  if (!domains.length)
@@ -50,11 +50,20 @@ export function formatMailboxFleetHealth(data) {
50
50
  }
51
51
  const mailboxes = Array.isArray(data.mailboxes) ? data.mailboxes.map(record) : null;
52
52
  if (mailboxes) {
53
+ const selection = record(data.mailbox_selection);
54
+ if (Object.keys(selection).length)
55
+ lines.push("", `Selected inbox details: ${count(selection.matched)} of ${count(selection.fleet_total)}; fleet totals remain unfiltered.`);
53
56
  lines.push("", `Inbox roster (${mailboxes.length}):`);
54
57
  for (const mailbox of mailboxes) {
55
58
  const mailboxWarmup = record(mailbox.warmup);
56
59
  const warnings = Array.isArray(mailbox.warnings) ? mailbox.warnings.join(", ") : "";
57
60
  lines.push(` ${text(mailbox.emailAddress)} · ${text(mailbox.status)} · health ${text(mailbox.healthState)} (${text(mailbox.healthFreshness)}) · stored warm-up rail ${text(mailboxWarmup.state)}${typeof mailboxWarmup.pauseCode === "string" ? ` / ${mailboxWarmup.pauseCode}` : ""}${warnings ? ` · ${warnings}` : ""}`);
61
+ if (data.view === "full") {
62
+ const evidence = record(mailbox.signals);
63
+ lines.push(` Last-known external score: ${count(mailbox.healthScore)} · ${text(mailbox.healthProvider)} · checked ${text(mailbox.healthCheckedAt)}`);
64
+ lines.push(` ${text(evidence.window)} evidence: ${count(evidence.coldSends7d)} sequence sends · ${count(evidence.hardBounces7d)} hard bounces · ${count(evidence.googleRejections7d)} Google rejections (all send sources)`);
65
+ lines.push(` Delivery notifications: ${counts(evidence.dsn7d)} · diagnostic codes: ${counts(evidence.dsnCodes7d)}`);
66
+ }
58
67
  }
59
68
  }
60
69
  else if (data.view === "fleet")
@@ -11,7 +11,8 @@ export function registerWorkspacePlacementCommands(root, handle) {
11
11
  .option("--request-id <id>", "Request UUID from preview.").option("--plan <hash>", "Exact preview plan hash.").option("--max-live-sends <n>", "Maximum approved diagnostic emails (1–50).")
12
12
  .option("--preview-cursor <id>", "Read the next manifest page using the same input, request ID and rendering time.").option("--preview-limit <n>", "Preview manifest page size (1–20); never use with approval.")
13
13
  .option("--preview-probe-id <id>", "Read exact rendered copy for a preview probe; reuse request ID and rendering time.").option("--preview-part <part>", "body_text or body_html.").option("--preview-offset <n>", "Unicode character offset returned by copy preview.").option("--preview-copy-limit <n>", "Copy page size (1–3000 characters).")
14
- .option("--allow-paused-probe", "Explicitly allow diagnostic sends from paused senders, preserving pauses.").option("--json", "Print JSON.")
14
+ .option("--allow-paused-probe", "Explicitly allow diagnostic sends from paused senders, preserving pauses.")
15
+ .option("--allow-daily-limit-override", "Staff only: approve daily sender/receiver quota exceptions for this exact test, one diagnostic per sender. Preserve mailbox settings.").option("--json", "Print JSON.")
15
16
  .action(async (options) => handle("deliverability workspace-test run", options, () => {
16
17
  const input = readJsonFileValue(resolve(options.input), "--input");
17
18
  if (!input || typeof input !== "object" || Array.isArray(input))
@@ -20,7 +21,7 @@ export function registerWorkspacePlacementCommands(root, handle) {
20
21
  ...(options.approved ? { approved: true } : {}), ...(options.requestId ? { request_id: options.requestId } : {}),
21
22
  ...(options.plan ? { plan_hash: options.plan } : {}), ...(options.renderedAt ? { rendered_at: options.renderedAt } : {}), ...(options.trackingOpens ? { tracking_opens: true } : {}), ...(options.trackingClicks ? { tracking_clicks: true } : {}), ...(options.maxLiveSends ? { max_live_sends: Number(options.maxLiveSends) } : {}),
22
23
  ...(options.receiverEnvironment ? { receiver_environment: options.receiverEnvironment } : {}), ...(options.receiverWorkspace ? { receiver_workspace_id: options.receiverWorkspace } : {}),
23
- ...(options.allowPausedProbe ? { allow_paused_probe: true } : {}), ...(options.receiverScope ? { receiver_scope: options.receiverScope } : {}), ...(options.receiverProviders ? { receiver_providers: String(options.receiverProviders).split(",") } : {}),
24
+ ...(options.allowPausedProbe ? { allow_paused_probe: true } : {}), ...(options.allowDailyLimitOverride ? { allow_daily_limit_override: true } : {}), ...(options.receiverScope ? { receiver_scope: options.receiverScope } : {}), ...(options.receiverProviders ? { receiver_providers: String(options.receiverProviders).split(",") } : {}),
24
25
  ...(options.previewCursor ? { preview_cursor: options.previewCursor } : {}), ...(options.previewLimit !== undefined ? { preview_limit: Number(options.previewLimit) } : {}),
25
26
  ...(options.previewProbeId ? { preview_probe_id: options.previewProbeId } : {}), ...(options.previewPart ? { preview_part: options.previewPart } : {}), ...(options.previewOffset !== undefined ? { preview_offset: Number(options.previewOffset) } : {}), ...(options.previewCopyLimit !== undefined ? { preview_copy_limit: Number(options.previewCopyLimit) } : {}) } });
26
27
  })))
@@ -26,7 +26,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
26
26
  posture: "workspace_write",
27
27
  gatewayTools: ["oxygen_visual_source_create", "oxygen_visual_render_preview", "oxygen_visual_render_get", "oxygen_visual_image_view"],
28
28
  gatewayCommands: ["visuals source-create", "visuals render-preview", "visuals render-get", "visuals download"],
29
- skills: ["oxygen-knowledge"],
29
+ skills: ["oxygen-linkedin-content", "oxygen-knowledge"],
30
30
  endpointSections: ["visuals"],
31
31
  intentTerms: ["infographic", "gtm flow image", "render html", "visual design", "carousel pages", "svg", "png", "pdf export", "graphic designer"],
32
32
  },
@@ -114,8 +114,8 @@ export const OXYGEN_CAPABILITY_ROUTES = [
114
114
  notFor: "Primitive run diagnosis when no support report is needed; inspect Observability first.",
115
115
  execution: "Start or continue a hosted support conversation, optionally file structured metadata, and return its deep link; never send a customer reply automatically.",
116
116
  posture: "workspace_write",
117
- gatewayTools: ["oxygen_feedback", "oxygen_support_ticket_create", "oxygen_support_tickets_list", "oxygen_chat_channel", "oxygen_chat_thread_get", "oxygen_chat_thread_create", "oxygen_chat_thread_reply", "oxygen_chat_file_upload", "oxygen_chat_file_get", "oxygen_chat_thread_read"],
118
- gatewayCommands: ["feedback", "support chat", "support file", "support list", "chat channel", "chat threads get", "chat threads create", "chat threads reply", "chat files upload", "chat files get", "chat threads read"],
117
+ gatewayTools: ["oxygen_feedback", "oxygen_support_ticket_create", "oxygen_support_tickets_list", "oxygen_chat_threads_list", "oxygen_chat_channel", "oxygen_chat_thread_get", "oxygen_chat_thread_create", "oxygen_chat_thread_reply", "oxygen_chat_file_upload", "oxygen_chat_file_get", "oxygen_chat_thread_read"],
118
+ gatewayCommands: ["feedback", "support chat", "support file", "support list", "chat threads list", "chat channel", "chat threads get", "chat threads create", "chat threads reply", "chat files upload", "chat files get", "chat threads read"],
119
119
  skills: ["oxygen-feedback"],
120
120
  endpointSections: ["feedback", "support", "chat"],
121
121
  intentTerms: ["support", "feedback", "bug report", "feature request", "report issue", "ticket", "complaint", "support chat", "support thread", "chat attachment"],
@@ -398,12 +398,12 @@ export const OXYGEN_CAPABILITY_ROUTES = [
398
398
  posture: "external_write",
399
399
  gatewayTools: ["oxygen_publishing_posts_create", "oxygen_publishing_posts_list", "oxygen_publishing_posts_approve", "oxygen_ugc_get"],
400
400
  gatewayCommands: ["publishing posts create", "publishing posts list", "publishing posts approve", "ugc programs list", "ugc programs duplicate", "ugc programs archive"],
401
- skills: ["oxygen-publishing", "oxygen-ugc"],
401
+ skills: ["oxygen-publishing", "oxygen-linkedin-content", "oxygen-ugc"],
402
402
  endpointSections: ["publishing", "ugc"],
403
403
  // "remove program" / "delete program" landed on route: null in a blind eval
404
404
  // (2026-09-10); a program is a UGC noun here, and its lifecycle verbs must
405
405
  // resolve to the group that owns `ugc programs archive|delete|duplicate`.
406
- intentTerms: ["publish", "publishing", "schedule post", "content calendar", "posting calendar", "approve post", "social publishing", "video cover", "video thumbnail", "cover image", "post preview", "ugc", "creator program", "user generated content", "creator voice", "sponsored creator", "ugc program", "archive program", "delete program", "remove program", "duplicate program", "copy program"],
406
+ intentTerms: ["publish", "publishing", "schedule post", "content calendar", "posting calendar", "approve post", "social publishing", "video cover", "video thumbnail", "cover image", "post preview", "ugc", "creator program", "user generated content", "creator voice", "sponsored creator", "ugc program", "archive program", "delete program", "remove program", "duplicate program", "copy program", "write posts", "write a post", "post ideas", "content pillars", "hook", "linkedin content"],
407
407
  },
408
408
  {
409
409
  id: "workflows",
@@ -486,7 +486,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
486
486
  posture: "mixed",
487
487
  gatewayTools: ["oxygen_integrations_list", "oxygen_integrations_connect", "oxygen_integrations_list_actions"],
488
488
  gatewayCommands: ["integrations list", "integrations connect", "custom-integrations list"],
489
- skills: ["oxygen-tables", "oxygen-gtm"],
489
+ skills: ["oxygen-custom-http", "oxygen-tables", "oxygen-gtm"],
490
490
  endpointSections: ["custom-integrations", "integrations"],
491
491
  intentTerms: ["integration", "integrations", "connect provider", "oauth", "byok", "bring your own key", "provider key", "provider connection", "custom http", "custom integration", "connection readiness"],
492
492
  },
@@ -591,6 +591,19 @@ export function inferCapabilityRoute(query) {
591
591
  const normalized = normalizeIntent(query);
592
592
  if (!normalized)
593
593
  return null;
594
+ if (/\b(custom http|custom integrations?|own (?:rest|http) api)\b/.test(normalized)) {
595
+ const integrations = ROUTE_BY_ID.get("connected-integrations");
596
+ if (integrations) {
597
+ const removal = /\b(remov\w*|delet\w*|disconnect\w*|cleanup|clean up|throwaway|disposable)\b/.test(normalized);
598
+ const tools = removal ? ["oxygen_custom_integrations_remove", "oxygen_custom_integrations_list"] : ["oxygen_custom_integrations_list", "oxygen_custom_integrations_apply", "oxygen_custom_integration_connect"];
599
+ const commands = removal ? ["custom-integrations remove", "custom-integrations list"] : ["custom-integrations list", "custom-integrations apply", "custom-integrations connect"];
600
+ return {
601
+ card: { ...integrations, gatewayTools: tools, gatewayCommands: commands, skills: ["oxygen-custom-http"], execution: removal ? "Preview the exact custom HTTP connection and tools, then approve the returned plan_hash. Caller definitions and history remain, but bound callers stop working; provider-side key revocation is separate." : "Read oxygen-custom-http for the manifest schema and auth types. Apply registers catalog tools without a provider call; connect stores credentials separately. Preview a tool request before approved live execution." },
602
+ recommendedTools: tools,
603
+ recommendedCommands: commands,
604
+ };
605
+ }
606
+ }
594
607
  if (/\b(mailbox(?:es)?|inbox(?:es)?)\b/.test(normalized) && /\b(standby|reserve|replacement|replace|burn(?:ed|t)?)\b/.test(normalized)) {
595
608
  const campaign = /\b(campaign|sequence|cadence)\b/.test(normalized);
596
609
  const card = ROUTE_BY_ID.get(campaign ? "sequences" : "sending-infrastructure") ?? ROUTE_BY_PRIMITIVE.get("sequences");
@@ -734,12 +747,14 @@ export const OXYGEN_PRODUCT_SKILL_AUDIENCES = Object.freeze({
734
747
  "oxygen-deliverability": "workspace",
735
748
  "oxygen-sequencer": "workspace",
736
749
  "oxygen-publishing": "workspace",
750
+ "oxygen-linkedin-content": "workspace",
737
751
  "oxygen-signals": "workspace",
738
752
  "oxygen-table-tidy": "workspace",
739
753
  "oxygen-diagnostics": "workspace",
740
754
  "oxygen-feedback": "workspace",
741
755
  "oxygen-clay-migration": "workspace",
742
756
  "oxygen-ugc": "workspace",
757
+ "oxygen-custom-http": "workspace",
743
758
  // Retired (RETIRED_PRODUCT_SKILLS): readable for existing pins, never served.
744
759
  "oxygen-playbooks": "workspace",
745
760
  "oxygen-visual-design": "workspace",
@@ -1149,6 +1164,20 @@ function readInspectionRecommendation(query) {
1149
1164
  if (action)
1150
1165
  return null;
1151
1166
  const stored = positive.replace(/\bwithout\s+(?:changing|approving|rejecting|publishing|running|executing|refreshing)\b(?:\s+(?:widget\s+)?data\b)?/g, "");
1167
+ // A daily cap is context for an inbox inspection, not a recurring job or a
1168
+ // request to change its volume. Keep explicit writes and other owners intact.
1169
+ const mailboxInspection = stored.replace(/\bdaily (?:(?:campaign|sending|email) )?(?:limits?|caps?)\b/g, "sending limit");
1170
+ if (/\b(?:mailbox(?:es)?|inbox(?:es)?|email accounts?|email senders?)\b/.test(stored)
1171
+ && /\b(?:health|reputation|rejections?|bounc\w*|deliverability|dsn|ndr)\b/.test(stored)
1172
+ && !isHostedWorkflowIntent(mailboxInspection)
1173
+ && !isSecondPartyDecisionIntent(stored)
1174
+ && !/\b(?:linkedin|whatsapp|sequences?|campaigns?|tables?|columns?|crm|posts?|messages?|conversations?|threads?|dns|spf|dkim|dmarc|verify|verification|create|write|edit|update|change|delete|remove|archive|save|approve|reject|publish|run|running|execute|send|refresh|launch|enable|disable|apply|import|sync|schedule|enroll|add|set|pause|resume|test)\b/.test(mailboxInspection))
1175
+ return {
1176
+ capabilityId: "sending-infrastructure",
1177
+ tools: ["oxygen_mailboxes_health", "oxygen_mailboxes_get", "oxygen_mailboxes_deliverability_evidence"],
1178
+ commands: ["mailboxes health", "mailboxes get", "mailboxes deliverability evidence"],
1179
+ execution: "Read stored reputation, measurement freshness and seven-day rejection/send evidence with oxygen_mailboxes_health. Its mailboxes selector accepts inbox ids or addresses and defaults to full evidence; fleet totals and coverage stay unfiltered. CLI: mailboxes health --mailboxes <id-or-address> --json. Read oxygen_mailboxes_get for today's effective cap; deliverability evidence adds stored placement and domain checks. These reads cost 0 credits and do not sync providers or change settings. External MCP uses oxygen_capabilities_read; hydrate the selected tool's schema before calling.",
1180
+ };
1152
1181
  // A trigger diagnosis needs the live definition, not run tailing or a call.
1153
1182
  // Keep execution, authoring and run-history requests with their own owners.
1154
1183
  if (/\bworkflows?\b/.test(stored)
@@ -1247,8 +1276,11 @@ function isPartnerBonusReadIntent(query) {
1247
1276
  /** The card a route reports: Publishing's own, or its UGC view for a creator-program ask. */
1248
1277
  function routeCardFor(card, query) {
1249
1278
  const readOnly = readInspectionRecommendation(query);
1250
- if (readOnly?.capabilityId === card.id)
1251
- return { ...card, posture: "read_only", execution: readOnly.execution };
1279
+ if (readOnly?.capabilityId === card.id) {
1280
+ const skill = preferredSkillForIntent(card, query);
1281
+ return { ...card, posture: "read_only", execution: readOnly.execution,
1282
+ ...(skill ? { skills: [skill, ...card.skills.filter(name => name !== skill)] } : {}) };
1283
+ }
1252
1284
  const placementGuide = card.id === "sending-infrastructure" ? placementGuideRecommendation(query) : null;
1253
1285
  if (placementGuide)
1254
1286
  card = { ...card, posture: "read_only", execution: placementGuide.execution };
@@ -1361,6 +1393,9 @@ function routeCardFor(card, query) {
1361
1393
  const removal = card.primitive === "tables" ? tableRowRemovalRecommendation(query) : null;
1362
1394
  if (removal)
1363
1395
  card = { ...card, posture: "workspace_write", execution: removal.execution };
1396
+ const insert = card.primitive === "tables" ? tableRowInsertRecommendation(query) : null;
1397
+ if (insert)
1398
+ card = { ...card, posture: "workspace_write", execution: insert.execution };
1364
1399
  const sent = card.id === "connected-linkedin" ? sentLinkedInInvitationReadRecommendation(query) : null;
1365
1400
  if (sent)
1366
1401
  card = { ...card, execution: sent.execution };
@@ -1400,7 +1435,7 @@ function preferredSkillForIntent(card, query) {
1400
1435
  return "oxygen-enrichment";
1401
1436
  if (card.id === "billing-and-budgets" && /\b(explain|why|reduce|increas\w*|burn|spent)\b/.test(query))
1402
1437
  return "oxygen-diagnostics";
1403
- if (card.id === "sending-infrastructure" && /\b(bounc\w*|health|placement|deliverability|spam)\b/.test(query))
1438
+ if (card.id === "sending-infrastructure" && /\b(bounc\w*|health|reputation|rejections?|placement|deliverability|spam)\b/.test(query))
1404
1439
  return "oxygen-deliverability";
1405
1440
  if (["signals", "linkedin-public-research"].includes(card.id) && /\blinkedin\b/.test(query))
1406
1441
  return "oxygen-signals";
@@ -1865,6 +1900,22 @@ function tableRowRemovalRecommendation(query) {
1865
1900
  execution: "Delete Table rows with tables rows remove-selection (oxygen_table_rows_remove_selection): the rows a readable filter tree matches (--filter-tree-json / filter_tree, the same tree tables query takes), exact row_ids, or an all_matching selection with search, sort, range and exclusions. Hydrate its schema first. Unreadable filter nodes and filters arrays are refused; unqualified all_matching deletes every row. This is permanent and costs 0 credits; to keep a restorable copy first, tables duplicate (oxygen_tables_duplicate, 0 credits). Read the matching rows before deletion and the survivors afterward; each removed row's values stay readable with rows history (oxygen_rows_history). Deleting rows does not stop campaign enrollment.",
1866
1901
  };
1867
1902
  }
1903
+ /**
1904
+ * Literal row values go in with the row-insert tool. Without this card "add rows"
1905
+ * ranked create, enrichment and the external Records import first (sweep 3).
1906
+ */
1907
+ function tableRowInsertRecommendation(query) {
1908
+ if (!/\brows?\b/.test(query) || !/\b(add|adding|insert\w*|append\w*|put|enter|write)\b/.test(query)
1909
+ || /\b(delete|remove|update|edit|change|set|mark|import\w*|csv|crm|records?|enrich\w*|sync\w*|workflows?|agents?|campaigns?|sequences?|webhooks?|feeds?|functions?|callables?|cells?|dedup\w*|duplicates?)\b/.test(query)
1910
+ || /\b(?:add|insert|new)\s+(?:an?\s+|the\s+)?(?:\w+\s+)?columns?\b/.test(query)
1911
+ || isSecondPartyDecisionIntent(query))
1912
+ return null;
1913
+ return {
1914
+ tools: ["oxygen_table_rows_insert", "oxygen_tables_create", "oxygen_tables_query"],
1915
+ commands: ["tables rows insert", "tables create", "tables query"],
1916
+ execution: "Add literal rows with tables rows insert (oxygen_table_rows_insert): table plus rows, an array of {column_key: value} objects. tables create (oxygen_tables_create) makes the columns but takes no rows, so insert after it. Manual rows are a free internal write. Read the rows back with tables query (oxygen_tables_query).",
1917
+ };
1918
+ }
1868
1919
  /** Reads of grid values/state are separate from executing or resolving actions. */
1869
1920
  function tableReadRecommendation(query) {
1870
1921
  if (isFunctionIntent(query) || isSecondPartyDecisionIntent(query)
@@ -2917,6 +2968,9 @@ function recommendationsFor(card, query) {
2917
2968
  const removal = tableRowRemovalRecommendation(query);
2918
2969
  if (removal)
2919
2970
  return removal;
2971
+ const insert = tableRowInsertRecommendation(query);
2972
+ if (insert)
2973
+ return insert;
2920
2974
  const read = tableReadRecommendation(query);
2921
2975
  if (read)
2922
2976
  return read;
@@ -1,3 +1,9 @@
1
- export declare function tagClayMigrationFailureState(error: unknown, encryptedState: string): void;
1
+ type FailureState = {
2
+ encryptedState: string;
3
+ expiresAt?: string;
4
+ };
5
+ export declare function tagClayMigrationFailureState(error: unknown, encryptedState: string, expiresAt?: string): void;
2
6
  /** Consume only state attached in this process by the migration adapter. */
7
+ export declare function consumeClayMigrationFailureState(error: unknown): FailureState | null;
3
8
  export declare function readClayMigrationFailureState(error: unknown): string | null;
9
+ export {};
@@ -1,15 +1,16 @@
1
- // Rotated credentials must survive a failed source read, without becoming part
2
- // of a customer error, operator log or serialized tool result.
3
1
  const failureStates = new WeakMap();
4
- export function tagClayMigrationFailureState(error, encryptedState) {
2
+ export function tagClayMigrationFailureState(error, encryptedState, expiresAt) {
5
3
  if (error !== null && typeof error === "object")
6
- failureStates.set(error, encryptedState);
4
+ failureStates.set(error, { encryptedState, ...(expiresAt === undefined ? {} : { expiresAt }) });
7
5
  }
8
6
  /** Consume only state attached in this process by the migration adapter. */
9
- export function readClayMigrationFailureState(error) {
7
+ export function consumeClayMigrationFailureState(error) {
10
8
  if (error === null || typeof error !== "object")
11
9
  return null;
12
10
  const state = failureStates.get(error) ?? null;
13
11
  failureStates.delete(error);
14
12
  return state;
15
13
  }
14
+ export function readClayMigrationFailureState(error) {
15
+ return consumeClayMigrationFailureState(error)?.encryptedState ?? null;
16
+ }
@@ -115,6 +115,7 @@ export const BROWSER_OPERATIONS = [
115
115
  return_to: text("Same-origin path to return to after Checkout, for example /home."),
116
116
  source: text("Optional upgrade-entry attribution; omit if unknown."),
117
117
  blocked: text("Optional capability that led to the upgrade; omit if unknown."),
118
+ allow_promotion_codes: bool({ description: "Show the promotion-code field instead of the first-month offer." }),
118
119
  }, ["plan"]),
119
120
  operation("billing.confirm", "/api/cli/billing/confirm", "POST", "billing confirm", "oxygen_billing_confirm", "billing", "Confirm a plan-change preview within 15 minutes; approved accepts payment, term and effective date. Unrestricted members allowed; restricted clients denied.", { token: text(), approved: bool() }, ["token", "approved"], { spends: true }),
120
121
  operation("billing.portal", "/api/cli/billing/portal", "POST", "billing portal", "oxygen_billing_portal", "billing", "Create a Stripe billing-portal URL. Unrestricted members allowed; restricted clients denied.", { returnUrl: text() }),
@@ -3,6 +3,41 @@ import { type ContractIssue, type Infer } from "./schema.js";
3
3
  export declare const CLAY_MIGRATION_ACTIONS: readonly ["connect", "status", "discover", "read", "transfer", "checkpoint", "report", "disconnect"];
4
4
  export declare const CLAY_RESOURCE_TYPES: readonly ["workspace", "folder", "workbook", "table", "audience", "workflow", "function", "claygent", "knowledge", "schedule", "campaign"];
5
5
  export declare const CLAY_RESOURCE_DISPOSITIONS: readonly ["pending", "copied", "rebuilt", "needs_decision", "unsupported", "inaccessible", "excluded", "failed"];
6
+ type GlobalAudienceScope = {
7
+ entity_type: "people" | "companies" | "deals";
8
+ global: true;
9
+ archived?: true;
10
+ };
11
+ export declare const CLAY_GLOBAL_AUDIENCE_SCOPES: {
12
+ readonly "global-people": {
13
+ readonly entity_type: "people";
14
+ readonly global: true;
15
+ };
16
+ readonly "global-companies": {
17
+ readonly entity_type: "companies";
18
+ readonly global: true;
19
+ };
20
+ readonly "global-deals": {
21
+ readonly entity_type: "deals";
22
+ readonly global: true;
23
+ };
24
+ readonly "global-archived-people": {
25
+ readonly entity_type: "people";
26
+ readonly global: true;
27
+ readonly archived: true;
28
+ };
29
+ readonly "global-archived-companies": {
30
+ readonly entity_type: "companies";
31
+ readonly global: true;
32
+ readonly archived: true;
33
+ };
34
+ readonly "global-archived-deals": {
35
+ readonly entity_type: "deals";
36
+ readonly global: true;
37
+ readonly archived: true;
38
+ };
39
+ };
40
+ export declare function clayGlobalAudienceScope(resourceId: string): GlobalAudienceScope | null;
6
41
  export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObject<{
7
42
  readonly action: {
8
43
  type: "string";
@@ -17,9 +52,10 @@ export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObje
17
52
  description?: string;
18
53
  };
19
54
  readonly resource_id: import("./schema.js").StringProperty;
55
+ readonly row_id: import("./schema.js").StringProperty;
20
56
  readonly section: {
21
57
  type: "string";
22
- enum: readonly ("columns" | "fields" | "graph" | "metadata" | "record_ids" | "records" | "rows" | "sequence")[];
58
+ enum: readonly ("columns" | "fields" | "graph" | "metadata" | "record_ids" | "records" | "row" | "rows" | "sequence")[];
23
59
  description?: string;
24
60
  };
25
61
  readonly path: import("./schema.js").StringProperty;
@@ -35,6 +71,7 @@ export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObje
35
71
  enum: readonly ("companies" | "deals" | "people")[];
36
72
  description?: string;
37
73
  };
74
+ readonly archived: import("./schema.js").BooleanProperty;
38
75
  readonly record_ids: import("./schema.js").ArrayProperty<{
39
76
  type: "integer";
40
77
  description?: string;
@@ -69,3 +106,4 @@ export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObje
69
106
  export type ClayMigrationInput = Infer<typeof CLAY_MIGRATION_CONTRACT>;
70
107
  export declare function clayMigrationInputIssues(args: Readonly<Record<string, unknown>>): ContractIssue[];
71
108
  export declare const CLAY_MIGRATION_OPERATION: OperationSpec<typeof CLAY_MIGRATION_CONTRACT>;
109
+ export {};