@oxygen-agent/cli 1.1014.1343 → 1.1014.1420

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/command-families/tables/register-reads.js +1 -1
  3. package/dist/command-support.js +4 -2
  4. package/dist/commands/blueprints.js +10 -6
  5. package/dist/commands/crm.js +5 -2
  6. package/dist/commands/dashboards.js +4 -2
  7. package/dist/commands/db.js +1 -1
  8. package/dist/commands/directory.js +7 -4
  9. package/dist/commands/inbox.js +5 -2
  10. package/dist/commands/prompts.js +4 -2
  11. package/dist/commands/publishing.js +5 -2
  12. package/dist/commands/sequences.js +13 -4
  13. package/dist/commands/support.js +30 -19
  14. package/dist/customer-operation-commands.js +0 -42
  15. package/dist/http-client.js +4 -1
  16. package/dist/workspace-placement-commands.js +1 -1
  17. package/node_modules/@oxygen/shared/dist/capability-args.d.ts +19 -0
  18. package/node_modules/@oxygen/shared/dist/capability-args.js +73 -0
  19. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +2 -1
  20. package/node_modules/@oxygen/shared/dist/contracts/operations.js +3 -6
  21. package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.d.ts +5 -1
  22. package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.js +25 -2
  23. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +1 -1
  24. package/node_modules/@oxygen/shared/dist/enrollment-origin.d.ts +3 -1
  25. package/node_modules/@oxygen/shared/dist/enrollment-origin.js +2 -0
  26. package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +2 -0
  27. package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.d.ts +9 -2
  28. package/node_modules/@oxygen/shared/dist/guided-trial-billing.d.ts +10 -2
  29. package/node_modules/@oxygen/shared/dist/guided-trial-billing.js +2 -5
  30. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -0
  31. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +1 -0
  32. package/node_modules/@oxygen/shared/dist/onboarding-plays.d.ts +68 -0
  33. package/node_modules/@oxygen/shared/dist/onboarding-plays.js +114 -0
  34. package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +37 -0
  35. package/node_modules/@oxygen/shared/dist/onboarding-research.js +57 -0
  36. package/node_modules/@oxygen/shared/dist/placement-copy.d.ts +8 -0
  37. package/node_modules/@oxygen/shared/dist/placement-copy.js +16 -0
  38. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +8 -0
  39. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +8 -0
  40. package/node_modules/@oxygen/shared/dist/sequences.d.ts +30 -2
  41. package/node_modules/@oxygen/shared/dist/sequences.js +67 -7
  42. package/node_modules/@oxygen/shared/dist/teammates.d.ts +3 -1
  43. package/node_modules/@oxygen/shared/dist/teammates.js +4 -2
  44. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +19 -40
  45. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  46. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  47. package/node_modules/@oxygen/shared/dist/workspace-placement.js +4 -0
  48. package/package.json +1 -1
  49. package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.d.ts +0 -30
  50. package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.js +0 -53
  51. package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.d.ts +0 -120
  52. package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.js +0 -48
  53. package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.d.ts +0 -267
  54. package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.js +0 -63
  55. package/node_modules/@oxygen/shared/dist/guided-company-files.d.ts +0 -13
  56. package/node_modules/@oxygen/shared/dist/guided-company-files.js +0 -14
  57. package/node_modules/@oxygen/shared/dist/guided-first-sourcing.d.ts +0 -116
  58. package/node_modules/@oxygen/shared/dist/guided-first-sourcing.js +0 -11
  59. package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.d.ts +0 -4
  60. package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.js +0 -42
  61. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.d.ts +0 -147
  62. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.d.ts +0 -12
  63. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.js +0 -83
  64. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.js +0 -44
  65. package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.d.ts +0 -7
  66. package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.js +0 -13
  67. package/node_modules/@oxygen/shared/dist/guided-onboarding-recommendations.d.ts +0 -54
  68. package/node_modules/@oxygen/shared/dist/guided-onboarding-recommendations.js +0 -100
  69. package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.d.ts +0 -4
  70. package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.js +0 -34
