@oxygen-agent/cli 1.750.4 → 1.782.1

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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +11 -1
  3. package/dist/help.js +8 -0
  4. package/dist/index.js +492 -96
  5. package/node_modules/@oxygen/shared/dist/billing.d.ts +88 -46
  6. package/node_modules/@oxygen/shared/dist/billing.js +134 -74
  7. package/node_modules/@oxygen/shared/dist/capability-discovery.js +12 -4
  8. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  9. package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +8 -0
  10. package/node_modules/@oxygen/shared/dist/copilot-journeys.js +23 -5
  11. package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +13 -2
  12. package/node_modules/@oxygen/shared/dist/future-signup-events.js +17 -2
  13. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +106 -1
  14. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +156 -45
  15. package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
  16. package/node_modules/@oxygen/shared/dist/index.js +5 -0
  17. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +33 -0
  18. package/node_modules/@oxygen/shared/dist/object-storage.js +69 -4
  19. package/node_modules/@oxygen/shared/dist/person-name.d.ts +40 -0
  20. package/node_modules/@oxygen/shared/dist/person-name.js +23 -0
  21. package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +82 -0
  22. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +130 -0
  23. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +2 -2
  24. package/node_modules/@oxygen/shared/dist/plan-limits.js +18 -2
  25. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +50 -56
  26. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +77 -90
  27. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +62 -0
  28. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +91 -0
  29. package/node_modules/@oxygen/shared/dist/provider-funding-errors.d.ts +44 -0
  30. package/node_modules/@oxygen/shared/dist/provider-funding-errors.js +81 -0
  31. package/node_modules/@oxygen/shared/dist/publishing-limits.d.ts +24 -0
  32. package/node_modules/@oxygen/shared/dist/publishing-limits.js +24 -0
  33. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +27 -6
  34. package/node_modules/@oxygen/shared/dist/spend-safety.js +34 -6
  35. package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +13 -0
  36. package/node_modules/@oxygen/shared/dist/table-capacity.js +14 -0
  37. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  38. package/node_modules/@oxygen/shared/dist/version.js +6 -3
  39. package/node_modules/@oxygen/shared/package.json +10 -0
  40. package/node_modules/@oxygen/workflows/dist/graph/diff.d.ts +33 -0
  41. package/node_modules/@oxygen/workflows/dist/graph/diff.js +75 -0
  42. package/node_modules/@oxygen/workflows/dist/graph/expression.js +17 -1
  43. package/node_modules/@oxygen/workflows/dist/graph/index.d.ts +1 -0
  44. package/node_modules/@oxygen/workflows/dist/graph/index.js +1 -0
  45. package/node_modules/@oxygen/workflows/dist/graph/lint.js +30 -28
  46. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +2 -2
  47. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +1 -1
  48. package/node_modules/@oxygen/workflows/dist/graph/remap.js +0 -5
  49. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +27 -0
  50. package/node_modules/@oxygen/workflows/dist/graph/topology.js +95 -0
  51. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +27 -8
  52. package/node_modules/@oxygen/workflows/dist/graph/types.js +2 -4
  53. package/node_modules/@oxygen/workflows/dist/index.js +0 -1
  54. package/node_modules/@oxygen/workflows/dist/portable.js +0 -9
  55. package/package.json +1 -1
@@ -111,102 +111,142 @@ export type CreditTopupPackId = CreditTopupPack["id"];
111
111
  export declare function resolveCreditTopupPack(id: string | null | undefined): CreditTopupPack | null;
112
112
  export declare const TRIAL_PERIOD_DAYS = 7;
113
113
  export declare const TRIAL_CREDIT_GRANT = 20000;
114
+ /**
115
+ * Free-tier entry (founder-ratified 2026-08-16, grant shape settled 2026-08-17).
116
+ *
117
+ * Two ONE-TIME grants and no monthly trickle. The free tier's *surface* is
118
+ * unmetered — CRM, Tables, Workflows, Knowledge, Agents, Copilot, Sequence
119
+ * authoring cost nothing marginal, so they are not rationed — and credits meter
120
+ * only real per-call spend. That is why `BASE_PRICING_PLANS.free` still carries
121
+ * `monthlyCredits: 0` and `rolloverCap: null`: 0 is the ANSWER (no monthly
122
+ * allowance exists), not a missing value, and null still means "never claw back
123
+ * a leftover balance".
124
+ *
125
+ * The split exists for a specific reason. Granting only on CLI/MCP activation
126
+ * would ship a Workspace Copilot with no funds for exactly the users who chose
127
+ * the in-product path — the default Copilot session budget is 5,000 credits, so
128
+ * the signup grant is precisely one session. The activation half is the pull
129
+ * toward the agent-native surface, and rewards the same action the Company KPI
130
+ * scorecard already counts as activation.
131
+ *
132
+ * Ceiling is therefore 10,000 credits ($10) per billing-owner organization,
133
+ * ever. Neither grant is minted for a churned or downgraded workspace, and no
134
+ * other path re-mints either one — not downgrade, churn, re-subscribe, or
135
+ * billing-owner re-link. Top-ups stay purchasable on free, which is what keeps
136
+ * the metered surface open-ended rather than capped at the grant.
137
+ */
138
+ export declare const FREE_SIGNUP_GRANT_CREDITS = 5000;
139
+ export declare const FREE_ACTIVATION_GRANT_CREDITS = 5000;
140
+ export declare const FREE_TIER_GRANTS_ENABLED_ENV_VAR = "OXYGEN_FREE_TIER_GRANTS_ENABLED";
141
+ /**
142
+ * Fail-closed rollout switch for the free-tier grants, matching the staged
143
+ * pattern used by CREDIT_COMMITMENTS_ENABLED.
144
+ *
145
+ * UNSET MEANS OFF, which is the opposite of the InboxKit-style kill switches and
146
+ * is deliberate: this flag mints credits. Until the rest of the free tier ships
147
+ * — the capability-scoped entitlement of OXP-10.3 and the card-less signup of
148
+ * OXP-10.4 — an armed grant would fund workspaces that still hit the binary
149
+ * subscription wall, which is money spent on an experience that does not work
150
+ * yet. It also means the grant cannot fire for the existing organization
151
+ * population before the cutover posture is decided.
152
+ */
153
+ export declare function freeTierGrantsEnabled(env?: Record<string, string | undefined>): boolean;
114
154
  export declare const WALKTHROUGH_COMPLETION_BONUS_CREDITS = 1000;
