@muxoai/platform 1.3.2 → 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 +6 -4
- package/dist/clerk.d.ts +2 -0
- package/dist/clerk.js +3 -0
- package/dist/cli.js +111 -17
- package/dist/sdk.d.ts +100 -5
- package/dist/sdk.js +72 -7
- package/dist/server.js +2 -1
- package/dist/types.d.ts +20 -2
- package/dist/ui/console-html.d.ts +7 -0
- package/dist/ui/console-html.js +158 -49
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Muxo (`@muxoai/platform` / `@muxoai/cli` / `@muxoai/sdk`)
|
|
2
2
|
|
|
3
|
-
Muxo (`v1.
|
|
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
|
|
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
|
|
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/clerk.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface ClerkProfile {
|
|
|
15
15
|
email: string | null;
|
|
16
16
|
emailVerified: boolean;
|
|
17
17
|
name: string | null;
|
|
18
|
+
/** Every verified address on the account, lowercased, primary included. */
|
|
19
|
+
verifiedEmails: string[];
|
|
18
20
|
}
|
|
19
21
|
export interface ClerkEnv {
|
|
20
22
|
CLERK_PUBLISHABLE_KEY?: string;
|
package/dist/clerk.js
CHANGED
|
@@ -187,6 +187,9 @@ export async function fetchClerkProfile(userId, secretKey) {
|
|
|
187
187
|
email: primary?.email_address ? primary.email_address.toLowerCase() : null,
|
|
188
188
|
emailVerified: primary?.verification?.status === "verified",
|
|
189
189
|
name,
|
|
190
|
+
verifiedEmails: addresses
|
|
191
|
+
.filter((e) => e.email_address && e.verification?.status === "verified")
|
|
192
|
+
.map((e) => e.email_address.toLowerCase()),
|
|
190
193
|
};
|
|
191
194
|
}
|
|
192
195
|
catch {
|
package/dist/cli.js
CHANGED
|
@@ -110,6 +110,12 @@ async function runCli() {
|
|
|
110
110
|
body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
111
111
|
});
|
|
112
112
|
const data = (await res.json().catch(() => ({})));
|
|
113
|
+
if (opts?.acceptPaymentRequired && res.status === 402 && data.checkoutUrl) {
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
if (opts?.acceptHumanRequired && res.status === 403 && data.code === "human_required") {
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
113
119
|
if (!res.ok && data.error) {
|
|
114
120
|
throw new Error(String(data.error));
|
|
115
121
|
}
|
|
@@ -171,6 +177,44 @@ async function runCli() {
|
|
|
171
177
|
};
|
|
172
178
|
return { store, engine: getEngine(), ctx, tokenAuth };
|
|
173
179
|
};
|
|
180
|
+
const maskKey = (key) => `${key.slice(0, 14)}…`;
|
|
181
|
+
// Hosted Muxo answers purchases with 402 + a Stripe Checkout URL; a self-hosted dev server may grant credits directly.
|
|
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
|
+
}
|
|
204
|
+
if (!data.checkoutUrl) {
|
|
205
|
+
console.log(JSON.stringify(data, null, 2));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
console.log(JSON.stringify({
|
|
209
|
+
ok: true,
|
|
210
|
+
paymentRequired: true,
|
|
211
|
+
kind: data.kind,
|
|
212
|
+
credits: data.credits,
|
|
213
|
+
checkoutUrl: data.checkoutUrl,
|
|
214
|
+
sessionId: data.sessionId,
|
|
215
|
+
next: `Open checkoutUrl to pay with Stripe. Muxo adds the credits once Stripe confirms the payment. Check with: muxo billing verify ${data.sessionId}`,
|
|
216
|
+
}, null, 2));
|
|
217
|
+
};
|
|
174
218
|
// CLI Login / Autonomous External Signup (`muxo login`)
|
|
175
219
|
if (cmd === "login" || cmd === "signup") {
|
|
176
220
|
const explicitKey = typeof flags["api-key"] === "string"
|
|
@@ -203,6 +247,7 @@ async function runCli() {
|
|
|
203
247
|
configPath: cfgPath,
|
|
204
248
|
workspace: ws,
|
|
205
249
|
...authPayload,
|
|
250
|
+
apiKey: maskKey(explicitKey),
|
|
206
251
|
}, null, 2));
|
|
207
252
|
return;
|
|
208
253
|
}
|
|
@@ -227,6 +272,7 @@ async function runCli() {
|
|
|
227
272
|
mode: "api_key",
|
|
228
273
|
configPath: cfgPath,
|
|
229
274
|
...authPayload,
|
|
275
|
+
apiKey: maskKey(explicitKey),
|
|
230
276
|
}, null, 2));
|
|
231
277
|
return;
|
|
232
278
|
}
|
|
@@ -402,7 +448,7 @@ async function runCli() {
|
|
|
402
448
|
else {
|
|
403
449
|
console.log(`\n✓ Muxo is ready!`);
|
|
404
450
|
console.log(` Workspace : ${ensuredConfig.workspaceName} (${ensuredConfig.workspaceId})`);
|
|
405
|
-
console.log(` API Key : ${ensuredConfig.apiKey}`);
|
|
451
|
+
console.log(` API Key : ${maskKey(ensuredConfig.apiKey)} (saved in ${getCliConfigPath()})`);
|
|
406
452
|
console.log(` MCP URL : ${mcpServerUrl}`);
|
|
407
453
|
console.log(` MCP Config: ${writtenFiles.join(", ")}`);
|
|
408
454
|
console.log(`\nTry it now:`);
|
|
@@ -976,7 +1022,18 @@ async function runCli() {
|
|
|
976
1022
|
if (!planId)
|
|
977
1023
|
throw new Error("Usage: muxo approve <plan_id>");
|
|
978
1024
|
if (remoteUrl) {
|
|
979
|
-
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
|
+
}
|
|
980
1037
|
console.log(JSON.stringify(approved.plan || approved, null, 2));
|
|
981
1038
|
return;
|
|
982
1039
|
}
|
|
@@ -1028,7 +1085,18 @@ async function runCli() {
|
|
|
1028
1085
|
const plan = planned.plan || planned;
|
|
1029
1086
|
if (plan.approvalStatus === "pending_approval") {
|
|
1030
1087
|
if (flags.approve || cmd === "provision") {
|
|
1031
|
-
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
|
+
}
|
|
1032
1100
|
}
|
|
1033
1101
|
else {
|
|
1034
1102
|
console.log(JSON.stringify({
|
|
@@ -1183,8 +1251,9 @@ async function runCli() {
|
|
|
1183
1251
|
const data = await remoteFetch("/api/v1/billing/topup", {
|
|
1184
1252
|
method: "POST",
|
|
1185
1253
|
body: { credits },
|
|
1254
|
+
acceptPaymentRequired: true,
|
|
1186
1255
|
});
|
|
1187
|
-
|
|
1256
|
+
printBillingResult(data);
|
|
1188
1257
|
return;
|
|
1189
1258
|
}
|
|
1190
1259
|
const { store, ctx } = getLocalContext();
|
|
@@ -1207,12 +1276,19 @@ async function runCli() {
|
|
|
1207
1276
|
return;
|
|
1208
1277
|
}
|
|
1209
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";
|
|
1210
1284
|
if (remoteUrl) {
|
|
1211
1285
|
const data = await remoteFetch("/api/v1/billing/upgrade", {
|
|
1212
1286
|
method: "POST",
|
|
1213
|
-
body: { tier
|
|
1287
|
+
body: { tier, interval },
|
|
1288
|
+
acceptPaymentRequired: true,
|
|
1289
|
+
acceptHumanRequired: true,
|
|
1214
1290
|
});
|
|
1215
|
-
|
|
1291
|
+
printBillingResult(data);
|
|
1216
1292
|
return;
|
|
1217
1293
|
}
|
|
1218
1294
|
const { store, ctx } = getLocalContext();
|
|
@@ -1258,6 +1334,27 @@ async function runCli() {
|
|
|
1258
1334
|
console.log(JSON.stringify({ ok: true, kind, ...session }, null, 2));
|
|
1259
1335
|
return;
|
|
1260
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
|
+
}
|
|
1261
1358
|
if (sub === "verify") {
|
|
1262
1359
|
const sessionId = positional[2] || String(flags.session || "");
|
|
1263
1360
|
if (!sessionId)
|
|
@@ -1483,21 +1580,18 @@ async function runCli() {
|
|
|
1483
1580
|
}
|
|
1484
1581
|
if (choice === "3") {
|
|
1485
1582
|
const action = (await promptUserLine("Type 'upgrade' for Starter Plan or credit amount (default 1000): ")).toLowerCase();
|
|
1486
|
-
|
|
1487
|
-
|
|
1583
|
+
const up = action === "upgrade"
|
|
1584
|
+
? await remoteFetch("/api/v1/billing/upgrade", {
|
|
1488
1585
|
method: "POST",
|
|
1489
1586
|
body: { tier: "starter", bonusCredits: 1000 },
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
else {
|
|
1494
|
-
const cr = Math.max(100, Number(action) || 1000);
|
|
1495
|
-
const up = await remoteFetch("/api/v1/billing/topup", {
|
|
1587
|
+
acceptPaymentRequired: true,
|
|
1588
|
+
})
|
|
1589
|
+
: await remoteFetch("/api/v1/billing/topup", {
|
|
1496
1590
|
method: "POST",
|
|
1497
|
-
body: { credits:
|
|
1591
|
+
body: { credits: Math.max(100, Number(action) || 1000) },
|
|
1592
|
+
acceptPaymentRequired: true,
|
|
1498
1593
|
});
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1594
|
+
printBillingResult(up);
|
|
1501
1595
|
return;
|
|
1502
1596
|
}
|
|
1503
1597
|
if (choice === "4") {
|
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,17 +469,41 @@ export declare class MuxoClient {
|
|
|
398
469
|
provider: string;
|
|
399
470
|
keys: ByokKeyRecord[];
|
|
400
471
|
}>;
|
|
401
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
settlementSummary: SupplierSettlementSummary;
|
|
491
|
+
url: string;
|
|
492
|
+
portalUrl: string;
|
|
406
493
|
}>;
|
|
407
|
-
createCheckoutSession(kind?: "starter_subscription" | "credit_topup"
|
|
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;
|
|
503
|
+
kind?: string;
|
|
504
|
+
credits?: number;
|
|
505
|
+
tier?: string;
|
|
506
|
+
interval?: string;
|
|
412
507
|
}>;
|
|
413
508
|
verifyCheckoutSession(sessionId: string): Promise<{
|
|
414
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
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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") {
|
|
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: { kind },
|
|
437
|
+
body: { kind, ...opts },
|
|
373
438
|
});
|
|
374
439
|
}
|
|
375
440
|
async verifyCheckoutSession(sessionId) {
|
package/dist/server.js
CHANGED
|
@@ -693,7 +693,8 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
693
693
|
<div class="badge">Ownership Bound</div>
|
|
694
694
|
<h1>Workspace Claimed</h1>
|
|
695
695
|
<p>Workspace <code>${redeemed.workspace.name}</code> (${redeemed.workspace.id}) claimed by <strong>${redeemed.targetEmail}</strong>.</p>
|
|
696
|
-
<
|
|
696
|
+
<p>Console key: <code>${claimConsoleKey}</code><br />Choose “Use an API key instead” in the console and paste it.</p>
|
|
697
|
+
<a class="btn" href="/?claimed=${encodeURIComponent(redeemed.workspace.slug)}">Open Muxo Console →</a>
|
|
697
698
|
</div>
|
|
698
699
|
</body>
|
|
699
700
|
</html>`);
|
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;
|
|
@@ -13,6 +13,13 @@ export interface OAuthConsentOptions {
|
|
|
13
13
|
frontendApi: string;
|
|
14
14
|
} | null;
|
|
15
15
|
request?: Record<string, string>;
|
|
16
|
+
/** Renders the workspace-claim variant, which posts to `action` instead of /oauth/authorize. */
|
|
17
|
+
claim?: {
|
|
18
|
+
workspaceName: string;
|
|
19
|
+
targetEmail: string;
|
|
20
|
+
action: string;
|
|
21
|
+
cancelUrl: string;
|
|
22
|
+
};
|
|
16
23
|
}
|
|
17
24
|
/** Consent screen for MCP clients (ChatGPT, Claude, CLIs) asking to use a Muxo workspace. */
|
|
18
25
|
export declare function renderOAuthConsentHtml(opts: OAuthConsentOptions): string;
|
package/dist/ui/console-html.js
CHANGED
|
@@ -238,6 +238,10 @@ body:has(dialog[open]){overflow:hidden}
|
|
|
238
238
|
.auth-alt{margin-top:12px}
|
|
239
239
|
.row-actions{display:inline-flex;align-items:center;gap:8px;flex:none}
|
|
240
240
|
.clerk-auth{display:flex;justify-content:center;min-height:120px;margin-top:20px;text-align:left}
|
|
241
|
+
.clerk-loading{display:flex;align-items:center;gap:10px;align-self:center;margin:0;color:var(--color-muted);font-size:15px}
|
|
242
|
+
.spinner{flex:none;width:18px;height:18px;border:2px solid var(--color-border);border-top-color:var(--color-text);border-radius:50%;animation:muxo-spin .8s linear infinite}
|
|
243
|
+
@keyframes muxo-spin{to{transform:rotate(360deg)}}
|
|
244
|
+
@media (prefers-reduced-motion:reduce){.spinner{animation:none}}
|
|
241
245
|
.auth-switch{margin-top:4px}
|
|
242
246
|
.auth-switch:has([hidden]){display:none}
|
|
243
247
|
.auth-foot{margin-top:16px;color:var(--color-muted);font-size:12px}
|
|
@@ -263,7 +267,7 @@ var LANE_LABELS = { composio_instant: 'Instant tool', composio_sdk: 'Connected a
|
|
|
263
267
|
var TOKEN_KEY = 'muxo_external_api_key';
|
|
264
268
|
var ESC = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
265
269
|
|
|
266
|
-
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 };
|
|
267
271
|
var currentTool = null;
|
|
268
272
|
var activeKeyHint = 'mk_live_...';
|
|
269
273
|
var externalApiKey = readToken();
|
|
@@ -468,6 +472,7 @@ async function api(path, opts) {
|
|
|
468
472
|
if (!res.ok || data.ok === false) {
|
|
469
473
|
var err = new Error(data.error || data.message || ('HTTP ' + res.status));
|
|
470
474
|
err.status = res.status;
|
|
475
|
+
err.data = data;
|
|
471
476
|
if (res.status === 401 && !opts.anonymous) handleUnauthorized();
|
|
472
477
|
throw err;
|
|
473
478
|
}
|
|
@@ -535,14 +540,20 @@ async function loadAuth() {
|
|
|
535
540
|
});
|
|
536
541
|
} catch (err) {
|
|
537
542
|
clerk = null;
|
|
543
|
+
removeClerkLoading();
|
|
538
544
|
setText('auth-error', err.message);
|
|
539
545
|
}
|
|
540
546
|
}
|
|
547
|
+
function removeClerkLoading() {
|
|
548
|
+
var loading = byId('clerk-loading');
|
|
549
|
+
if (loading) loading.remove();
|
|
550
|
+
}
|
|
541
551
|
function mountClerkAuth(view) {
|
|
542
552
|
if (!clerk || !clerk.loaded || clerkSignedIn()) return;
|
|
543
553
|
var key = view === 'signup' ? 'signup' : 'login';
|
|
544
554
|
if (clerkMounted === key) return;
|
|
545
555
|
unmountClerkAuth();
|
|
556
|
+
removeClerkLoading();
|
|
546
557
|
var target = location.origin + '/';
|
|
547
558
|
var opts = { routing: 'hash', forceRedirectUrl: target, signInForceRedirectUrl: target, signUpForceRedirectUrl: target, appearance: clerkAppearance() };
|
|
548
559
|
if (key === 'signup') clerk.mountSignUp(byId('clerk-auth'), opts); else clerk.mountSignIn(byId('clerk-auth'), opts);
|
|
@@ -1238,10 +1249,19 @@ async function loadPlans() {
|
|
|
1238
1249
|
: (p.approvalStatus === 'approved' || p.approvalStatus === 'auto_approved')
|
|
1239
1250
|
? '<button class="btn btn-sm" data-action="execute-plan" data-id="' + id + '">Run</button>'
|
|
1240
1251
|
: '<span class="mono small muted">' + esc(p.runId ? shortId(p.runId) : 'Done') + '</span>';
|
|
1241
|
-
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>' +
|
|
1242
1253
|
'<td>' + riskBadge(p.riskLevel) + ' ' + pill(verdict, verdict === 'deny' ? 'danger' : '') + '</td>' +
|
|
1243
1254
|
'<td>' + statusBadge(p.approvalStatus) + '</td><td class="cell-actions">' + action + '</td></tr>';
|
|
1244
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
|
+
}
|
|
1245
1265
|
}
|
|
1246
1266
|
async function approveAndRun(planId) {
|
|
1247
1267
|
await post('/api/v1/plans/' + planId + '/approve');
|
|
@@ -1339,6 +1359,7 @@ async function loadLedger() {
|
|
|
1339
1359
|
'<td class="mono" style="color:' + (delta >= 0 ? 'var(--color-ok)' : 'var(--color-text)') + '">' + (delta >= 0 ? '+' : '') + esc(fmt(delta)) + '</td>' +
|
|
1340
1360
|
'<td class="mono">' + esc(fmt(e.balanceAfter)) + '</td></tr>';
|
|
1341
1361
|
}).join('') : emptyRow(4, 'No billing activity yet.');
|
|
1362
|
+
await renderPlanCards();
|
|
1342
1363
|
await loadByokKeys();
|
|
1343
1364
|
}
|
|
1344
1365
|
async function loadByokKeys() {
|
|
@@ -1409,19 +1430,6 @@ async function runNaturalLanguagePrompt() {
|
|
|
1409
1430
|
throw err;
|
|
1410
1431
|
}
|
|
1411
1432
|
}
|
|
1412
|
-
async function instantUpgradeStarter() {
|
|
1413
|
-
var res = await post('/api/v1/billing/upgrade', { tier: 'starter' });
|
|
1414
|
-
showToast(res.message || 'Upgraded to the Starter plan');
|
|
1415
|
-
await refreshOverview();
|
|
1416
|
-
await loadLedger();
|
|
1417
|
-
}
|
|
1418
|
-
async function instantTopupCustom() {
|
|
1419
|
-
var credits = Math.max(1, Number(byId('topup-credits-input').value || 1000));
|
|
1420
|
-
var res = await post('/api/v1/billing/topup', { credits: credits });
|
|
1421
|
-
showToast('Added ' + fmt(res.creditsAdded) + ' credits' + (res.usdPaid != null ? ' ($' + Number(res.usdPaid).toFixed(2) + ')' : ''));
|
|
1422
|
-
await refreshOverview();
|
|
1423
|
-
await loadLedger();
|
|
1424
|
-
}
|
|
1425
1433
|
async function startCheckout(kind) {
|
|
1426
1434
|
showToast('Opening Stripe Checkout…');
|
|
1427
1435
|
var credits = Math.max(100, Number(byId('topup-credits-input').value || 1000));
|
|
@@ -1429,19 +1437,105 @@ async function startCheckout(kind) {
|
|
|
1429
1437
|
if (res.url) window.location.href = res.url;
|
|
1430
1438
|
}
|
|
1431
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
|
+
|
|
1432
1519
|
async function checkCheckoutReturn() {
|
|
1433
1520
|
var params = new URLSearchParams(window.location.search);
|
|
1434
1521
|
var clean = false;
|
|
1435
1522
|
var sessionId = params.get('session_id');
|
|
1436
1523
|
if (params.get('checkout') === 'success' && sessionId) {
|
|
1437
1524
|
clean = true;
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1525
|
+
if (isSignedIn()) {
|
|
1526
|
+
try {
|
|
1527
|
+
var res = await post('/api/v1/billing/verify-session', { sessionId: sessionId });
|
|
1528
|
+
showToast(res.fulfilled ? 'Payment confirmed. Your workspace has been updated.' : 'Payment is processing. Credits appear as soon as Stripe confirms it.');
|
|
1529
|
+
} catch (err) { showToast(err.message, 'danger'); }
|
|
1530
|
+
} else {
|
|
1531
|
+
showToast('Payment received. Sign in to see your updated balance.');
|
|
1532
|
+
}
|
|
1442
1533
|
}
|
|
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.'); }
|
|
1443
1536
|
if (params.get('connected')) { clean = true; showToast(params.get('connected') + ' is connected'); }
|
|
1444
|
-
|
|
1537
|
+
// Keys are never accepted from the URL: a crafted link could sign a person into someone else's workspace.
|
|
1538
|
+
if (params.get('api_key')) clean = true;
|
|
1445
1539
|
if (params.get('claimed')) { clean = true; showToast('You now own this workspace (' + params.get('claimed') + ')'); }
|
|
1446
1540
|
if (clean) window.history.replaceState({}, '', window.location.pathname);
|
|
1447
1541
|
}
|
|
@@ -1473,14 +1567,15 @@ var ACTIONS = {
|
|
|
1473
1567
|
'delete-memory': function (el) { return deleteMemoryItem(el.dataset.id); },
|
|
1474
1568
|
'facts-mode': function (el) { setFactsMode(el.dataset.mode); },
|
|
1475
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(); },
|
|
1476
1573
|
'execute-plan': function (el) { return executeExistingPlan(el.dataset.id); },
|
|
1477
1574
|
'kill-resume': resumeKillSwitch,
|
|
1478
1575
|
'refresh-governance': loadPlans,
|
|
1479
1576
|
'refresh-runs': loadRuns,
|
|
1480
1577
|
'inspect-receipt': function (el) { return inspectReceipt(el.dataset.id); },
|
|
1481
1578
|
'checkout': function (el) { return startCheckout(el.dataset.kind); },
|
|
1482
|
-
'instant-upgrade': instantUpgradeStarter,
|
|
1483
|
-
'instant-topup': instantTopupCustom,
|
|
1484
1579
|
'delete-byok': function (el) { return deleteByokKeyFromUi(el.dataset.provider); },
|
|
1485
1580
|
'plan-only': submitPlanOnly,
|
|
1486
1581
|
'plan-run': submitPlanAndExecute,
|
|
@@ -2057,17 +2152,18 @@ export function renderConsoleHtml() {
|
|
|
2057
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>
|
|
2058
2153
|
</div>
|
|
2059
2154
|
<div class="split even">
|
|
2060
|
-
<div class="panel">
|
|
2061
|
-
<div class="panel-header"
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
</
|
|
2068
|
-
<div class="
|
|
2069
|
-
|
|
2070
|
-
<button class="btn" type="button" data-action="
|
|
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">
|
|
@@ -2075,7 +2171,6 @@ export function renderConsoleHtml() {
|
|
|
2075
2171
|
<label class="field"><span>Credits</span><input id="topup-credits-input" type="number" value="1000" min="100" step="100" inputmode="numeric" /><small class="field-help">1,000 credits cost $10.</small></label>
|
|
2076
2172
|
<div class="page-actions" style="margin-top:16px">
|
|
2077
2173
|
<button class="btn btn-primary" type="button" data-action="checkout" data-kind="credit_topup">Pay with Stripe</button>
|
|
2078
|
-
<button class="btn" type="button" data-action="instant-topup">Add now (test mode)</button>
|
|
2079
2174
|
</div>
|
|
2080
2175
|
</div>
|
|
2081
2176
|
</div>
|
|
@@ -2139,7 +2234,7 @@ export function renderConsoleHtml() {
|
|
|
2139
2234
|
<p id="auth-error" class="field-error" role="alert"></p>
|
|
2140
2235
|
<button id="auth-submit" class="btn btn-primary btn-lg btn-block" type="submit">Sign in</button>
|
|
2141
2236
|
</form>
|
|
2142
|
-
<div id="clerk-auth" class="clerk-auth" hidden></div>
|
|
2237
|
+
<div id="clerk-auth" class="clerk-auth" hidden><p id="clerk-loading" class="clerk-loading" role="status"><span class="spinner" aria-hidden="true"></span>Loading sign-in…</p></div>
|
|
2143
2238
|
<p class="auth-switch"><button id="auth-switch-link" class="btn btn-quiet btn-sm" type="button" data-action="auth-mode" data-mode="signup" hidden>New to Muxo? Create an account</button></p>
|
|
2144
2239
|
<div class="auth-alt"><button class="btn btn-quiet" type="button" data-action="switch-workspace">${uiIcon("key")}Use an API key instead</button></div>
|
|
2145
2240
|
<p class="auth-foot">Setting up an agent? Run <code>npx -y @muxoai/cli@latest login --signup</code></p>
|
|
@@ -2450,20 +2545,21 @@ const consentScript = String.raw `
|
|
|
2450
2545
|
}
|
|
2451
2546
|
async function decide(decision) {
|
|
2452
2547
|
if (busy) return;
|
|
2548
|
+
if (decision === 'deny' && C.cancelUrl) { location.assign(C.cancelUrl); return; }
|
|
2453
2549
|
busy = true;
|
|
2454
2550
|
fail('');
|
|
2455
2551
|
byId('consent-allow').disabled = true;
|
|
2456
2552
|
byId('consent-deny').disabled = true;
|
|
2457
2553
|
try {
|
|
2458
2554
|
var token = await clerk.session.getToken();
|
|
2459
|
-
var res = await fetch('/oauth/authorize', {
|
|
2555
|
+
var res = await fetch(C.action || '/oauth/authorize', {
|
|
2460
2556
|
method: 'POST',
|
|
2461
2557
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
|
|
2462
2558
|
body: JSON.stringify(Object.assign({}, C.request, { decision: decision }))
|
|
2463
2559
|
});
|
|
2464
2560
|
var data = await res.json().catch(function () { return {}; });
|
|
2465
2561
|
if (!res.ok || !data.redirect) throw new Error(data.error || 'Muxo could not finish this request. Try again.');
|
|
2466
|
-
byId(decision === 'allow' ? 'consent-allow' : 'consent-deny').textContent = decision === 'allow' ? 'Returning…' : 'Cancelling…';
|
|
2562
|
+
byId(decision === 'allow' ? 'consent-allow' : 'consent-deny').textContent = decision === 'allow' ? (C.action ? 'Opening…' : 'Returning…') : 'Cancelling…';
|
|
2467
2563
|
location.assign(data.redirect);
|
|
2468
2564
|
} catch (err) {
|
|
2469
2565
|
fail(err.message);
|
|
@@ -2501,7 +2597,7 @@ export function renderOAuthConsentHtml(opts) {
|
|
|
2501
2597
|
<head>
|
|
2502
2598
|
<meta charset="utf-8" />
|
|
2503
2599
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
2504
|
-
<title>${opts.error ? "Connection problem" : `Connect ${clientNameMidSentence}`} · Muxo</title>
|
|
2600
|
+
<title>${opts.error ? (opts.claim ? "Claim problem" : "Connection problem") : opts.claim ? `Claim ${escapeHtml(opts.claim.workspaceName)}` : `Connect ${clientNameMidSentence}`} · Muxo</title>
|
|
2505
2601
|
<meta name="theme-color" content="${muxoThemes.light.bg}" />
|
|
2506
2602
|
<meta name="color-scheme" content="light dark" />
|
|
2507
2603
|
<meta name="robots" content="noindex" />
|
|
@@ -2515,30 +2611,43 @@ export function renderOAuthConsentHtml(opts) {
|
|
|
2515
2611
|
<main class="auth-view">
|
|
2516
2612
|
<div class="auth-card">
|
|
2517
2613
|
<div class="auth-mark">${muxoMark}</div>
|
|
2518
|
-
<h1
|
|
2614
|
+
<h1>${opts.claim ? "This claim link isn't valid" : "This connection link isn't valid"}</h1>
|
|
2519
2615
|
<p class="lede">${escapeHtml(opts.error)}</p>
|
|
2520
|
-
<p class="auth-foot"
|
|
2616
|
+
<p class="auth-foot">${opts.claim ? "Ask the agent for a new claim link" : "Start the connection again from your app"}, or <a href="${DOCS_URL}">read the setup guide</a>.</p>
|
|
2521
2617
|
</div>
|
|
2522
2618
|
</main>
|
|
2523
2619
|
</body>
|
|
2524
2620
|
</html>`;
|
|
2525
2621
|
}
|
|
2622
|
+
const claim = opts.claim;
|
|
2526
2623
|
const config = {
|
|
2527
2624
|
clerk: opts.clerk || null,
|
|
2528
2625
|
request: opts.request || {},
|
|
2626
|
+
action: claim?.action,
|
|
2627
|
+
cancelUrl: claim?.cancelUrl,
|
|
2529
2628
|
};
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
<
|
|
2534
|
-
|
|
2629
|
+
const intro = claim
|
|
2630
|
+
? `<h1>Claim ${escapeHtml(claim.workspaceName)}</h1>
|
|
2631
|
+
<p class="lede">An agent created this Muxo workspace and invited you to own it.</p>
|
|
2632
|
+
<ul class="consent-list">
|
|
2633
|
+
<li>You become the owner of this workspace and its credits</li>
|
|
2634
|
+
<li>You manage its keys, safety rules, memory, and billing</li>
|
|
2635
|
+
<li>The agent keeps working with its key until you revoke it</li>
|
|
2636
|
+
</ul>
|
|
2637
|
+
<p class="consent-redirect">Sign in as <strong>${escapeHtml(claim.targetEmail)}</strong> to accept.</p>`
|
|
2638
|
+
: `<h1>Connect ${clientNameMidSentence} to Muxo</h1>
|
|
2535
2639
|
<p class="lede">${clientName} wants to use your Muxo workspace.</p>
|
|
2536
2640
|
<ul class="consent-list">
|
|
2537
2641
|
<li>Find tools and plan actions in your workspace</li>
|
|
2538
2642
|
<li>Run tools under your workspace's safety rules and approvals</li>
|
|
2539
2643
|
<li>Read the status of connections, plans, and runs</li>
|
|
2540
2644
|
</ul>
|
|
2541
|
-
<p class="consent-redirect">After you decide, you'll return to <strong>${escapeHtml(opts.redirectOrigin || "")}</strong></p
|
|
2645
|
+
<p class="consent-redirect">After you decide, you'll return to <strong>${escapeHtml(opts.redirectOrigin || "")}</strong></p>`;
|
|
2646
|
+
return `${head}
|
|
2647
|
+
<main class="auth-view">
|
|
2648
|
+
<div class="auth-card">
|
|
2649
|
+
<div class="auth-mark">${muxoMark}</div>
|
|
2650
|
+
${intro}
|
|
2542
2651
|
<div class="consent-state">
|
|
2543
2652
|
<p id="consent-loading" class="muted">Checking your sign-in…</p>
|
|
2544
2653
|
<div id="consent-signin" hidden>
|
|
@@ -2548,14 +2657,14 @@ export function renderOAuthConsentHtml(opts) {
|
|
|
2548
2657
|
<div id="consent-approve" hidden>
|
|
2549
2658
|
<p class="consent-account">Signed in as <strong id="consent-email"></strong></p>
|
|
2550
2659
|
<div class="consent-actions">
|
|
2551
|
-
<button id="consent-deny" class="btn btn-lg" type="button"
|
|
2552
|
-
<button id="consent-allow" class="btn btn-primary btn-lg" type="button"
|
|
2660
|
+
<button id="consent-deny" class="btn btn-lg" type="button">${claim ? "Not now" : "Cancel"}</button>
|
|
2661
|
+
<button id="consent-allow" class="btn btn-primary btn-lg" type="button">${claim ? "Claim workspace" : "Allow"}</button>
|
|
2553
2662
|
</div>
|
|
2554
2663
|
<p class="consent-switch"><button id="consent-switch" class="btn btn-quiet btn-sm" type="button">Use a different account</button></p>
|
|
2555
2664
|
</div>
|
|
2556
2665
|
<p id="consent-error" class="field-error" role="alert"></p>
|
|
2557
2666
|
</div>
|
|
2558
|
-
<p class="auth-foot"
|
|
2667
|
+
<p class="auth-foot">${claim ? "Only the invited email can claim this workspace, and the link works once." : "You can remove this connection anytime by deleting its key in the Muxo console."}</p>
|
|
2559
2668
|
</div>
|
|
2560
2669
|
</main>
|
|
2561
2670
|
<script>window.MUXO_CONSENT = ${inlineJson(config)};${consentScript}</script>
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MUXO_VERSION = "1.
|
|
1
|
+
export declare const MUXO_VERSION = "1.4.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MUXO_VERSION = "1.
|
|
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
|
+
"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",
|