@@ -424,6 +424,54 @@ export const SEQUENCE_CRM_IDENTITY_KINDS = [
424
424
  * route connected → then_id, not → else_id (no waiting).
425
425
  */
426
426
  export const SEQUENCE_CONNECTION_BRANCH_CONDITIONS = ["connection_accepted", "already_connected", "open_profile"];
427
+ /**
428
+ * A boolean expression a signal-branch routes on. Two leaf kinds, plus the
429
+ * all/any/not combinators:
430
+ * - a SIGNAL leaf `{ signal, present? }` tests whether an accumulated enrollment
431
+ * signal is present (default) or absent (present:false).
432
+ * - a DATA leaf `{ has_column, present? }` tests whether the lead's source row
433
+ * carries a non-empty value in the named row_values column — present (default)
434
+ * or absent (present:false). This is the LinkedIn/Lemlist "does the lead have an
435
+ * email / phone / … ?" branch: route by what data you HAVE about the lead,
436
+ * evaluated synchronously against the enrollment's row_values (no signal, no
437
+ * wait). A value counts as present when it is a non-empty trimmed string, or any
438
+ * number / boolean; null / undefined / "" / whitespace count as absent.
439
+ *
440
+ * Lemlist condition-key → Oxygen mapping (13 keys), so a Lemlist journey ports
441
+ * across without a lossy translation table:
442
+ * hasEmailAddress → { has_column: "email" } (or any SEQUENCE_EMAIL_COLUMN_KEYS key)
443
+ * hasLinkedinUrl → { has_column: "<linkedin url column>" }
444
+ * hasPhoneNumber → { has_column: "phone" } (or any SEQUENCE_PHONE_COLUMN_KEYS key)
445
+ * linkedinInviteAccepted → a CONNECTION branch (condition: "connection_accepted")
446
+ * opened → { signal: "email_opened" }
447
+ * clicked → { signal: "email_clicked" }
448
+ * replied → { signal: "email_replied" } (or linkedin_replied / whatsapp_replied)
449
+ * meetingBooked → { signal: "meeting_booked" }
450
+ * emailsUnsubscribed → { signal: "email_unsubscribed" }
451
+ * aircallDone → out of scope (no Aircall integration)
452
+ * Negate any leaf with present:false (e.g. "no email on file" = { has_column: "email", present: false }).
453
+ *
454
+ * A CONTACT leaf `{ has_contact: "email" | "linkedin" | "phone", present? }` asks
455
+ * whether the step that channel needs could reach the lead, resolved exactly as
456
+ * dispatch resolves it: email and phone through the sequence's mapped column with
457
+ * the built-in fallbacks, LinkedIn through the profile captured at enrollment. It
458
+ * is what "If has email / LinkedIn / phone" in the builder writes, so one campaign
459
+ * can route every lead instead of splitting the audience by data.
460
+ */
461
+ export const SEQUENCE_CONTACT_CHANNELS = ["email", "linkedin", "phone"];
462
+ /**
463
+ * Resolve a lead's reachable contacts the way dispatch does: the mapped email or
464
+ * phone column first, then the built-in keys, and LinkedIn from the profile the
465
+ * enrollment captured (a row value alone never makes a LinkedIn step sendable).
466
+ */
467
+ export function sequenceLeadContacts(input) {
468
+ const phoneKey = input.settings?.phone_column_key ?? null;
469
+ return {
470
+ email: recipientEmailFromRow(input.rowValues, input.settings?.email_column_key ?? null) !== null,
471
+ linkedin: input.linkedin,
472
+ phone: callPhoneFromRow(input.rowValues, phoneKey) !== null || whatsAppAttendeeIdFromRow(input.rowValues, phoneKey) !== null,
473
+ };
474
+ }
427
475
  const MAX_SEQUENCE_STEPS = 50;
428
476
  const MAX_TEMPLATE_LENGTH = 8_000;
429
477
  // The Premium ceiling, and only an authoring hint: the limit that actually
@@ -835,7 +883,7 @@ function collectConditionSignals(condition, out) {
835
883
  // it contributes nothing — and must be handled BEFORE the not-fallthrough below
836
884
  // (which would otherwise read condition.not off a data leaf). This is also why
837
885
  // the native-untracked engagement-gate walker never trips on a has_column branch.
838
- else if ("has_column" in condition)
886
+ else if ("has_column" in condition || "has_contact" in condition)
839
887
  return;
840
888
  else if ("all" in condition)
841
889
  condition.all.forEach((c) => collectConditionSignals(c, out));
@@ -1810,6 +1858,13 @@ function normalizeCondition(raw, path, issues, depth = 0) {
1810
1858
  return undefined;
1811
1859
  return { has_column: key, present: raw.present === false ? false : true };
1812
1860
  }
1861
+ if (raw.has_contact !== undefined) {
1862
+ if (typeof raw.has_contact !== "string" || !SEQUENCE_CONTACT_CHANNELS.includes(raw.has_contact)) {
1863
+ issues.push({ path: `${path}.has_contact`, message: `has_contact must be one of ${SEQUENCE_CONTACT_CHANNELS.join(", ")}.` });
1864
+ return undefined;
1865
+ }
1866
+ return { has_contact: raw.has_contact, present: raw.present === false ? false : true };
1867
+ }
1813
1868
  if (Array.isArray(raw.all)) {
1814
1869
  return { all: normalizeConditionList(raw.all, `${path}.all`, issues, depth) };
1815
1870
  }
@@ -1820,7 +1875,7 @@ function normalizeCondition(raw, path, issues, depth = 0) {
1820
1875
  const inner = normalizeCondition(raw.not, `${path}.not`, issues, depth + 1);
1821
1876
  return inner ? { not: inner } : undefined;
1822
1877
  }
1823
- issues.push({ path, message: "condition must be {signal}, {has_column}, {all}, {any}, or {not}." });
1878
+ issues.push({ path, message: "condition must be {signal}, {has_column}, {has_contact}, {all}, {any}, or {not}." });
1824
1879
  return undefined;
1825
1880
  }
1826
1881
  function normalizeConditionList(raw, path, issues, depth) {
@@ -2103,9 +2158,10 @@ export function sequenceTemplateVariables(definition) {
2103
2158
  * `rowValues` is optional and backward-compatible: when omitted (undefined), a data
2104
2159
  * leaf ({ has_column }) evaluates as ABSENT — present:false matches, present:true
2105
2160
  * does not — so an existing signal-only caller is unaffected. Signal leaves ignore
2106
- * rowValues entirely.
2161
+ * rowValues entirely. A contact leaf ({ has_contact }) reads `contacts`; without
2162
+ * it, the contact counts as absent.
2107
2163
  */
2108
- export function evaluateSequenceCondition(condition, firedSignals, rowValues) {
2164
+ export function evaluateSequenceCondition(condition, firedSignals, rowValues, contacts) {
2109
2165
  const fired = firedSignals instanceof Set ? firedSignals : new Set(firedSignals);
2110
2166
  if ("signal" in condition) {
2111
2167
  const present = fired.has(condition.signal);
@@ -2115,11 +2171,15 @@ export function evaluateSequenceCondition(condition, firedSignals, rowValues) {
2115
2171
  const present = rowValueIsPresent(rowValues?.[condition.has_column]);
2116
2172
  return condition.present === false ? !present : present;
2117
2173
  }
2174
+ if ("has_contact" in condition) {
2175
+ const present = contacts?.[condition.has_contact] === true;
2176
+ return condition.present === false ? !present : present;
2177
+ }
2118
2178
  if ("all" in condition)
2119
- return condition.all.every((c) => evaluateSequenceCondition(c, fired, rowValues));
2179
+ return condition.all.every((c) => evaluateSequenceCondition(c, fired, rowValues, contacts));
2120
2180
  if ("any" in condition)
2121
- return condition.any.some((c) => evaluateSequenceCondition(c, fired, rowValues));
2122
- return !evaluateSequenceCondition(condition.not, fired, rowValues);
2181
+ return condition.any.some((c) => evaluateSequenceCondition(c, fired, rowValues, contacts));
2182
+ return !evaluateSequenceCondition(condition.not, fired, rowValues, contacts);
2123
2183
  }
2124
2184
  /**
2125
2185
  * Whether a lead's row_values value counts as "present" for a data-leaf branch: a
@@ -29,6 +29,8 @@ export declare function teammatesAvailableTo(env: Record<string, string | undefi
29
29
  export declare const DEFAULT_TEAMMATE_SLUG = "workspace";
30
30
  /** What an Oxygent is called until someone names it, as Grok Bot's are (Tim, 2026-10-13). */
31
31
  export declare const NEW_TEAMMATE_NAME = "New Bot";
32
+ /** What the first Oxygent is called until someone renames it (Philipp, 2026-10-19). */
33
+ export declare const DEFAULT_TEAMMATE_NAME = "Oxygent";
32
34
  export declare const TEAMMATE_NAME_MAX_CHARS = 80;
33
35
  export declare const TEAMMATE_DESCRIPTION_MAX_CHARS = 500;
34
36
  /**
@@ -79,7 +81,7 @@ export declare function teammateDefaultLook(slug: string): {
79
81
  };
80
82
  /**
81
83
  * How an Oxygent presents: its name, what it does and its look. The first
82
- * Oxygent is "New Bot" until its owner names it; nothing renames the stored
84
+ * Oxygent is "Oxygent" until its owner names it; nothing renames the stored
83
85
  * Agent to get there, and the Workspace Agent's stock description is no job.
84
86
  */
85
87
  export declare function teammatePresentation(agent: {
@@ -30,6 +30,8 @@ export function teammatesAvailableTo(env, viewer) {
30
30
  export const DEFAULT_TEAMMATE_SLUG = "workspace";
31
31
  /** What an Oxygent is called until someone names it, as Grok Bot's are (Tim, 2026-10-13). */
32
32
  export const NEW_TEAMMATE_NAME = "New Bot";
33
+ /** What the first Oxygent is called until someone renames it (Philipp, 2026-10-19). */
34
+ export const DEFAULT_TEAMMATE_NAME = "Oxygent";
33
35
  const LEGACY_WORKSPACE_AGENT_NAME = "Workspace Agent";
34
36
  const LEGACY_WORKSPACE_AGENT_DESCRIPTION = "General organization-grounded workspace worker.";
35
37
  export const TEAMMATE_NAME_MAX_CHARS = 80;
@@ -103,12 +105,12 @@ export function teammateDefaultLook(slug) {
103
105
  }
104
106
  /**
105
107
  * How an Oxygent presents: its name, what it does and its look. The first
106
- * Oxygent is "New Bot" until its owner names it; nothing renames the stored
108
+ * Oxygent is "Oxygent" until its owner names it; nothing renames the stored
107
109
  * Agent to get there, and the Workspace Agent's stock description is no job.
108
110
  */
109
111
  export function teammatePresentation(agent) {
110
112
  const first = agent.slug === DEFAULT_TEAMMATE_SLUG;
111
- const name = first && agent.name === LEGACY_WORKSPACE_AGENT_NAME ? NEW_TEAMMATE_NAME : agent.name;
113
+ const name = first && agent.name === LEGACY_WORKSPACE_AGENT_NAME ? DEFAULT_TEAMMATE_NAME : agent.name;
112
114
  const stored = agent.description?.trim() ?? "";
113
115
  const description = first && stored === LEGACY_WORKSPACE_AGENT_DESCRIPTION ? "" : stored;
114
116
  const look = teammateDefaultLook(agent.slug);
@@ -14,6 +14,19 @@ export function capabilityDiscoveryRecovery(query, surface) {
14
14
  // connection routes.
15
15
  export function inferUserCapabilityRoute(query) {
16
16
  const normalized = query.toLowerCase().replace(/[_-]+/g, " ");
17
+ if (/\b(?:buy|purchase|order|provision|subscribe)\b/.test(normalized)
18
+ && /\b(?:inbox(?:es)?|mailbox(?:es)?)\b/.test(normalized)
19
+ && !/\b(?:buy|purchase|order)\s+(?:more\s+)?credits?\b|^\s*(?:cancel|delete|remove)\b/.test(normalized)) {
20
+ const route = getCapabilityRouteMatch("sending-infrastructure");
21
+ if (route) {
22
+ const recommendedTools = ["oxygen_managed_inboxes_subscribe", "oxygen_managed_inboxes_list", "oxygen_domains_list", "oxygen_managed_inboxes_add_mailboxes"];
23
+ const recommendedCommands = ["managed-inboxes subscribe", "managed-inboxes list", "domains list", "managed-inboxes add-inboxes"];
24
+ return { ...route, recommendedTools, recommendedCommands, card: { ...route.card,
25
+ gatewayTools: recommendedTools, gatewayCommands: recommendedCommands,
26
+ execution: "Inspect owned domains, existing subscriptions and occupied/pending addresses before quoting. An owned domain without a subscription uses managed-inboxes subscribe with existing_domain=true (CLI --existing-domain), with no registration charge; a managed subscription uses add-inboxes. Preserve the requested provider and total per-domain cap. Preview first; exact quote approval authorizes only its inboxes and recurring cost. Warmup is included only when warmup_activation.automatic_after_provisioning=true. Discovery buys nothing and grants no spend authority.",
27
+ } };
28
+ }
29
+ }
17
30
  if (/\breply oxygent\b/.test(normalized) || (/\brepl(?:y|ies)\b/.test(normalized) && /\bbatch(?:es)?\b/.test(normalized)
18
31
  && /\b(?:approv(?:al|e)|voice|writing style)\b/.test(normalized))) {
19
32
  const route = getCapabilityRouteMatch("messages");
@@ -26,22 +39,6 @@ export function inferUserCapabilityRoute(query) {
26
39
  } };
27
40
  }
28
41
  }
29
- const journeyAction = savedOnboardingJourneyAction(normalized);
30
- if (journeyAction) {
31
- const route = getCapabilityRouteMatch("agents");
32
- if (route) {
33
- const recommendedTools = [`oxygen_onboarding_journey_${journeyAction}`, ...(journeyAction === "get" ? [] : ["oxygen_onboarding_journey_get"])];
34
- const recommendedCommands = [`onboarding journey ${journeyAction}`, ...(journeyAction === "get" ? [] : ["onboarding journey get"])];
35
- return { ...route, recommendedTools, recommendedCommands, card: { ...route.card,
36
- owns: "Creator-private saved guided onboarding answers, current step and pause/resume position.",
37
- notFor: "Human workspace invitations or permissions, destructive workspace reset, paid Oxygent Start or routine execution.",
38
- gatewayTools: recommendedTools, gatewayCommands: recommendedCommands,
39
- posture: journeyAction === "get" ? "read_only" : "workspace_write",
40
- execution: journeyAction === "get" ? "Read saved onboarding and its current native step. This read starts no research, model, billing reconciliation or message send."
41
- : "Read activationId and current revision first, then hydrate the selected native operation. Use a stable mutation_id; Back and non-destructive restart preserve resources and funding. Company answers prepare native sources and existing bounded research consent. Discovery executes nothing and supplies no approval or billing authority.",
42
- } };
43
- }
44
- }
45
42
  // Exact native session activation must outrank the generic Agent gateways.
46
43
  // Discovery supplies no approval, preview token or invented session binding.
47
44
  if (/\bagent session activate\b/.test(normalized)
@@ -108,13 +105,13 @@ export function inferUserCapabilityRoute(query) {
108
105
  if (isGuidedOnboardingDiscoveryIntent(normalized)) {
109
106
  const route = getCapabilityRouteMatch("onboarding-and-copilot");
110
107
  if (route) {
111
- const recommendedTools = ["oxygen_onboarding_activation_status", "oxygen_onboarding_journey_get"];
112
- const recommendedCommands = ["onboarding activation status", "onboarding journey get"];
108
+ const recommendedTools = ["oxygen_onboarding_activation_status"];
109
+ const recommendedCommands = ["onboarding activation status"];
113
110
  return { ...route, recommendedTools, recommendedCommands, card: { ...route.card,
114
- owns: "Creator-owned DEV guided onboarding answers, native resources, current step and saved navigation.",
111
+ owns: "Creator-owned DEV guided onboarding activation and its native resources.",
115
112
  notFor: "Production onboarding execution, human membership or ordinary GTM consulting.",
116
113
  posture: "read_only", gatewayTools: recommendedTools, gatewayCommands: recommendedCommands,
117
- execution: "DEV-only two-part guided onboarding; unavailable in production, shadow, local or production-data workspaces. Read your activation first, then use its activationId to read the saved journey and revision. If absent, inspect the free activation-start schema and choose own_agent or oxygent. Resume the native step; answers, prospects, TEST billing, campaigns and routines retain their own schemas, eligibility and approvals. These reads start no research, checkout, model, schedule or send. Ordinary GTM consulting remains with Recipes/Copilot.",
114
+ execution: "DEV-only guided onboarding runs in the workspace Oxygent chat; unavailable in production, shadow, local or production-data workspaces. Read your activation first; its activationId binds native resources and routines. If absent, inspect the free activation-start schema and choose own_agent or oxygent. The Oxygent researches the company and proposes plays; campaigns and routines keep their own previews and approvals. This read starts no research, model, schedule or send. Ordinary GTM consulting remains with Recipes/Copilot.",
118
115
  }, exactCommand: "oxygen onboarding activation status --json",
119
116
  exactMcpCall: { tool: "oxygen_onboarding_activation_status", arguments: {} } };
120
117
  }
@@ -217,26 +214,8 @@ export function inferUserCapabilityRoute(query) {
217
214
  // intent is exactly what this wrapper exists to answer.
218
215
  return getCapabilityRouteMatch("connected-integrations") ?? route;
219
216
  }
220
- /** Saved progress is distinct from general signup research, attended Start,
221
- * routine schedules and human membership. Pause wins over a simultaneous resume. */
222
- function savedOnboardingJourneyAction(query) {
223
- if (!/\bonboarding\b/.test(query) || !/\b(?:journey|guided|answers?|steps?|tour)\b/.test(query)
224
- || /\b(?:invite|members?|membership|permissions?|roles?|human|colleague|coworker|delete|destroy|wipe)\b/.test(query))
225
- return null;
226
- if (/\b(?:pause|paused|navigation)\b/.test(query) || (/\btour\b/.test(query) && /\bresume\b/.test(query)))
227
- return "navigation";
228
- if (/\brestart\b/.test(query))
229
- return "restart";
230
- if (/\bback\b/.test(query))
231
- return "back";
232
- if (/\bsave\b/.test(query))
233
- return "save";
234
- if (/\b(?:resume|read|get)\b/.test(query))
235
- return "get";
236
- return null;
237
- }
238
- /** A saved action is handled first. This is discovery of the multi-step
239
- * product journey, not generic advice about improving a GTM operation. */
217
+ /** Discovery of the guided DEV onboarding activation, not generic advice
218
+ * about improving a GTM operation. */
240
219
  function isGuidedOnboardingDiscoveryIntent(query) {
241
220
  if (!/\bonboarding\b/.test(query)
242
221
  || /\b(?:invite|members?|membership|permissions?|roles?|human|colleague|coworker|delete|destroy|wipe|paid)\b/.test(query))
@@ -1 +1 @@
1
- export declare const OXYGEN_BUILD_VERSION = "1.1014.1343";
1
+ export declare const OXYGEN_BUILD_VERSION = "1.1014.1420";
@@ -1,2 +1,2 @@
1
1
  // GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
2
- export const OXYGEN_BUILD_VERSION = "1.1014.1343";
2
+ export const OXYGEN_BUILD_VERSION = "1.1014.1420";
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { CliHttpError } from "./cli-http-error.js";
3
+ import { placementCopyIssue } from "./placement-copy.js";
3
4
  const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i;
4
5
  export function workspacePlacementId(value) { return typeof value === "string" && UUID.test(value); }
5
6
  function invalid(message) { throw new CliHttpError(400, "workspace_test_invalid", message); }
@@ -21,6 +22,9 @@ export function readWorkspacePlacementVariants(value) {
21
22
  invalid("body_html must contain between one and 200,000 characters when supplied.");
22
23
  if (/\{\{[^}]+\}\}/.test(v.subject + v.body_text + (v.body_html ?? "")))
23
24
  invalid("Resolve personalization variables before testing the exact copy.");
25
+ const copyIssue = placementCopyIssue(v.subject, v.body_text, v.body_html);
26
+ if (copyIssue)
27
+ invalid(copyIssue);
24
28
  keys.add(v.key);
25
29
  return { key: v.key, subject: v.subject, body_text: v.body_text, ...(typeof v.body_html === "string" ? { body_html: v.body_html } : {}) };
26
30
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.1014.1343",
3
+ "version": "1.1014.1420",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,30 +0,0 @@
1
- import { type ContractIssue } from "./schema.js";
2
- import type { OperationSpec } from "./operations.js";
3
- export declare const GUIDED_COMPANY_FILE_UPLOAD_CONTRACT: import("./schema.js").ContractObject<{
4
- readonly revision: {
5
- type: "integer";
6
- description?: string;
7
- minimum?: number;
8
- maximum?: number;
9
- "x-clamp"?: true;
10
- };
11
- readonly upload_id: import("./schema.js").StringProperty;
12
- readonly sha256: import("./schema.js").StringProperty;
13
- readonly size_bytes: {
14
- type: "integer";
15
- description?: string;
16
- minimum?: number;
17
- maximum?: number;
18
- "x-clamp"?: true;
19
- };
20
- readonly file_name: import("./schema.js").StringProperty;
21
- readonly content_type: {
22
- type: "string";
23
- enum: readonly ("application/pdf" | "text/markdown" | "text/plain")[];
24
- description?: string;
25
- };
26
- readonly content_base64: import("./schema.js").StringProperty;
27
- }, "content_base64" | "content_type" | "file_name" | "revision" | "sha256" | "size_bytes" | "upload_id">;
28
- /** Raw wire values are never trimmed or renamed; the native owner verifies original SHA/bytes. */
29
- export declare function guidedCompanyFileUploadInputIssues(args: Readonly<Record<string, unknown>>): readonly ContractIssue[];
30
- export declare const GUIDED_COMPANY_FILE_UPLOAD_OPERATION: OperationSpec;
@@ -1,53 +0,0 @@
1
- import { GUIDED_COMPANY_FILE_CONTENT_TYPES, GUIDED_COMPANY_FILE_LIMITS, guidedCompanyFileByteLimit } from "../guided-company-files.js";
2
- import { enumOf, int, obj, str } from "./schema.js";
3
- const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
4
- export const GUIDED_COMPANY_FILE_UPLOAD_CONTRACT = obj({
5
- revision: int({ minimum: 1 }), upload_id: str({ pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" }),
6
- sha256: str({ pattern: "^[0-9a-f]{64}$" }), size_bytes: int({ minimum: 1, maximum: GUIDED_COMPANY_FILE_LIMITS.pdfBytes }),
7
- file_name: str({ minLength: 1, maxLength: GUIDED_COMPANY_FILE_LIMITS.nameBytes }),
8
- content_type: enumOf(GUIDED_COMPANY_FILE_CONTENT_TYPES),
9
- content_base64: str({ minLength: 4, maxLength: 4 * Math.ceil(GUIDED_COMPANY_FILE_LIMITS.pdfBytes / 3) }),
10
- }, { required: ["revision", "upload_id", "sha256", "size_bytes", "file_name", "content_type", "content_base64"] });
11
- /** Raw wire values are never trimmed or renamed; the native owner verifies original SHA/bytes. */
12
- export function guidedCompanyFileUploadInputIssues(args) {
13
- const issues = [];
14
- const issue = (path, message) => issues.push({ path, message });
15
- for (const key of ["activationId", "upload_id"])
16
- if (typeof args[key] !== "string" || !uuid.test(args[key]))
17
- issue(key, "Use a native UUID.");
18
- const name = args.file_name;
19
- if (typeof name !== "string" || !name.trim() || /[\\/\u0000-\u001f\u007f]/u.test(name) || new TextEncoder().encode(name).length > GUIDED_COMPANY_FILE_LIMITS.nameBytes)
20
- issue("file_name", "Use a filename without paths or controls, at most 255 UTF-8 bytes.");
21
- const type = args.content_type;
22
- if (!GUIDED_COMPANY_FILE_CONTENT_TYPES.includes(type))
23
- issue("content_type", "Use UTF-8 text, Markdown or PDF.");
24
- else if (typeof args.size_bytes !== "number" || args.size_bytes > guidedCompanyFileByteLimit(type))
25
- issue("size_bytes", "Use at most 1500000 text bytes or 5242880 PDF bytes.");
26
- const encoded = args.content_base64;
27
- if (typeof encoded !== "string" || !encoded || encoded.length % 4 !== 0 || /[^A-Za-z0-9+/=]/u.test(encoded) || !/^[^=]*={0,2}$/u.test(encoded))
28
- issue("content_base64", "Use canonical non-empty base64 file bytes.");
29
- else {
30
- try {
31
- const decoded = atob(encoded);
32
- if (btoa(decoded) !== encoded)
33
- issue("content_base64", "Use canonical base64 without altered padding bits.");
34
- if (decoded.length !== args.size_bytes)
35
- issue("size_bytes", "size_bytes must equal the original decoded byte length.");
36
- }
37
- catch {
38
- issue("content_base64", "Use valid base64 file bytes.");
39
- }
40
- }
41
- return issues;
42
- }
43
- export const GUIDED_COMPANY_FILE_UPLOAD_OPERATION = {
44
- id: "onboarding.company_files.upload", route: "/api/cli/onboarding/activation/{activationId}/company-files", method: "POST",
45
- parameters: ["activationId"], contract: GUIDED_COMPANY_FILE_UPLOAD_CONTRACT, inputCheck: guidedCompanyFileUploadInputIssues,
46
- multipart: { contentField: "content_base64", nameField: "file_name", typeField: "content_type", fileField: "file",
47
- fileMetadata: { sha256Field: "sha256", sizeBytesField: "size_bytes" } },
48
- cli: { path: ["onboarding", "company-files", "upload"], summary: "Upload an optional company source file for your private DEV onboarding activation." },
49
- mcp: { name: "oxygen_onboarding_company_files_upload", title: "Oxygen: Upload onboarding company file", pack: "agents",
50
- description: "Current creator and revision only, genuine enabled DEV: upload UTF-8 text/Markdown up to 1500000 bytes or PDF up to 5242880 bytes. Supply original filename, MIME, size and SHA-256 plus stable upload_id UUID; exact retries reuse the native file. PDF retains original bytes with text unavailable. No account connection, parser, model or message send.",
51
- annotations: { destructiveHint: false, openWorldHint: true, idempotentHint: true } },
52
- mutates: true, spends: false,
53
- };
@@ -1,120 +0,0 @@
1
- import { type ContractIssue } from "./schema.js";
2
- import type { OperationSpec } from "./operations.js";
3
- /** Matches the actual native people-search and page safety ceilings. */
4
- export declare const GUIDED_FIRST_SOURCING_LIMITS: {
5
- readonly targetCount: 50000;
6
- readonly maxPages: 1000;
7
- };
8
- export declare const GUIDED_FIRST_SOURCING_BOUNDS_CONTRACT: import("./schema.js").ContractObject<{
9
- readonly targetCount: {
10
- type: "integer";
11
- description?: string;
12
- minimum?: number;
13
- maximum?: number;
14
- "x-clamp"?: true;
15
- };
16
- readonly maxPages: {
17
- type: "integer";
18
- description?: string;
19
- minimum?: number;
20
- maximum?: number;
21
- "x-clamp"?: true;
22
- };
23
- readonly maxCredits: {
24
- readonly type: "number";
25
- readonly description?: string;
26
- readonly minimum?: number;
27
- readonly maximum?: number;
28
- readonly "x-clamp"?: true;
29
- readonly exclusiveMinimum: 0;
30
- };
31
- }, "maxCredits" | "maxPages" | "targetCount">;
32
- export declare const GUIDED_FIRST_SOURCING_PREVIEW_CONTRACT: import("./schema.js").ContractObject<{
33
- revision: {
34
- type: "integer";
35
- description?: string;
36
- minimum?: number;
37
- maximum?: number;
38
- "x-clamp"?: true;
39
- };
40
- bounds: import("./schema.js").ContractObject<{
41
- readonly targetCount: {
42
- type: "integer";
43
- description?: string;
44
- minimum?: number;
45
- maximum?: number;
46
- "x-clamp"?: true;
47
- };
48
- readonly maxPages: {
49
- type: "integer";
50
- description?: string;
51
- minimum?: number;
52
- maximum?: number;
53
- "x-clamp"?: true;
54
- };
55
- readonly maxCredits: {
56
- readonly type: "number";
57
- readonly description?: string;
58
- readonly minimum?: number;
59
- readonly maximum?: number;
60
- readonly "x-clamp"?: true;
61
- readonly exclusiveMinimum: 0;
62
- };
63
- }, "maxCredits" | "maxPages" | "targetCount">;
64
- }, "bounds" | "revision">;
65
- export declare const GUIDED_FIRST_SOURCING_RUN_CONTRACT: import("./schema.js").ContractObject<{
66
- readonly revision: {
67
- type: "integer";
68
- description?: string;
69
- minimum?: number;
70
- maximum?: number;
71
- "x-clamp"?: true;
72
- };
73
- readonly bounds: import("./schema.js").ContractObject<{
74
- readonly targetCount: {
75
- type: "integer";
76
- description?: string;
77
- minimum?: number;
78
- maximum?: number;
79
- "x-clamp"?: true;
80
- };
81
- readonly maxPages: {
82
- type: "integer";
83
- description?: string;
84
- minimum?: number;
85
- maximum?: number;
86
- "x-clamp"?: true;
87
- };
88
- readonly maxCredits: {
89
- readonly type: "number";
90
- readonly description?: string;
91
- readonly minimum?: number;
92
- readonly maximum?: number;
93
- readonly "x-clamp"?: true;
94
- readonly exclusiveMinimum: 0;
95
- };
96
- }, "maxCredits" | "maxPages" | "targetCount">;
97
- readonly previewId: import("./schema.js").StringProperty;
98
- readonly previewDigest: import("./schema.js").StringProperty;
99
- readonly approved: import("./schema.js").BooleanProperty;
100
- }, "approved" | "bounds" | "previewDigest" | "previewId" | "revision">;
101
- export declare const GUIDED_FIRST_SOURCING_GET_CONTRACT: import("./schema.js").ContractObject<{
102
- readonly revision: {
103
- type: "integer";
104
- description?: string;
105
- minimum?: number;
106
- maximum?: number;
107
- "x-clamp"?: true;
108
- };
109
- readonly generation: {
110
- type: "integer";
111
- description?: string;
112
- minimum?: number;
113
- maximum?: number;
114
- "x-clamp"?: true;
115
- };
116
- readonly timezoneHint: import("./schema.js").StringProperty;
117
- }, never>;
118
- export declare function guidedFirstSourcingGetInputIssues(args: Readonly<Record<string, unknown>>): readonly ContractIssue[];
119
- export type GuidedFirstSourcingAction = "get" | "preview" | "run";
120
- export declare const GUIDED_FIRST_SOURCING_OPERATIONS: readonly OperationSpec[];
@@ -1,48 +0,0 @@
1
- import { bool, int, num, obj, str } from "./schema.js";
2
- import { isValidTimeZone } from "../zoned-time.js";
3
- /** Matches the actual native people-search and page safety ceilings. */
4
- export const GUIDED_FIRST_SOURCING_LIMITS = { targetCount: 50000, maxPages: 1000 };
5
- export const GUIDED_FIRST_SOURCING_BOUNDS_CONTRACT = obj({
6
- targetCount: int({ minimum: 1, maximum: GUIDED_FIRST_SOURCING_LIMITS.targetCount }),
7
- maxPages: int({ minimum: 1, maximum: GUIDED_FIRST_SOURCING_LIMITS.maxPages }),
8
- maxCredits: { ...num(), exclusiveMinimum: 0 },
9
- }, { required: ["targetCount", "maxPages", "maxCredits"] });
10
- const request = { revision: int({ minimum: 1 }), bounds: GUIDED_FIRST_SOURCING_BOUNDS_CONTRACT };
11
- export const GUIDED_FIRST_SOURCING_PREVIEW_CONTRACT = obj(request, { required: ["revision", "bounds"] });
12
- export const GUIDED_FIRST_SOURCING_RUN_CONTRACT = obj({ ...request,
13
- previewId: str({ minLength: 36, maxLength: 36, pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" }),
14
- previewDigest: str({ minLength: 64, maxLength: 64, pattern: "^[a-f0-9]{64}$" }), approved: bool(),
15
- }, { required: ["revision", "bounds", "previewId", "previewDigest", "approved"] });
16
- export const GUIDED_FIRST_SOURCING_GET_CONTRACT = obj({
17
- revision: int({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
18
- generation: int({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
19
- timezoneHint: str({ minLength: 1, maxLength: 80, description: "Provisional saved/browser IANA hint for current nightly defaults; never confirms a schedule." }),
20
- });
21
- export function guidedFirstSourcingGetInputIssues(args) {
22
- const fields = ["revision", "generation", "timezoneHint"];
23
- if (!fields.some(field => args[field] !== undefined))
24
- return [];
25
- const issues = fields.filter(field => args[field] === undefined).map(path => ({ path,
26
- message: "Supply revision, generation and timezoneHint together to read current routine defaults." }));
27
- if (typeof args.timezoneHint !== "string" || args.timezoneHint !== args.timezoneHint.trim() || !isValidTimeZone(args.timezoneHint)) {
28
- issues.push({ path: "timezoneHint", message: "Use a valid customer/browser IANA timezone suggestion; no schedule is confirmed." });
29
- }
30
- return issues;
31
- }
32
- const base = "/api/cli/onboarding/activation/{activationId}/first-sourcing";
33
- export const GUIDED_FIRST_SOURCING_OPERATIONS = [
34
- { id: "onboarding.first_sourcing.get", route: base, method: "GET", contract: GUIDED_FIRST_SOURCING_GET_CONTRACT, inputCheck: guidedFirstSourcingGetInputIssues, parameters: ["activationId"],
35
- cli: { path: ["onboarding", "first-sourcing", "get"], summary: "Read first sourcing; optionally quote current nightly defaults with revision, generation and timezoneHint." },
36
- mcp: { name: "oxygen_onboarding_first_sourcing_get", title: "Oxygen: Read first sourcing", pack: "agents",
37
- description: "Read the current creator-private DEV source, native ingestion receipt and actual settled continuation. No sourcing provider call or new sourcing; historical approval cannot authorize another pull. Verifies native billing and may reconcile an earned grant. Optional revision, generation and provisional IANA timezoneHint together return current native nightly defaults after the source transaction; no draft, schedule confirmation or approval.", annotations: { openWorldHint: true } }, mutates: true, spends: false },
38
- { id: "onboarding.first_sourcing.preview", route: `${base}/preview`, method: "POST", parameters: ["activationId"], contract: GUIDED_FIRST_SOURCING_PREVIEW_CONTRACT,
39
- cli: { path: ["onboarding", "first-sourcing", "preview"], summary: "Review a bounded native first pull into First leads; no provider search." },
40
- mcp: { name: "oxygen_onboarding_first_sourcing_preview", title: "Oxygen: Preview first sourcing", pack: "agents",
41
- description: "Current creator and revision, confirmed company, saved audience and eligible trial/paid billing required. Bounds contain targetCount, maxPages and maxCredits. Creates/reuses the native First leads Table and persists a managed source quote. Returns previewId/digest; no provider search, model or outreach.",
42
- annotations: { openWorldHint: true, destructiveHint: false } }, mutates: true, spends: false },
43
- { id: "onboarding.first_sourcing.run", route: `${base}/run`, method: "POST", parameters: ["activationId"], contract: GUIDED_FIRST_SOURCING_RUN_CONTRACT,
44
- cli: { path: ["onboarding", "first-sourcing", "run"], summary: "Approve the exact stored preview and caps; enqueue or recover the same native first-source run." },
45
- mcp: { name: "oxygen_onboarding_first_sourcing_run", title: "Oxygen: Run first sourcing", pack: "agents",
46
- description: "approved=true plus stored previewId/previewDigest, current revision and identical bounds authorize the reviewed managed people source into the activation's First leads Table. Current native admission, billing, quote and worker fences apply. Retry/Back/Restart with the same actual source recovers its native run. No model, enrollment or outreach.",
47
- annotations: { openWorldHint: true, destructiveHint: false, idempotentHint: true } }, mutates: true, spends: true },
48
- ];