115
155
  export declare const WALKTHROUGH_SCRAPE_MAX_CREDITS = 2000;
116
156
  export declare const BASE_PRICING_PLANS: {
117
157
  readonly free: {
118
- readonly tier: "free";
119
- readonly name: "Free";
120
- readonly monthlyPriceCents: 0;
121
- readonly monthlyCredits: 0;
122
- readonly weeklyCreditsLimit: null;
123
- readonly rolloverCap: null;
124
158
  readonly monthlyAutomationActions: null;
125
159
  readonly automationOverageCentsPerMillion: null;
126
160
  readonly automationOverageEnabledDefault: false;
127
- readonly byokEnabled: false;
128
- readonly description: "No active plan. Start a 7-day trial on a self-serve plan to use OXYGEN — existing credit balances remain spendable.";
129
- readonly ctaLabel: "Start trial";
130
- readonly features: readonly ["Existing credits stay spendable", "Read access to your workspace"];
161
+ readonly description: "Your CRM, tables, and workflows — free forever. Credits pay for enrichment and AI; connecting a sender, publishing, managed email infrastructure, and your own provider keys need a plan.";
162
+ readonly ctaLabel: "Upgrade";
163
+ readonly features: readonly ["CRM, Tables, Workflows, and Knowledge — unmetered", "Sequences: author, preview, and enroll", "Agents and Workspace Copilot", "Credits for enrichment and AI, topped up any time"];
164
+ readonly monthlyPriceCents: number | null;
165
+ readonly monthlyCredits: number | null;
166
+ readonly weeklyCreditsLimit: number | null;
167
+ readonly rolloverCap: number | null;
168
+ readonly byokEnabled: boolean;
169
+ readonly tier: "free";
170
+ readonly name: "Free";
131
171
  };
132
172
  readonly starter: {
133
- readonly tier: "starter";
134
- readonly name: "Starter";
135
- readonly monthlyPriceCents: 9900;
136
- readonly monthlyCredits: 99000;
137
- readonly weeklyCreditsLimit: null;
138
- readonly rolloverCap: 99000;
139
173
  readonly monthlyAutomationActions: null;
140
174
  readonly automationOverageCentsPerMillion: null;
141
175
  readonly automationOverageEnabledDefault: false;
142
- readonly byokEnabled: true;
143
176
  readonly description: "For founders running focused managed enrichment and AI columns.";
144
177
  readonly ctaLabel: "Choose Starter";
145
178
  readonly features: readonly ["99,000 credits every month", "All integrations", "Workflows", "Bring your own keys", "Standard support"];
179
+ readonly monthlyPriceCents: number | null;
180
+ readonly monthlyCredits: number | null;
181
+ readonly weeklyCreditsLimit: number | null;
182
+ readonly rolloverCap: number | null;
183
+ readonly byokEnabled: boolean;
184
+ readonly tier: "starter";
185
+ readonly name: "Starter";
146
186
  };
147
187
  readonly pro: {
148
- readonly tier: "pro";
149
- readonly name: "Pro";
150
- readonly monthlyPriceCents: 24900;
151
- readonly monthlyCredits: 249000;
152
- readonly weeklyCreditsLimit: null;
153
- readonly rolloverCap: 249000;
154
188
  readonly monthlyAutomationActions: null;
155
189
  readonly automationOverageCentsPerMillion: null;
156
190
  readonly automationOverageEnabledDefault: false;
157
- readonly byokEnabled: true;
158
191
  readonly description: "For recurring GTM enrichment and AI-column workflows on managed credits.";
159
192
  readonly badge: "Most popular";
160
193
  readonly highlighted: true;
161
194
  readonly ctaLabel: "Choose Pro";
162
195
  readonly features: readonly ["249,000 credits every month", "All integrations", "Workflows", "Bring your own keys", "Standard support"];
196
+ readonly monthlyPriceCents: number | null;
197
+ readonly monthlyCredits: number | null;
198
+ readonly weeklyCreditsLimit: number | null;
199
+ readonly rolloverCap: number | null;
200
+ readonly byokEnabled: boolean;
201
+ readonly tier: "pro";
202
+ readonly name: "Pro";
163
203
  };
164
204
  readonly team: {
165
- readonly tier: "team";
166
- readonly name: "Team";
167
- readonly monthlyPriceCents: 74900;
168
- readonly monthlyCredits: 749000;
169
- readonly weeklyCreditsLimit: null;
170
- readonly rolloverCap: 749000;
171
205
  readonly monthlyAutomationActions: null;
172
206
  readonly automationOverageCentsPerMillion: null;
173
207
  readonly automationOverageEnabledDefault: false;
174
- readonly byokEnabled: true;
175
208
  readonly description: "For small GTM teams and agencies running shared workflows with their own AI keys.";
176
209
  readonly ctaLabel: "Choose Team";
177
210
  readonly features: readonly ["749,000 credits every month", "All integrations", "Workflows", "Bring your own keys", "Premium support"];
211
+ readonly monthlyPriceCents: number | null;
212
+ readonly monthlyCredits: number | null;
213
+ readonly weeklyCreditsLimit: number | null;
214
+ readonly rolloverCap: number | null;
215
+ readonly byokEnabled: boolean;
216
+ readonly tier: "team";
217
+ readonly name: "Team";
178
218
  };
179
219
  readonly scale: {
180
- readonly tier: "scale";
181
- readonly name: "Scale";
182
- readonly monthlyPriceCents: null;
183
- readonly monthlyCredits: 10000000;
184
- readonly weeklyCreditsLimit: null;
185
- readonly rolloverCap: 10000000;
186
220
  readonly monthlyAutomationActions: null;
187
221
  readonly automationOverageCentsPerMillion: null;
188
222
  readonly automationOverageEnabledDefault: false;
189
- readonly byokEnabled: true;
190
223
  readonly description: "For high-volume GTM teams that need custom limits and rollout support.";
191
224
  readonly ctaLabel: "Contact sales";
192
225
  readonly contactHref: "https://cal.com/tim-scheuer-mxbib9/45";
193
226
  readonly features: readonly ["10,000,000+ managed credits / month", "Workflows", "Phone number enrichment on managed credits", "Custom provider routing and BYOK policies", "Dedicated onboarding"];
227
+ readonly monthlyPriceCents: number | null;
228
+ readonly monthlyCredits: number | null;
229
+ readonly weeklyCreditsLimit: number | null;
230
+ readonly rolloverCap: number | null;
231
+ readonly byokEnabled: boolean;
232
+ readonly tier: "scale";
233
+ readonly name: "Scale";
194
234
  };
195
235
  readonly enterprise: {
196
- readonly tier: "enterprise";
197
- readonly name: "Enterprise";
198
- readonly monthlyPriceCents: null;
199
- readonly monthlyCredits: null;
200
- readonly weeklyCreditsLimit: null;
201
- readonly rolloverCap: null;
202
236
  readonly monthlyAutomationActions: null;
203
237
  readonly automationOverageCentsPerMillion: null;
204
238
  readonly automationOverageEnabledDefault: false;
205
- readonly byokEnabled: true;
206
239
  readonly description: "Custom procurement, invoicing, limits, and rollout support.";
207
240
  readonly ctaLabel: "Talk to us";
208
241
  readonly contactHref: "https://cal.com/tim-scheuer-mxbib9/45";
209
242
  readonly features: readonly ["Everything in Team", "Custom usage limits", "Volume pricing", "Custom invoicing and billing", "Dedicated support"];
243
+ readonly monthlyPriceCents: number | null;
244
+ readonly monthlyCredits: number | null;
245
+ readonly weeklyCreditsLimit: number | null;
246
+ readonly rolloverCap: number | null;
247
+ readonly byokEnabled: boolean;
248
+ readonly tier: "enterprise";
249
+ readonly name: "Enterprise";
210
250
  };
211
251
  };
