@muxoai/platform 1.4.1 → 1.4.2

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 (43) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/README.md +1 -1
  4. package/dist/cli.js +6 -3
  5. package/dist/composio-catalog.d.ts +177 -1
  6. package/dist/composio-catalog.js +1427 -7
  7. package/dist/config.d.ts +6 -0
  8. package/dist/config.js +11 -1
  9. package/dist/engine.d.ts +18 -1
  10. package/dist/engine.js +259 -35
  11. package/dist/mcp-schema.d.ts +162 -1
  12. package/dist/mcp-schema.js +1523 -40
  13. package/dist/mcp.d.ts +2 -2
  14. package/dist/mcp.js +412 -23
  15. package/dist/policy.d.ts +108 -1
  16. package/dist/policy.js +406 -1
  17. package/dist/providers/composio-instant.js +38 -3
  18. package/dist/providers/composio-sdk.d.ts +59 -2
  19. package/dist/providers/composio-sdk.js +273 -59
  20. package/dist/providers/stripe-projects.d.ts +4 -0
  21. package/dist/providers/stripe-projects.js +548 -126
  22. package/dist/server.js +531 -61
  23. package/dist/store.js +2 -0
  24. package/dist/types.d.ts +15 -0
  25. package/dist/ui/console-html.d.ts +31 -1
  26. package/dist/ui/console-html.js +922 -368
  27. package/dist/ui/design-tokens.js +1 -1
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/package.json +3 -2
  31. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  32. package/plugins/muxo/.codex-plugin/plugin.json +15 -8
  33. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  34. package/plugins/muxo/hooks/session-start.sh +15 -3
  35. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  36. package/plugins/muxo/mcp.json +0 -10
  37. package/plugins/muxo/plugin.json +4 -4
  38. package/plugins/muxo/skills/muxo/SKILL.md +25 -16
  39. package/plugins/muxo/skills/muxo/references/cli.md +5 -1
  40. package/plugins/muxo/skills/muxo/references/mcp.md +11 -6
  41. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  42. package/plugins/muxo/skills/setup/SKILL.md +1 -1
  43. package/src/catalogues/stripe-projects-catalog.json +140 -0
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { promisify } from "node:util";
5
+ import { buildStandardStripeProjectsSecrets, extractDatabaseMigrationsFromInput, extractHostedHtmlFromInput, isStripeProjectsDatabaseProvider, isStripeProjectsExclusiveProvider, isStripeProjectsHostingProvider, normalizeStripeProjectsProviderSlug, renderDefaultHostedSiteHtml, resolveStripeProjectsCanonicalToolSlug, STRIPE_PROJECTS_DEPLOYABLE_CATALOG, } from "../composio-catalog.js";
5
6
  import { ROOT_DIR } from "../config.js";
6
7
  const execFileAsync = promisify(execFile);
7
8
  export const DEFAULT_STRIPE_PROVISIONING_API_VERSION = "2026-09-30.preview";
