@muxoai/platform 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Muxo (`@muxoai/platform` / `@muxoai/cli` / `@muxoai/sdk`)
2
2
 
3
- Muxo (`v1.3.3`) is the unified execution, cloud infrastructure provisioning, contextual memory, and cryptographic governance platform for AI agents and applications. Customers and their agents interact exclusively with **Muxo** — either via structured tool slugs or free-form **Natural Language** (`muxo do`, `POST /api/v1/do`, `client.do()`, and MCP `plan_action`).
3
+ Muxo (`v1.4.0`) is the unified execution, cloud infrastructure provisioning, contextual memory, and cryptographic governance platform for AI agents and applications. Customers and their agents interact exclusively with **Muxo** — either via structured tool slugs or free-form **Natural Language** (`muxo do`, `POST /api/v1/do`, `client.do()`, and MCP `plan_action`).
4
4
 
5
5
  See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the complete internal engineering blueprint, open-source ecosystem Keep/Adopt/Drop matrix, and 4-pillar architecture.
6
6
 
@@ -37,10 +37,12 @@ muxo byok set openai # Interactive hidden/line prompt if key omitted
37
37
  muxo byok set composio # Executes app.* and instant.* tools at 0 Muxo credit cost
38
38
  muxo byok list
39
39
 
40
- # 6. Manage Billing, Credits & Starter Plan ($29/mo)
40
+ # 6. Manage Billing, Credits & Plans (Starter $29/mo, Pro $99/mo, yearly 2 months free)
41
41
  muxo billing status
42
+ muxo billing plans
42
43
  muxo billing topup 1000
43
- muxo billing upgrade
44
+ muxo billing upgrade --plan pro --yearly
45
+ muxo billing portal
44
46
 
45
47
  # 7. Connect an OAuth SaaS App (browser consent handoff) & Provision Live Cloud Infrastructure
46
48
  muxo link gmail --wait