212
252
  export declare const PUBLIC_PLAN_ORDER: readonly PlanTier[];
@@ -217,7 +257,7 @@ export type ActiveSubscriptionStatus = (typeof ACTIVE_SUBSCRIPTION_STATUSES)[num
217
257
  export declare const BILLING_VISIBLE_SUBSCRIPTION_STATUSES: readonly ["active", "trialing", "past_due", "unpaid", "paused", "incomplete"];
218
258
  export type BillingVisibleSubscriptionStatus = (typeof BILLING_VISIBLE_SUBSCRIPTION_STATUSES)[number];
219
259
  export declare const PAST_DUE_GRACE_DAYS = 7;
220
- export type SubscriptionEntitlementReason = "active" | "trialing" | "past_due_grace" | "past_due_grace_expired" | "missing_past_due_period_start" | "trial_expired" | "missing_trial_end" | "unpaid" | "paused" | "incomplete" | "canceled" | "incomplete_expired" | "missing_subscription";
260
+ export type SubscriptionEntitlementReason = "active" | "trialing" | "past_due_grace" | "past_due_grace_expired" | "missing_past_due_period_start" | "trial_expired" | "missing_trial_end" | "unpaid" | "paused" | "incomplete" | "canceled" | "incomplete_expired" | "missing_subscription" | "free_tier";
221
261
  export type SubscriptionEntitlement = {
222
262
  entitled: boolean;
223
263
  reason: SubscriptionEntitlementReason;
@@ -229,6 +269,8 @@ export declare function resolveSubscriptionEntitlement(input: {
229
269
  currentPeriodEnd?: Date | null;
230
270
  trialEnd?: Date | null;
231
271
  now?: Date;
272
+ /** Overrides the env switch. Injected by tests; production reads the flag. */
273
+ freeTierEntitled?: boolean;
232
274
  }): SubscriptionEntitlement;
233
275
  export type ResolvedPricingPlan = PricingPlanDefinition;
234
276
  export declare function resolveBasePricingPlan(tier: string | null | undefined): ResolvedPricingPlan | null;
@@ -1,3 +1,5 @@
1
+ import { AUTOMATION_ACTION_CREDITS as SNAPSHOT_AUTOMATION_ACTION_CREDITS, CREDITS_PER_USD as SNAPSHOT_CREDITS_PER_USD, PRICING_PLAN_SNAPSHOT, TOPUP_USD_CENTS_PER_1K_CREDITS as SNAPSHOT_TOPUP_USD_CENTS_PER_1K_CREDITS, } from "./pricing-snapshot.generated.js";
2
+ import { freeTierEntitlementEnabled } from "./plan-capabilities.js";
1
3
  export const WEEKLY_USAGE_WINDOW_DAYS = 7;
2
4
  export const BILLING_CURRENCIES = ["usd"];
3
5
  export const DEFAULT_BILLING_CURRENCY = "usd";
@@ -7,8 +9,13 @@ export const CONTACT_SALES_URL = "https://cal.com/tim-scheuer-mxbib9/45";
7
9
  // exactly 1,000 credits — while on-demand top-up packs price the same credit
8
10
  // at $1.25 per 1,000, making subscription credits exactly 20% cheaper than
9
11
  // credits bought on demand.
10
- export const CREDITS_PER_USD = 1_000;
11
- export const TOPUP_USD_CENTS_PER_1K_CREDITS = 125;
12
+ //
13
+ // Sourced from the generated snapshot rather than declared here: this pair was
14
+ // previously written down in seven production modules, and if any two ever
15
+ // disagreed every markup-derived price would be silently wrong by the ratio.
16
+ // Change them in packages/control-db/src/pricing/seed.ts.
17
+ export const CREDITS_PER_USD = SNAPSHOT_CREDITS_PER_USD;
18
+ export const TOPUP_USD_CENTS_PER_1K_CREDITS = SNAPSHOT_TOPUP_USD_CENTS_PER_1K_CREDITS;
12
19
  // On-demand purchases start at the original fixed packs' $10 floor and run up
13
20
  // to a $1,250 ceiling, in any 1,000-credit increment. The ceiling is a
14
21
  // single-checkout guard rail, not a spend cap: a workspace that needs more
@@ -31,7 +38,8 @@ export function creditTopupUsdCents(credits) {
31
38
  // Workflow/automation actions (steps, tool calls, row writes, retries) draw
32
39
  // from the same credit pool at a flat capacity-guard rate — the old separate
33
40
  // monthly action allowance + USD overage meter economy is retired (2026-07-14).
34
- export const AUTOMATION_ACTION_CREDITS = 0.01;
41
+ // Seed key: charge.automation.action.
42
+ export const AUTOMATION_ACTION_CREDITS = SNAPSHOT_AUTOMATION_ACTION_CREDITS;
35
43
  // ---------------------------------------------------------------------------
36
44
  // FIXED (committed) credit commitments — recurring per-resource monthly charges
37
45
  // ---------------------------------------------------------------------------
@@ -152,6 +160,52 @@ export function resolveCreditTopupPack(id) {
152
160
  // whichever comes first. Starter remains the signup default.
153
161
  export const TRIAL_PERIOD_DAYS = 7;
154
162
  export const TRIAL_CREDIT_GRANT = 20_000;
163
+ /**
164
+ * Free-tier entry (founder-ratified 2026-08-16, grant shape settled 2026-08-17).
165
+ *
166
+ * Two ONE-TIME grants and no monthly trickle. The free tier's *surface* is
167
+ * unmetered — CRM, Tables, Workflows, Knowledge, Agents, Copilot, Sequence
168
+ * authoring cost nothing marginal, so they are not rationed — and credits meter
169
+ * only real per-call spend. That is why `BASE_PRICING_PLANS.free` still carries
170
+ * `monthlyCredits: 0` and `rolloverCap: null`: 0 is the ANSWER (no monthly
171
+ * allowance exists), not a missing value, and null still means "never claw back
172
+ * a leftover balance".
173
+ *
174
+ * The split exists for a specific reason. Granting only on CLI/MCP activation
175
+ * would ship a Workspace Copilot with no funds for exactly the users who chose
176
+ * the in-product path — the default Copilot session budget is 5,000 credits, so
177
+ * the signup grant is precisely one session. The activation half is the pull
178
+ * toward the agent-native surface, and rewards the same action the Company KPI
179
+ * scorecard already counts as activation.
180
+ *
181
+ * Ceiling is therefore 10,000 credits ($10) per billing-owner organization,
182
+ * ever. Neither grant is minted for a churned or downgraded workspace, and no
183
+ * other path re-mints either one — not downgrade, churn, re-subscribe, or
184
+ * billing-owner re-link. Top-ups stay purchasable on free, which is what keeps
185
+ * the metered surface open-ended rather than capped at the grant.
186
+ */
187
+ export const FREE_SIGNUP_GRANT_CREDITS = 5_000;
188
+ export const FREE_ACTIVATION_GRANT_CREDITS = 5_000;
189
+ export const FREE_TIER_GRANTS_ENABLED_ENV_VAR = "OXYGEN_FREE_TIER_GRANTS_ENABLED";
190
+ /**
191
+ * Fail-closed rollout switch for the free-tier grants, matching the staged
192
+ * pattern used by CREDIT_COMMITMENTS_ENABLED.
193
+ *
194
+ * UNSET MEANS OFF, which is the opposite of the InboxKit-style kill switches and
195
+ * is deliberate: this flag mints credits. Until the rest of the free tier ships
196
+ * — the capability-scoped entitlement of OXP-10.3 and the card-less signup of
197
+ * OXP-10.4 — an armed grant would fund workspaces that still hit the binary
198
+ * subscription wall, which is money spent on an experience that does not work
199
+ * yet. It also means the grant cannot fire for the existing organization
200
+ * population before the cutover posture is decided.
201
+ */
202
+ export function freeTierGrantsEnabled(env = process.env) {
203
+ const raw = env[FREE_TIER_GRANTS_ENABLED_ENV_VAR];
204
+ if (!raw)
205
+ return false;
206
+ const normalized = raw.trim().toLowerCase();
207
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
208
+ }
155
209
  // Onboarding walkthrough (2026-07-21): completing the in-product walkthrough
156
210
  // grants a one-off bonus (idempotent per billing owner), and the walkthrough's
157
211
  // "start scraping engagers" step may spend at most this many credits on the
@@ -159,41 +213,70 @@ export const TRIAL_CREDIT_GRANT = 20_000;
159
213
  // is free connected-account reads).
160
214
  export const WALKTHROUGH_COMPLETION_BONUS_CREDITS = 1_000;
161
215
  export const WALKTHROUGH_SCRAPE_MAX_CREDITS = 2_000;
216
+ /**
217
+ * The five NUMERIC fields of a plan, read from the generated pricing snapshot.
218
+ *
219
+ * Prices live in packages/control-db/src/pricing/seed.ts; the marketing copy
220
+ * (name, description, ctaLabel, features) stays here, because a feature bullet
221
+ * is not a price and does not belong in a pricing table. Split that way, a plan
222
+ * re-price is one edit in the seed and this file does not move at all.
223
+ *
224
+ * Sync on purpose: this catalog is a module-level constant that has to exist
225
+ * before any request does. Async callers that want a staff override applied
226
+ * should read loadPricingBook() instead.
227
+ */
228
+ function planPricing(planKey) {
229
+ const row = PRICING_PLAN_SNAPSHOT.find((plan) => plan.planKey === planKey);
230
+ if (!row)
231
+ throw new Error(`pricing snapshot has no plan "${planKey}"`);
232
+ return {
233
+ monthlyPriceCents: row.monthlyPriceCents,
234
+ monthlyCredits: row.monthlyCredits,
235
+ weeklyCreditsLimit: row.weeklyCreditsLimit,
236
+ rolloverCap: row.rolloverCap,
237
+ byokEnabled: row.byokEnabled,
238
+ };
239
+ }
162
240
  export const BASE_PRICING_PLANS = {
163
- // Fully retired as a distribution surface (2026-07-22; entry is the
164
- // card-required self-serve trial). The tier survives only as the unentitled
165
- // fallback for churned / never-subscribed orgs: zero monthly credits means
166
- // ensureCurrentPlanCreditGrant short-circuits — no monthly minting AND no
167
- // rollover-cap claw-back of leftover balances, which stay spendable.
241
+ // The ENTRY tier since 2026-08-16, superseding the 2026-07-22 retirement that
242
+ // left this as the unentitled fallback for churned / never-subscribed orgs.
243
+ //
244
+ // `free` now serves TWO populations and deliberately does not distinguish
245
+ // them here: a newly signed-up workspace and a voluntarily churned one get the
246
+ // same capability set. Only the grants differ — a newcomer draws
247
+ // FREE_SIGNUP_GRANT_CREDITS (and FREE_ACTIVATION_GRANT_CREDITS on first
248
+ // CLI/MCP activation), a returner draws neither and keeps whatever balance it
249
+ // already had. Anything branching on `plan.tier === "free"` must be read under
250
+ // BOTH meanings: code written when this could only mean "churned org that can
251
+ // spend nothing" now also applies to a funded newcomer.
252
+ //
253
+ // The seeded numbers are unchanged and load-bearing: monthlyCredits 0 is the
254
+ // ANSWER (there is no monthly allowance — ensureCurrentPlanCreditGrant
255
+ // short-circuits, and the grants are one-time), and rolloverCap null rather
256
+ // than 0 is what stops a churned customer's leftover balance being clawed back.
168
257
  free: {
169
258
  tier: "free",
170
259
  name: "Free",
171
- monthlyPriceCents: 0,
172
- monthlyCredits: 0,
173
- weeklyCreditsLimit: null,
174
- rolloverCap: null,
260
+ ...planPricing("free"),
175
261
  monthlyAutomationActions: null,
176
262
  automationOverageCentsPerMillion: null,
177
263
  automationOverageEnabledDefault: false,
178
- byokEnabled: false,
179
- description: "No active plan. Start a 7-day trial on a self-serve plan to use OXYGEN — existing credit balances remain spendable.",
180
- ctaLabel: "Start trial",
264
+ description: "Your CRM, tables, and workflows — free forever. Credits pay for enrichment and AI; connecting a sender, publishing, managed email infrastructure, and your own provider keys need a plan.",
265
+ ctaLabel: "Upgrade",
181
266
  features: [
182
- "Existing credits stay spendable",
183
- "Read access to your workspace",
267
+ "CRM, Tables, Workflows, and Knowledge — unmetered",
268
+ "Sequences: author, preview, and enroll",
269
+ "Agents and Workspace Copilot",
270
+ "Credits for enrichment and AI, topped up any time",
184
271
  ],
185
272
  },
186
273
  starter: {
187
274
  tier: "starter",
188
275
  name: "Starter",
189
- monthlyPriceCents: 9_900,
190
- monthlyCredits: 99_000,
191
- weeklyCreditsLimit: null,
192
- rolloverCap: 99_000,
276
+ ...planPricing("starter"),
193
277
  monthlyAutomationActions: null,
194
278
  automationOverageCentsPerMillion: null,
195
279
  automationOverageEnabledDefault: false,
196
- byokEnabled: true,
197
280
  description: "For founders running focused managed enrichment and AI columns.",
198
281
  ctaLabel: "Choose Starter",
199
282
  features: [
@@ -207,14 +290,10 @@ export const BASE_PRICING_PLANS = {
207
290
  pro: {
208
291
  tier: "pro",
209
292
  name: "Pro",
210
- monthlyPriceCents: 24_900,
211
- monthlyCredits: 249_000,
212
- weeklyCreditsLimit: null,
213
- rolloverCap: 249_000,
293
+ ...planPricing("pro"),
214
294
  monthlyAutomationActions: null,
215
295
  automationOverageCentsPerMillion: null,
216
296
  automationOverageEnabledDefault: false,
217
- byokEnabled: true,
218
297
  description: "For recurring GTM enrichment and AI-column workflows on managed credits.",
219
298
  badge: "Most popular",
220
299
  highlighted: true,
@@ -230,14 +309,10 @@ export const BASE_PRICING_PLANS = {
230
309
  team: {
231
310
  tier: "team",
232
311
  name: "Team",
233
- monthlyPriceCents: 74_900,
234
- monthlyCredits: 749_000,
235
- weeklyCreditsLimit: null,
236
- rolloverCap: 749_000,
312
+ ...planPricing("team"),
237
313
  monthlyAutomationActions: null,
238
314
  automationOverageCentsPerMillion: null,
239
315
  automationOverageEnabledDefault: false,
240
- byokEnabled: true,
241
316
  description: "For small GTM teams and agencies running shared workflows with their own AI keys.",
242
317
  ctaLabel: "Choose Team",
243
318
  features: [
@@ -251,14 +326,10 @@ export const BASE_PRICING_PLANS = {
251
326
  scale: {
252
327
  tier: "scale",
253
328
  name: "Scale",
254
- monthlyPriceCents: null,
255
- monthlyCredits: 10_000_000,
256
- weeklyCreditsLimit: null,
257
- rolloverCap: 10_000_000,
329
+ ...planPricing("scale"),
258
330
  monthlyAutomationActions: null,
259
331
  automationOverageCentsPerMillion: null,
260
332
  automationOverageEnabledDefault: false,
261
- byokEnabled: true,
262
333
  description: "For high-volume GTM teams that need custom limits and rollout support.",
263
334
  ctaLabel: "Contact sales",
264
335
  contactHref: CONTACT_SALES_URL,
@@ -273,14 +344,10 @@ export const BASE_PRICING_PLANS = {
273
344
  enterprise: {
274
345
  tier: "enterprise",
275
346
  name: "Enterprise",
276
- monthlyPriceCents: null,
277
- monthlyCredits: null,
278
- weeklyCreditsLimit: null,
279
- rolloverCap: null,
347
+ ...planPricing("enterprise"),
280
348
  monthlyAutomationActions: null,
281
349
  automationOverageCentsPerMillion: null,
282
350
  automationOverageEnabledDefault: false,
283
- byokEnabled: true,
284
351
  description: "Custom procurement, invoicing, limits, and rollout support.",
285
352
  ctaLabel: "Talk to us",
286
353
  contactHref: CONTACT_SALES_URL,
@@ -322,6 +389,23 @@ export const PAST_DUE_GRACE_DAYS = 7;
322
389
  export function resolveSubscriptionEntitlement(input) {
323
390
  const now = input.now ?? new Date();
324
391
  if (!input.status) {
392
+ // A workspace that has NEVER subscribed is a free workspace, and free is a
393
+ // real entitled tier once free-tier entry is armed (founder, 2026-08-16).
394
+ // This is the switch that makes `assertSubscriptionExecutionAccess` stop
395
+ // being the interesting gate and hands the decision to the capability
396
+ // resolver instead.
397
+ //
398
+ // Scoped deliberately to "no subscription row at all". A `canceled` status
399
+ // is NOT included, even though OXP-10.1.6 puts a voluntarily churned
400
+ // workspace on this same free tier: Stripe also lands a FAILED-PAYMENT
401
+ // customer in `canceled` after its recovery attempts are exhausted, and the
402
+ // ratified 2026-08-02 lifecycle suspends execution and freezes credits for
403
+ // exactly that case. Status alone cannot tell the two apart, so treating
404
+ // `canceled` as free here would silently reverse the failed-payment freeze.
405
+ // Churned-to-free needs that distinction built first.
406
+ if (input.freeTierEntitled ?? freeTierEntitlementEnabled()) {
407
+ return { entitled: true, reason: "free_tier", graceEndsAt: null };
408
+ }
325
409
  return { entitled: false, reason: "missing_subscription", graceEndsAt: null };
326
410
  }
327
411
  if (input.status === "active") {
@@ -362,70 +446,46 @@ const LEGACY_PRICING_PLAN_OVERRIDES = {
362
446
  launch_50: {
363
447
  ...BASE_PRICING_PLANS.starter,
364
448
  name: "Legacy Launch",
365
- monthlyPriceCents: 5_000,
366
- monthlyCredits: 200_000,
367
- weeklyCreditsLimit: 200_000,
368
- rolloverCap: 200_000,
449
+ ...planPricing("launch_50"),
369
450
  description: "Legacy Launch plan.",
370
451
  },
371
452
  starter_50: {
372
453
  ...BASE_PRICING_PLANS.starter,
373
454
  name: "Legacy Starter",
374
- monthlyPriceCents: 5_000,
375
- monthlyCredits: 200_000,
376
- weeklyCreditsLimit: 200_000,
377
- rolloverCap: 200_000,
455
+ ...planPricing("starter_50"),
378
456
  description: "Legacy Starter plan.",
379
457
  },
380
458
  pro_149: {
381
459
  ...BASE_PRICING_PLANS.pro,
382
460
  name: "Legacy Pro",
383
- monthlyPriceCents: 14_900,
384
- monthlyCredits: 1_000_000,
385
- weeklyCreditsLimit: 1_000_000,
386
- rolloverCap: 1_000_000,
461
+ ...planPricing("pro_149"),
387
462
  description: "Legacy Pro plan.",
388
463
  },
389
464
  team_399: {
390
465
  ...BASE_PRICING_PLANS.team,
391
466
  name: "Legacy Team",
392
- monthlyPriceCents: 39_900,
393
- monthlyCredits: 3_000_000,
394
- weeklyCreditsLimit: 3_000_000,
395
- rolloverCap: 3_000_000,
467
+ ...planPricing("team_399"),
396
468
  description: "Legacy Team plan.",
397
469
  },
398
470
  growth_250: {
399
471
  ...BASE_PRICING_PLANS.pro,
400
- monthlyPriceCents: 25_000,
401
- monthlyCredits: 250_000,
402
- weeklyCreditsLimit: 100_000,
403
- rolloverCap: 250_000,
472
+ ...planPricing("growth_250"),
404
473
  description: "Legacy Growth plan.",
405
474
  },
406
475
  scale_750: {
407
476
  ...BASE_PRICING_PLANS.team,
408
477
  name: "Legacy Scale",
409
- monthlyPriceCents: 75_000,
410
- monthlyCredits: 750_000,
411
- weeklyCreditsLimit: 300_000,
412
- rolloverCap: 750_000,
478
+ ...planPricing("scale_750"),
413
479
  description: "Legacy Scale plan.",
414
480
  },
415
481
  starter_legacy: {
416
482
  ...BASE_PRICING_PLANS.starter,
417
- monthlyPriceCents: null,
418
- monthlyCredits: 200_000,
419
- weeklyCreditsLimit: BASE_PRICING_PLANS.starter.weeklyCreditsLimit,
420
- rolloverCap: 200_000,
483
+ ...planPricing("starter_legacy"),
421
484
  description: "Legacy Starter plan.",
422
485
  },
423
486
  scale_legacy: {
424
487
  ...BASE_PRICING_PLANS.scale,
425
- monthlyPriceCents: null,
426
- monthlyCredits: 1_200_000,
427
- weeklyCreditsLimit: BASE_PRICING_PLANS.scale.weeklyCreditsLimit,
428
- rolloverCap: 2_400_000,
488
+ ...planPricing("scale_legacy"),
429
489
  description: "Legacy Scale plan.",
430
490
  },
431
491
  };
@@ -39,11 +39,19 @@ export const OXYGEN_CAPABILITY_ROUTES = [
39
39
  notFor: "Recurring deterministic automation or standing adaptive jobs; use Workflows or Agents.",
40
40
  execution: "Start onboarding or a hosted Copilot session; every resulting action still uses its native primitive gate.",
41
41
  posture: "mixed",
42
- gatewayTools: ["oxygen_whoami", "oxygen_context_resolve", "oxygen_copilot_start"],
43
- gatewayCommands: ["whoami", "context resolve", "copilot start"],
42
+ gatewayTools: ["oxygen_whoami", "oxygen_activation_state", "oxygen_context_resolve", "oxygen_copilot_start"],
43
+ gatewayCommands: ["whoami", "activation state", "context resolve", "copilot start"],
44
44
  skills: ["oxygen-quickstart", "oxygen-gtm"],
45
- endpointSections: ["copilot", "home"],
46
- intentTerms: ["onboard", "onboarding", "first run", "first day", "setup oxygen", "copilot", "assistant", "guided"],
45
+ // `activation` carries the prescribed-play step state and the one next step. It
46
+ // belongs here rather than under a primitive because "where am I in setup" is a
47
+ // Control question — and without the section on this card, the Copilot's very
48
+ // first tool call routes past the only read that answers it.
49
+ endpointSections: ["activation", "copilot", "home"],
50
+ // "next step" alone is deliberately absent: it is a phrase every primitive's
51
+ // users say ("next step of this workflow run"), and a 6-point multi-word hit
52
+ // would drag those queries here. The terms kept are the ones only a founder
53
+ // asking where they are in setup actually types.
54
+ intentTerms: ["onboard", "onboarding", "first run", "first day", "setup oxygen", "copilot", "assistant", "guided", "activation", "where am i", "next step in setup", "what do i do next"],
47
55
  },
48
56
  {
49
57
  id: "billing-and-budgets",
@@ -57,6 +57,7 @@ export function exitCodeForErrorCode(code) {
57
57
  case "workspace_credit_cap_exceeded":
58
58
  case "byok_requires_paid_plan":
59
59
  case "subscription_access_required":
60
+ case "upgrade_required":
60
61
  return 7;
61
62
  case "network_timeout":
62
63
  return 8;
@@ -7,6 +7,14 @@ export type CopilotJourney = {
7
7
  /** The first user message a journey card seeds into a new session. */
8
8
  seedMessage: string;
9
9
  };
10
+ /**
11
+ * The one journey a workspace with no play yet is pointed at. Exported as a named
12
+ * constant because every consumer that needs "the prescribed play" must key on the
13
+ * slug: the array is display-ordered, and `COPILOT_JOURNEYS[0]` would silently
14
+ * re-designate a different journey the first time someone reorders the cards.
15
+ * `home/suggestions.tsx` keys its glyphs the same way, for the same reason.
16
+ */
17
+ export declare const PRESCRIBED_COPILOT_JOURNEY_SLUG = "inbound-led-outbound";
10
18
  export declare const COPILOT_JOURNEYS: readonly CopilotJourney[];
11
19
  export type CopilotJourneySlug = (typeof COPILOT_JOURNEYS)[number]["slug"];
12
20
  export declare function getCopilotJourney(slug: string): CopilotJourney | null;
@@ -4,17 +4,35 @@
4
4
  // Each journey is a thin descriptor over EXISTING advisory catalog Recipes:
5
5
  // journeys add no runtime behavior, and every guardrail (never enroll/send/buy/
6
6
  // change DNS without approval) rides those recipes' own approval gates.
7
+ /**
8
+ * The one journey a workspace with no play yet is pointed at. Exported as a named
9
+ * constant because every consumer that needs "the prescribed play" must key on the
10
+ * slug: the array is display-ordered, and `COPILOT_JOURNEYS[0]` would silently
11
+ * re-designate a different journey the first time someone reorders the cards.
12
+ * `home/suggestions.tsx` keys its glyphs the same way, for the same reason.
13
+ */
14
+ export const PRESCRIBED_COPILOT_JOURNEY_SLUG = "inbound-led-outbound";
7
15
  export const COPILOT_JOURNEYS = [
8
16
  {
9
- slug: "inbound-led-outbound",
17
+ slug: PRESCRIBED_COPILOT_JOURNEY_SLUG,
10
18
  title: "Inbound-led outbound",
11
- description: "Turn LinkedIn engagement, profile views, and replies into a segmented outreach motion.",
19
+ description: "Work the LinkedIn network you already have — connections and post engagers — as a capped warm motion.",
20
+ // The network-first play leads: connections and post engagers are the two
21
+ // sources a founder actually has on day 1, and profile viewers (the old lead)
22
+ // are a thin, anonymity-capped sample by comparison. Posting is second because
23
+ // it CREATES the engager stream this play captures, and replies close the loop.
12
24
  recipeSlugs: [
13
- "profile-viewer-outreach",
14
- "daily-warm-lead-queue",
25
+ "linkedin-network-first-motion",
26
+ "founder-posting-system",
15
27
  "reply-to-meeting",
16
28
  ],
17
- seedMessage: "Help me turn my recent LinkedIn engagement (profile views, post reactions, replies) into a safe, segmented outreach motion. Start by showing me what signals and connected identities I have.",
29
+ // Deliberately free of steps, commands, table names, and caps: encoding
30
+ // mechanics here would fork a second, unversioned copy of the play that no
31
+ // catalog gate can lint (ADR 0013 §11 — a journey is a thin pointer at
32
+ // advisory recipes, never a hardcoded kit). The activation-state instruction
33
+ // is the one piece of procedure that belongs here rather than in the recipe:
34
+ // it is about where THIS workspace already stands, which the catalog cannot know.
35
+ seedMessage: "I want to turn the LinkedIn network I already have — my connections and the people who engage with my posts — into a small, safe, approval-gated outreach motion. First read my workspace's current activation state and tell me where I already am, then resume from there instead of starting over. Follow the linkedin-network-first-motion recipe for the play itself. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.",
18
36
  },
19
37
  {
20
38
  slug: "tam-sourcing",