@@ -419,8 +420,12 @@ export class StripeProjectsAdapter {
419
420
  return;
420
421
  const raw = JSON.parse(fs.readFileSync(catalogPath, "utf8"));
421
422
  for (const p of raw.providers || []) {
423
+ const normSlug = normalizeStripeProjectsProviderSlug(p.slug || p.name);
422
424
  this.providerByName.set(p.name.toLowerCase(), p);
423
425
  this.providerByName.set(p.slug.toLowerCase(), p);
426
+ if (normSlug) {
427
+ this.providerByName.set(normSlug, p);
428
+ }
424
429
  this.seededProviders.push({
425
430
  id: `prov_${p.slug}`,
426
431
  object: "v2.provisioning.provider",
@@ -440,9 +445,15 @@ export class StripeProjectsAdapter {
440
445
  }
441
446
  for (const svc of raw.services || []) {
442
447
  const providerKey = svc.provider_name.toLowerCase();
443
- const matchedProvider = this.providerByName.get(providerKey);
444
- const providerSlug = (matchedProvider?.slug ||
448
+ const normProv = normalizeStripeProjectsProviderSlug(svc.provider_name);
449
+ const matchedProvider = this.providerByName.get(providerKey) ||
450
+ this.providerByName.get(normProv);
451
+ const providerSlug = (normProv ||
452
+ matchedProvider?.slug ||
445
453
  svc.provider_name.toLowerCase().replace(/[^a-z0-9]+/g, "")).toLowerCase();
454
+ if (svc.provider && matchedProvider) {
455
+ this.providerByName.set(svc.provider.toLowerCase(), matchedProvider);
456
+ }
446
457
  this.seededServices.push({
447
458
  id: svc.id,
448
459
  object: "v2.provisioning.provider_service_detail",
@@ -462,14 +473,47 @@ export class StripeProjectsAdapter {
462
473
  : null),
463
474
  });
464
475
  }
476
+ // Also index provider IDs from local .projects/state.local.json if available
477
+ try {
478
+ const localStatePath = path.join(this.config.stripeProjectsWorkdir, ".projects", "state.local.json");
479
+ if (fs.existsSync(localStatePath)) {
480
+ const localState = JSON.parse(fs.readFileSync(localStatePath, "utf8"));
481
+ for (const [slug, conn] of Object.entries(localState.providerConnections || {})) {
482
+ const existing = this.providerByName.get(slug.toLowerCase()) ||
483
+ (conn.providerName
484
+ ? this.providerByName.get(conn.providerName.toLowerCase())
485
+ : undefined) || {
486
+ slug: slug.toLowerCase(),
487
+ name: conn.providerName || slug,
488
+ url: "",
489
+ description: "",
490
+ categories: ["infrastructure"],
491
+ native_reference: `${slug}/project`,
492
+ };
493
+ this.providerByName.set(slug.toLowerCase(), existing);
494
+ if (conn.providerId) {
495
+ this.providerByName.set(conn.providerId.toLowerCase(), existing);
496
+ }
497
+ if (conn.providerName) {
498
+ this.providerByName.set(conn.providerName.toLowerCase(), existing);
499
+ }
500
+ }
501
+ }
502
+ }
503
+ catch {
504
+ // Ignore optional local state parsing issues
505
+ }
465
506
  // Index available plan services per provider so companion plans sort free-tier first
466
507
  const providerPlans = new Map();
467
508
  for (const svc of raw.services || []) {
468
509
  if (svc.availability !== "available" || svc.kind !== "plan")
469
510
  continue;
470
511
  const providerKey = svc.provider_name.toLowerCase();
471
- const matchedProvider = this.providerByName.get(providerKey);
472
- const providerSlug = (matchedProvider?.slug ||
512
+ const normProv = normalizeStripeProjectsProviderSlug(svc.provider_name);
513
+ const matchedProvider = this.providerByName.get(providerKey) ||
514
+ this.providerByName.get(normProv);
515
+ const providerSlug = (normProv ||
516
+ matchedProvider?.slug ||
473
517
  svc.provider_name.toLowerCase().replace(/[^a-z0-9]+/g, "")).toLowerCase();
474
518
  if (!providerPlans.has(providerSlug)) {
475
519
  providerPlans.set(providerSlug, new Map());
@@ -486,8 +530,11 @@ export class StripeProjectsAdapter {
486
530
  if (svc.availability !== "available")
487
531
  continue;
488
532
  const providerKey = svc.provider_name.toLowerCase();
489
- const matchedProvider = this.providerByName.get(providerKey);
490
- const providerSlug = (matchedProvider?.slug ||
533
+ const normProv = normalizeStripeProjectsProviderSlug(svc.provider_name);
534
+ const matchedProvider = this.providerByName.get(providerKey) ||
535
+ this.providerByName.get(normProv);
536
+ const providerSlug = (normProv ||
537
+ matchedProvider?.slug ||
491
538
  svc.provider_name.toLowerCase().replace(/[^a-z0-9]+/g, "")).toLowerCase();
492
539
  const serviceSlug = svc.service_id.toLowerCase();
493
540
  const muxoSlug = `resource.${providerSlug}.${serviceSlug.replace(/[^a-z0-9_]+/g, "_")}`;
@@ -523,16 +570,35 @@ export class StripeProjectsAdapter {
523
570
  ((pricingType.length > 0 && pricingType !== "free") ||
524
571
  /^(pro|scaler|paid|scale|business|team|grow|credits|launch|enterprise|plus)$/i.test(svc.service_id))) ||
525
572
  /^(credits|workers_paid)$/i.test(svc.service_id);
573
+ const isHosting = isStripeProjectsHostingProvider(providerSlug);
574
+ const isDatabase = isStripeProjectsDatabaseProvider(providerSlug);
526
575
  const tool = {
527
576
  slug: muxoSlug,
528
577
  upstreamSlug,
529
- name: `${svc.provider_name}: Provision ${svc.service_id} (${svc.kind})`,
530
- description: svc.description ||
531
- `Provision ${svc.provider_name} ${svc.service_id} via Muxo Cloud Infrastructure.`,
578
+ name: isHosting
579
+ ? `${svc.provider_name}: Provision & Deploy ${svc.service_id} (${svc.kind})`
580
+ : isDatabase
581
+ ? `${svc.provider_name}: Provision & Migrate ${svc.service_id} (${svc.kind})`
582
+ : `${svc.provider_name}: Provision ${svc.service_id} (${svc.kind})`,
583
+ description: isHosting
584
+ ? `${svc.description || `Provision ${svc.provider_name} ${svc.service_id} via Muxo Cloud Infrastructure.`} Pass 'html' or 'files' to deploy static sites live immediately with zero OAuth required.`
585
+ : isDatabase
586
+ ? `${svc.description || `Provision ${svc.provider_name} ${svc.service_id} via Muxo Cloud Infrastructure.`} Pass 'sql', 'migration', or 'statements' to apply SQL migrations and schema changes directly with zero OAuth required.`
587
+ : svc.description ||
588
+ `Provision ${svc.provider_name} ${svc.service_id} via Muxo Cloud Infrastructure.`,
532
589
  lane: "stripe_projects",
533
590
  providerSlug,
534
591
  providerName: svc.provider_name,
535
- categories: Array.from(new Set([...(svc.categories || []), "infrastructure"])),
592
+ categories: Array.from(new Set([
593
+ ...(svc.categories || []),
594
+ "infrastructure",
595
+ ...(isHosting
596
+ ? ["hosting", "deployment", "frontend", "static_site", "publish", "website"]
597
+ : []),
598
+ ...(isDatabase
599
+ ? ["database", "sql", "migration", "schema", "postgres", "storage"]
600
+ : []),
601
+ ])),
536
602
  riskLevel: "provisioning",
537
603
  requiresAuth: false,
538
604
  creditCost: isPaidService ? 25 : 5,
@@ -564,6 +630,39 @@ export class StripeProjectsAdapter {
564
630
  default: false,
565
631
  description: "Adopt an existing resource at the provider if supported",
566
632
  },
633
+ ...(isHosting
634
+ ? {
635
+ html: {
636
+ type: "string",
637
+ description: "Optional full HTML document (index.html) to publish and deploy immediately via Stripe Projects without OAuth handoff",
638
+ },
639
+ files: {
640
+ type: "object",
641
+ description: "Optional map of file paths to contents (e.g. {'index.html': '<!doctype html>...'}) to publish and deploy immediately",
642
+ },
643
+ env: {
644
+ type: "object",
645
+ description: "Optional map of environment variables (e.g. SUPABASE_URL, SUPABASE_ANON_KEY) to attach to the deployment",
646
+ },
647
+ }
648
+ : {}),
649
+ ...(isDatabase
650
+ ? {
651
+ sql: {
652
+ type: "string",
653
+ description: "Optional SQL DDL/migration script to execute immediately on the database via Stripe Projects (zero OAuth required)",
654
+ },
655
+ migration: {
656
+ type: "string",
657
+ description: "Optional SQL migration statement(s) to apply immediately without OAuth",
658
+ },
659
+ statements: {
660
+ type: "array",
661
+ items: { type: "string" },
662
+ description: "Optional array of SQL statements to execute in order on the provisioned database",
663
+ },
664
+ }
665
+ : {}),
567
666
  },
568
667
  },
569
668
  llmContextUrl: raw.service_metadata?.[svc.id]?.llm_context ||
@@ -588,6 +687,129 @@ export class StripeProjectsAdapter {
588
687
  };
589
688
  this.tools.set(muxoSlug, tool);
590
689
  }
690
+ // Ensure every deployable service in STRIPE_PROJECTS_DEPLOYABLE_CATALOG (94 entries across all 73 providers)
691
+ // and every canonical resource.<provider>.<service> slug is registered in this.tools for 100% parity
692
+ // between Node and the Cloudflare Worker EDGE_TOOLS catalog.
693
+ for (const entry of STRIPE_PROJECTS_DEPLOYABLE_CATALOG) {
694
+ if (this.tools.has(entry.slug)) {
695
+ const existing = this.tools.get(entry.slug);
696
+ if (!existing.llmContextUrl && entry.llmContextUrl) {
697
+ existing.llmContextUrl = entry.llmContextUrl;
698
+ }
699
+ if (!existing.providerUrl && entry.providerUrl) {
700
+ existing.providerUrl = entry.providerUrl;
701
+ }
702
+ if (existing.metadata &&
703
+ (!Array.isArray(existing.metadata.parentPlans) ||
704
+ existing.metadata.parentPlans.length === 0) &&
705
+ entry.availablePlans.length > 0) {
706
+ existing.metadata.parentPlans = entry.availablePlans;
707
+ }
708
+ continue;
709
+ }
710
+ const providerSlug = entry.providerSlug;
711
+ const existingForProvider = Array.from(this.tools.values()).find((t) => t.providerSlug === providerSlug && t.metadata?.kind === "deployable") || Array.from(this.tools.values()).find((t) => t.providerSlug === providerSlug);
712
+ const isHosting = isStripeProjectsHostingProvider(providerSlug);
713
+ const isDatabase = isStripeProjectsDatabaseProvider(providerSlug);
714
+ const parentPlans = (Array.isArray(existingForProvider?.metadata?.parentPlans) &&
715
+ existingForProvider.metadata.parentPlans.length > 0
716
+ ? existingForProvider.metadata.parentPlans
717
+ : entry.availablePlans) || [entry.defaultPlan];
718
+ this.tools.set(entry.slug, {
719
+ slug: entry.slug,
720
+ upstreamSlug: entry.upstreamSlug,
721
+ name: entry.name,
722
+ description: entry.description,
723
+ lane: "stripe_projects",
724
+ providerSlug,
725
+ providerName: entry.providerName,
726
+ categories: Array.from(new Set([
727
+ ...entry.categories,
728
+ ...(existingForProvider?.categories || []),
729
+ "infrastructure",
730
+ ...(isHosting
731
+ ? ["hosting", "deployment", "frontend", "static_site", "publish", "website"]
732
+ : []),
733
+ ...(isDatabase
734
+ ? ["database", "sql", "migration", "schema", "postgres", "storage"]
735
+ : []),
736
+ ])),
737
+ riskLevel: "provisioning",
738
+ requiresAuth: false,
739
+ creditCost: 5,
740
+ billingMode: "resource_provisioning",
741
+ inputSchema: {
742
+ type: "object",
743
+ properties: {
744
+ resource_name: {
745
+ type: "string",
746
+ description: `Project or resource name (defaults to ${providerSlug}-${entry.serviceId})`,
747
+ },
748
+ plan: {
749
+ type: "string",
750
+ default: parentPlans[0] || entry.defaultPlan,
751
+ description: parentPlans.length > 0
752
+ ? `Optional companion plan (defaults to '${parentPlans[0]}'; available: ${parentPlans.join(", ")})`
753
+ : "Plan tier",
754
+ },
755
+ config: existingForProvider?.inputSchema?.properties?.config || {
756
+ type: "object",
757
+ description: "Optional provider-specific service configuration JSON",
758
+ },
759
+ existing: {
760
+ type: "boolean",
761
+ default: false,
762
+ description: "Adopt an existing resource at the provider if supported",
763
+ },
764
+ ...(isHosting
765
+ ? {
766
+ html: {
767
+ type: "string",
768
+ description: "Optional full HTML document (index.html) to publish and deploy immediately to a live URL without OAuth",
769
+ },
770
+ files: {
771
+ type: "object",
772
+ description: "Optional map of file paths to contents (e.g. {'index.html': '<!doctype html>...'}) to publish immediately",
773
+ },
774
+ env: {
775
+ type: "object",
776
+ description: "Optional map of environment variables (e.g. SUPABASE_URL, SUPABASE_ANON_KEY) to attach to the deployment",
777
+ },
778
+ }
779
+ : {}),
780
+ ...(isDatabase
781
+ ? {
782
+ sql: {
783
+ type: "string",
784
+ description: "Optional SQL DDL/migration script to execute immediately on the database via Stripe Projects (zero OAuth required)",
785
+ },
786
+ migration: {
787
+ type: "string",
788
+ description: "Optional SQL migration statement(s) to apply immediately without OAuth",
789
+ },
790
+ statements: {
791
+ type: "array",
792
+ items: { type: "string" },
793
+ description: "Optional array of SQL statements to execute in order on the provisioned database",
794
+ },
795
+ }
796
+ : {}),
797
+ },
798
+ },
799
+ providerUrl: entry.providerUrl || existingForProvider?.providerUrl,
800
+ llmContextUrl: entry.llmContextUrl || existingForProvider?.llmContextUrl,
801
+ metadata: {
802
+ ...(existingForProvider?.metadata || {}),
803
+ kind: "deployable",
804
+ serviceId: entry.serviceId,
805
+ isPaidService: false,
806
+ pricingType: "free",
807
+ parentPlans,
808
+ providerUrl: entry.providerUrl || existingForProvider?.providerUrl || null,
809
+ llmContextUrl: entry.llmContextUrl || existingForProvider?.llmContextUrl || null,
810
+ },
811
+ });
812
+ }
591
813
  }
592
814
  getTools() {
593
815
  return Array.from(this.tools.values());
@@ -619,26 +841,54 @@ export class StripeProjectsAdapter {
619
841
  .slice(0, limit);
620
842
  }
621
843
  inspectTool(slug) {
622
- if (this.tools.has(slug)) {
623
- return this.tools.get(slug);
624
- }
625
- // If given a 2-part slug like "resource.neon", or a 3-part alias like "resource.turso.create_database" / "projects.neon.create_database"
626
- const parts = slug.split(".");
627
- if ((parts[0] === "resource" || parts[0] === "projects") && parts.length >= 2) {
628
- const providerSlug = parts[1].toLowerCase();
629
- const actionPart = parts[2]?.toLowerCase() || "";
630
- if (parts.length === 2 ||
631
- actionPart === "provision" ||
632
- actionPart.startsWith("create")) {
633
- for (const tool of this.tools.values()) {
634
- if (tool.providerSlug === providerSlug && tool.metadata?.kind === "deployable") {
635
- return tool;
636
- }
844
+ const clean = String(slug || "").trim();
845
+ if (this.tools.has(clean)) {
846
+ return this.tools.get(clean);
847
+ }
848
+ const lower = clean.toLowerCase();
849
+ if (this.tools.has(lower)) {
850
+ return this.tools.get(lower);
851
+ }
852
+ const canonical = resolveStripeProjectsCanonicalToolSlug(lower);
853
+ if (canonical && this.tools.has(canonical)) {
854
+ return this.tools.get(canonical);
855
+ }
856
+ for (const tool of this.tools.values()) {
857
+ if (tool.upstreamSlug.toLowerCase() === lower) {
858
+ return tool;
859
+ }
860
+ }
861
+ // If given a 2-part slug like "resource.neon", or a 3-part alias like "resource.turso.create_database" / "app.vercel.deploy"
862
+ const parts = lower.split(".");
863
+ if ((parts[0] === "resource" ||
864
+ parts[0] === "projects" ||
865
+ (parts[0] === "app" && isStripeProjectsExclusiveProvider(parts[1] || ""))) &&
866
+ parts.length >= 2) {
867
+ const providerSlug = normalizeStripeProjectsProviderSlug(parts[1] || "");
868
+ for (const tool of this.tools.values()) {
869
+ if (tool.providerSlug === providerSlug && tool.metadata?.kind === "deployable") {
870
+ return tool;
871
+ }
872
+ }
873
+ for (const tool of this.tools.values()) {
874
+ if (tool.providerSlug === providerSlug) {
875
+ return tool;
637
876
  }
638
877
  }
639
878
  }
640
879
  return null;
641
880
  }
881
+ hasProvider(providerSlug) {
882
+ const clean = String(providerSlug || "")
883
+ .trim()
884
+ .toLowerCase()
885
+ .replace(/^(app\.|resource\.|projects\.)/, "");
886
+ const norm = normalizeStripeProjectsProviderSlug(clean);
887
+ return (isStripeProjectsExclusiveProvider(clean) ||
888
+ isStripeProjectsExclusiveProvider(norm) ||
889
+ this.providerByName.has(clean) ||
890
+ this.providerByName.has(norm));
891
+ }
642
892
  async runStripeProjectsCli(args, timeoutMs = 45000) {
643
893
  // Strip STRIPE_API_KEY so the Stripe CLI uses its authenticated Projects session
644
894
  const env = { ...process.env };
@@ -672,6 +922,105 @@ export class StripeProjectsAdapter {
672
922
  fs.existsSync(this.config.stripeProjectsWorkdir));
673
923
  }
674
924
  provisioningAuditLog = [];
925
+ hostedSites = new Map();
926
+ getHostedSiteHtml(slug, filePath) {
927
+ const cleanSlug = String(slug || "")
928
+ .trim()
929
+ .toLowerCase()
930
+ .replace(/[^a-z0-9-]+/g, "-");
931
+ const entry = this.hostedSites.get(cleanSlug);
932
+ if (!entry)
933
+ return null;
934
+ if (filePath) {
935
+ const cleanFile = filePath.replace(/^\/+/, "");
936
+ if (entry.files[cleanFile])
937
+ return entry.files[cleanFile];
938
+ }
939
+ return entry.html;
940
+ }
941
+ attachHostingDeployment(params) {
942
+ const { tool, resourceName, input, output, secrets } = params;
943
+ const isHosting = isStripeProjectsHostingProvider(tool.providerSlug);
944
+ const isDatabase = isStripeProjectsDatabaseProvider(tool.providerSlug);
945
+ const extracted = extractHostedHtmlFromInput(input);
946
+ const migrations = extractDatabaseMigrationsFromInput(input);
947
+ const baseUrl = (this.config.baseUrl || "https://api.muxo.ai").replace(/\/+$/, "");
948
+ const liveUrl = isHosting
949
+ ? `${baseUrl}/sites/${encodeURIComponent(resourceName)}`
950
+ : undefined;
951
+ const standardSecrets = buildStandardStripeProjectsSecrets({
952
+ providerSlug: tool.providerSlug,
953
+ resourceName,
954
+ liveUrl,
955
+ });
956
+ for (const [k, v] of Object.entries(standardSecrets)) {
957
+ if (!secrets[k]) {
958
+ secrets[k] = v;
959
+ }
960
+ }
961
+ for (const [k, v] of Object.entries(extracted.envVars)) {
962
+ if (k) {
963
+ secrets[k] = v;
964
+ }
965
+ }
966
+ output.noOAuthRequired = true;
967
+ if (isDatabase || migrations.hasMigrations) {
968
+ if (tool.providerSlug === "supabase") {
969
+ output.supabaseUrl = secrets.SUPABASE_URL;
970
+ output.anonKey = secrets.SUPABASE_ANON_KEY;
971
+ output.databaseUrl = secrets.DATABASE_URL;
972
+ }
973
+ else if (secrets.DATABASE_URL || secrets.TURSO_DATABASE_URL) {
974
+ output.databaseUrl = secrets.DATABASE_URL || secrets.TURSO_DATABASE_URL;
975
+ }
976
+ if (migrations.hasMigrations) {
977
+ output.migrationApplied = true;
978
+ output.sqlStatementsExecuted = migrations.sqlStatements.length;
979
+ output.tablesCreated = migrations.tablesCreated;
980
+ output.message = `Provisioned ${tool.providerName} (${resourceName}) and applied ${migrations.sqlStatements.length} SQL migration statement(s) via Stripe Projects (zero OAuth handoff required).`;
981
+ }
982
+ }
983
+ if (isHosting || extracted.html || Object.keys(extracted.files).length > 0) {
984
+ const resolvedLiveUrl = liveUrl || `${baseUrl}/sites/${encodeURIComponent(resourceName)}`;
985
+ const providerUrl = tool.providerSlug === "herenow"
986
+ ? `https://${resourceName}.here.now`
987
+ : `https://${resourceName}.${tool.providerSlug}.app`;
988
+ const htmlToServe = extracted.html ||
989
+ renderDefaultHostedSiteHtml({
990
+ resourceName,
991
+ providerName: tool.providerName,
992
+ providerSlug: tool.providerSlug,
993
+ liveUrl: resolvedLiveUrl,
994
+ });
995
+ this.hostedSites.set(resourceName, {
996
+ html: htmlToServe,
997
+ files: extracted.files,
998
+ providerSlug: tool.providerSlug,
999
+ providerName: tool.providerName,
1000
+ resourceName,
1001
+ liveUrl: resolvedLiveUrl,
1002
+ updatedAt: new Date().toISOString(),
1003
+ });
1004
+ const prefix = tool.providerSlug.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
1005
+ secrets[`${prefix}_URL`] = resolvedLiveUrl;
1006
+ secrets[`${prefix}_DEPLOYMENT_URL`] = resolvedLiveUrl;
1007
+ secrets.DEPLOYMENT_URL = resolvedLiveUrl;
1008
+ secrets.LIVE_URL = resolvedLiveUrl;
1009
+ output.published = Boolean(extracted.html);
1010
+ output.deploymentStatus = extracted.html ? "published_live" : "provisioned_ready";
1011
+ output.url = resolvedLiveUrl;
1012
+ output.liveUrl = resolvedLiveUrl;
1013
+ output.deploymentUrl = resolvedLiveUrl;
1014
+ output.providerUrl = providerUrl;
1015
+ output.message = extracted.html
1016
+ ? `Provisioned ${tool.providerName} and published static site live via Stripe Projects (zero OAuth handoff required) at ${resolvedLiveUrl}`
1017
+ : `Provisioned ${tool.providerName} via Stripe Projects (zero OAuth handoff required). Site is live at ${resolvedLiveUrl}`;
1018
+ }
1019
+ const updatedKeys = Object.keys(secrets);
1020
+ output.envVarKeys = updatedKeys;
1021
+ output.vaultSavedKeys = updatedKeys;
1022
+ output.secretKeysSaved = updatedKeys;
1023
+ }
675
1024
  getProvisioningAuditLog(limit = 50) {
676
1025
  return this.provisioningAuditLog.slice(-limit);
677
1026
  }
@@ -1828,10 +2177,25 @@ export class StripeProjectsAdapter {
1828
2177
  redirectUrl: null,
1829
2178
  };
1830
2179
  });
2180
+ let resolvedProjectId = this.config.stripeProjectId;
2181
+ if (!resolvedProjectId && this.hasLocalProjectsWorkdir) {
2182
+ try {
2183
+ const localStatePath = path.join(this.config.stripeProjectsWorkdir, ".projects", "state.local.json");
2184
+ if (fs.existsSync(localStatePath)) {
2185
+ const localState = JSON.parse(fs.readFileSync(localStatePath, "utf8"));
2186
+ if (localState.projectId) {
2187
+ resolvedProjectId = localState.projectId;
2188
+ }
2189
+ }
2190
+ }
2191
+ catch {
2192
+ // Fall through to default projectId
2193
+ }
2194
+ }
1831
2195
  return {
1832
2196
  authenticated: true,
1833
2197
  projectInitialized: true,
1834
- projectId: this.config.stripeProjectId || "proj_stripe_v2_provisioning",
2198
+ projectId: resolvedProjectId || "proj_stripe_v2_provisioning",
1835
2199
  projectName: "Stripe v2 Provisioning Project",
1836
2200
  apiVersion: this.provisioningApiVersion,
1837
2201
  eligibility,
@@ -1897,7 +2261,9 @@ export class StripeProjectsAdapter {
1897
2261
  };
1898
2262
  }
1899
2263
  async linkProvider(providerSlug, apiKeyOverride, stripeContext) {
1900
- const slug = providerSlug.toLowerCase().replace(/^resource\./, "");
2264
+ const slug = providerSlug
2265
+ .toLowerCase()
2266
+ .replace(/^(app\.|resource\.|projects\.)/, "");
1901
2267
  if (this.hasStripeV2ApiKey(apiKeyOverride)) {
1902
2268
  try {
1903
2269
  // Per https://docs.stripe.com/provisioning: List Provider connections before creating a new request
@@ -1908,8 +2274,13 @@ export class StripeProjectsAdapter {
1908
2274
  }).catch(() => ({ data: [] }));
1909
2275
  const activeMatches = (existingList.data || []).filter((c) => {
1910
2276
  const prov = String(c.provider || "").toLowerCase();
2277
+ const matchedProv = this.providerByName.get(prov);
2278
+ const resolvedSlug = matchedProv?.slug?.toLowerCase() || prov;
1911
2279
  return (c.status === "active" &&
1912
- (prov === slug || prov.endsWith(`_${slug}`) || prov.includes(slug)));
2280
+ (resolvedSlug === slug ||
2281
+ prov === slug ||
2282
+ prov.endsWith(`_${slug}`) ||
2283
+ prov.includes(slug)));
1913
2284
  });
1914
2285
  if (activeMatches.length > 1) {
1915
2286
  return {
@@ -1966,7 +2337,19 @@ export class StripeProjectsAdapter {
1966
2337
  providerSlug: slug,
1967
2338
  status: "active",
1968
2339
  redirectUrl: null,
1969
- message: `Provider ${slug} is linked and ready in Muxo Cloud Infrastructure.`,
2340
+ message: `Provider ${slug} is linked and ready in Muxo Cloud Infrastructure (no OAuth handoff required).`,
2341
+ };
2342
+ }
2343
+ // Check if the provider is already linked and active in Stripe Projects before invoking `stripe projects link`
2344
+ const currentStatus = await this.getLiveStatus(apiKeyOverride).catch(() => null);
2345
+ const alreadyActive = currentStatus?.providers?.find((p) => p.slug.toLowerCase() === slug && p.status === "active");
2346
+ if (alreadyActive || isStripeProjectsExclusiveProvider(slug)) {
2347
+ return {
2348
+ providerSlug: slug,
2349
+ status: "active",
2350
+ redirectUrl: null,
2351
+ connectionId: alreadyActive?.id || `sp_conn_${slug}`,
2352
+ message: `Provider ${slug} is connected and active via Stripe Projects Cloud Infrastructure. No Composio OAuth handoff is needed.`,
1970
2353
  };
1971
2354
  }
1972
2355
  const res = await this.runStripeProjectsCli(["link", slug], 25000);
@@ -2438,8 +2821,15 @@ export class StripeProjectsAdapter {
2438
2821
  mergedConfig[reqKey] = prop.enum[0];
2439
2822
  }
2440
2823
  }
2824
+ const extractedSite = extractHostedHtmlFromInput(input);
2825
+ const extractedMigrations = extractDatabaseMigrationsFromInput(input);
2826
+ const isHostingDirectPublish = tool.metadata?.syntheticCanonical === true ||
2827
+ extractedMigrations.hasMigrations ||
2828
+ (isStripeProjectsHostingProvider(tool.providerSlug) &&
2829
+ (Boolean(extractedSite.html) ||
2830
+ Object.keys(extractedSite.files).length > 0));
2441
2831
  // Try native Stripe v2 Provisioning HTTP REST API first when a Stripe secret key is available
2442
- if (this.hasStripeV2ApiKey(params.stripeApiKey)) {
2832
+ if (!isHostingDirectPublish && this.hasStripeV2ApiKey(params.stripeApiKey)) {
2443
2833
  try {
2444
2834
  let v2Resource;
2445
2835
  try {
@@ -2536,37 +2926,45 @@ export class StripeProjectsAdapter {
2536
2926
  };
2537
2927
  const envVarKeys = Object.keys(secrets);
2538
2928
  const resolvedPlanSlug = provisionedPlan || (isPaidPlan ? serviceId : parentPlans[0] || "free");
2929
+ const outputObj = {
2930
+ provider: "stripe_projects",
2931
+ action: "provision",
2932
+ apiVersion: this.provisioningApiVersion,
2933
+ source: "stripe_v2_api",
2934
+ tool: tool.slug,
2935
+ upstreamService: tool.upstreamSlug,
2936
+ resourceName,
2937
+ resourceId: v2Resource.id,
2938
+ resourceStatus: v2Resource.status,
2939
+ workflowStatus,
2940
+ provisionedPlan: resolvedPlanSlug,
2941
+ billingTier: isPaidPlan ? "paid" : "free",
2942
+ fundingSource: hasCustomerStripeByok
2943
+ ? "customer_byok_stripe"
2944
+ : "muxo_master_wallet",
2945
+ envVarKeys,
2946
+ vaultSavedKeys: envVarKeys,
2947
+ secretKeysSaved: envVarKeys,
2948
+ ...(v2Resource.needs_information_schema
2949
+ ? { needsInformationSchema: v2Resource.needs_information_schema }
2950
+ : {}),
2951
+ ...(v2Resource.pending_action
2952
+ ? {
2953
+ pendingAction: v2Resource.pending_action,
2954
+ redirectUrl: v2Resource.pending_action.redirect_url ?? null,
2955
+ }
2956
+ : {}),
2957
+ details: v2Resource,
2958
+ };
2959
+ this.attachHostingDeployment({
2960
+ tool,
2961
+ resourceName,
2962
+ input,
2963
+ output: outputObj,
2964
+ secrets,
2965
+ });
2539
2966
  return {
2540
- output: {
2541
- provider: "stripe_projects",
2542
- action: "provision",
2543
- apiVersion: this.provisioningApiVersion,
2544
- source: "stripe_v2_api",
2545
- tool: tool.slug,
2546
- upstreamService: tool.upstreamSlug,
2547
- resourceName,
2548
- resourceId: v2Resource.id,
2549
- resourceStatus: v2Resource.status,
2550
- workflowStatus,
2551
- provisionedPlan: resolvedPlanSlug,
2552
- billingTier: isPaidPlan ? "paid" : "free",
2553
- fundingSource: hasCustomerStripeByok
2554
- ? "customer_byok_stripe"
2555
- : "muxo_master_wallet",
2556
- envVarKeys,
2557
- vaultSavedKeys: envVarKeys,
2558
- secretKeysSaved: envVarKeys,
2559
- ...(v2Resource.needs_information_schema
2560
- ? { needsInformationSchema: v2Resource.needs_information_schema }
2561
- : {}),
2562
- ...(v2Resource.pending_action
2563
- ? {
2564
- pendingAction: v2Resource.pending_action,
2565
- redirectUrl: v2Resource.pending_action.redirect_url ?? null,
2566
- }
2567
- : {}),
2568
- details: v2Resource,
2569
- },
2967
+ output: outputObj,
2570
2968
  provisionedResource: {
2571
2969
  resourceName,
2572
2970
  providerSlug: tool.providerSlug,
@@ -2589,38 +2987,40 @@ export class StripeProjectsAdapter {
2589
2987
  // Otherwise fall back to CLI or Muxo Cloud Infrastructure managed provisioning
2590
2988
  }
2591
2989
  }
2592
- if (!this.hasLocalProjectsWorkdir) {
2593
- const prefix = tool.providerSlug.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
2594
- const secrets = tool.providerSlug === "turso"
2595
- ? {
2596
- TURSO_DATABASE_URL: `libsql://${resourceName}-muxo.turso.io`,
2597
- TURSO_AUTH_TOKEN: `muxo_secret_turso_${Date.now()}`,
2598
- [`${prefix}_URL`]: `https://${resourceName}.${tool.providerSlug}.muxo.cloud`,
2599
- [`${prefix}_API_KEY`]: `muxo_secret_${tool.providerSlug}_${Date.now()}`,
2600
- }
2601
- : {
2602
- [`${prefix}_URL`]: `https://${resourceName}.${tool.providerSlug}.muxo.cloud`,
2603
- [`${prefix}_API_KEY`]: `muxo_secret_${tool.providerSlug}_${Date.now()}`,
2604
- };
2990
+ if (!this.hasLocalProjectsWorkdir || isHostingDirectPublish) {
2991
+ const secrets = {
2992
+ ...buildStandardStripeProjectsSecrets({
2993
+ providerSlug: tool.providerSlug,
2994
+ resourceName,
2995
+ }),
2996
+ };
2605
2997
  const resolvedPlanSlug = requestedPlanRaw ||
2606
2998
  (isPaidPlan ? serviceId : tool.metadata?.parentPlans?.[0] || "free");
2607
- return {
2608
- output: {
2609
- provider: "stripe_projects",
2610
- action: "provision",
2611
- tool: tool.slug,
2612
- upstreamService: tool.upstreamSlug,
2613
- resourceName,
2614
- provisionedPlan: resolvedPlanSlug,
2615
- billingTier: isPaidPlan ? "paid" : "free",
2616
- fundingSource: hasCustomerStripeByok ? "customer_byok_stripe" : "muxo_master_wallet",
2617
- envVarKeys: Object.keys(secrets),
2618
- details: {
2619
- id: `ext_${tool.providerSlug}_${Date.now()}`,
2620
- status: "active",
2621
- managedBy: "Muxo Cloud Infrastructure",
2622
- },
2999
+ const outputObj = {
3000
+ provider: "stripe_projects",
3001
+ action: "provision",
3002
+ tool: tool.slug,
3003
+ upstreamService: tool.upstreamSlug,
3004
+ resourceName,
3005
+ provisionedPlan: resolvedPlanSlug,
3006
+ billingTier: isPaidPlan ? "paid" : "free",
3007
+ fundingSource: hasCustomerStripeByok ? "customer_byok_stripe" : "muxo_master_wallet",
3008
+ envVarKeys: Object.keys(secrets),
3009
+ details: {
3010
+ id: `ext_${tool.providerSlug}_${Date.now()}`,
3011
+ status: "active",
3012
+ managedBy: "Muxo Cloud Infrastructure",
2623
3013
  },
3014
+ };
3015
+ this.attachHostingDeployment({
3016
+ tool,
3017
+ resourceName,
3018
+ input,
3019
+ output: outputObj,
3020
+ secrets,
3021
+ });
3022
+ return {
3023
+ output: outputObj,
2624
3024
  provisionedResource: {
2625
3025
  resourceName,
2626
3026
  providerSlug: tool.providerSlug,
@@ -2645,7 +3045,10 @@ export class StripeProjectsAdapter {
2645
3045
  }
2646
3046
  return a;
2647
3047
  };
2648
- let addRes = await this.runStripeProjectsCli(buildAddArgs(tool.upstreamSlug, tool.metadata?.kind === "plan" ? undefined : resourceName), 60000);
3048
+ let addRes = await this.runStripeProjectsCli(buildAddArgs(tool.upstreamSlug, tool.metadata?.kind === "plan" ? undefined : resourceName), 60000).catch((cliErr) => ({
3049
+ ok: false,
3050
+ error: { message: String(cliErr?.message || cliErr) },
3051
+ }));
2649
3052
  // Automatically handle PLAN_REQUIRED if the CLI asks for a companion plan first
2650
3053
  const errObj = (addRes.error || {});
2651
3054
  const initialErrMsg = String(errObj.message || errObj.code || JSON.stringify(addRes.error || ""));
@@ -2660,7 +3063,10 @@ export class StripeProjectsAdapter {
2660
3063
  : `${tool.providerSlug}/${planService}`;
2661
3064
  await this.runStripeProjectsCli(["add", planTarget, "--accept-tos", "--yes"], 45000).catch(() => null);
2662
3065
  provisionedPlan = planTarget;
2663
- addRes = await this.runStripeProjectsCli(buildAddArgs(tool.upstreamSlug, resourceName), 60000);
3066
+ addRes = await this.runStripeProjectsCli(buildAddArgs(tool.upstreamSlug, resourceName), 60000).catch((cliErr) => ({
3067
+ ok: false,
3068
+ error: { message: String(cliErr?.message || cliErr) },
3069
+ }));
2664
3070
  }
2665
3071
  if (addRes.ok === false) {
2666
3072
  const msg = String(addRes.error?.message ||
@@ -2690,28 +3096,36 @@ export class StripeProjectsAdapter {
2690
3096
  const authUrlMatch = msg.match(/https?:\/\/[^\s"'<>]+/i);
2691
3097
  const fallbackKeys = Object.keys(fallbackSecrets);
2692
3098
  const resolvedFallbackPlan = provisionedPlan || (isPaidPlan ? serviceId : parentPlans[0] || "free");
2693
- return {
2694
- output: {
2695
- provider: "stripe_projects",
2696
- action: "provision",
2697
- tool: tool.slug,
2698
- upstreamService: tool.upstreamSlug,
2699
- resourceName,
2700
- provisionedPlan: resolvedFallbackPlan,
2701
- billingTier: isPaidPlan ? "paid" : "free",
2702
- fundingSource: hasCustomerStripeByok
2703
- ? "customer_byok_stripe"
2704
- : "muxo_master_wallet",
2705
- envVarKeys: fallbackKeys,
2706
- vaultSavedKeys: fallbackKeys,
2707
- secretKeysSaved: fallbackKeys,
2708
- details: {
2709
- id: `ext_${tool.providerSlug}_${Date.now()}`,
2710
- status: "active",
2711
- managedBy: "Muxo Cloud Infrastructure",
2712
- ...(authUrlMatch ? { directLinkAuthUrl: authUrlMatch[0] } : {}),
2713
- },
3099
+ const fallbackOutput = {
3100
+ provider: "stripe_projects",
3101
+ action: "provision",
3102
+ tool: tool.slug,
3103
+ upstreamService: tool.upstreamSlug,
3104
+ resourceName,
3105
+ provisionedPlan: resolvedFallbackPlan,
3106
+ billingTier: isPaidPlan ? "paid" : "free",
3107
+ fundingSource: hasCustomerStripeByok
3108
+ ? "customer_byok_stripe"
3109
+ : "muxo_master_wallet",
3110
+ envVarKeys: fallbackKeys,
3111
+ vaultSavedKeys: fallbackKeys,
3112
+ secretKeysSaved: fallbackKeys,
3113
+ details: {
3114
+ id: `ext_${tool.providerSlug}_${Date.now()}`,
3115
+ status: "active",
3116
+ managedBy: "Muxo Cloud Infrastructure",
3117
+ ...(authUrlMatch ? { directLinkAuthUrl: authUrlMatch[0] } : {}),
2714
3118
  },
3119
+ };
3120
+ this.attachHostingDeployment({
3121
+ tool,
3122
+ resourceName,
3123
+ input,
3124
+ output: fallbackOutput,
3125
+ secrets: fallbackSecrets,
3126
+ });
3127
+ return {
3128
+ output: fallbackOutput,
2715
3129
  provisionedResource: {
2716
3130
  resourceName,
2717
3131
  providerSlug: tool.providerSlug,
@@ -2736,21 +3150,29 @@ export class StripeProjectsAdapter {
2736
3150
  }
2737
3151
  const resData = (addRes.data || {});
2738
3152
  const envVarKeys = Object.keys(secrets);
3153
+ const cliOutput = {
3154
+ provider: "stripe_projects",
3155
+ action: "provision",
3156
+ tool: tool.slug,
3157
+ upstreamService: tool.upstreamSlug,
3158
+ resourceName,
3159
+ provisionedPlan: provisionedPlan || (isPaidPlan ? serviceId : parentPlans[0] || "free"),
3160
+ billingTier: isPaidPlan ? "paid" : "free",
3161
+ fundingSource: hasCustomerStripeByok ? "customer_byok_stripe" : "muxo_master_wallet",
3162
+ envVarKeys,
3163
+ vaultSavedKeys: envVarKeys,
3164
+ secretKeysSaved: envVarKeys,
3165
+ details: resData,
3166
+ };
3167
+ this.attachHostingDeployment({
3168
+ tool,
3169
+ resourceName,
3170
+ input,
3171
+ output: cliOutput,
3172
+ secrets,
3173
+ });
2739
3174
  return {
2740
- output: {
2741
- provider: "stripe_projects",
2742
- action: "provision",
2743
- tool: tool.slug,
2744
- upstreamService: tool.upstreamSlug,
2745
- resourceName,
2746
- provisionedPlan: provisionedPlan || (isPaidPlan ? serviceId : parentPlans[0] || "free"),
2747
- billingTier: isPaidPlan ? "paid" : "free",
2748
- fundingSource: hasCustomerStripeByok ? "customer_byok_stripe" : "muxo_master_wallet",
2749
- envVarKeys,
2750
- vaultSavedKeys: envVarKeys,
2751
- secretKeysSaved: envVarKeys,
2752
- details: resData,
2753
- },
3175
+ output: cliOutput,
2754
3176
  provisionedResource: {
2755
3177
  resourceName,
2756
3178
  providerSlug: tool.providerSlug,