@@ -115,7 +117,7 @@ Muxo ships a portable OpenAI Plugin (`plugins/muxo/plugin.json` & `plugins/muxo/
115
117
  | `src/providers/composio-sdk.ts` | Live Composio v3 Connected Apps adapter (catalog search, OAuth sessions/links, tool execution) |
116
118
  | `src/providers/composio-instant.ts` | Live Composio Instant adapter (`search`, `scrape`, `crawl`, `seo.audit`, `hackernews`, `github.repo_intel`, `dns.lookup`, `workbench`) |
117
119
  | `src/providers/stripe-projects.ts` | Live Stripe Projects CLI bridge (`status`, `catalog`, `add`, `rotate`, `remove`, `env --pull`) |
118
- | `src/providers/stripe-billing.ts` | Live Stripe Checkout billing (`$29/mo Starter Plan` & `$10 / +1,000 Credits Pack`) |
120
+ | `src/providers/stripe-billing.ts` | Live Stripe Checkout billing (Starter `$29/mo`, Pro `$99/mo`, yearly billing & credit packs with volume discounts) |
119
121
  | `src/providers/microsoft-governance.ts` | Microsoft Agent Governance Toolkit v5.0.0 (`McpSecurityScanner`, `PolicyEngine`, `AgentIdentity` Ed25519 DID, `AuditLogger`) |
120
122
  | `src/providers/supermemory.ts` | Supermemory v5 Contextual Brain (`profile`, hybrid `search`, secret-redacted `add`, `delete`) |
121
123
  | `src/engine.ts` | Unified Governance, Memory & Execution Engine implementing all 19 canonical MCP operations |
package/dist/cli.js CHANGED
@@ -113,6 +113,9 @@ async function runCli() {
113
113
  if (opts?.acceptPaymentRequired && res.status === 402 && data.checkoutUrl) {
114
114
  return data;
115
115
  }
116
+ if (opts?.acceptHumanRequired && res.status === 403 && data.code === "human_required") {
117
+ return data;
118
+ }
116
119
  if (!res.ok && data.error) {
117
120
  throw new Error(String(data.error));
118
121
  }
@@ -177,6 +180,27 @@ async function runCli() {
177
180
  const maskKey = (key) => `${key.slice(0, 14)}…`;
178
181
  // Hosted Muxo answers purchases with 402 + a Stripe Checkout URL; a self-hosted dev server may grant credits directly.
179
182
  const printBillingResult = (data) => {
183
+ if (data.code === "human_required" || data.status === "human_required") {
184
+ console.log(JSON.stringify({
185
+ ok: false,
186
+ humanRequired: true,
187
+ url: data.url,
188
+ error: data.error,
189
+ next: "Open the url in a browser and sign in to the Muxo console to finish this step. Agent API keys cannot manage billing or approvals.",
190
+ }, null, 2));
191
+ return;
192
+ }
193
+ if (data.portalUrl) {
194
+ console.log(JSON.stringify({
195
+ ok: true,
196
+ actionRequired: true,
197
+ tier: data.tier,
198
+ interval: data.interval,
199
+ portalUrl: data.portalUrl,
200
+ next: data.nextStep || "Open portalUrl in a browser; a signed-in person confirms the plan change in the Stripe billing portal.",
201
+ }, null, 2));
202
+ return;
203
+ }
180
204
  if (!data.checkoutUrl) {
181
205
  console.log(JSON.stringify(data, null, 2));
182
206
  return;
@@ -998,7 +1022,18 @@ async function runCli() {
998
1022
  if (!planId)
999
1023
  throw new Error("Usage: muxo approve <plan_id>");
1000
1024
  if (remoteUrl) {
1001
- const approved = await remoteFetch(`/api/v1/plans/${encodeURIComponent(planId)}/approve`, { method: "POST", body: {} });
1025
+ const approved = await remoteFetch(`/api/v1/plans/${encodeURIComponent(planId)}/approve`, { method: "POST", body: {}, acceptHumanRequired: true });
1026
+ if (approved.code === "human_required") {
1027
+ console.log(JSON.stringify({
1028
+ ok: false,
1029
+ humanRequired: true,
1030
+ planId,
1031
+ approvalUrl: approved.url,
1032
+ error: approved.error,
1033
+ hint: "Open approvalUrl in a browser; a signed-in person approves the plan in the Muxo console. Agent API keys cannot approve plans.",
1034
+ }, null, 2));
1035
+ return;
1036
+ }
1002
1037
  console.log(JSON.stringify(approved.plan || approved, null, 2));
1003
1038
  return;
1004
1039
  }
@@ -1050,7 +1085,18 @@ async function runCli() {
1050
1085
  const plan = planned.plan || planned;
1051
1086
  if (plan.approvalStatus === "pending_approval") {
1052
1087
  if (flags.approve || cmd === "provision") {
1053
- await remoteFetch(`/api/v1/plans/${encodeURIComponent(plan.id)}/approve`, { method: "POST", body: {} });
1088
+ const approved = await remoteFetch(`/api/v1/plans/${encodeURIComponent(plan.id)}/approve`, { method: "POST", body: {}, acceptHumanRequired: true });
1089
+ if (approved.code === "human_required") {
1090
+ console.log(JSON.stringify({
1091
+ ok: false,
1092
+ status: "pending_approval",
1093
+ humanRequired: true,
1094
+ plan,
1095
+ approvalUrl: approved.url,
1096
+ hint: "This plan needs approval from a signed-in person. Open approvalUrl in a browser to approve it in the Muxo console; agent API keys cannot approve plans.",
1097
+ }, null, 2));
1098
+ return;
1099
+ }
1054
1100
  }
1055
1101
  else {
1056
1102
  console.log(JSON.stringify({
@@ -1230,11 +1276,17 @@ async function runCli() {
1230
1276
  return;
1231
1277
  }
1232
1278
  if (sub === "upgrade") {
1279
+ const planArg = String(flags.plan || positional[2] || "starter").toLowerCase();
1280
+ const tier = planArg === "pro" ? "pro" : "starter";
1281
+ const interval = flags.yearly || flags.year || String(flags.interval || "").startsWith("y")
1282
+ ? "year"
1283
+ : "month";
1233
1284
  if (remoteUrl) {
1234
1285
  const data = await remoteFetch("/api/v1/billing/upgrade", {
1235
1286
  method: "POST",
1236
- body: { tier: "starter", bonusCredits: 1000 },
1287
+ body: { tier, interval },
1237
1288
  acceptPaymentRequired: true,
1289
+ acceptHumanRequired: true,
1238
1290
  });
1239
1291
  printBillingResult(data);
1240
1292
  return;
@@ -1282,6 +1334,27 @@ async function runCli() {
1282
1334
  console.log(JSON.stringify({ ok: true, kind, ...session }, null, 2));
1283
1335
  return;
1284
1336
  }
1337
+ if (sub === "plans") {
1338
+ const plansUrl = remoteUrl || config.baseUrl;
1339
+ const res = await fetch(`${plansUrl}/api/v1/billing/plans`, {
1340
+ headers: { Accept: "application/json" },
1341
+ });
1342
+ const data = (await res.json().catch(() => ({})));
1343
+ console.log(JSON.stringify(data, null, 2));
1344
+ return;
1345
+ }
1346
+ if (sub === "portal") {
1347
+ if (remoteUrl) {
1348
+ const data = await remoteFetch("/api/v1/billing/portal", {
1349
+ method: "POST",
1350
+ body: {},
1351
+ acceptHumanRequired: true,
1352
+ });
1353
+ printBillingResult(data);
1354
+ return;
1355
+ }
1356
+ throw new Error("muxo billing portal is only available for the hosted Muxo service (set --remote or log in first).");
1357
+ }
1285
1358
  if (sub === "verify") {
1286
1359
  const sessionId = positional[2] || String(flags.session || "");
1287
1360
  if (!sessionId)
package/dist/sdk.d.ts CHANGED
@@ -11,6 +11,77 @@ export interface MuxoClientOptions {
11
11
  fetch?: typeof fetch;
12
12
  timeoutMs?: number;
13
13
  }
14
+ /** Error thrown for any non-OK Muxo API response. Carries the machine-readable code and payload. */
15
+ export declare class MuxoApiError extends Error {
16
+ readonly status: number;
17
+ readonly code?: string;
18
+ readonly url?: string;
19
+ readonly checkoutUrl?: string;
20
+ readonly data: Record<string, unknown>;
21
+ constructor(status: number, payload: Record<string, unknown>);
22
+ }
23
+ export interface BillingPlanInfo {
24
+ tier: "free" | "starter" | "pro";
25
+ name: string;
26
+ tagline: string;
27
+ monthlyActions: number;
28
+ monthlyCredits: number;
29
+ prices: {
30
+ month: {
31
+ cents: number;
32
+ label: string;
33
+ };
34
+ year: {
35
+ cents: number;
36
+ label: string;
37
+ perMonthLabel: string;
38
+ };
39
+ };
40
+ features: string[];
41
+ }
42
+ export interface PlanCatalog {
43
+ plans: BillingPlanInfo[];
44
+ yearlyDiscount: string;
45
+ creditPriceCents: number;
46
+ creditPacks: Array<{
47
+ credits: number;
48
+ cents: number;
49
+ label: string;
50
+ }>;
51
+ minCheckoutCredits: number;
52
+ }
53
+ export type UpgradePlanResult = {
54
+ ok: true;
55
+ unchanged: true;
56
+ tier: string;
57
+ message: string;
58
+ workspace?: Workspace;
59
+ } | {
60
+ ok: true;
61
+ mode: "portal";
62
+ action: "billing_portal";
63
+ tier: string;
64
+ interval: string;
65
+ portalUrl: string;
66
+ url: string;
67
+ nextStep: string;
68
+ } | {
69
+ ok: false;
70
+ code: "payment_required";
71
+ paymentRequired: true;
72
+ checkoutUrl: string;
73
+ sessionId: string;
74
+ kind: string;
75
+ tier: string;
76
+ interval: string;
77
+ credits: number;
78
+ error: string;
79
+ } | {
80
+ ok: false;
81
+ code: "human_required";
82
+ url: string;
83
+ error: string;
84
+ };
14
85
  export declare class MuxoClient {
15
86
  readonly baseUrl: string;
16
87
  private readonly apiKeyResolver?;
@@ -398,19 +469,41 @@ export declare class MuxoClient {
398
469
  provider: string;
399
470
  keys: ByokKeyRecord[];
400
471
  }>;
401
- upgradePlan(): Promise<{
472
+ /** Lists plans, prices, and credit packs. No auth required. */
473
+ getPlans(): Promise<{
474
+ ok: true;
475
+ } & PlanCatalog>;
476
+ /**
477
+ * Moves the workspace to a plan. A first subscription returns a Stripe checkout URL
478
+ * (`payment_required`); changing an existing subscription returns a billing-portal URL.
479
+ * Plan changes are confirmed by a signed-in person; API keys get `human_required`.
480
+ */
481
+ upgradePlan(options?: {
482
+ tier?: "starter" | "pro";
483
+ interval?: "month" | "year";
484
+ }): Promise<UpgradePlanResult>;
485
+ /**
486
+ * Opens the Stripe billing portal to manage payment details, switch plans, or cancel.
487
+ * Signed-in people only; API keys get a `human_required` MuxoApiError.
488
+ */
489
+ openBillingPortal(): Promise<{
402
490
  ok: true;
403
- tier: string;
404
- workspace: Workspace;
405
- settlementSummary: SupplierSettlementSummary;
491
+ url: string;
492
+ portalUrl: string;
406
493
  }>;
407
- createCheckoutSession(kind?: "starter_subscription" | "credit_topup", credits?: number): Promise<{
494
+ createCheckoutSession(kind?: "subscription" | "starter_subscription" | "credit_topup", optionsOrCredits?: number | {
495
+ credits?: number;
496
+ tier?: "starter" | "pro";
497
+ interval?: "month" | "year";
498
+ }): Promise<{
408
499
  ok: true;
409
500
  sessionId: string;
410
501
  url: string;
411
502
  mode?: string;
412
503
  kind?: string;
413
504
  credits?: number;
505
+ tier?: string;
506
+ interval?: string;
414
507
  }>;
415
508
  verifyCheckoutSession(sessionId: string): Promise<{
416
509
  ok: true;
package/dist/sdk.js CHANGED
@@ -1,3 +1,21 @@
1
+ /** Error thrown for any non-OK Muxo API response. Carries the machine-readable code and payload. */
2
+ export class MuxoApiError extends Error {
3
+ status;
4
+ code;
5
+ url;
6
+ checkoutUrl;
7
+ data;
8
+ constructor(status, payload) {
9
+ super(String(payload.error || `Muxo request failed with HTTP ${status}`));
10
+ this.name = "MuxoApiError";
11
+ this.status = status;
12
+ this.code = typeof payload.code === "string" ? payload.code : undefined;
13
+ this.url = typeof payload.url === "string" ? payload.url : undefined;
14
+ this.checkoutUrl =
15
+ typeof payload.checkoutUrl === "string" ? payload.checkoutUrl : undefined;
16
+ this.data = payload;
17
+ }
18
+ }
1
19
  export class MuxoClient {
2
20
  baseUrl;
3
21
  apiKeyResolver;
@@ -41,7 +59,7 @@ export class MuxoClient {
41
59
  });
42
60
  const payload = (await res.json());
43
61
  if (!res.ok || payload.ok === false) {
44
- throw new Error(String(payload.error || `Muxo request failed with HTTP ${res.status}`));
62
+ throw new MuxoApiError(res.status, payload);
45
63
  }
46
64
  return payload;
47
65
  }
@@ -166,8 +184,21 @@ export class MuxoClient {
166
184
  });
167
185
  let plan = planned.plan;
168
186
  if (plan.approvalStatus === "pending_approval" && options.approve) {
169
- const approved = await this.approvePlan(plan.id);
170
- plan = approved.plan;
187
+ try {
188
+ const approved = await this.approvePlan(plan.id);
189
+ plan = approved.plan;
190
+ }
191
+ catch (err) {
192
+ if (err instanceof MuxoApiError && err.code === "human_required") {
193
+ throw new MuxoApiError(err.status, {
194
+ ...err.data,
195
+ planId: plan.id,
196
+ approvalUrl: err.url,
197
+ error: `Plan ${plan.id} needs approval from a signed-in person at ${err.url || "the Muxo console"}; API keys cannot approve plans.`,
198
+ });
199
+ }
200
+ throw err;
201
+ }
171
202
  }
172
203
  const executed = await this.executePlan(plan.id);
173
204
  return {
@@ -360,16 +391,50 @@ export class MuxoClient {
360
391
  method: "DELETE",
361
392
  });
362
393
  }
363
- async upgradePlan() {
364
- return this.request("/api/v1/billing/upgrade", {
394
+ /** Lists plans, prices, and credit packs. No auth required. */
395
+ async getPlans() {
396
+ return this.request("/api/v1/billing/plans");
397
+ }
398
+ /**
399
+ * Moves the workspace to a plan. A first subscription returns a Stripe checkout URL
400
+ * (`payment_required`); changing an existing subscription returns a billing-portal URL.
401
+ * Plan changes are confirmed by a signed-in person; API keys get `human_required`.
402
+ */
403
+ async upgradePlan(options = {}) {
404
+ try {
405
+ return await this.request("/api/v1/billing/upgrade", {
406
+ method: "POST",
407
+ body: {
408
+ tier: options.tier || "starter",
409
+ interval: options.interval || "month",
410
+ },
411
+ });
412
+ }
413
+ catch (err) {
414
+ if (err instanceof MuxoApiError &&
415
+ (err.code === "payment_required" || err.code === "human_required")) {
416
+ return err.data;
417
+ }
418
+ throw err;
419
+ }
420
+ }
421
+ /**
422
+ * Opens the Stripe billing portal to manage payment details, switch plans, or cancel.
423
+ * Signed-in people only; API keys get a `human_required` MuxoApiError.
424
+ */
425
+ async openBillingPortal() {
426
+ return this.request("/api/v1/billing/portal", {
365
427
  method: "POST",
366
428
  body: {},
367
429
  });
368
430
  }
369
- async createCheckoutSession(kind = "credit_topup", credits) {
431
+ async createCheckoutSession(kind = "credit_topup", optionsOrCredits) {
432
+ const opts = typeof optionsOrCredits === "number"
433
+ ? { credits: optionsOrCredits }
434
+ : optionsOrCredits || {};
370
435
  return this.request("/api/v1/billing/checkout", {
371
436
  method: "POST",
372
- body: credits === undefined ? { kind } : { kind, credits },
437
+ body: { kind, ...opts },
373
438
  });
374
439
  }
375
440
  async verifyCheckoutSession(sessionId) {
package/dist/types.d.ts CHANGED
@@ -1,6 +1,23 @@
1
1
  export type RiskLevel = "read" | "write" | "admin" | "financial" | "provisioning" | "destructive";
2
2
  export type ProviderLane = "composio_sdk" | "composio_instant" | "stripe_projects";
3
- export type SubscriptionTier = "free" | "starter";
3
+ export type SubscriptionTier = "free" | "starter" | "pro";
4
+ export type SubscriptionStatus = "none" | "active" | "trialing" | "past_due" | "unpaid" | "canceled" | "incomplete" | "comped";
5
+ export interface WorkspaceBilling {
6
+ interval: "month" | "year" | null;
7
+ status: SubscriptionStatus;
8
+ cancelAtPeriodEnd: boolean;
9
+ /** When Stripe next renews or ends the subscription. */
10
+ currentPeriodEnd: string | null;
11
+ /** Monthly usage periods are counted from this moment. */
12
+ usagePeriodAnchor: string;
13
+ usagePeriodStart: string;
14
+ usagePeriodEnd: string;
15
+ /** usagePeriodStart of the last period whose plan credits were granted. */
16
+ creditsGrantedFor: string | null;
17
+ paymentFailedAt?: string | null;
18
+ /** Subscription whose start (fresh usage period and first credits) was already applied. */
19
+ startedSubscriptionId?: string | null;
20
+ }
4
21
  export interface GovernancePolicy {
5
22
  mode: "monitor" | "enforce";
6
23
  requireApprovalFor: RiskLevel[];
@@ -32,6 +49,7 @@ export interface Workspace {
32
49
  creditsBalance: number;
33
50
  stripeCustomerId: string | null;
34
51
  stripeSubscriptionId: string | null;
52
+ billing?: WorkspaceBilling;
35
53
  projectsEnvName: string;
36
54
  policy: GovernancePolicy;
37
55
  createdAt: string;
@@ -360,7 +378,7 @@ export interface ExecutionReceipt {
360
378
  export interface LedgerEntry {
361
379
  id: string;
362
380
  workspaceId: string;
363
- kind: "plan_grant" | "credit_topup" | "run_deduction" | "subscription_upgrade" | "manual_adjustment";
381
+ kind: "plan_grant" | "credit_topup" | "run_deduction" | "subscription_upgrade" | "plan_change" | "refund" | "manual_adjustment";
364
382
  actionsDelta: number;
365
383
  creditsDelta: number;
366
384
  balanceAfter: number;
@@ -267,7 +267,7 @@ var LANE_LABELS = { composio_instant: 'Instant tool', composio_sdk: 'Connected a
267
267
  var TOKEN_KEY = 'muxo_external_api_key';
268
268
  var ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
269
269
 
270
- var state = { view: 'overview', boot: null, harness: 'plugin', lane: 'all', factsMode: 'current', facts: null, authMode: 'login' };
270
+ var state = { view: 'overview', boot: null, harness: 'plugin', lane: 'all', factsMode: 'current', facts: null, authMode: 'login', billingInterval: 'month', planCatalog: null };
271
271
  var currentTool = null;
272
272
  var activeKeyHint = 'mk_live_...';
273
273
  var externalApiKey = readToken();
@@ -472,6 +472,7 @@ async function api(path, opts) {
472
472
  if (!res.ok || data.ok === false) {
473
473
  var err = new Error(data.error || data.message || ('HTTP ' + res.status));
474
474
  err.status = res.status;
475
+ err.data = data;
475
476
  if (res.status === 401 && !opts.anonymous) handleUnauthorized();
476
477
  throw err;
477
478
  }
@@ -1248,10 +1249,19 @@ async function loadPlans() {
1248
1249
  : (p.approvalStatus === 'approved' || p.approvalStatus === 'auto_approved')
1249
1250
  ? '<button class="btn btn-sm" data-action="execute-plan" data-id="' + id + '">Run</button>'
1250
1251
  : '<span class="mono small muted">' + esc(p.runId ? shortId(p.runId) : 'Done') + '</span>';
1251
- return '<tr><td class="mono small">' + esc(shortId(p.id)) + '</td><td class="mono small">' + esc(p.toolSlug) + '</td>' +
1252
+ return '<tr id="plan-row-' + id + '"><td class="mono small">' + esc(shortId(p.id)) + '</td><td class="mono small">' + esc(p.toolSlug) + '</td>' +
1252
1253
  '<td>' + riskBadge(p.riskLevel) + ' ' + pill(verdict, verdict === 'deny' ? 'danger' : '') + '</td>' +
1253
1254
  '<td>' + statusBadge(p.approvalStatus) + '</td><td class="cell-actions">' + action + '</td></tr>';
1254
1255
  }).join('') : emptyRow(5, 'Nothing waiting. Plans appear here when an agent asks to run a tool.');
1256
+ var focusPlan = new URLSearchParams(window.location.search).get('plan');
1257
+ if (focusPlan && /^[A-Za-z0-9_-]+$/.test(focusPlan)) {
1258
+ var row = byId('plan-row-' + focusPlan);
1259
+ if (row) {
1260
+ row.style.background = 'rgba(99, 102, 241, 0.14)';
1261
+ row.scrollIntoView({ behavior: 'smooth', block: 'center' });
1262
+ showToast('This plan is waiting for your approval.');
1263
+ }
1264
+ }
1255
1265
  }
1256
1266
  async function approveAndRun(planId) {
1257
1267
  await post('/api/v1/plans/' + planId + '/approve');
@@ -1349,6 +1359,7 @@ async function loadLedger() {
1349
1359
  '<td class="mono" style="color:' + (delta >= 0 ? 'var(--color-ok)' : 'var(--color-text)') + '">' + (delta >= 0 ? '+' : '') + esc(fmt(delta)) + '</td>' +
1350
1360
  '<td class="mono">' + esc(fmt(e.balanceAfter)) + '</td></tr>';
1351
1361
  }).join('') : emptyRow(4, 'No billing activity yet.');
1362
+ await renderPlanCards();
1352
1363
  await loadByokKeys();
1353
1364
  }
1354
1365
  async function loadByokKeys() {
@@ -1426,6 +1437,85 @@ async function startCheckout(kind) {
1426
1437
  if (res.url) window.location.href = res.url;
1427
1438
  }
1428
1439
 
1440
+ var FALLBACK_PLANS = [
1441
+ { tier: 'free', name: 'Free', tagline: 'Try Muxo with your first agent.', monthlyActions: 500, monthlyCredits: 0, prices: { month: { cents: 0, label: '$0' }, year: { cents: 0, label: '$0', perMonthLabel: '$0' } }, features: ['500 governed actions a month', '250 trial credits', 'Approvals, audit log, and workspace memory', 'Bring your own provider keys'] },
1442
+ { tier: 'starter', name: 'Starter', tagline: 'For builders running agents every day.', monthlyActions: 5000, monthlyCredits: 1000, prices: { month: { cents: 2900, label: '$29' }, year: { cents: 29000, label: '$290', perMonthLabel: '$24' } }, features: ['5,000 governed actions a month', '1,000 credits every month', 'Everything in Free', 'Email support'] },
1443
+ { tier: 'pro', name: 'Pro', tagline: 'For teams with agents in production.', monthlyActions: 25000, monthlyCredits: 5000, prices: { month: { cents: 9900, label: '$99' }, year: { cents: 99000, label: '$990', perMonthLabel: '$82.50' } }, features: ['25,000 governed actions a month', '5,000 credits every month', 'Everything in Starter', 'Priority support'] }
1444
+ ];
1445
+ var TIER_ORDER = { free: 0, starter: 1, pro: 2 };
1446
+
1447
+ async function planCatalog() {
1448
+ if (!state.planCatalog) {
1449
+ try { state.planCatalog = await api('/api/v1/billing/plans', { anonymous: true }); } catch (e) { state.planCatalog = { plans: FALLBACK_PLANS }; }
1450
+ }
1451
+ return state.planCatalog;
1452
+ }
1453
+
1454
+ async function renderPlanCards() {
1455
+ var box = byId('plan-cards');
1456
+ if (!box) return;
1457
+ var catalog = await planCatalog();
1458
+ var org = (state.boot && state.boot.organization) || {};
1459
+ var billing = (state.boot && state.boot.billing) || {};
1460
+ var tier = String(org.tier || 'free');
1461
+ var interval = state.billingInterval;
1462
+ box.innerHTML = (catalog.plans || FALLBACK_PLANS).map(function (p) {
1463
+ var current = tier === p.tier;
1464
+ var price = p.prices[interval] || p.prices.month;
1465
+ var priceLine = p.tier === 'free'
1466
+ ? '<p class="price">$0 <span>forever</span></p>'
1467
+ : '<p class="price">' + esc(price.label) + ' <span>' + (interval === 'year' ? 'per year · ' + esc(price.perMonthLabel) + '/mo' : 'per month') + '</span></p>';
1468
+ var action;
1469
+ if (current) action = pill('Current plan', 'ok');
1470
+ else if (TIER_ORDER[p.tier] > TIER_ORDER[tier]) action = '<button class="btn btn-primary" type="button" data-action="change-plan" data-tier="' + esc(p.tier) + '">Upgrade to ' + esc(p.name) + '</button>';
1471
+ else action = '<button class="btn" type="button" data-action="change-plan" data-tier="' + esc(p.tier) + '">Switch to ' + esc(p.name) + '</button>';
1472
+ return '<div class="panel"><div class="panel-header"><div><h2>' + esc(p.name) + '</h2><p>' + esc(p.tagline || '') + '</p></div></div>' +
1473
+ priceLine +
1474
+ '<ul class="plan-list">' + (p.features || []).map(function (f) { return '<li>' + esc(f) + '</li>'; }).join('') + '</ul>' +
1475
+ '<div class="page-actions">' + action + '</div></div>';
1476
+ }).join('');
1477
+ var manage = byId('manage-billing-btn');
1478
+ if (manage) manage.hidden = !billing.canManageBilling;
1479
+ var note = [];
1480
+ if (billing.status === 'comped') note.push('Complimentary plan');
1481
+ else if (billing.status && billing.status !== 'none') note.push('Subscription ' + String(billing.status).replace(/_/g, ' '));
1482
+ if (billing.cancelAtPeriodEnd && billing.renewsAt) note.push('ends ' + String(billing.renewsAt).slice(0, 10));
1483
+ else if (billing.renewsAt && billing.status !== 'comped') note.push('renews ' + String(billing.renewsAt).slice(0, 10));
1484
+ if (billing.paymentFailed) note.push('Payment failed — update your card in Manage billing');
1485
+ if (billing.usage && billing.usage.resetsAt) note.push('Usage resets ' + String(billing.usage.resetsAt).slice(0, 10));
1486
+ setText('billing-status-note', note.join(' · '));
1487
+ }
1488
+
1489
+ function setBillingInterval(interval) {
1490
+ state.billingInterval = interval === 'year' ? 'year' : 'month';
1491
+ document.querySelectorAll('[data-action="billing-interval"]').forEach(function (b) {
1492
+ b.setAttribute('aria-pressed', String(b.dataset.interval === state.billingInterval));
1493
+ });
1494
+ renderPlanCards().catch(showError);
1495
+ }
1496
+
1497
+ async function changePlan(tier) {
1498
+ showToast('Talking to Stripe…');
1499
+ try {
1500
+ var res = await post('/api/v1/billing/upgrade', { tier: tier, interval: state.billingInterval });
1501
+ if (res.portalUrl || (res.action === 'billing_portal' && res.url)) { window.location.href = res.portalUrl || res.url; return; }
1502
+ if (res.url) { window.location.href = res.url; return; }
1503
+ showToast(res.message || 'Plan is unchanged.');
1504
+ await refreshOverview();
1505
+ await renderPlanCards();
1506
+ } catch (err) {
1507
+ var data = err && err.data;
1508
+ if (data && (data.checkoutUrl || data.url)) { window.location.href = data.checkoutUrl || data.url; return; }
1509
+ showError(err);
1510
+ }
1511
+ }
1512
+
1513
+ async function manageBilling() {
1514
+ showToast('Opening the Stripe billing portal…');
1515
+ var res = await post('/api/v1/billing/portal', {});
1516
+ if (res.url) window.location.href = res.url;
1517
+ }
1518
+
1429
1519
  async function checkCheckoutReturn() {
1430
1520
  var params = new URLSearchParams(window.location.search);
1431
1521
  var clean = false;
@@ -1442,6 +1532,7 @@ async function checkCheckoutReturn() {
1442
1532
  }
1443
1533
  }
1444
1534
  if (params.get('checkout') === 'cancelled') { clean = true; showToast('Checkout cancelled. You were not charged.'); }
1535
+ if (params.get('plan') === 'updated') { clean = true; showToast('Your plan change is confirmed.'); }
1445
1536
  if (params.get('connected')) { clean = true; showToast(params.get('connected') + ' is connected'); }
1446
1537
  // Keys are never accepted from the URL: a crafted link could sign a person into someone else's workspace.
1447
1538
  if (params.get('api_key')) clean = true;
@@ -1476,6 +1567,9 @@ var ACTIONS = {
1476
1567
  'delete-memory': function (el) { return deleteMemoryItem(el.dataset.id); },
1477
1568
  'facts-mode': function (el) { setFactsMode(el.dataset.mode); },
1478
1569
  'approve-plan': function (el) { return approveAndRun(el.dataset.id); },
1570
+ 'billing-interval': function (el) { setBillingInterval(el.dataset.interval); },
1571
+ 'change-plan': function (el) { return changePlan(el.dataset.tier); },
1572
+ 'manage-billing': function () { return manageBilling(); },
1479
1573
  'execute-plan': function (el) { return executeExistingPlan(el.dataset.id); },
1480
1574
  'kill-resume': resumeKillSwitch,
1481
1575
  'refresh-governance': loadPlans,
@@ -2058,16 +2152,18 @@ export function renderConsoleHtml() {
2058
2152
  <div class="metric"><p class="metric-label">Paid to providers</p><p class="metric-value" id="billing-wholesale">$0.00</p><p class="metric-sub">Settled by Muxo for you</p></div>
2059
2153
  </div>
2060
2154
  <div class="split even">
2061
- <div class="panel">
2062
- <div class="panel-header"><div><h2>Starter plan</h2><p>For teams running agents every day.</p></div></div>
2063
- <p class="price">$29 <span>per month</span></p>
2064
- <ul class="plan-list">
2065
- <li>10,000 governed actions a month</li>
2066
- <li>1,000 credits included</li>
2067
- <li>Approvals, audit log, and workspace memory</li>
2068
- </ul>
2069
- <div class="page-actions">
2070
- <button class="btn btn-primary" type="button" data-action="checkout" data-kind="starter_subscription">Upgrade with Stripe</button>
2155
+ <div class="panel" style="grid-column:1/-1">
2156
+ <div class="panel-header">
2157
+ <div><h2>Plans</h2><p>Every plan includes approvals, the audit log, and workspace memory. Yearly billing gives you 2 months free.</p></div>
2158
+ <div class="row-actions" role="group" aria-label="Billing period">
2159
+ <button class="chip" type="button" data-action="billing-interval" data-interval="month" aria-pressed="true">Monthly</button>
2160
+ <button class="chip" type="button" data-action="billing-interval" data-interval="year" aria-pressed="false">Yearly · 2 months free</button>
2161
+ </div>
2162
+ </div>
2163
+ <div id="plan-cards" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px"><p class="empty-note">Loading plans…</p></div>
2164
+ <div class="page-actions" style="margin-top:16px;align-items:center">
2165
+ <button class="btn" type="button" data-action="manage-billing" id="manage-billing-btn" hidden>Manage billing in Stripe</button>
2166
+ <small class="muted" id="billing-status-note"></small>
2071
2167
  </div>
2072
2168
  </div>
2073
2169
  <div class="panel">
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MUXO_VERSION = "1.3.3";
1
+ export declare const MUXO_VERSION = "1.4.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const MUXO_VERSION = "1.3.3";
1
+ export const MUXO_VERSION = "1.4.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muxoai/platform",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "Muxo — Unified Execution, Provisioning & Governance Platform over Composio, Stripe Projects, Microsoft Agent Governance & Supermemory",
5
5
  "private": false,
6
6
  "license": "MIT",