@muxoai/platform 1.2.3 → 1.3.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 (41) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +19 -4
  3. package/.codex/config.toml +9 -2
  4. package/README.md +72 -28
  5. package/dist/cli.js +1047 -43
  6. package/dist/engine.d.ts +29 -0
  7. package/dist/engine.js +221 -16
  8. package/dist/mcp.d.ts +6 -1
  9. package/dist/mcp.js +120 -26
  10. package/dist/nl.d.ts +34 -0
  11. package/dist/nl.js +601 -0
  12. package/dist/providers/composio-instant.d.ts +1 -0
  13. package/dist/providers/composio-instant.js +6 -5
  14. package/dist/providers/composio-sdk.d.ts +1 -0
  15. package/dist/providers/composio-sdk.js +6 -2
  16. package/dist/providers/stripe-billing.d.ts +13 -3
  17. package/dist/providers/stripe-billing.js +250 -36
  18. package/dist/providers/stripe-projects.js +61 -4
  19. package/dist/providers/supermemory.d.ts +2 -0
  20. package/dist/providers/supermemory.js +58 -40
  21. package/dist/sdk.d.ts +77 -1
  22. package/dist/sdk.js +46 -0
  23. package/dist/server.js +202 -29
  24. package/dist/store.d.ts +19 -2
  25. package/dist/store.js +216 -11
  26. package/dist/types.d.ts +14 -2
  27. package/dist/ui/console-html.js +329 -119
  28. package/package.json +1 -1
  29. package/plugins/muxo/.app.json +1 -1
  30. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  31. package/plugins/muxo/.codex-plugin/plugin.json +7 -5
  32. package/plugins/muxo/.mcp.json +1 -33
  33. package/plugins/muxo/assets/logo.png +0 -0
  34. package/plugins/muxo/hooks/session-start.sh +1 -1
  35. package/plugins/muxo/plugin.json +63 -6
  36. package/plugins/muxo/skills/muxo/SKILL.md +92 -14
  37. package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
  38. package/plugins/muxo/skills/muxo/references/cli.md +4 -2
  39. package/plugins/muxo/skills/muxo/references/mcp.md +15 -9
  40. package/plugins/muxo/skills/setup/SKILL.md +8 -2
  41. package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
package/dist/store.js CHANGED
@@ -1,5 +1,16 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
2
  import crypto from "node:crypto";
3
+ export const BYOK_PROVIDER_CATALOG = {
4
+ openai: { category: "llm", envVarName: "OPENAI_API_KEY", label: "OpenAI (GPT-4o / o3-mini)" },
5
+ anthropic: { category: "llm", envVarName: "ANTHROPIC_API_KEY", label: "Anthropic (Claude 3.7 Sonnet)" },
6
+ gemini: { category: "llm", envVarName: "GEMINI_API_KEY", label: "Google Gemini" },
7
+ openrouter: { category: "llm", envVarName: "OPENROUTER_API_KEY", label: "OpenRouter (Universal LLM)" },
8
+ composio: { category: "supplier", envVarName: "COMPOSIO_API_KEY", label: "Composio SDK & Instant" },
9
+ stripe: { category: "supplier", envVarName: "STRIPE_SECRET_KEY", label: "Stripe Projects & Billing" },
10
+ supermemory: { category: "supplier", envVarName: "SUPERMEMORY_API_KEY", label: "Supermemory v5 Knowledge Graph" },
11
+ firecrawl: { category: "supplier", envVarName: "FIRECRAWL_API_KEY", label: "Firecrawl Web Scrape & Crawl" },
12
+ exa: { category: "supplier", envVarName: "EXA_API_KEY", label: "Exa Semantic Web Search" },
13
+ };
3
14
  const DEFAULT_POLICY = {
4
15
  mode: "enforce",
5
16
  requireApprovalFor: ["admin", "financial", "provisioning", "destructive"],
@@ -201,6 +212,17 @@ export class MuxoStore {
201
212
  event_id TEXT PRIMARY KEY,
202
213
  processed_at TEXT NOT NULL
203
214
  );
215
+
216
+ CREATE TABLE IF NOT EXISTS byok_keys (
217
+ workspace_id TEXT NOT NULL,
218
+ provider TEXT NOT NULL,
219
+ category TEXT NOT NULL,
220
+ env_var_name TEXT NOT NULL,
221
+ key_preview TEXT NOT NULL,
222
+ encrypted_secret_hex TEXT NOT NULL,
223
+ updated_at TEXT NOT NULL,
224
+ PRIMARY KEY (workspace_id, provider)
225
+ );
204
226
  `);
205
227
  // Backwards-compatible column migrations for existing databases
206
228
  for (const stmt of [
@@ -311,10 +333,16 @@ export class MuxoStore {
311
333
  description: "Free Plan initial monthly allowance (1,000 actions + 250 trial credits)",
312
334
  });
313
335
  const sessionToken = this.createSession(userId, workspaceId);
336
+ const defaultKey = this.createApiKey({
337
+ workspaceId,
338
+ name: "Default Live Key",
339
+ environment: "live",
340
+ });
314
341
  return {
315
342
  user: this.getUser(userId),
316
343
  workspace: this.getWorkspace(workspaceId),
317
344
  sessionToken,
345
+ apiKey: defaultKey.secretKey,
318
346
  };
319
347
  }
320
348
  loginUser(email, password) {
@@ -560,8 +588,146 @@ export class MuxoStore {
560
588
  for (const row of rows) {
561
589
  Object.assign(merged, this.decryptSecrets(workspaceId, row.encrypted_secrets_hex));
562
590
  }
591
+ const byokRows = this.db
592
+ .prepare("SELECT encrypted_secret_hex FROM byok_keys WHERE workspace_id = ?")
593
+ .all(workspaceId);
594
+ for (const row of byokRows) {
595
+ Object.assign(merged, this.decryptSecrets(workspaceId, row.encrypted_secret_hex));
596
+ }
563
597
  return merged;
564
598
  }
599
+ // --- BYOK (Bring Your Own Key) Encrypted Vault ---
600
+ normalizeByokProvider(providerInput) {
601
+ const raw = providerInput.trim().toLowerCase();
602
+ for (const [slug, meta] of Object.entries(BYOK_PROVIDER_CATALOG)) {
603
+ if (raw === slug || raw === meta.envVarName.toLowerCase()) {
604
+ return { provider: slug, category: meta.category, envVarName: meta.envVarName };
605
+ }
606
+ }
607
+ const clean = raw.replace(/[^a-z0-9_]/g, "");
608
+ const envVarName = raw.endsWith("_key") || raw.endsWith("_token")
609
+ ? raw.toUpperCase()
610
+ : `${clean.toUpperCase()}_API_KEY`;
611
+ return {
612
+ provider: clean || "custom",
613
+ category: "supplier",
614
+ envVarName,
615
+ };
616
+ }
617
+ setByokKey(workspaceId, providerInput, secretKey) {
618
+ const trimmed = secretKey.trim();
619
+ if (!trimmed) {
620
+ throw new Error("BYOK secretKey must not be empty");
621
+ }
622
+ const { provider, category, envVarName } = this.normalizeByokProvider(providerInput);
623
+ const now = new Date().toISOString();
624
+ const keyPreview = trimmed.length <= 8
625
+ ? `${trimmed.slice(0, 2)}***`
626
+ : `${trimmed.slice(0, 6)}...${trimmed.slice(-4)}`;
627
+ const encryptedHex = this.encryptSecrets(workspaceId, { [envVarName]: trimmed });
628
+ this.db
629
+ .prepare(`INSERT INTO byok_keys (
630
+ workspace_id, provider, category, env_var_name, key_preview, encrypted_secret_hex, updated_at
631
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
632
+ ON CONFLICT(workspace_id, provider) DO UPDATE SET
633
+ category = excluded.category,
634
+ env_var_name = excluded.env_var_name,
635
+ key_preview = excluded.key_preview,
636
+ encrypted_secret_hex = excluded.encrypted_secret_hex,
637
+ updated_at = excluded.updated_at`)
638
+ .run(workspaceId, provider, category, envVarName, keyPreview, encryptedHex, now);
639
+ return {
640
+ provider,
641
+ providerSlug: provider,
642
+ category,
643
+ envVarName,
644
+ keyPreview,
645
+ configured: true,
646
+ updatedAt: now,
647
+ };
648
+ }
649
+ getByokKey(workspaceId, providerInput) {
650
+ const { provider, envVarName } = this.normalizeByokProvider(providerInput);
651
+ const row = this.db
652
+ .prepare("SELECT encrypted_secret_hex FROM byok_keys WHERE workspace_id = ? AND provider = ?")
653
+ .get(workspaceId, provider);
654
+ if (!row)
655
+ return null;
656
+ const dec = this.decryptSecrets(workspaceId, row.encrypted_secret_hex);
657
+ return dec[envVarName] || Object.values(dec)[0] || null;
658
+ }
659
+ deleteByokKey(workspaceId, providerInput) {
660
+ const { provider } = this.normalizeByokProvider(providerInput);
661
+ const res = this.db
662
+ .prepare("DELETE FROM byok_keys WHERE workspace_id = ? AND provider = ?")
663
+ .run(workspaceId, provider);
664
+ return Number(res.changes || 0) > 0;
665
+ }
666
+ listByokKeys(workspaceId) {
667
+ const rows = this.db
668
+ .prepare("SELECT provider, category, env_var_name, key_preview, updated_at FROM byok_keys WHERE workspace_id = ? ORDER BY updated_at DESC")
669
+ .all(workspaceId);
670
+ const configuredMap = new Map();
671
+ for (const r of rows) {
672
+ const p = String(r.provider);
673
+ configuredMap.set(p, {
674
+ provider: p,
675
+ providerSlug: p,
676
+ category: String(r.category),
677
+ envVarName: String(r.env_var_name),
678
+ keyPreview: String(r.key_preview),
679
+ configured: true,
680
+ updatedAt: String(r.updated_at),
681
+ });
682
+ }
683
+ const result = [];
684
+ for (const [slug, meta] of Object.entries(BYOK_PROVIDER_CATALOG)) {
685
+ const existing = configuredMap.get(slug);
686
+ if (existing) {
687
+ result.push(existing);
688
+ configuredMap.delete(slug);
689
+ }
690
+ else {
691
+ result.push({
692
+ provider: slug,
693
+ providerSlug: slug,
694
+ category: meta.category,
695
+ envVarName: meta.envVarName,
696
+ keyPreview: "",
697
+ configured: false,
698
+ updatedAt: "",
699
+ });
700
+ }
701
+ }
702
+ for (const extra of configuredMap.values()) {
703
+ result.push(extra);
704
+ }
705
+ return result;
706
+ }
707
+ hasByokForLane(workspaceId, lane, toolSlug) {
708
+ const slug = (toolSlug || "").toLowerCase();
709
+ if (slug.startsWith("billing."))
710
+ return false;
711
+ if (lane === "composio_sdk") {
712
+ return Boolean(this.getByokKey(workspaceId, "composio"));
713
+ }
714
+ if (lane === "composio_instant") {
715
+ if (this.getByokKey(workspaceId, "composio"))
716
+ return true;
717
+ if ((slug.includes("scrape") || slug.includes("crawl") || slug.includes("seo")) &&
718
+ this.getByokKey(workspaceId, "firecrawl")) {
719
+ return true;
720
+ }
721
+ if (slug.includes("search") && this.getByokKey(workspaceId, "exa")) {
722
+ return true;
723
+ }
724
+ return false;
725
+ }
726
+ if (lane === "stripe_projects") {
727
+ return Boolean(this.getByokKey(workspaceId, "stripe"));
728
+ }
729
+ return false;
730
+ }
565
731
  // --- Plans ---
566
732
  createPlan(params) {
567
733
  const id = `plan_${crypto.randomBytes(8).toString("hex")}`;
@@ -627,7 +793,8 @@ export class MuxoStore {
627
793
  SET monthly_actions_used = ?, credits_balance = ?, updated_at = ?
628
794
  WHERE id = ?`)
629
795
  .run(newActionsUsed, newCreditsBalance, now, params.workspaceId);
630
- const settlement = this.computeSupplierSettlement(run.lane, run.toolSlug, params.creditsMetered, "settled_by_muxo_master_wallet");
796
+ const byokUsed = this.hasByokForLane(params.workspaceId, run.lane, run.toolSlug);
797
+ const settlement = this.computeSupplierSettlement(run.lane, run.toolSlug, params.creditsMetered, byokUsed ? "settled_by_customer_byok" : "settled_by_muxo_master_wallet", params.workspaceId);
631
798
  this.addLedgerEntry({
632
799
  workspaceId: params.workspaceId,
633
800
  kind: "run_deduction",
@@ -635,7 +802,9 @@ export class MuxoStore {
635
802
  creditsDelta: -params.creditsMetered,
636
803
  balanceAfter: newCreditsBalance,
637
804
  referenceId: run.id,
638
- description: `Executed ${run.toolSlug} (${run.lane}) — User paid Muxo ${params.creditsMetered} credits ($${(settlement.userRetailChargeCents / 100).toFixed(2)}) -> Muxo paid ${settlement.supplierName} ($${(settlement.supplierWholesalePayableCents / 100).toFixed(2)})`,
805
+ description: byokUsed
806
+ ? `Executed ${run.toolSlug} (${run.lane}) — BYOK 0-credit execution (Customer's own ${settlement.supplierName} key)`
807
+ : `Executed ${run.toolSlug} (${run.lane}) — User paid Muxo ${params.creditsMetered} credits ($${(settlement.userRetailChargeCents / 100).toFixed(2)}) -> Muxo paid ${settlement.supplierName} ($${(settlement.supplierWholesalePayableCents / 100).toFixed(2)})`,
639
808
  });
640
809
  }
641
810
  const receipt = this.issueReceipt({
@@ -774,7 +943,8 @@ export class MuxoStore {
774
943
  if (!ws)
775
944
  throw new Error(`Workspace ${params.workspaceId} not found`);
776
945
  const now = new Date().toISOString();
777
- const newCredits = ws.creditsBalance + 1000;
946
+ const bonus = params.bonusCredits !== undefined ? Number(params.bonusCredits) : 1000;
947
+ const newCredits = ws.creditsBalance + bonus;
778
948
  this.db
779
949
  .prepare(`UPDATE workspaces
780
950
  SET tier = 'starter',
@@ -789,10 +959,10 @@ export class MuxoStore {
789
959
  workspaceId: params.workspaceId,
790
960
  kind: "subscription_upgrade",
791
961
  actionsDelta: 4000,
792
- creditsDelta: 1000,
962
+ creditsDelta: bonus,
793
963
  balanceAfter: newCredits,
794
964
  referenceId: params.referenceId,
795
- description: "Upgraded to Starter Plan ($29/mo — 5,000 actions/mo + 1,000 credits)",
965
+ description: `Upgraded to Starter Plan ($29/mo — 5,000 actions/mo + ${bonus} credits)`,
796
966
  });
797
967
  return this.getWorkspace(params.workspaceId);
798
968
  }
@@ -840,12 +1010,35 @@ export class MuxoStore {
840
1010
  createdAt: now,
841
1011
  };
842
1012
  }
843
- computeSupplierSettlement(lane, toolSlug, creditCost, settlementStatus = "settled_by_muxo_master_wallet") {
1013
+ computeSupplierSettlement(lane, toolSlug, creditCost, settlementStatus = "settled_by_muxo_master_wallet", workspaceId) {
1014
+ const useByok = settlementStatus === "settled_by_customer_byok" ||
1015
+ settlementStatus === "planned_customer_byok" ||
1016
+ (Number(creditCost || 0) === 0 && workspaceId
1017
+ ? this.hasByokForLane(workspaceId, lane, toolSlug)
1018
+ : false);
1019
+ const upstreamProvider = toolSlug.split(".")[1] || lane;
1020
+ if (useByok) {
1021
+ const isPlanned = settlementStatus === "planned_muxo_master_wallet" ||
1022
+ settlementStatus === "planned_customer_byok";
1023
+ return {
1024
+ fundingMode: "byok",
1025
+ supplier: lane,
1026
+ supplierName: "Customer BYOK Upstream Key (0% Muxo Markup)",
1027
+ upstreamProvider,
1028
+ userChargeCredits: 0,
1029
+ userRetailChargeCents: 0,
1030
+ supplierWholesalePayableCents: 0,
1031
+ muxoNetMarginCents: 0,
1032
+ settlementStatus: isPlanned
1033
+ ? "planned_customer_byok"
1034
+ : "settled_by_customer_byok",
1035
+ masterAccountRef: "customer_byok_vault",
1036
+ };
1037
+ }
844
1038
  const userChargeCredits = Math.max(0, Number(creditCost || 0));
845
1039
  const userRetailChargeCents = Math.round(userChargeCredits * 100) / 100;
846
1040
  const supplierWholesalePayableCents = Math.round(userRetailChargeCents * 0.6 * 100) / 100;
847
1041
  const muxoNetMarginCents = Math.round((userRetailChargeCents - supplierWholesalePayableCents) * 100) / 100;
848
- const upstreamProvider = toolSlug.split(".")[1] || lane;
849
1042
  const supplierMeta = {
850
1043
  composio_instant: {
851
1044
  supplierName: "Composio Instant Wholesale Wallet",
@@ -878,6 +1071,9 @@ export class MuxoStore {
878
1071
  const ws = this.getWorkspace(workspaceId);
879
1072
  const ledger = this.listLedgerEntries(workspaceId, 500);
880
1073
  const runs = this.listRuns(workspaceId, 500).filter((r) => r.status === "succeeded");
1074
+ const byokProviders = this.listByokKeys(workspaceId)
1075
+ .filter((k) => k.configured)
1076
+ .map((k) => k.provider);
881
1077
  const totalCreditsPurchasedOrGranted = ledger
882
1078
  .filter((e) => e.creditsDelta > 0)
883
1079
  .reduce((acc, e) => acc + e.creditsDelta, 0);
@@ -909,7 +1105,9 @@ export class MuxoStore {
909
1105
  let totalSupplierWholesalePaidCents = 0;
910
1106
  let totalMuxoNetMarginCents = 0;
911
1107
  for (const run of runs) {
912
- const s = this.computeSupplierSettlement(run.lane, run.toolSlug, run.creditsMetered, "settled_by_muxo_master_wallet");
1108
+ const s = this.computeSupplierSettlement(run.lane, run.toolSlug, run.creditsMetered, run.creditsMetered === 0 && this.hasByokForLane(workspaceId, run.lane, run.toolSlug)
1109
+ ? "settled_by_customer_byok"
1110
+ : "settled_by_muxo_master_wallet", workspaceId);
913
1111
  const bucket = bySupplier[run.lane] || bySupplier.composio_instant;
914
1112
  bucket.runsCount += 1;
915
1113
  bucket.creditsConsumed += s.userChargeCredits;
@@ -930,6 +1128,8 @@ export class MuxoStore {
930
1128
  return {
931
1129
  workspaceId,
932
1130
  fundingModel: "user_pays_muxo_muxo_pays_suppliers",
1131
+ activeFundingMode: byokProviders.length > 0 ? "byok_hybrid" : "muxo_funded",
1132
+ byokProviders,
933
1133
  creditPriceCents: 1,
934
1134
  totalCreditsPurchasedOrGranted,
935
1135
  totalCreditsConsumed,
@@ -952,7 +1152,12 @@ export class MuxoStore {
952
1152
  if (kind === "run_deduction" && refId) {
953
1153
  const run = this.getRun(workspaceId, refId);
954
1154
  if (run) {
955
- supplierSettlement = this.computeSupplierSettlement(run.lane, run.toolSlug, Math.abs(creditsDelta), "settled_by_muxo_master_wallet");
1155
+ const isByokEntry = creditsDelta === 0 &&
1156
+ (String(r.description || "").includes("BYOK") ||
1157
+ this.hasByokForLane(workspaceId, run.lane, run.toolSlug));
1158
+ supplierSettlement = this.computeSupplierSettlement(run.lane, run.toolSlug, Math.abs(creditsDelta), isByokEntry
1159
+ ? "settled_by_customer_byok"
1160
+ : "settled_by_muxo_master_wallet", workspaceId);
956
1161
  }
957
1162
  }
958
1163
  return {
@@ -1203,7 +1408,7 @@ export class MuxoStore {
1203
1408
  : null,
1204
1409
  supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditCost, approvalStatus === "executed"
1205
1410
  ? "settled_by_muxo_master_wallet"
1206
- : "planned_muxo_master_wallet"),
1411
+ : "planned_muxo_master_wallet", String(row.workspace_id)),
1207
1412
  createdAt: String(row.created_at),
1208
1413
  expiresAt: String(row.expires_at),
1209
1414
  };
@@ -1253,7 +1458,7 @@ export class MuxoStore {
1253
1458
  decisionRecord: row.decision_record_json
1254
1459
  ? JSON.parse(String(row.decision_record_json))
1255
1460
  : null,
1256
- supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditsMetered, "settled_by_muxo_master_wallet"),
1461
+ supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditsMetered, "settled_by_muxo_master_wallet", String(row.workspace_id)),
1257
1462
  issuedAt: String(row.issued_at),
1258
1463
  };
1259
1464
  }
package/dist/types.d.ts CHANGED
@@ -62,6 +62,7 @@ export interface MuxoTool {
62
62
  providerUrl?: string;
63
63
  metadata?: Record<string, unknown>;
64
64
  }
65
+ export type ToolDescriptor = MuxoTool;
65
66
  export interface ConnectionRecord {
66
67
  id: string;
67
68
  workspaceId: string;
@@ -168,8 +169,17 @@ export interface WorkspaceMemoryRecord {
168
169
  metadata: Record<string, unknown>;
169
170
  createdAt: string;
170
171
  }
172
+ export interface ByokKeyRecord {
173
+ provider: string;
174
+ providerSlug?: string;
175
+ category: "llm" | "supplier";
176
+ envVarName: string;
177
+ keyPreview: string;
178
+ configured: boolean;
179
+ updatedAt: string;
180
+ }
171
181
  export interface SupplierSettlement {
172
- fundingMode: "muxo_funded";
182
+ fundingMode: "muxo_funded" | "byok";
173
183
  supplier: ProviderLane;
174
184
  supplierName: string;
175
185
  upstreamProvider: string;
@@ -177,12 +187,14 @@ export interface SupplierSettlement {
177
187
  userRetailChargeCents: number;
178
188
  supplierWholesalePayableCents: number;
179
189
  muxoNetMarginCents: number;
180
- settlementStatus: "settled_by_muxo_master_wallet" | "planned_muxo_master_wallet";
190
+ settlementStatus: "settled_by_muxo_master_wallet" | "planned_muxo_master_wallet" | "settled_by_customer_byok" | "planned_customer_byok";
181
191
  masterAccountRef: string;
182
192
  }
183
193
  export interface SupplierSettlementSummary {
184
194
  workspaceId: string;
185
195
  fundingModel: "user_pays_muxo_muxo_pays_suppliers";
196
+ activeFundingMode?: "muxo_funded" | "byok_hybrid";
197
+ byokProviders?: string[];
186
198
  creditPriceCents: number;
187
199
  totalCreditsPurchasedOrGranted: number;
188
200
  totalCreditsConsumed: number;