@muxoai/sdk 1.2.3 → 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 (208) hide show
  1. package/.agents/plugins/marketplace.json +22 -0
  2. package/.claude-plugin/marketplace.json +32 -0
  3. package/.codex/config.toml +12 -0
  4. package/ARCHITECTURE.md +121 -0
  5. package/README.md +85 -31
  6. package/dist/clerk.d.ts +35 -0
  7. package/dist/clerk.js +201 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +2380 -0
  10. package/dist/composio-catalog.d.ts +234 -0
  11. package/dist/composio-catalog.js +1894 -0
  12. package/dist/config.d.ts +36 -0
  13. package/dist/config.js +115 -0
  14. package/dist/durable-workflow.d.ts +45 -0
  15. package/dist/durable-workflow.js +90 -0
  16. package/dist/engine.d.ts +465 -0
  17. package/dist/engine.js +2560 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.js +7 -0
  20. package/dist/mcp-schema.d.ts +286 -0
  21. package/dist/mcp-schema.js +2559 -0
  22. package/dist/mcp.d.ts +22 -0
  23. package/dist/mcp.js +1356 -0
  24. package/dist/memory-graph.d.ts +21 -0
  25. package/dist/memory-graph.js +173 -0
  26. package/dist/nl.d.ts +34 -0
  27. package/dist/nl.js +756 -0
  28. package/dist/oauth.d.ts +32 -0
  29. package/dist/oauth.js +134 -0
  30. package/dist/policy.d.ts +190 -0
  31. package/dist/policy.js +1597 -0
  32. package/dist/providers/composio-instant.d.ts +18 -0
  33. package/dist/providers/composio-instant.js +1169 -0
  34. package/dist/providers/composio-sdk.d.ts +162 -0
  35. package/dist/providers/composio-sdk.js +1395 -0
  36. package/dist/providers/microsoft-governance.d.ts +152 -0
  37. package/dist/providers/microsoft-governance.js +1062 -0
  38. package/dist/providers/stripe-billing.d.ts +83 -0
  39. package/dist/providers/stripe-billing.js +611 -0
  40. package/dist/providers/stripe-projects.d.ts +508 -0
  41. package/dist/providers/stripe-projects.js +3188 -0
  42. package/dist/providers/supermemory.d.ts +160 -0
  43. package/dist/providers/supermemory.js +778 -0
  44. package/dist/provisioning-managed.d.ts +50 -0
  45. package/dist/provisioning-managed.js +155 -0
  46. package/dist/sdk.d.ts +951 -0
  47. package/dist/sdk.js +748 -0
  48. package/dist/server.d.ts +10 -0
  49. package/dist/server.js +3575 -0
  50. package/dist/store.d.ts +348 -0
  51. package/dist/store.js +2323 -0
  52. package/dist/types.d.ts +593 -0
  53. package/dist/types.js +1 -0
  54. package/dist/ui/console-html.d.ts +64 -0
  55. package/dist/ui/console-html.js +3857 -0
  56. package/dist/ui/design-tokens.d.ts +177 -0
  57. package/dist/ui/design-tokens.js +210 -0
  58. package/dist/version.d.ts +1 -0
  59. package/dist/version.js +1 -0
  60. package/package.json +68 -7
  61. package/plugins/muxo/.app.json +8 -0
  62. package/plugins/muxo/.claude-plugin/plugin.json +16 -0
  63. package/plugins/muxo/.codex-plugin/plugin.json +64 -0
  64. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  65. package/plugins/muxo/.mcp.json +8 -0
  66. package/plugins/muxo/assets/logo.png +0 -0
  67. package/plugins/muxo/assets/logo.svg +6 -0
  68. package/plugins/muxo/hooks/hooks.json +27 -0
  69. package/plugins/muxo/hooks/session-start.sh +97 -0
  70. package/plugins/muxo/hooks/user-prompt-submit.sh +58 -0
  71. package/plugins/muxo/mcp.json +28 -0
  72. package/plugins/muxo/plugin.json +116 -0
  73. package/plugins/muxo/skills/muxo/SKILL.md +153 -0
  74. package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
  75. package/plugins/muxo/skills/muxo/references/cli.md +63 -0
  76. package/plugins/muxo/skills/muxo/references/mcp.md +37 -0
  77. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  78. package/plugins/muxo/skills/setup/SKILL.md +42 -0
  79. package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
  80. package/src/catalogues/stripe-projects-catalog.json +9972 -0
  81. package/vendor/microsoft-governance/README.md +363 -0
  82. package/vendor/microsoft-governance/dist/audit.d.ts +26 -0
  83. package/vendor/microsoft-governance/dist/audit.d.ts.map +1 -0
  84. package/vendor/microsoft-governance/dist/audit.js +107 -0
  85. package/vendor/microsoft-governance/dist/audit.js.map +1 -0
  86. package/vendor/microsoft-governance/dist/cascade-containment.d.ts +84 -0
  87. package/vendor/microsoft-governance/dist/cascade-containment.d.ts.map +1 -0
  88. package/vendor/microsoft-governance/dist/cascade-containment.js +387 -0
  89. package/vendor/microsoft-governance/dist/cascade-containment.js.map +1 -0
  90. package/vendor/microsoft-governance/dist/client.d.ts +34 -0
  91. package/vendor/microsoft-governance/dist/client.d.ts.map +1 -0
  92. package/vendor/microsoft-governance/dist/client.js +112 -0
  93. package/vendor/microsoft-governance/dist/client.js.map +1 -0
  94. package/vendor/microsoft-governance/dist/context-poisoning.d.ts +58 -0
  95. package/vendor/microsoft-governance/dist/context-poisoning.d.ts.map +1 -0
  96. package/vendor/microsoft-governance/dist/context-poisoning.js +410 -0
  97. package/vendor/microsoft-governance/dist/context-poisoning.js.map +1 -0
  98. package/vendor/microsoft-governance/dist/credential-vault.d.ts +213 -0
  99. package/vendor/microsoft-governance/dist/credential-vault.d.ts.map +1 -0
  100. package/vendor/microsoft-governance/dist/credential-vault.js +567 -0
  101. package/vendor/microsoft-governance/dist/credential-vault.js.map +1 -0
  102. package/vendor/microsoft-governance/dist/discovery.d.ts +80 -0
  103. package/vendor/microsoft-governance/dist/discovery.d.ts.map +1 -0
  104. package/vendor/microsoft-governance/dist/discovery.js +404 -0
  105. package/vendor/microsoft-governance/dist/discovery.js.map +1 -0
  106. package/vendor/microsoft-governance/dist/encryption/channel.d.ts +29 -0
  107. package/vendor/microsoft-governance/dist/encryption/channel.d.ts.map +1 -0
  108. package/vendor/microsoft-governance/dist/encryption/channel.js +78 -0
  109. package/vendor/microsoft-governance/dist/encryption/channel.js.map +1 -0
  110. package/vendor/microsoft-governance/dist/encryption/index.d.ts +9 -0
  111. package/vendor/microsoft-governance/dist/encryption/index.d.ts.map +1 -0
  112. package/vendor/microsoft-governance/dist/encryption/index.js +23 -0
  113. package/vendor/microsoft-governance/dist/encryption/index.js.map +1 -0
  114. package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts +267 -0
  115. package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts.map +1 -0
  116. package/vendor/microsoft-governance/dist/encryption/mesh-client.js +1001 -0
  117. package/vendor/microsoft-governance/dist/encryption/mesh-client.js.map +1 -0
  118. package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts +38 -0
  119. package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts.map +1 -0
  120. package/vendor/microsoft-governance/dist/encryption/ratchet.js +223 -0
  121. package/vendor/microsoft-governance/dist/encryption/ratchet.js.map +1 -0
  122. package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts +136 -0
  123. package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts.map +1 -0
  124. package/vendor/microsoft-governance/dist/encryption/registry-client.js +369 -0
  125. package/vendor/microsoft-governance/dist/encryption/registry-client.js.map +1 -0
  126. package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts +80 -0
  127. package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts.map +1 -0
  128. package/vendor/microsoft-governance/dist/encryption/x3dh.js +238 -0
  129. package/vendor/microsoft-governance/dist/encryption/x3dh.js.map +1 -0
  130. package/vendor/microsoft-governance/dist/framework-adapter.d.ts +63 -0
  131. package/vendor/microsoft-governance/dist/framework-adapter.d.ts.map +1 -0
  132. package/vendor/microsoft-governance/dist/framework-adapter.js +253 -0
  133. package/vendor/microsoft-governance/dist/framework-adapter.js.map +1 -0
  134. package/vendor/microsoft-governance/dist/identity.d.ts +124 -0
  135. package/vendor/microsoft-governance/dist/identity.d.ts.map +1 -0
  136. package/vendor/microsoft-governance/dist/identity.js +446 -0
  137. package/vendor/microsoft-governance/dist/identity.js.map +1 -0
  138. package/vendor/microsoft-governance/dist/index.d.ts +39 -0
  139. package/vendor/microsoft-governance/dist/index.d.ts.map +1 -0
  140. package/vendor/microsoft-governance/dist/index.js +95 -0
  141. package/vendor/microsoft-governance/dist/index.js.map +1 -0
  142. package/vendor/microsoft-governance/dist/kill-switch.d.ts +22 -0
  143. package/vendor/microsoft-governance/dist/kill-switch.d.ts.map +1 -0
  144. package/vendor/microsoft-governance/dist/kill-switch.js +60 -0
  145. package/vendor/microsoft-governance/dist/kill-switch.js.map +1 -0
  146. package/vendor/microsoft-governance/dist/lifecycle.d.ts +50 -0
  147. package/vendor/microsoft-governance/dist/lifecycle.d.ts.map +1 -0
  148. package/vendor/microsoft-governance/dist/lifecycle.js +113 -0
  149. package/vendor/microsoft-governance/dist/lifecycle.js.map +1 -0
  150. package/vendor/microsoft-governance/dist/mcp.d.ts +41 -0
  151. package/vendor/microsoft-governance/dist/mcp.d.ts.map +1 -0
  152. package/vendor/microsoft-governance/dist/mcp.js +233 -0
  153. package/vendor/microsoft-governance/dist/mcp.js.map +1 -0
  154. package/vendor/microsoft-governance/dist/metrics.d.ts +123 -0
  155. package/vendor/microsoft-governance/dist/metrics.d.ts.map +1 -0
  156. package/vendor/microsoft-governance/dist/metrics.js +289 -0
  157. package/vendor/microsoft-governance/dist/metrics.js.map +1 -0
  158. package/vendor/microsoft-governance/dist/oci-manifest.d.ts +49 -0
  159. package/vendor/microsoft-governance/dist/oci-manifest.d.ts.map +1 -0
  160. package/vendor/microsoft-governance/dist/oci-manifest.js +182 -0
  161. package/vendor/microsoft-governance/dist/oci-manifest.js.map +1 -0
  162. package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts +15 -0
  163. package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts.map +1 -0
  164. package/vendor/microsoft-governance/dist/policy-backends/cedar.js +88 -0
  165. package/vendor/microsoft-governance/dist/policy-backends/cedar.js.map +1 -0
  166. package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts +17 -0
  167. package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts.map +1 -0
  168. package/vendor/microsoft-governance/dist/policy-backends/opa.js +74 -0
  169. package/vendor/microsoft-governance/dist/policy-backends/opa.js.map +1 -0
  170. package/vendor/microsoft-governance/dist/policy.d.ts +77 -0
  171. package/vendor/microsoft-governance/dist/policy.d.ts.map +1 -0
  172. package/vendor/microsoft-governance/dist/policy.js +760 -0
  173. package/vendor/microsoft-governance/dist/policy.js.map +1 -0
  174. package/vendor/microsoft-governance/dist/prompt-defense.d.ts +37 -0
  175. package/vendor/microsoft-governance/dist/prompt-defense.d.ts.map +1 -0
  176. package/vendor/microsoft-governance/dist/prompt-defense.js +229 -0
  177. package/vendor/microsoft-governance/dist/prompt-defense.js.map +1 -0
  178. package/vendor/microsoft-governance/dist/protocol-facets.d.ts +87 -0
  179. package/vendor/microsoft-governance/dist/protocol-facets.d.ts.map +1 -0
  180. package/vendor/microsoft-governance/dist/protocol-facets.js +728 -0
  181. package/vendor/microsoft-governance/dist/protocol-facets.js.map +1 -0
  182. package/vendor/microsoft-governance/dist/rings.d.ts +17 -0
  183. package/vendor/microsoft-governance/dist/rings.d.ts.map +1 -0
  184. package/vendor/microsoft-governance/dist/rings.js +61 -0
  185. package/vendor/microsoft-governance/dist/rings.js.map +1 -0
  186. package/vendor/microsoft-governance/dist/sandbox.d.ts +61 -0
  187. package/vendor/microsoft-governance/dist/sandbox.d.ts.map +1 -0
  188. package/vendor/microsoft-governance/dist/sandbox.js +199 -0
  189. package/vendor/microsoft-governance/dist/sandbox.js.map +1 -0
  190. package/vendor/microsoft-governance/dist/surface-parity.d.ts +45 -0
  191. package/vendor/microsoft-governance/dist/surface-parity.d.ts.map +1 -0
  192. package/vendor/microsoft-governance/dist/surface-parity.js +216 -0
  193. package/vendor/microsoft-governance/dist/surface-parity.js.map +1 -0
  194. package/vendor/microsoft-governance/dist/trust.d.ts +28 -0
  195. package/vendor/microsoft-governance/dist/trust.d.ts.map +1 -0
  196. package/vendor/microsoft-governance/dist/trust.js +209 -0
  197. package/vendor/microsoft-governance/dist/trust.js.map +1 -0
  198. package/vendor/microsoft-governance/dist/types.d.ts +422 -0
  199. package/vendor/microsoft-governance/dist/types.d.ts.map +1 -0
  200. package/vendor/microsoft-governance/dist/types.js +29 -0
  201. package/vendor/microsoft-governance/dist/types.js.map +1 -0
  202. package/vendor/microsoft-governance/dist/verify.d.ts +135 -0
  203. package/vendor/microsoft-governance/dist/verify.d.ts.map +1 -0
  204. package/vendor/microsoft-governance/dist/verify.js +356 -0
  205. package/vendor/microsoft-governance/dist/verify.js.map +1 -0
  206. package/vendor/microsoft-governance/package.json +64 -0
  207. package/index.d.ts +0 -177
  208. package/index.js +0 -1
@@ -0,0 +1,3857 @@
1
+ import { THEME_STORAGE_KEY, muxoBaseCss, muxoMark, muxoThemeBootstrap, muxoThemes, uiIcon, } from "./design-tokens.js";
2
+ /** Every path that serves the console shell, mapped to the view it opens. */
3
+ export const CONSOLE_ROUTES = Object.freeze({
4
+ "/": "overview",
5
+ "/console": "overview",
6
+ "/dashboard": "overview",
7
+ "/catalog": "catalog",
8
+ "/tools": "catalog",
9
+ "/connections": "overview",
10
+ "/infrastructure": "overview",
11
+ "/keys": "setup",
12
+ "/setup": "setup",
13
+ "/workspaces": "workspaces",
14
+ "/activity": "activity",
15
+ "/receipts": "activity",
16
+ "/governance": "activity",
17
+ "/memory": "activity",
18
+ "/billing": "billing",
19
+ "/usage": "billing",
20
+ "/login": "login",
21
+ "/signup": "signup",
22
+ "/register": "signup",
23
+ });
24
+ export function isConsolePath(pathname) {
25
+ const clean = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
26
+ return Object.prototype.hasOwnProperty.call(CONSOLE_ROUTES, clean);
27
+ }
28
+ const DOCS_URL = "https://docs.muxo.ai";
29
+ const PRICING_URL = "https://docs.muxo.ai/billing";
30
+ const WEBSITE_URL = "https://muxo.ai";
31
+ const consoleCss = `
32
+ .skip-link{position:fixed;top:8px;left:8px;z-index:90;padding:10px 14px;border-radius:var(--radius-sm);background:var(--color-elevated);border:1px solid var(--color-border);color:var(--color-text);font-size:13px;transform:translateY(-200%)}
33
+ .skip-link:focus{transform:none}
34
+
35
+ .console-topbar{position:sticky;top:0;z-index:40;padding-top:env(safe-area-inset-top);background:var(--color-navigation);border-bottom:1px solid var(--color-border);backdrop-filter:saturate(180%) blur(20px);-webkit-backdrop-filter:saturate(180%) blur(20px)}
36
+ .topbar-in{display:flex;align-items:center;gap:16px;min-height:var(--header-height);padding:4px max(24px,env(safe-area-inset-right)) 4px max(24px,env(safe-area-inset-left))}
37
+ .brand{display:inline-flex;align-items:center;gap:8px;min-height:44px;flex:none;color:var(--color-text);text-decoration:none;font-size:20px;font-weight:600;letter-spacing:var(--tracking-heading)}
38
+ .brand svg{width:24px;height:24px}
39
+ .brand-section{padding-left:12px;margin-left:4px;border-left:1px solid var(--color-border-strong);color:var(--color-muted);font-size:13px;font-weight:500;letter-spacing:0}
40
+ .workspace-chip{display:inline-flex;align-items:center;gap:8px;min-width:0;max-width:340px;min-height:32px;padding:4px 12px;border:1px solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-surface);font-size:13px;color:var(--color-text)}
41
+ .workspace-chip:hover{background:var(--color-surface-2)}
42
+ .workspace-chip #workspace-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
43
+ .workspace-chip .tier{flex:none;color:var(--color-muted);font-size:12px}
44
+ .workspace-chip>.ui-icon{width:12px;height:12px;transform:rotate(90deg)}
45
+ .topbar-links{display:flex;align-items:center;gap:20px;margin-left:auto}
46
+ .nav-link{display:inline-flex;align-items:center;min-height:44px;color:var(--color-muted);font-size:13px;text-decoration:none}
47
+ .nav-link:hover{color:var(--color-text)}
48
+ .icon-button{display:grid;place-items:center;width:40px;height:40px;flex:none;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-muted)}
49
+ .icon-button:hover{background:var(--color-surface-2);color:var(--color-text)}
50
+ .theme-sun{display:none}html[data-theme=dark] .theme-sun{display:inline-block}html[data-theme=dark] .theme-moon{display:none}
51
+ .account{display:flex;align-items:center;gap:12px}
52
+ .account-label{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-muted);font-size:13px}
53
+ .mobile-menu-toggle{display:none}
54
+ .mobile-menu-content{display:flex;flex-direction:column;gap:24px;min-height:100%;padding:24px}
55
+ dialog.sheet.mobile-menu{inset:0 auto 0 0;width:min(340px,calc(100% - 32px));height:100dvh;max-height:100dvh;margin:0;border-radius:0 20px 20px 0}
56
+ .mobile-menu .nav-item{min-height:46px;margin:0}
57
+ .mobile-menu .nav-group p{padding-inline:12px}
58
+ .mobile-menu-account{display:grid;gap:12px;margin-top:auto;padding-top:20px;border-top:1px solid var(--color-border)}
59
+ .mobile-menu-account p{overflow-wrap:anywhere;color:var(--color-muted);font-size:13px}
60
+
61
+ .console-layout{display:grid;grid-template-columns:var(--sidebar-width) minmax(0,1fr);min-height:calc(100dvh - var(--header-height))}
62
+ .console-sidebar{position:sticky;top:var(--header-height);display:flex;flex-direction:column;gap:24px;height:calc(100dvh - var(--header-height));overflow-y:auto;overscroll-behavior:contain;padding:24px;border-right:1px solid var(--color-border);background:var(--color-sidebar)}
63
+ .nav-group p{margin:0 0 6px;color:var(--color-muted);font-size:12px;font-weight:500}
64
+ .nav-item{display:flex;align-items:center;gap:12px;min-height:38px;padding:8px 12px;margin-inline:-12px;border-radius:var(--radius-sm);color:var(--color-muted);font-size:13px;text-decoration:none}
65
+ .nav-item:hover{color:var(--color-text);background:var(--color-surface-2)}
66
+ .nav-item[aria-current=page]{color:var(--color-text);background:var(--color-surface-2);font-weight:500}
67
+ .nav-item[aria-current=page]>.ui-icon:first-child{color:var(--color-accent)}
68
+ .nav-item .ui-icon{width:18px;height:18px}
69
+ .nav-item .count{margin-left:auto;color:var(--color-muted);font-size:12px;font-weight:400;font-variant-numeric:tabular-nums}
70
+ .sidebar-usage{display:grid;gap:8px;margin-top:auto;padding-top:16px;border-top:1px solid var(--color-border);font-size:12px;color:var(--color-muted)}
71
+ .usage-row{display:flex;justify-content:space-between;gap:8px}
72
+ .usage-row strong{color:var(--color-text);font-weight:500;font-variant-numeric:tabular-nums}
73
+ .meter{height:4px;border-radius:var(--radius-pill);background:var(--color-border);overflow:hidden}
74
+ .meter>span{display:block;height:100%;border-radius:inherit;background:var(--color-button-primary);transition:width var(--motion-normal) var(--ease-out)}
75
+ .sidebar-usage a{color:var(--color-accent);text-decoration:none}
76
+ .sidebar-usage a:hover{text-decoration:underline}
77
+
78
+ .console-main{min-width:0;padding:40px max(48px,env(safe-area-inset-right)) 96px 48px;outline:none}
79
+ .page{max-width:1120px;margin-inline:auto}
80
+ .page-header{display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:20px 24px;margin-bottom:28px}
81
+ .page-header h1{font-size:clamp(28px,3vw,34px);line-height:1.15}
82
+ .lede{max-width:62ch;margin-top:8px;color:var(--color-muted);font-size:15px;text-wrap:pretty}
83
+ .page-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
84
+ .overview-header{display:block}
85
+ .overview-title{display:flex;align-items:center;justify-content:space-between;gap:16px}
86
+ .overview-title .page-actions{flex:none}
87
+ .stack{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px}
88
+ .split{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);gap:20px}
89
+ .split.even{grid-template-columns:repeat(2,minmax(0,1fr))}
90
+ .panel{min-width:0;padding:24px;border:1px solid var(--color-border);border-radius:18px;background:var(--color-surface)}
91
+ .panel-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}
92
+ .panel-header h2{font-size:17px;line-height:1.3}
93
+ .panel-header p{margin-top:4px;color:var(--color-muted);font-size:13px}
94
+ .panel-header>.pill,.panel-header>.btn{flex:none}
95
+ .dashboard-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:20px;align-items:start}
96
+ .dashboard-aside{display:grid;gap:20px}
97
+ .section-label{margin-bottom:8px;color:var(--color-muted);font-size:12px;font-weight:500}
98
+ .workspace-summary{display:flex;align-items:center;gap:14px;min-width:0}
99
+ .workspace-avatar,.action-icon{display:grid;place-items:center;flex:none;width:40px;height:40px;border:1px solid var(--color-border);border-radius:12px;background:var(--color-surface-2);color:var(--color-muted)}
100
+ .workspace-avatar .ui-icon,.action-icon .ui-icon{width:18px;height:18px}
101
+ .workspace-summary h2{font-size:16px;overflow-wrap:anywhere}
102
+ .workspace-summary p{margin-top:3px;color:var(--color-muted);font-size:12px}
103
+ .workspace-summary>div:last-child{min-width:0}
104
+ .summary-links{display:grid;margin-top:20px;border-top:1px solid var(--color-border)}
105
+ .summary-link{display:flex;align-items:center;gap:10px;min-height:46px;border-bottom:1px solid var(--color-border);color:var(--color-text);font-size:13px;text-decoration:none}
106
+ .summary-link:last-child{border-bottom:0}
107
+ .summary-link .ui-icon{width:16px;height:16px;color:var(--color-muted)}
108
+ .summary-link .ui-icon:last-child{margin-left:auto}
109
+ .summary-link:hover{color:var(--color-accent)}
110
+ .activity-row{display:flex;align-items:center;gap:12px;padding:18px 0;border-bottom:1px solid var(--color-border)}
111
+ .activity-row:last-child{border-bottom:0;padding-bottom:0}
112
+ .activity-row>div:nth-child(2){min-width:0;flex:1}
113
+ .activity-row strong{display:block;font-size:14px;font-weight:500;overflow-wrap:anywhere}
114
+ .activity-row small{display:block;margin-top:5px;color:var(--color-muted);font-size:12px}
115
+ .activity-row .pill{font-size:11px}
116
+ .activity-row .icon-button{width:32px;height:32px}
117
+ .activity-row .icon-button .ui-icon{width:16px;height:16px}
118
+ .activity-empty{display:grid;justify-items:center;gap:10px;padding:32px 20px;text-align:center}
119
+ .activity-empty h3{margin-top:6px;font-size:16px}
120
+ .activity-empty p{max-width:36ch;color:var(--color-muted);font-size:13px}
121
+ .activity-empty .btn{margin-top:8px}
122
+ .action-icon[data-tone=ok]{border-color:var(--color-ok-border);background:var(--color-ok-bg);color:var(--color-ok)}
123
+ .action-icon[data-tone=danger]{border-color:var(--color-danger-border);background:var(--color-danger-bg);color:var(--color-danger)}
124
+ .action-icon[data-tone=warn]{border-color:var(--color-warn-border);background:var(--color-warn-bg);color:var(--color-warn)}
125
+ .approval-notice{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 20px;border:1px solid var(--color-warn-border);border-radius:14px;background:var(--color-warn-bg);font-size:13px}
126
+ .approval-notice p{margin-top:3px;color:var(--color-muted);font-size:12px}
127
+ .tool-row{display:flex;align-items:center;gap:12px;padding:13px 0;border-bottom:1px solid var(--color-border);color:var(--color-text);text-decoration:none}
128
+ .tool-row:last-child{border-bottom:0;padding-bottom:0}
129
+ .tool-row>div{flex:1;min-width:0}
130
+ .tool-row strong{display:block;font-size:13px;font-weight:500;overflow-wrap:anywhere}
131
+ .tool-row small{display:block;margin-top:3px;color:var(--color-muted);font-size:12px}
132
+ .tool-row>.ui-icon{width:16px;height:16px;color:var(--color-muted)}
133
+ .tool-row:hover strong{color:var(--color-accent)}
134
+ .docs-link{display:inline-flex;align-items:center;gap:7px;color:var(--color-accent);font-size:13px;text-decoration:none}
135
+ .docs-link:hover{text-decoration:underline}
136
+ .docs-link .ui-icon{width:15px;height:15px}
137
+ .workspace-context{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 20px;border:1px solid var(--color-border);border-radius:14px;background:var(--color-sidebar)}
138
+ .access-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:20px;align-items:start}
139
+ .endpoint{padding:14px 0;border-bottom:1px solid var(--color-border)}
140
+ .endpoint:last-child{border-bottom:0;padding-bottom:0}
141
+ .endpoint>div{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:13px}
142
+ .endpoint code{display:block;margin-top:6px;color:var(--color-muted);font:12px/1.6 var(--font-mono);overflow-wrap:anywhere}
143
+ .team-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}
144
+ .team-form>.field:first-child{grid-column:1/-1}
145
+ .team-form-footer{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;grid-column:1/-1;padding-top:8px}
146
+ .team-form-footer p{max-width:44ch;color:var(--color-muted);font-size:12px}
147
+ .team-kit-secret{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;padding:12px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg)}
148
+ .team-kit-secret code{min-width:0;overflow-wrap:anywhere;font-size:12px}
149
+
150
+ .metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));overflow:hidden;border:1px solid var(--color-border);border-radius:18px;background:var(--color-surface)}
151
+ .metric{min-width:0;padding:20px 24px;border-left:1px solid var(--color-border)}
152
+ .metric:first-child{border-left:0}
153
+ .metric-label{color:var(--color-muted);font-size:12px}
154
+ .metric-value{margin-top:6px;font-size:28px;font-weight:600;line-height:1.1;letter-spacing:var(--tracking-display);font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
155
+ .metric-value[data-tone=ok]{color:var(--color-ok)}
156
+ .metric-value[data-tone=danger]{color:var(--color-danger)}
157
+ .metric-sub{margin-top:4px;color:var(--color-muted);font-size:12px}
158
+ .metric-link{display:block;color:var(--color-text);text-decoration:none;transition:background-color var(--motion-fast)}
159
+ .metric-link:hover{background:var(--color-surface-2)}
160
+ .metric-link:focus-visible{position:relative;z-index:1;outline-offset:-3px}
161
+ .metric-link .metric-label{display:flex;align-items:center;justify-content:space-between;gap:8px}
162
+ .metric-link .metric-label .ui-icon{width:13px;height:13px;color:var(--color-muted)}
163
+ .metric-value[data-tone=warn]{color:var(--color-warn)}
164
+
165
+ .btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;padding:7px 16px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);background:transparent;color:var(--color-text);font-size:13px;line-height:1.3;text-decoration:none;white-space:nowrap;transition:background-color var(--motion-fast),border-color var(--motion-fast),opacity var(--motion-fast)}
166
+ .btn:not(:disabled):hover{background:var(--color-surface-2);text-decoration:none}
167
+ .btn .ui-icon{width:16px;height:16px}
168
+ .btn-primary{border-color:transparent;background:var(--color-button-primary);color:var(--color-button-text)}
169
+ .btn-primary:not(:disabled):hover{background:var(--color-button-primary-hover)}
170
+ .btn-danger{border-color:transparent;background:var(--color-button-danger);color:#fff}
171
+ .btn-danger:not(:disabled):hover{background:var(--color-button-danger-hover)}
172
+ .btn-quiet{border-color:transparent;color:var(--color-accent)}
173
+ .btn-quiet:not(:disabled):hover{background:var(--color-accent-soft)}
174
+ .btn-sm{min-height:30px;padding:4px 12px;font-size:12px}
175
+ .btn-lg{min-height:44px;padding:11px 22px;font-size:15px}
176
+ .btn-block{width:100%}
177
+ [aria-busy=true]{opacity:.55;pointer-events:none}
178
+
179
+ .field{display:grid;gap:6px;min-width:0}
180
+ .field>span{font-size:13px;font-weight:500}
181
+ .field>span em{color:var(--color-muted);font-style:normal;font-weight:400}
182
+ .field-help{color:var(--color-muted);font-size:12px}
183
+ .field-error{color:var(--color-danger);font-size:13px}
184
+ .field-error:empty{display:none}
185
+ input[type=text],input[type=email],input[type=password],input[type=number],input[type=search],select,textarea{width:100%;min-height:40px;padding:9px 12px;border:1px solid var(--color-control-border);border-radius:var(--radius-field);background:var(--color-bg);color:var(--color-text);font-size:14px;transition:border-color var(--motion-fast),box-shadow var(--motion-fast)}
186
+ textarea{resize:vertical;line-height:var(--leading-body)}
187
+ textarea.mono{font:13px/1.6 var(--font-mono)}
188
+ input:focus,select:focus,textarea:focus{outline:none;border-color:var(--color-accent);box-shadow:0 0 0 4px var(--color-accent-soft)}
189
+ input[type=search]::-webkit-search-cancel-button{display:none}
190
+ input[type=checkbox]{width:16px;height:16px;margin:0;accent-color:var(--color-button-primary)}
191
+ .form-grid{display:grid;gap:14px}
192
+ .form-row{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:12px}
193
+ .provider-key-form{grid-template-columns:minmax(0,1fr) minmax(0,1.4fr) auto;align-items:end;margin-bottom:16px}
194
+ .inline-form{display:flex;align-items:center;gap:8px}
195
+ .inline-form input{flex:1}
196
+ .checks{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
197
+ .check{display:flex;align-items:center;gap:8px;min-height:40px;padding:8px 12px;border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:13px;cursor:pointer}
198
+ .check:hover{background:var(--color-surface-2)}
199
+ .toggle-row{display:flex;align-items:flex-start;gap:10px;padding:12px;border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:13px;cursor:pointer}
200
+ .toggle-row input{margin-top:2px}
201
+ .toggle-row small{display:block;margin-top:2px;color:var(--color-muted);font-size:12px}
202
+
203
+ .segmented{display:inline-flex;flex-wrap:wrap;gap:2px;padding:3px;border-radius:var(--radius-pill);background:var(--color-surface-2)}
204
+ .segmented button{min-height:30px;padding:5px 14px;border:0;border-radius:var(--radius-pill);background:transparent;color:var(--color-muted);font-size:13px;white-space:nowrap}
205
+ .segmented button:hover{color:var(--color-text)}
206
+ .segmented button[aria-pressed=true]{background:var(--color-elevated);color:var(--color-text);font-weight:500;box-shadow:0 1px 3px rgba(0,0,0,.1),0 0 0 .5px rgba(0,0,0,.06)}
207
+ .toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:12px}
208
+ .toolbar-note{min-height:20px;margin-bottom:16px;color:var(--color-muted);font-size:12px}
209
+ .search-field{display:flex;align-items:center;gap:8px;width:min(320px,100%);padding-left:12px;border:1px solid var(--color-control-border);border-radius:var(--radius-field);background:var(--color-bg);color:var(--color-muted)}
210
+ .search-field:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 4px var(--color-accent-soft)}
211
+ .search-field input{border:0;padding-left:0;background:transparent}
212
+ .search-field input:focus{box-shadow:none}
213
+ .search-field .ui-icon{width:16px;height:16px}
214
+ .search-field .clear-search{width:32px;height:32px;margin-right:4px}
215
+ .catalog-toolbar .search-field{width:min(420px,100%)}
216
+ .catalog-empty{display:grid;justify-items:center;gap:10px}
217
+ .catalog-empty h2{font-size:17px;color:var(--color-text)}
218
+ .catalog-empty p{max-width:38ch;font-size:13px}
219
+ .catalog-empty .btn{margin-top:6px}
220
+ .key-dismiss{float:right;margin:-6px -6px 4px 8px}
221
+
222
+ .pill{display:inline-flex;align-items:center;gap:6px;padding:1px 10px;border-radius:var(--radius-pill);background:var(--color-surface-2);color:var(--color-muted);font-size:12px;line-height:20px;white-space:nowrap;vertical-align:middle}
223
+ .pill.dot::before{content:"";width:6px;height:6px;flex:none;border-radius:50%;background:currentColor}
224
+ .pill[data-tone=ok]{background:var(--color-ok-bg);color:var(--color-ok)}
225
+ .pill[data-tone=warn]{background:var(--color-warn-bg);color:var(--color-warn)}
226
+ .pill[data-tone=danger]{background:var(--color-danger-bg);color:var(--color-danger)}
227
+ .pill[data-tone=accent]{background:var(--color-accent-soft);color:var(--color-accent)}
228
+
229
+ .code{margin:0;max-height:360px;overflow:auto;padding:16px 18px;border-radius:var(--radius-md);background:var(--color-surface-2);color:var(--color-text);font:12.5px/1.65 var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere}
230
+ .code.tall{min-height:220px}
231
+ .result-card{display:grid;gap:14px;min-height:220px;padding:18px;border-radius:var(--radius-md);background:var(--color-surface-2);font-size:13.5px}
232
+ .result-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--color-border)}
233
+ .result-meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
234
+ .result-body{display:grid;gap:10px}
235
+ .result-kv{display:grid;grid-template-columns:minmax(110px,auto) minmax(0,1fr);gap:6px 14px;font-size:13px}
236
+ .result-kv dt{color:var(--color-muted);font-weight:500}
237
+ .result-kv dd{margin:0;color:var(--color-text);overflow-wrap:anywhere}
238
+ .recall-box{display:grid;gap:18px;min-height:120px;max-height:420px;overflow:auto}
239
+ .recall-group h3{margin:0 0 4px;color:var(--color-muted);font-size:12px;font-weight:600}
240
+ .recall-raw{margin-top:14px}
241
+ .recall-raw summary{display:flex;align-items:center;min-height:32px;color:var(--color-accent);font-size:13px;cursor:pointer}
242
+ .recall-raw pre{margin-top:8px}
243
+ .mono{font-family:var(--font-mono)}
244
+ .small{font-size:12px}
245
+ .muted{color:var(--color-muted)}
246
+ .ok-text{color:var(--color-ok);font-size:13px}
247
+ .empty-note{padding:20px 0;color:var(--color-muted);font-size:13px}
248
+ .key-value{display:block;margin:10px 0;padding:10px 12px;border-radius:var(--radius-sm);background:var(--color-bg);font-size:12.5px;overflow-wrap:anywhere}
249
+
250
+ .table-wrap{position:relative;min-width:0;max-width:100%;overflow-x:auto;border:1px solid var(--color-border);border-radius:18px;background:var(--color-surface)}
251
+ .panel .table-wrap{border-radius:var(--radius-md)}
252
+ table{width:100%;border-collapse:collapse;text-align:left;font-size:13px;font-variant-numeric:tabular-nums}
253
+ th{padding:12px 16px;border-bottom:1px solid var(--color-border);color:var(--color-muted);font-size:12px;font-weight:500;white-space:nowrap}
254
+ td{padding:14px 16px;border-bottom:1px solid var(--color-border);vertical-align:middle}
255
+ tbody tr:last-child td{border-bottom:0}
256
+ td.empty{padding:40px 16px;color:var(--color-muted);text-align:center}
257
+ th.right,.cell-actions{text-align:right}
258
+ .cell-actions{white-space:nowrap}
259
+ .cell-actions .btn+.btn{margin-left:6px}
260
+ .clip{max-width:360px;overflow:hidden;text-overflow:ellipsis}
261
+
262
+ .rows{display:grid}
263
+ .row{display:flex;align-items:center;justify-content:space-between;gap:16px;min-width:0;padding:14px 0;border-bottom:1px solid var(--color-border)}
264
+ .row:first-child{padding-top:4px}
265
+ .row:last-child{border-bottom:0;padding-bottom:0}
266
+ .row>div{min-width:0}
267
+ .row strong{display:block;font-size:14px;font-weight:500;overflow-wrap:anywhere}
268
+ .row small{display:block;margin-top:2px;color:var(--color-muted);font-size:12px;overflow-wrap:anywhere}
269
+ .row>.btn,.row>.pill{flex:none}
270
+ .api-key-row{flex-wrap:wrap}
271
+ .api-key-row>.row-actions{max-width:100%;flex-wrap:wrap}
272
+ .api-key-row .pill{max-width:100%;overflow:hidden;text-overflow:ellipsis}
273
+
274
+ .ask{display:flex;align-items:center;gap:10px;padding:5px 5px 5px 16px;border:1px solid var(--color-control-border);border-radius:var(--radius-pill);background:var(--color-bg);color:var(--color-muted)}
275
+ .ask:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 4px var(--color-accent-soft)}
276
+ .ask input{flex:1;min-width:0;min-height:40px;padding:0;border:0;background:transparent;font-size:16px}
277
+ .ask input:focus{box-shadow:none}
278
+ .chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
279
+ .chip{min-height:32px;padding:5px 14px;border:1px solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-surface);color:var(--color-text);font-size:13px}
280
+ .chip:hover{background:var(--color-surface-2)}
281
+
282
+ .tool-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:16px}
283
+ .tool-tile{display:flex;flex-direction:column;gap:8px;min-width:0;padding:20px;border:1px solid var(--color-border);border-radius:18px;background:var(--color-surface);transition:border-color var(--motion-fast)}
284
+ .tool-tile:hover{border-color:var(--color-border-strong)}
285
+ .tool-meta{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:4px}
286
+ .tool-tile h3{font-size:15px;line-height:1.35}
287
+ .tool-slug{color:var(--color-muted);font:12px/1.5 var(--font-mono);overflow-wrap:anywhere}
288
+ .tool-desc{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;color:var(--color-muted);font-size:13px}
289
+ .tool-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;padding-top:12px;border-top:1px solid var(--color-border);color:var(--color-muted);font-size:12px}
290
+ .empty-state{grid-column:1/-1;padding:48px 24px;border:1px dashed var(--color-border-strong);border-radius:18px;color:var(--color-muted);text-align:center;font-size:14px}
291
+
292
+ .callout{padding:14px 16px;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface-2);font-size:13px}
293
+ .callout[data-tone=ok]{border-color:var(--color-ok-border);background:var(--color-ok-bg)}
294
+ .callout[data-tone=warn]{border-color:var(--color-warn-border);background:var(--color-warn-bg)}
295
+ .callout[data-tone=danger]{border-color:var(--color-danger-border);background:var(--color-danger-bg)}
296
+ .callout h3{font-size:14px;margin-bottom:4px}
297
+ .callout ul{margin:8px 0 0;padding-left:18px}
298
+ .callout li{margin:6px 0}
299
+ .callout li span{color:var(--color-muted)}
300
+ .callout-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
301
+ .module-list{display:flex;flex-wrap:wrap;gap:6px}
302
+ .price{font-size:40px;font-weight:600;letter-spacing:var(--tracking-display);line-height:1.1}
303
+ .price span{color:var(--color-muted);font-size:15px;font-weight:400;letter-spacing:0}
304
+ .plan-list{display:grid;gap:8px;margin:16px 0 20px;padding:0;list-style:none;color:var(--color-muted);font-size:14px}
305
+ .plan-list li{display:flex;gap:8px}
306
+ .plan-list li::before{content:"✓";color:var(--color-ok)}
307
+
308
+ dialog.sheet{width:min(640px,calc(100% - 32px));max-height:calc(100dvh - 48px);padding:0;border:1px solid var(--color-border);border-radius:24px;background:var(--color-elevated);color:var(--color-text);box-shadow:var(--shadow-dialog);overscroll-behavior:contain}
309
+ dialog.sheet.narrow{width:min(460px,calc(100% - 32px))}
310
+ dialog.sheet::backdrop{background:var(--color-scrim)}
311
+ body:has(dialog[open]){overflow:hidden}
312
+ .sheet-inner{display:grid;gap:16px;padding:24px 28px 28px}
313
+ .sheet-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
314
+ .sheet-head h2{font-size:22px;line-height:1.25}
315
+ .sheet-head p{margin-top:4px}
316
+ .sheet-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}
317
+
318
+ .toast{position:fixed;left:50%;bottom:max(24px,env(safe-area-inset-bottom));z-index:100;max-width:min(520px,calc(100% - 32px));padding:12px 18px;border-radius:14px;background:var(--color-text);color:var(--color-bg);font-size:13px;box-shadow:var(--shadow-dialog);opacity:0;pointer-events:none;transform:translate(-50%,12px);transition:opacity var(--motion-normal) var(--ease-out),transform var(--motion-normal) var(--ease-out)}
319
+ .toast.show{opacity:1;transform:translate(-50%,0)}
320
+ .toast[data-tone=danger]{background:var(--color-button-danger);color:#fff}
321
+
322
+ .auth-view{display:grid;place-items:center;min-height:calc(100dvh - var(--header-height));padding:48px 20px}
323
+ body:not(:has(.console-topbar)) .auth-view{min-height:100dvh;padding:36px 20px}
324
+ .auth-card{width:min(460px,100%);padding:36px;border:1px solid var(--color-border);border-radius:20px;background:var(--color-surface);text-align:left;box-shadow:0 24px 64px rgba(0,0,0,.18)}
325
+ .auth-card.oauth-card{width:min(480px,100%);padding:0;overflow:hidden}
326
+ .oauth-card-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 24px;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);color:var(--color-muted);font:400 11.5px/1.4 var(--font-mono)}
327
+ .oauth-brand{display:inline-flex;align-items:center;gap:8px;color:var(--color-text);text-decoration:none;font:600 15px/1.2 var(--font-sans);letter-spacing:var(--tracking-heading)}
328
+ .oauth-brand svg{width:20px;height:20px}
329
+ .oauth-top-meta{display:inline-flex;align-items:center;gap:10px}
330
+ .oauth-step-tag{padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-surface);color:var(--color-muted);font:400 11px/1.5 var(--font-mono)}
331
+ .oauth-theme-btn{width:30px;height:30px;border-radius:8px}
332
+ .oauth-theme-btn .ui-icon{width:15px;height:15px}
333
+ .oauth-card-body{padding:28px 28px 24px}
334
+ .oauth-card-foot{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:14px 28px;border-top:1px solid var(--color-border);background:var(--color-surface-2);color:var(--color-muted);font-size:12px}
335
+ .oauth-card-foot strong{color:var(--color-text);font-weight:600}
336
+ .oauth-card-foot a{color:var(--color-accent);text-decoration:none}
337
+ .oauth-card-foot a:hover{text-decoration:underline}
338
+ .auth-mark{display:inline-grid;place-items:center;width:44px;height:44px;margin-bottom:16px;border:1px solid var(--color-border);border-radius:12px;background:var(--color-surface-2);color:var(--color-text)}
339
+ .auth-mark svg{width:24px;height:24px}
340
+ .auth-card h1{font-size:clamp(22px,2.6vw,26px);line-height:1.18;letter-spacing:var(--tracking-heading)}
341
+ .auth-card .lede{margin:8px 0 0;color:var(--color-muted);font-size:14px;line-height:1.55}
342
+ .auth-card .segmented{display:grid;grid-template-columns:1fr 1fr;width:100%;margin-top:20px}
343
+ .auth-form{display:grid;gap:14px;margin-top:20px;text-align:left}
344
+ .auth-alt{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-top:18px;padding-top:16px;border-top:1px solid var(--color-border)}
345
+ .row-actions{display:inline-flex;align-items:center;gap:8px;flex:none}
346
+ .clerk-auth{display:flex;justify-content:center;min-height:120px;margin-top:18px;text-align:left}
347
+ .clerk-loading{display:flex;align-items:center;gap:10px;align-self:center;margin:0;color:var(--color-muted);font-size:14px}
348
+ .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}
349
+ @keyframes muxo-spin{to{transform:rotate(360deg)}}
350
+ @media (prefers-reduced-motion:reduce){.spinner{animation:none}}
351
+ .auth-switch{margin-top:8px}
352
+ .auth-switch:has([hidden]){display:none}
353
+ .auth-foot{margin-top:16px;color:var(--color-muted);font-size:12px;line-height:1.5}
354
+ .auth-foot code{font-size:11.5px}
355
+
356
+ @media (max-width:1100px){.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.metric:nth-child(3){border-left:0}.metric:nth-child(n+3){border-top:1px solid var(--color-border)}.split,.split.even,.dashboard-layout,.access-layout{grid-template-columns:1fr}.dashboard-aside{grid-template-columns:repeat(2,minmax(0,1fr))}}
357
+ @media (max-width:900px){
358
+ .console-layout{grid-template-columns:1fr}
359
+ .console-sidebar{display:none}
360
+ .mobile-menu-toggle{display:grid}
361
+ .console-main{padding:28px 20px 80px}
362
+ .topbar-links .nav-link,.topbar-links .account,.account-label{display:none}
363
+ .topbar-in{gap:12px}
364
+ .workspace-chip{max-width:300px;flex:1}.workspace-chip .tier{display:none}
365
+ .brand-section{display:none}
366
+ .catalog-toolbar{align-items:stretch}
367
+ .catalog-toolbar .search-field{width:100%}
368
+ .catalog-toolbar .segmented{width:100%;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border-radius:var(--radius-md)}
369
+ .catalog-toolbar .segmented button{padding-inline:8px;white-space:normal;border-radius:var(--radius-sm)}
370
+ .checks{grid-template-columns:repeat(2,minmax(0,1fr))}
371
+ }
372
+ @media (max-width:560px){
373
+ .console-main{padding:24px 16px 64px}
374
+ .page-header{margin-bottom:20px}
375
+ .overview-title{gap:8px}.overview-title h1{font-size:28px}
376
+ .overview-title .btn{padding-inline:12px}
377
+ .overview-header .lede{font-size:14px}
378
+ .metric{padding:16px}.metric-value{font-size:26px}
379
+ .metric-sub{font-size:11px}
380
+ .form-row,.team-form,.dashboard-aside{grid-template-columns:1fr}
381
+ .panel{padding:20px}.panel-header{gap:10px}.panel-header h2{font-size:16px}
382
+ .panel-header .btn{padding-inline:8px}
383
+ .auth-card{padding:28px 22px}.auth-card.oauth-card{padding:0}.oauth-card-top{padding:12px 18px}.oauth-card-body{padding:22px 18px 20px}.oauth-card-foot{padding:12px 18px}.inline-form{flex-wrap:wrap}
384
+ .topbar-in{gap:8px;padding-inline:12px}.topbar-links{gap:4px}
385
+ .workspace-chip{max-width:none;padding-inline:10px}.brand{font-size:18px;gap:5px}.brand svg{width:22px;height:22px}
386
+ .activity-row{display:grid;grid-template-columns:32px minmax(0,1fr) auto;gap:8px 10px}
387
+ .activity-row>.action-icon{width:32px;height:32px;grid-row:1/3;align-self:start}
388
+ .activity-row>div:nth-child(2){grid-column:2;grid-row:1}
389
+ .activity-row>.pill{grid-column:2;grid-row:2;justify-self:start}
390
+ .activity-row>.icon-button{grid-column:3;grid-row:1/3}
391
+ .activity-empty{padding:24px 8px}
392
+ .workspace-context{align-items:flex-start;flex-wrap:wrap}
393
+ .team-kit-secret{align-items:flex-start;flex-wrap:wrap}
394
+ .tool-grid{grid-template-columns:minmax(0,1fr)}
395
+ input[type=text],input[type=email],input[type=password],input[type=number],input[type=search],select,textarea{font-size:16px}
396
+ .provider-key-form{grid-template-columns:1fr}
397
+ }
398
+ @media (forced-colors:active){.btn,.chip,.segmented button,input,select,textarea{border:1px solid ButtonText}}
399
+ @media (hover:none){.btn,.chip,.segmented button,.icon-button,.workspace-chip,.nav-item{min-height:44px}.icon-button{min-width:44px}}
400
+ `;
401
+ const consoleScript = String.raw `
402
+ var VIEW_PATHS = { overview: '/', catalog: '/catalog', setup: '/keys', workspaces: '/workspaces', activity: '/activity', billing: '/billing', login: '/login', signup: '/signup' };
403
+ var VIEW_TITLES = { overview: 'Overview', catalog: 'Tools', setup: 'API access', workspaces: 'Workspaces', activity: 'Activity', billing: 'Billing and credits', login: 'Sign in', signup: 'Create account' };
404
+ var LANE_LABELS = { composio_instant: 'Instant tool', composio_sdk: 'Connected app', stripe_projects: 'Cloud service' };
405
+ var STATUS_LABELS = {
406
+ executed: 'Completed',
407
+ succeeded: 'Completed',
408
+ completed: 'Completed',
409
+ guardrail_applied: 'Guardrail active',
410
+ memory_saved: 'Saved to memory',
411
+ planned_dry_run: 'Planned',
412
+ pending_approval: 'Needs your review',
413
+ awaiting_approval: 'Needs your review',
414
+ awaiting_oauth_connection: 'Connect app to continue',
415
+ running: 'In progress',
416
+ in_progress: 'In progress',
417
+ queued: 'In progress',
418
+ disabled: 'Not enabled',
419
+ not_enabled: 'Not enabled',
420
+ unknown: 'Could not verify',
421
+ unverified: 'Could not verify'
422
+ };
423
+ var DEFAULT_PARAM_LABELS = {
424
+ defaultRegion: 'Default cloud region',
425
+ defaultGithubOwner: 'Default GitHub owner',
426
+ defaultGithubRepo: 'Default GitHub repo',
427
+ defaultSlackChannel: 'Default Slack channel',
428
+ defaultEmail: 'Default notification email',
429
+ defaultDomain: 'Default web domain'
430
+ };
431
+ var MODULE_LABELS = {
432
+ McpSecurityScanner: 'Prompt injection scan',
433
+ PromptInjectionScanner: 'Prompt injection scan',
434
+ RingEnforcer: 'Access level guard',
435
+ ExecutionRings: 'Access level guard',
436
+ PolicyEngine: 'Approval policy',
437
+ AuditLogger: 'Signed audit log',
438
+ HashChainAuditLog: 'Signed audit log',
439
+ KillSwitch: 'Emergency stop',
440
+ KillSwitchManager: 'Emergency stop',
441
+ TrustEngine: 'Trust score monitor',
442
+ TrustScoreEngine: 'Trust score monitor',
443
+ CredentialVault: 'Encrypted vault',
444
+ SecretRedactor: 'Secret redaction',
445
+ SqlAstGuard: 'Destructive SQL guard',
446
+ Ed25519Identity: 'Signed decisions',
447
+ ReceiptSigner: 'Signed audit records',
448
+ RateLimiter: 'Rate protection'
449
+ };
450
+ var TOKEN_KEY = 'muxo_external_api_key';
451
+ var ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
452
+
453
+ function formatToolLabel(slug) {
454
+ if (!slug) return 'Tool';
455
+ var parts = String(slug).split('.');
456
+ var last = parts[parts.length - 1] || slug;
457
+ var provider = parts.length > 1 ? parts[1] : '';
458
+ var providerNames = { hackernews: 'Hacker News', github: 'GitHub', dns: 'DNS', web: 'Web', seo: 'SEO', projects: 'Cloud services', neon: 'Neon Postgres', turso: 'Turso SQLite', upstash: 'Upstash Redis', supabase: 'Supabase', cloudflare: 'Cloudflare', vercel: 'Vercel', gmail: 'Gmail', slack: 'Slack', notion: 'Notion', linear: 'Linear' };
459
+ var pLabel = providerNames[provider] || (provider ? provider.charAt(0).toUpperCase() + provider.slice(1) : '');
460
+ var aLabel = last.replace(/_/g, ' ');
461
+ return pLabel ? (pLabel + ' · ' + aLabel) : aLabel;
462
+ }
463
+ function sanitizeForDisplay(val) {
464
+ if (!val || typeof val !== 'object') return val;
465
+ if (Array.isArray(val)) return val.map(sanitizeForDisplay);
466
+ var out = {};
467
+ var hiddenKeys = { supplierSettlement: 1, wholesaleCostCents: 1, muxoMarginCents: 1, totalSupplierWholesalePaidCents: 1, totalMuxoNetMarginCents: 1, supplierWholesalePaidCents: 1, muxoNetMarginCents: 1, upstreamSlug: 1, supermemoryNamespace: 1 };
468
+ Object.keys(val).forEach(function (k) {
469
+ if (hiddenKeys[k]) return;
470
+ out[k] = sanitizeForDisplay(val[k]);
471
+ });
472
+ return out;
473
+ }
474
+
475
+ var state = { view: 'overview', boot: null, lane: 'all', factsMode: 'current', facts: null, authMode: 'login', activeMemoryDefaults: {}, plainEnglishGuardrails: null, billingInterval: 'month', planCatalog: null };
476
+ var currentTool = null;
477
+ var externalApiKey = readToken();
478
+ var latestSpawnedKey = '';
479
+ var currentWorkspaceId = '';
480
+ var activeWorkspaceOverrideId = '';
481
+ var pendingModalPlan = null;
482
+ var pendingModalHandoff = null;
483
+ var toastTimer = null;
484
+ var catalogRequestId = 0;
485
+ var authConfig = { provider: 'password', clerk: null };
486
+ var clerk = null;
487
+ var clerkMounted = '';
488
+
489
+ function readToken() { try { return localStorage.getItem(TOKEN_KEY) || ''; } catch (e) { return ''; } }
490
+ function writeToken(v) { try { if (v) localStorage.setItem(TOKEN_KEY, v); else localStorage.removeItem(TOKEN_KEY); } catch (e) {} }
491
+ function byId(id) { return document.getElementById(id); }
492
+ function setText(id, v) { var el = byId(id); if (el) el.textContent = v == null ? '' : String(v); }
493
+ function esc(v) { return String(v == null ? '' : v).replace(/[&<>"']/g, function (c) { return ESC[c]; }); }
494
+ function fmt(n) { return Number(n || 0).toLocaleString(); }
495
+ function json(v) { return JSON.stringify(v, null, 2); }
496
+ function pill(label, tone, dot) { return '<span class="pill' + (dot ? ' dot' : '') + '"' + (tone ? ' data-tone="' + tone + '"' : '') + '>' + esc(label) + '</span>'; }
497
+ function emptyRow(cols, msg) { return '<tr><td class="empty" colspan="' + cols + '">' + esc(msg) + '</td></tr>'; }
498
+ function shortId(id) { id = String(id || ''); return id.length > 20 ? id.slice(0, 20) + '…' : id; }
499
+ function timeAgo(iso) {
500
+ if (!iso) return '';
501
+ var d = new Date(iso);
502
+ var s = Math.round((Date.now() - d.getTime()) / 1000);
503
+ if (!isFinite(s)) return '';
504
+ if (s < 45) return 'Just now';
505
+ if (s < 3600) return Math.max(1, Math.floor(s / 60)) + ' min ago';
506
+ if (s < 86400) return Math.floor(s / 3600) + ' hr ago';
507
+ return d.toLocaleDateString();
508
+ }
509
+ function laneBadge(lane) { return pill(LANE_LABELS[lane] || lane || 'Tool'); }
510
+ function riskBadge(risk) {
511
+ var tone = risk === 'read' ? '' : risk === 'write' ? 'accent' : risk === 'destructive' ? 'danger' : 'warn';
512
+ return pill(risk || 'read', tone);
513
+ }
514
+ function statusBadge(status) {
515
+ var s = String(status || 'unknown');
516
+ var tone = /^(active|succeeded|approved|auto_approved|completed|executed|guardrail_applied|memory_saved)$/.test(s) ? 'ok'
517
+ : /(fail|error|denied|rejected|blocked|revoked|expired|unknown|unverified)/.test(s) ? 'danger'
518
+ : /(pending|initiated|waiting|awaiting)/.test(s) ? 'warn' : '';
519
+ return pill(STATUS_LABELS[s] || s.replace(/_/g, ' '), tone, true);
520
+ }
521
+
522
+ function showToast(msg, tone) {
523
+ var t = byId('toast');
524
+ if (!t) return;
525
+ t.textContent = msg;
526
+ if (tone) t.dataset.tone = tone; else delete t.dataset.tone;
527
+ t.classList.add('show');
528
+ clearTimeout(toastTimer);
529
+ toastTimer = setTimeout(function () { t.classList.remove('show'); }, 4200);
530
+ }
531
+ function showError(err) {
532
+ console.error(err);
533
+ showToast((err && err.message) || String(err), 'danger');
534
+ }
535
+ async function withBusy(el, fn) {
536
+ if (el && el.getAttribute('aria-busy') === 'true') return;
537
+ if (el) el.setAttribute('aria-busy', 'true');
538
+ try { return await fn(); } finally { if (el) el.removeAttribute('aria-busy'); }
539
+ }
540
+
541
+ function applyTheme(theme) {
542
+ document.documentElement.dataset.theme = theme;
543
+ var meta = document.querySelector('meta[name="theme-color"]');
544
+ if (meta) meta.setAttribute('content', theme === 'dark' ? THEME_BG.dark : THEME_BG.light);
545
+ var b = byId('theme-toggle');
546
+ if (b) {
547
+ b.setAttribute('aria-pressed', String(theme === 'dark'));
548
+ b.setAttribute('aria-label', theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode');
549
+ }
550
+ if (clerk && clerkMounted && (state.view === 'login' || state.view === 'signup')) {
551
+ var prev = clerkMounted;
552
+ unmountClerkAuth();
553
+ mountClerkAuth(prev);
554
+ }
555
+ }
556
+ function toggleTheme() {
557
+ var next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
558
+ applyTheme(next);
559
+ try { localStorage.setItem(THEME_KEY, next); } catch (e) {}
560
+ }
561
+
562
+ function openDialog(id) { var d = byId(id); if (d && !d.open) { if (d.showModal) d.showModal(); else d.setAttribute('open', ''); } }
563
+ function closeDialog(id) { var d = byId(id); if (d && d.open) { if (d.close) d.close(); else d.removeAttribute('open'); } }
564
+ function openMobileMenu() {
565
+ openDialog('mobile-menu');
566
+ byId('mobile-menu-toggle').setAttribute('aria-expanded', 'true');
567
+ }
568
+ function askInput(o) {
569
+ return new Promise(function (resolve) {
570
+ var d = byId('ask-dialog');
571
+ var form = byId('ask-form');
572
+ var input = byId('ask-input');
573
+ var wantsInput = o.input !== false;
574
+ var settled = false;
575
+ function finish(v) {
576
+ if (settled) return;
577
+ settled = true;
578
+ form.onsubmit = null;
579
+ d.removeEventListener('close', onClose);
580
+ closeDialog('ask-dialog');
581
+ resolve(v);
582
+ }
583
+ function onClose() { finish(null); }
584
+ setText('ask-title', o.title || 'Continue');
585
+ setText('ask-message', o.message || '');
586
+ byId('ask-message').hidden = !o.message;
587
+ byId('ask-field').hidden = !wantsInput;
588
+ setText('ask-label', o.label || '');
589
+ input.type = o.type || 'text';
590
+ input.value = o.value || '';
591
+ input.placeholder = o.placeholder || '';
592
+ var confirmBtn = byId('ask-confirm');
593
+ confirmBtn.textContent = o.confirm || 'Continue';
594
+ confirmBtn.className = 'btn ' + (o.danger ? 'btn-danger' : 'btn-primary');
595
+ form.onsubmit = function (e) {
596
+ e.preventDefault();
597
+ if (!wantsInput) return finish(true);
598
+ var v = input.value.trim();
599
+ if (!v) { input.focus(); return; }
600
+ finish(v);
601
+ };
602
+ d.addEventListener('close', onClose);
603
+ openDialog('ask-dialog');
604
+ if (wantsInput) setTimeout(function () { input.focus(); input.select(); }, 30);
605
+ });
606
+ }
607
+ function confirmAction(o) { return askInput(Object.assign({ input: false }, o)); }
608
+ function showDetail(title, sub, data, summaryHtml) {
609
+ setText('detail-title', title);
610
+ setText('detail-sub', sub || '');
611
+ var summary = byId('detail-summary');
612
+ summary.innerHTML = summaryHtml || '';
613
+ summary.hidden = !summaryHtml;
614
+ var clean = typeof data === 'string' ? data : json(sanitizeForDisplay(data));
615
+ setText('detail-body', clean);
616
+ openDialog('detail-dialog');
617
+ }
618
+ async function copyFrom(id) {
619
+ var el = byId(id);
620
+ if (!el) return;
621
+ var text = el.dataset.copyText || el.textContent || '';
622
+ try {
623
+ await navigator.clipboard.writeText(text);
624
+ showToast('Copied to clipboard');
625
+ } catch (e) {
626
+ var range = document.createRange();
627
+ range.selectNodeContents(el);
628
+ var sel = window.getSelection();
629
+ sel.removeAllRanges();
630
+ sel.addRange(range);
631
+ showToast('Selected. Press Cmd+C to copy.');
632
+ }
633
+ }
634
+
635
+ function viewForPath(p) {
636
+ var clean = p.length > 1 ? p.replace(/\/+$/, '') : p;
637
+ return ROUTES[clean] || 'overview';
638
+ }
639
+ function navigate(view, opts) {
640
+ opts = opts || {};
641
+ if (!VIEW_PATHS[view]) view = 'overview';
642
+ var authView = view === 'login' || view === 'signup';
643
+ closeDialog('mobile-menu');
644
+ byId('mobile-menu-toggle').hidden = authView;
645
+ state.view = view;
646
+ byId('console-shell').hidden = authView;
647
+ byId('auth-view').hidden = !authView;
648
+ document.querySelectorAll('[data-view]').forEach(function (el) { el.hidden = el.dataset.view !== view; });
649
+ document.querySelectorAll('.nav-item[data-nav]').forEach(function (a) {
650
+ if (a.dataset.nav === view) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current');
651
+ });
652
+ if (authView) setAuthMode(view);
653
+ var path = VIEW_PATHS[view];
654
+ if (opts.push !== false && location.pathname !== path) history.pushState({ view: view }, '', path);
655
+ document.title = VIEW_TITLES[view] + ' · Muxo';
656
+ if (!opts.skipLoad) loadView(view);
657
+ if (opts.focus !== false) {
658
+ window.scrollTo(0, 0);
659
+ var target = authView ? byId('auth-email') : byId('main');
660
+ if (target) target.focus({ preventScroll: true });
661
+ }
662
+ }
663
+ function loadView(view) {
664
+ var job = null;
665
+ if (view === 'overview' || view === 'setup' || view === 'workspaces') job = refreshOverview();
666
+ else if (view === 'catalog') job = loadCatalog();
667
+ else if (view === 'activity') job = Promise.all([loadRuns(), loadPlans()]);
668
+ else if (view === 'billing') job = loadLedger();
669
+ if (job && job.catch) job.catch(showError);
670
+ }
671
+ function switchTab(tabId) { navigate(tabId === 'receipts' ? 'activity' : tabId); }
672
+
673
+ async function api(path, opts) {
674
+ opts = opts || {};
675
+ var headers = Object.assign({ 'Content-Type': 'application/json', Accept: 'application/json' }, opts.headers || {});
676
+ if (activeWorkspaceOverrideId && !headers['X-Muxo-Workspace-Id']) {
677
+ headers['X-Muxo-Workspace-Id'] = activeWorkspaceOverrideId;
678
+ }
679
+ if (!opts.anonymous && !headers.Authorization) {
680
+ if (externalApiKey) headers.Authorization = 'Bearer ' + externalApiKey;
681
+ else if (clerkSignedIn()) {
682
+ var sessionToken = await clerk.session.getToken();
683
+ if (sessionToken) headers.Authorization = 'Bearer ' + sessionToken;
684
+ }
685
+ }
686
+ var res = await fetch(path, { method: opts.method || 'GET', headers: headers, body: opts.body });
687
+ var data;
688
+ try { data = await res.json(); } catch (e) { data = { ok: false, error: 'Unexpected response from Muxo (HTTP ' + res.status + ')' }; }
689
+ if (!res.ok || data.ok === false) {
690
+ var err = new Error(data.error || data.message || ('HTTP ' + res.status));
691
+ err.status = res.status;
692
+ err.data = data;
693
+ if (res.status === 401 && !opts.anonymous) handleUnauthorized();
694
+ throw err;
695
+ }
696
+ return data;
697
+ }
698
+ function post(path, body, opts) { return api(path, Object.assign({}, opts || {}, { method: 'POST', body: JSON.stringify(body || {}) })); }
699
+
700
+ function clerkSignedIn() { return Boolean(clerk && clerk.session && clerk.user); }
701
+ function isSignedIn() { return Boolean(externalApiKey) || clerkSignedIn(); }
702
+ function handleUnauthorized() {
703
+ if (externalApiKey) {
704
+ externalApiKey = '';
705
+ writeToken('');
706
+ showToast('That key no longer works. Sign in again.', 'danger');
707
+ }
708
+ if (!clerkSignedIn() && state.view !== 'login' && state.view !== 'signup') navigate('login', { focus: false });
709
+ }
710
+ function clerkAppearance() {
711
+ var cs = getComputedStyle(document.documentElement);
712
+ function v(name) { return cs.getPropertyValue(name).trim(); }
713
+ return {
714
+ variables: {
715
+ colorPrimary: v('--color-button-primary'), colorText: v('--color-text'), colorTextSecondary: v('--color-muted'),
716
+ colorBackground: v('--color-surface'), colorInputBackground: v('--color-bg'), colorInputText: v('--color-text'),
717
+ colorDanger: v('--color-danger'), borderRadius: '12px', fontFamily: v('--font-sans'), fontSize: '15px'
718
+ },
719
+ elements: {
720
+ rootBox: { width: '100%' }, cardBox: { width: '100%', boxShadow: 'none', border: '0' },
721
+ card: { boxShadow: 'none', background: 'transparent', padding: '4px 0 0' },
722
+ header: { display: 'none' }, footer: { background: 'transparent' }
723
+ }
724
+ };
725
+ }
726
+ function loadClerkScript(cfg) {
727
+ return new Promise(function (resolve, reject) {
728
+ if (window.Clerk) { resolve(window.Clerk); return; }
729
+ if (!/^https:\/\/[a-z0-9.-]+$/i.test(cfg.frontendApi || '')) { reject(new Error('Sign-in is misconfigured on this server.')); return; }
730
+ var s = document.createElement('script');
731
+ s.src = cfg.frontendApi + '/npm/@clerk/clerk-js@5/dist/clerk.browser.js';
732
+ s.async = true;
733
+ s.crossOrigin = 'anonymous';
734
+ s.setAttribute('data-clerk-publishable-key', cfg.publishableKey);
735
+ var timer = setTimeout(function () { reject(new Error('Sign-in took too long to load. Check your connection and reload.')); }, 15000);
736
+ s.onload = function () { clearTimeout(timer); if (window.Clerk) resolve(window.Clerk); else reject(new Error('Sign-in could not load.')); };
737
+ s.onerror = function () { clearTimeout(timer); reject(new Error('Sign-in could not load. Check your connection and reload.')); };
738
+ document.head.appendChild(s);
739
+ });
740
+ }
741
+ async function loadAuth() {
742
+ try { authConfig = await api('/api/v1/auth/config', { anonymous: true }); } catch (e) { authConfig = { provider: 'password', clerk: null }; }
743
+ if (authConfig.provider !== 'clerk' || !authConfig.clerk) return;
744
+ byId('auth-form').hidden = true;
745
+ byId('auth-tabs').hidden = true;
746
+ byId('clerk-auth').hidden = false;
747
+ setAuthMode(state.view === 'signup' ? 'signup' : 'login');
748
+ try {
749
+ clerk = await loadClerkScript(authConfig.clerk);
750
+ await clerk.load({ appearance: clerkAppearance() });
751
+ var lastSession = clerk.session ? clerk.session.id : '';
752
+ clerk.addListener(function (e) {
753
+ var id = e && e.session ? e.session.id : '';
754
+ if (id === lastSession) return;
755
+ lastSession = id;
756
+ onClerkSessionChange();
757
+ });
758
+ } catch (err) {
759
+ clerk = null;
760
+ removeClerkLoading();
761
+ setText('auth-error', err.message);
762
+ }
763
+ }
764
+ function removeClerkLoading() {
765
+ var loading = byId('clerk-loading');
766
+ if (loading) loading.remove();
767
+ }
768
+ function mountClerkAuth(view) {
769
+ if (!clerk || !clerk.loaded || clerkSignedIn()) return;
770
+ var key = view === 'signup' ? 'signup' : 'login';
771
+ if (clerkMounted === key) return;
772
+ unmountClerkAuth();
773
+ removeClerkLoading();
774
+ var target = location.origin + '/';
775
+ var opts = { routing: 'hash', forceRedirectUrl: target, signInForceRedirectUrl: target, signUpForceRedirectUrl: target, appearance: clerkAppearance() };
776
+ if (key === 'signup') clerk.mountSignUp(byId('clerk-auth'), opts); else clerk.mountSignIn(byId('clerk-auth'), opts);
777
+ clerkMounted = key;
778
+ }
779
+ function unmountClerkAuth() {
780
+ if (!clerk || !clerkMounted) return;
781
+ try { if (clerkMounted === 'signup') clerk.unmountSignUp(byId('clerk-auth')); else clerk.unmountSignIn(byId('clerk-auth')); } catch (e) {}
782
+ clerkMounted = '';
783
+ }
784
+ function onClerkSessionChange() {
785
+ if (clerkSignedIn()) {
786
+ unmountClerkAuth();
787
+ if (state.view === 'login' || state.view === 'signup') navigate('overview');
788
+ else { refreshOverview().catch(showError); loadView(state.view); }
789
+ } else if (!externalApiKey) {
790
+ state.boot = null;
791
+ navigate('login', { focus: false });
792
+ }
793
+ }
794
+
795
+ function renderWorkspaceList(workspaces) {
796
+ var list = workspaces || (state.boot && state.boot.workspaces) || [];
797
+ var html = list.length ? list.map(function (w) {
798
+ var active = Boolean(w.isCurrent) || (w.id && w.id === currentWorkspaceId);
799
+ var badges = [
800
+ pill(String(w.role || 'member'), w.role === 'owner' || w.role === 'admin' ? 'accent' : ''),
801
+ w.teamName ? pill(w.teamName, 'ok') : '',
802
+ pill(fmt(w.creditsBalance != null ? w.creditsBalance : 0) + ' credits')
803
+ ].filter(Boolean).join(' ');
804
+ var actionHtml = active
805
+ ? pill('Active', 'ok', true)
806
+ : '<button class="btn btn-sm btn-primary" type="button" data-action="select-workspace" data-id="' + esc(w.id) + '">Switch</button>';
807
+ return '<div class="row"><div><strong>' + esc(w.name) + '</strong><small>' + esc(w.slug) + ' · ' + esc(String(w.tier || 'free')) + ' plan</small><div style="margin-top:4px">' + badges + '</div></div><span class="row-actions">' + actionHtml + '</span></div>';
808
+ }).join('') : '<p class="empty-note">Sign in or paste a workspace API key below to link a workspace.</p>';
809
+ var dialogBox = byId('workspace-dialog-list');
810
+ if (dialogBox) dialogBox.innerHTML = html;
811
+ var setupBox = byId('workspaces-list');
812
+ if (setupBox) setupBox.innerHTML = html;
813
+ }
814
+ async function switchExternalWorkspacePrompt() {
815
+ renderWorkspaceList();
816
+ openDialog('workspace-dialog');
817
+ }
818
+ async function openWorkspaceForm(kind) {
819
+ await switchExternalWorkspacePrompt();
820
+ var input = byId(kind === 'create' ? 'create-workspace-name-input' : 'join-workspace-key-input');
821
+ if (input) input.focus();
822
+ }
823
+ async function selectWorkspaceFromUi(workspaceId) {
824
+ if (!workspaceId) return;
825
+ activeWorkspaceOverrideId = workspaceId;
826
+ if (externalApiKey && externalApiKey.indexOf('mxs_') !== 0) {
827
+ externalApiKey = '';
828
+ writeToken('');
829
+ }
830
+ var res = await post('/api/v1/workspaces/switch', { workspaceId: workspaceId });
831
+ if (res.workspaces) renderWorkspaceList(res.workspaces);
832
+ closeDialog('workspace-dialog');
833
+ await refreshOverview();
834
+ showToast('Switched to ' + ((res.workspace && res.workspace.name) || workspaceId));
835
+ if (state.view === 'login' || state.view === 'signup') navigate('overview');
836
+ else loadView(state.view);
837
+ }
838
+ async function joinWorkspaceFromForm() {
839
+ var input = byId('join-workspace-key-input');
840
+ var val = (input && input.value ? input.value : '').trim();
841
+ if (!val) { showToast('Paste a workspace or team API key (mk_live_...)'); if (input) input.focus(); return; }
842
+ if (val.toLowerCase() === 'new') {
843
+ var signupRes = await post('/api/v1/agents/signup', { agentName: 'Console User', harness: 'console-ui' }, { anonymous: true });
844
+ if (!signupRes.apiKey) throw new Error('Signup did not return an API key.');
845
+ if (input) input.value = '';
846
+ closeDialog('workspace-dialog');
847
+ await setExternalWorkspaceKey(signupRes.apiKey, 'Opened new workspace ' + ((signupRes.workspace && signupRes.workspace.slug) || ''));
848
+ return;
849
+ }
850
+ var res = await post('/api/v1/workspaces/join', { apiKey: val });
851
+ if (input) input.value = '';
852
+ closeDialog('workspace-dialog');
853
+ activeWorkspaceOverrideId = (res.workspace && res.workspace.id) || '';
854
+ if (!clerkSignedIn()) {
855
+ externalApiKey = res.sessionToken || val;
856
+ writeToken(externalApiKey);
857
+ } else if (externalApiKey && externalApiKey.indexOf('mxs_') !== 0) {
858
+ externalApiKey = '';
859
+ writeToken('');
860
+ }
861
+ await refreshOverview();
862
+ showToast(res.message || ('Joined ' + ((res.workspace && res.workspace.name) || 'workspace')));
863
+ if (state.view === 'login' || state.view === 'signup') navigate('overview');
864
+ else loadView(state.view);
865
+ }
866
+ async function createWorkspaceFromForm() {
867
+ var nameInput = byId('create-workspace-name-input');
868
+ var teamInput = byId('create-workspace-team-input');
869
+ var name = (nameInput && nameInput.value ? nameInput.value : '').trim();
870
+ var teamName = (teamInput && teamInput.value ? teamInput.value : '').trim();
871
+ if (!name) { showToast('Enter a name for the new workspace'); if (nameInput) nameInput.focus(); return; }
872
+ var res;
873
+ if (isSignedIn()) {
874
+ res = await post('/api/v1/workspaces', { name: name, teamName: teamName || undefined });
875
+ activeWorkspaceOverrideId = (res.workspace && res.workspace.id) || '';
876
+ } else {
877
+ res = await post('/api/v1/agents/signup', { agentName: name, workspaceName: name, harness: 'console-ui' }, { anonymous: true });
878
+ if (res.apiKey) {
879
+ externalApiKey = res.apiKey;
880
+ writeToken(res.apiKey);
881
+ }
882
+ }
883
+ if (nameInput) nameInput.value = '';
884
+ if (teamInput) teamInput.value = '';
885
+ closeDialog('workspace-dialog');
886
+ await refreshOverview();
887
+ if (res.apiKey) {
888
+ showNewKey(res.apiKey, 'Created ' + ((res.workspace && res.workspace.name) || name) + '. Copy this key now; it will not be shown again.', false);
889
+ }
890
+ showToast('Created workspace ' + ((res.workspace && res.workspace.name) || name));
891
+ }
892
+ async function onboardTeamFromForm() {
893
+ var teamInput = byId('team-onboard-name');
894
+ var modeSelect = byId('team-onboard-mode');
895
+ var roleSelect = byId('team-onboard-role');
896
+ var teamName = (teamInput && teamInput.value ? teamInput.value : '').trim();
897
+ if (!teamName) { showToast('Enter the team name to onboard (for example, Platform Engineering)'); if (teamInput) teamInput.focus(); return; }
898
+ var mode = modeSelect ? modeSelect.value : 'shared_workspace';
899
+ var role = roleSelect ? roleSelect.value : 'operator';
900
+ var res = await post('/api/v1/workspaces/onboard-team', { teamName: teamName, mode: mode, role: role });
901
+ var kit = res.kit || {};
902
+ var box = byId('team-kit-box');
903
+ if (box) {
904
+ box.hidden = false;
905
+ setText('team-kit-title', (kit.teamName || teamName) + ' is ready');
906
+ setText('team-kit-workspace', (kit.workspace && kit.workspace.name) || (res.workspace && res.workspace.name) || '');
907
+ setText('team-kit-key', kit.apiKey || '');
908
+ setText('team-kit-cli', kit.cliSetupCommand || '');
909
+ setText('team-kit-instructions', kit.instructions || '');
910
+ }
911
+ if (teamInput) teamInput.value = '';
912
+ showToast('Created team access for ' + teamName);
913
+ await refreshOverview();
914
+ }
915
+ async function setExternalWorkspaceKey(key, toastMsg) {
916
+ try {
917
+ var joined = await post('/api/v1/workspaces/join', { apiKey: key });
918
+ activeWorkspaceOverrideId = (joined.workspace && joined.workspace.id) || '';
919
+ if (!clerkSignedIn()) {
920
+ externalApiKey = joined.sessionToken || key;
921
+ writeToken(externalApiKey);
922
+ }
923
+ } catch (e) {
924
+ externalApiKey = key;
925
+ writeToken(key);
926
+ }
927
+ await refreshOverview();
928
+ if (toastMsg) showToast(toastMsg);
929
+ if (state.view === 'login' || state.view === 'signup') navigate('overview');
930
+ else loadView(state.view);
931
+ }
932
+ async function signOut() {
933
+ if (externalApiKey) {
934
+ externalApiKey = '';
935
+ writeToken('');
936
+ if (authConfig.provider === 'clerk' && !clerkSignedIn()) { showToast('Signed out'); navigate('login'); return; }
937
+ showToast(clerkSignedIn() ? 'Key removed. Showing your workspace.' : 'Signed out');
938
+ await refreshOverview();
939
+ loadView(state.view);
940
+ return;
941
+ }
942
+ if (clerkSignedIn()) {
943
+ await clerk.signOut({ redirectUrl: location.origin + '/login' });
944
+ return;
945
+ }
946
+ navigate('login');
947
+ }
948
+ async function useLatestSpawnedKey() {
949
+ if (!latestSpawnedKey) return;
950
+ await setExternalWorkspaceKey(latestSpawnedKey, 'Opened the new agent workspace');
951
+ }
952
+ async function createClaimLinkPrompt() {
953
+ var email = await askInput({
954
+ title: 'Create an owner claim link',
955
+ message: 'Send this link to the person who should own the workspace. They can approve risky actions and manage billing.',
956
+ label: 'Owner email',
957
+ type: 'email',
958
+ placeholder: 'name@company.com',
959
+ confirm: 'Create link'
960
+ });
961
+ if (!email) return;
962
+ var forSpawned = latestSpawnedKey && !byId('use-spawned-key-btn').hidden;
963
+ var res = await post('/api/v1/agents/claim', { email: email }, forSpawned ? { headers: { Authorization: 'Bearer ' + latestSpawnedKey } } : undefined);
964
+ byId('new-key-banner').hidden = false;
965
+ setText('new-key-title', 'Claim link for ' + email);
966
+ setText('new-key-value', res.claimUrl || (res.claim && res.claim.claimUrl) || '');
967
+ byId('use-spawned-key-btn').hidden = true;
968
+ showToast('Claim link created');
969
+ }
970
+
971
+ function setAuthMode(view) {
972
+ var signup = view === 'signup';
973
+ state.authMode = signup ? 'signup' : 'login';
974
+ setText('auth-title', signup ? 'Create your Muxo account' : 'Sign in to Muxo');
975
+ setText('auth-lede', signup ? 'Get a workspace with 250 starter credits. No card required.' : 'Manage tools, approvals, memory, and billing for your agents.');
976
+ if (authConfig.provider === 'clerk') {
977
+ var authLink = byId('auth-switch-link');
978
+ authLink.hidden = false;
979
+ authLink.textContent = signup ? 'Already have an account? Sign in' : 'New to Muxo? Create an account';
980
+ authLink.dataset.mode = signup ? 'login' : 'signup';
981
+ mountClerkAuth(view);
982
+ return;
983
+ }
984
+ byId('auth-name-field').hidden = !signup;
985
+ byId('auth-workspace-field').hidden = !signup;
986
+ setText('auth-submit', signup ? 'Create account' : 'Sign in');
987
+ byId('auth-password').setAttribute('autocomplete', signup ? 'new-password' : 'current-password');
988
+ setText('auth-error', '');
989
+ document.querySelectorAll('#auth-tabs button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.mode === state.authMode)); });
990
+ }
991
+ async function submitAuth() {
992
+ var signup = state.authMode === 'signup';
993
+ var email = byId('auth-email').value.trim();
994
+ var password = byId('auth-password').value;
995
+ setText('auth-error', '');
996
+ if (!email || email.indexOf('@') < 1) { setText('auth-error', 'Enter a valid email address.'); byId('auth-email').focus(); return; }
997
+ if (password.length < 6) { setText('auth-error', 'Use a password with at least 6 characters.'); byId('auth-password').focus(); return; }
998
+ try {
999
+ var res = signup
1000
+ ? await post('/api/v1/auth/register', { name: byId('auth-name').value.trim() || email.split('@')[0], email: email, password: password, workspaceName: byId('auth-workspace').value.trim() || undefined }, { anonymous: true })
1001
+ : await post('/api/v1/auth/login', { email: email, password: password }, { anonymous: true });
1002
+ var token = res.sessionToken || res.apiKey;
1003
+ if (!token) throw new Error('Muxo did not return a session. Try again.');
1004
+ externalApiKey = token;
1005
+ writeToken(token);
1006
+ byId('auth-password').value = '';
1007
+ await refreshOverview();
1008
+ navigate('overview');
1009
+ showToast(signup ? 'Account created. Welcome to Muxo.' : 'Signed in');
1010
+ if (signup && res.apiKey) {
1011
+ latestSpawnedKey = '';
1012
+ showNewKey(res.apiKey, 'Your first API key. Copy it now; it will not be shown again.', false);
1013
+ }
1014
+ } catch (err) {
1015
+ setText('auth-error', err.message);
1016
+ }
1017
+ }
1018
+
1019
+ function renderAccount(s) {
1020
+ var user = (s && s.profile && s.profile.user) || {};
1021
+ var signedIn = isSignedIn();
1022
+ var usingKey = Boolean(externalApiKey) && externalApiKey.indexOf('mxs_') !== 0;
1023
+ byId('account-signin').hidden = signedIn;
1024
+ byId('account-signed-in').hidden = !signedIn;
1025
+ setText('account-label', user.email || user.name || 'Signed in');
1026
+ setText('mobile-account-label', user.email || user.name || 'Signed in');
1027
+ byId('mobile-account-actions').hidden = !signedIn;
1028
+ var badge = byId('workspace-auth-badge');
1029
+ if (badge) {
1030
+ badge.textContent = usingKey ? 'API key ' + externalApiKey.slice(0, 12) + '…' : signedIn ? 'Signed in' : 'Local workspace';
1031
+ if (signedIn) badge.dataset.tone = 'accent'; else delete badge.dataset.tone;
1032
+ }
1033
+ byId('reset-workspace-row').hidden = !usingKey;
1034
+ }
1035
+ function syncPolicyForm(policy) {
1036
+ if (!policy) return;
1037
+ if (byId('policy-mode')) byId('policy-mode').value = policy.mode || 'enforce';
1038
+ var req = {};
1039
+ (policy.requireApprovalFor || []).forEach(function (r) { req[r] = true; });
1040
+ document.querySelectorAll('#policy-risks input[type=checkbox]').forEach(function (cb) { cb.checked = Boolean(req[cb.value]); });
1041
+ if (byId('policy-ring')) byId('policy-ring').value = String(policy.executionRing != null ? policy.executionRing : 2);
1042
+ if (byId('policy-block-sql')) byId('policy-block-sql').checked = policy.blockDestructiveSql !== false;
1043
+ }
1044
+ function renderIdentity() {}
1045
+ function renderApiKeys(keys) {
1046
+ byId('api-keys-list').innerHTML = keys.length ? keys.map(function (k) {
1047
+ var revoked = Boolean(k.revokedAt);
1048
+ var metaBadges = [
1049
+ k.teamName ? pill('Team: ' + k.teamName, 'accent') : '',
1050
+ k.role ? pill(k.role) : '',
1051
+ pill(k.environment === 'test' ? 'Test' : 'Live', k.environment === 'test' ? 'warn' : 'ok')
1052
+ ].filter(Boolean).join(' ');
1053
+ return '<div class="row api-key-row"><div><strong>' + esc(k.name) + '</strong><small class="mono">' + esc(k.keyPrefix) + '</small></div>' +
1054
+ (revoked ? pill('Revoked', 'danger') : '<span class="row-actions">' + metaBadges + ' ' +
1055
+ '<button class="btn btn-sm" type="button" data-action="revoke-key" data-id="' + esc(k.id) + '" data-name="' + esc(k.name) + '">Revoke</button></span>') + '</div>';
1056
+ }).join('') : '<p class="empty-note">No API keys yet. Create one to connect an agent or app.</p>';
1057
+ }
1058
+ function renderRecentRuns(runs) {
1059
+ byId('recent-runs').innerHTML = runs.length ? runs.slice(0, 6).map(function (r) {
1060
+ var meta = [LANE_LABELS[r.lane] || '', timeAgo(r.createdAt || r.startedAt), r.durationMs != null ? r.durationMs + ' ms' : ''].filter(Boolean).join(' · ');
1061
+ var failed = /(fail|error|denied|rejected|blocked)/.test(r.status || '');
1062
+ var completed = /^(succeeded|completed|executed)$/.test(r.status || '');
1063
+ var tone = failed ? 'danger' : completed ? 'ok' : 'warn';
1064
+ var icon = failed ? '${uiIcon("close")}' : completed ? '${uiIcon("check")}' : '${uiIcon("activity")}';
1065
+ return '<div class="activity-row"><span class="action-icon" data-tone="' + tone + '">' + icon + '</span><div><strong>' + esc(formatToolLabel(r.toolSlug)) + '</strong><small>' + esc(meta) + '</small></div>' + statusBadge(r.status) +
1066
+ (r.receiptId ? '<button class="icon-button" type="button" data-action="inspect-receipt" data-id="' + esc(r.receiptId) + '" aria-label="Inspect audit record for ' + esc(formatToolLabel(r.toolSlug)) + '">${uiIcon("arrow")}</button>' : '') + '</div>';
1067
+ }).join('') : '<div class="activity-empty"><span class="action-icon">${uiIcon("shield")}</span><h3>No actions yet</h3><p>Choose a tool to get started. Every action leaves an audit trail.</p><a class="btn" href="/catalog" data-nav="catalog">Explore tools ${uiIcon("arrow")}</a></div>';
1068
+ var seen = {};
1069
+ var recentTools = runs.filter(function (r) {
1070
+ if (!r.toolSlug || seen[r.toolSlug]) return false;
1071
+ seen[r.toolSlug] = true;
1072
+ return true;
1073
+ }).slice(0, 4);
1074
+ byId('recent-tools').innerHTML = recentTools.length ? recentTools.map(function (r) {
1075
+ return '<a class="tool-row" href="/catalog" data-action="run-tool" data-slug="' + esc(r.toolSlug) + '"><div><strong>' + esc(formatToolLabel(r.toolSlug)) + '</strong><small>' + esc(LANE_LABELS[r.lane] || 'Tool') + '</small></div>${uiIcon("chevron")}</a>';
1076
+ }).join('') : '<a class="tool-row" href="/catalog" data-action="browse-lane" data-lane="composio_instant"><div><strong>Search &amp; research</strong><small>Web, scraping, and data</small></div>${uiIcon("chevron")}</a><a class="tool-row" href="/catalog" data-action="browse-lane" data-lane="composio_sdk"><div><strong>Connected apps</strong><small>Tools for your team’s apps</small></div>${uiIcon("chevron")}</a><a class="tool-row" href="/catalog" data-action="browse-lane" data-lane="stripe_projects"><div><strong>Cloud services</strong><small>Provision and manage resources</small></div>${uiIcon("chevron")}</a>';
1077
+ setText('recent-tools-title', recentTools.length ? 'Recently used tools' : 'Explore tools');
1078
+ }
1079
+ function countActive(list) { return (list || []).filter(function (x) { return x.status === 'active'; }).length; }
1080
+ function setCount(id, n) { setText(id, n ? fmt(n) : ''); }
1081
+
1082
+ async function refreshOverview() {
1083
+ var s = await api('/api/v1/bootstrap');
1084
+ state.boot = s;
1085
+ var org = s.organization || {};
1086
+ var usage = org.usage || {};
1087
+ var credits = usage.creditsBalance != null ? usage.creditsBalance : (org.creditsBalance || 0);
1088
+ var used = usage.monthlyActionsUsed != null ? usage.monthlyActionsUsed : (org.monthlyActionsUsed || 0);
1089
+ var limit = usage.monthlyActionsLimit != null ? usage.monthlyActionsLimit : (org.monthlyActionsLimit || 1000);
1090
+ var plans = s.plans || [];
1091
+ var runs = s.runs || [];
1092
+ var activeConns = countActive(s.connections);
1093
+ var activeRes = countActive(s.resources);
1094
+ var pending = plans.filter(function (p) { return p.approvalStatus === 'pending_approval'; }).length;
1095
+ var tier = String(org.tier || 'free');
1096
+ var tierLabel = tier.charAt(0).toUpperCase() + tier.slice(1) + ' plan';
1097
+ if (currentWorkspaceId && currentWorkspaceId !== org.id) {
1098
+ byId('new-key-banner').hidden = true;
1099
+ setText('new-key-value', '');
1100
+ byId('team-kit-box').hidden = true;
1101
+ ['team-kit-key', 'team-kit-cli', 'team-kit-instructions', 'team-kit-workspace'].forEach(function (id) { setText(id, ''); });
1102
+ }
1103
+ currentWorkspaceId = org.id || '';
1104
+ setText('workspace-name', org.name || 'Workspace');
1105
+ setText('workspace-tier', tierLabel);
1106
+ setText('workspace-slug', org.name || org.slug || 'Primary workspace');
1107
+ setText('overview-workspace-name', org.name || 'Workspace');
1108
+ setText('overview-workspace-tier', tierLabel);
1109
+ setText('access-workspace-name', org.name || 'Workspace');
1110
+ setText('workspace-count', fmt((s.workspaces || []).length) + ((s.workspaces || []).length === 1 ? ' workspace' : ' workspaces'));
1111
+ setText('kpi-pending', fmt(pending));
1112
+ byId('kpi-pending').dataset.tone = pending ? 'warn' : '';
1113
+ setText('kpi-pending-sub', pending ? 'Ready for your review' : 'You’re all caught up');
1114
+ byId('approval-notice').hidden = !pending;
1115
+ setText('approval-notice-title', fmt(pending) + (pending === 1 ? ' action needs approval' : ' actions need approval'));
1116
+ setText('api-base-url', window.location.origin + '/api/v1');
1117
+ setText('api-mcp-url', window.location.origin + '/mcp');
1118
+ setText('kpi-credits', fmt(credits));
1119
+ setText('kpi-tier-badge', tierLabel);
1120
+ setText('kpi-actions', fmt(used));
1121
+ setText('kpi-actions-sub', 'of ' + fmt(limit) + ' included');
1122
+ setText('kpi-connections', fmt(activeConns + activeRes));
1123
+ setText('meter-actions-text', fmt(used) + ' / ' + fmt(limit));
1124
+ byId('meter-actions-bar').style.width = Math.min(100, Math.max(0, Math.round((used / Math.max(1, limit)) * 100))) + '%';
1125
+ setText('meter-credits-text', fmt(credits));
1126
+ setText('billing-plan', tierLabel);
1127
+ setText('billing-credits', fmt(credits));
1128
+ setCount('nav-res-count', activeRes);
1129
+ setCount('nav-conn-count', activeConns);
1130
+ setCount('nav-plan-count', pending);
1131
+ setCount('nav-run-count', runs.length);
1132
+ renderAccount(s);
1133
+ syncPolicyForm(org.policy);
1134
+ state.activeMemoryDefaults = s.activeMemoryDefaults || (s.supermemory && s.supermemory.activeDefaults) || {};
1135
+ state.plainEnglishGuardrails = s.plainEnglishGuardrails || null;
1136
+ renderPlainEnglishGuardrails(state.plainEnglishGuardrails, org.policy);
1137
+ renderActiveMemoryDefaults(state.activeMemoryDefaults, s.activeGuardrails || (s.supermemory && s.supermemory.activeGuardrails) || []);
1138
+ renderIdentity(s.governance);
1139
+ renderApiKeys(s.apiKeys || []);
1140
+ renderWorkspaceList(s.workspaces || []);
1141
+ renderRecentRuns(runs);
1142
+ return s;
1143
+ }
1144
+
1145
+ function showNewKey(key, title, canSwitch) {
1146
+ byId('new-key-banner').hidden = false;
1147
+ setText('new-key-title', title);
1148
+ setText('new-key-value', key);
1149
+ byId('use-spawned-key-btn').hidden = !canSwitch;
1150
+ if (state.view !== 'setup') navigate('setup');
1151
+ }
1152
+ async function spawnAutonomousAgent() {
1153
+ var res = await post('/api/v1/agents/signup', { agentName: 'Autonomous Agent', harness: 'console-ui' }, { anonymous: true });
1154
+ latestSpawnedKey = res.apiKey;
1155
+ var slug = (res.workspace && res.workspace.slug) || '';
1156
+ showNewKey(res.apiKey, 'New agent workspace ' + slug + '. Copy its key now; it will not be shown again.', true);
1157
+ showToast('Created agent workspace ' + slug);
1158
+ }
1159
+ async function generateApiKey() {
1160
+ var name = await askInput({ title: 'Create an API key', message: 'Name the key so you can recognize it later.', label: 'Key name', value: 'Agent key', confirm: 'Create key' });
1161
+ if (!name) return;
1162
+ var res = await post('/api/v1/api-keys', { name: name, environment: 'live' });
1163
+ showNewKey(res.secretKey || res.apiKey, 'Copy this key now. It will not be shown again.', false);
1164
+ showToast('API key created');
1165
+ await refreshOverview();
1166
+ }
1167
+
1168
+ async function revokeApiKey(id, name) {
1169
+ var ok = await confirmAction({ title: 'Revoke ' + name + '?', message: 'Agents and apps using this key lose access right away. This cannot be undone.', confirm: 'Revoke key', danger: true });
1170
+ if (!ok) return;
1171
+ await api('/api/v1/api-keys/' + encodeURIComponent(id), { method: 'DELETE' });
1172
+ showToast('Key revoked');
1173
+ await refreshOverview();
1174
+ }
1175
+
1176
+ async function planAndRun(toolSlug, input, reason) {
1177
+ var planned = await post('/api/v1/plans', { toolSlug: toolSlug, input: input || {}, reason: reason });
1178
+ if (planned.plan.approvalStatus === 'pending_approval') await post('/api/v1/plans/' + planned.plan.id + '/approve');
1179
+ var executed = await post('/api/v1/plans/' + planned.plan.id + '/execute');
1180
+ return { planned: planned, executed: executed };
1181
+ }
1182
+ function runSummary(planned, executed) {
1183
+ var d = planned.decisionRecord;
1184
+ var m = planned.memoryContext;
1185
+ return {
1186
+ runId: executed.run.id,
1187
+ tool: executed.run.toolSlug,
1188
+ status: executed.run.status,
1189
+ durationMs: executed.run.durationMs,
1190
+ safety: d ? { verdict: d.verdict, decisionId: d.id, checksPassed: (d.modulesEvaluated || []).length } : null,
1191
+ memory: m ? { recalledItems: (m.recalledMemories || []).length } : null,
1192
+ auditRecord: executed.receipt ? { id: executed.receipt.id, verified: true } : null,
1193
+ output: sanitizeForDisplay(executed.run.output)
1194
+ };
1195
+ }
1196
+ function renderQuickResult(opts) {
1197
+ var box = byId('quick-run-output');
1198
+ var rawJson = json(sanitizeForDisplay(opts.raw));
1199
+ box.dataset.copyText = rawJson;
1200
+ var kv = (opts.kv || []).filter(function (row) { return row && row[1] != null && row[1] !== ''; }).map(function (row) {
1201
+ return '<div class="result-kv"><span>' + esc(row[0]) + '</span><strong>' + esc(row[1]) + '</strong></div>';
1202
+ }).join('');
1203
+ box.innerHTML = '<div class="result-card">' +
1204
+ '<div class="result-head"><div><strong>' + esc(opts.title) + '</strong>' + (opts.subtitle ? '<div class="result-meta">' + esc(opts.subtitle) + '</div>' : '') + '</div>' + (opts.badgeHtml || '') + '</div>' +
1205
+ (kv ? '<div class="result-body">' + kv + '</div>' : '') +
1206
+ (opts.actionsHtml ? '<div class="result-actions" style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">' + opts.actionsHtml + '</div>' : '') +
1207
+ '<details class="recall-raw"><summary>Full output</summary><pre class="code">' + esc( json(sanitizeForDisplay(opts.output !== undefined ? opts.output : opts.raw)) ) + '</pre></details>' +
1208
+ '</div>';
1209
+ }
1210
+ async function quickRun(toolSlug, input) {
1211
+ if (state.view !== 'overview') navigate('overview');
1212
+ var box = byId('quick-run-output');
1213
+ var label = formatToolLabel(toolSlug);
1214
+ setText('quick-run-status', 'Running ' + label + '…');
1215
+ box.innerHTML = '<p class="empty-note">Checking policy and running ' + esc(label) + '…</p>';
1216
+ try {
1217
+ var r = await planAndRun(toolSlug, input, 'Console quick run');
1218
+ var summary = runSummary(r.planned, r.executed);
1219
+ renderQuickResult({
1220
+ title: label,
1221
+ subtitle: (r.executed.run.durationMs != null ? r.executed.run.durationMs + ' ms' : '') + (summary.auditRecord ? ' · Signed audit record' : ''),
1222
+ badgeHtml: statusBadge(r.executed.run.status),
1223
+ kv: [
1224
+ ['Policy check', summary.safety && summary.safety.verdict === 'allow' ? 'Allowed' : 'Approved'],
1225
+ ['Audit record', summary.auditRecord ? shortId(summary.auditRecord.id) : 'Recorded']
1226
+ ],
1227
+ output: summary.output,
1228
+ raw: summary
1229
+ });
1230
+ setText('quick-run-status', label + ' completed in ' + r.executed.run.durationMs + ' ms');
1231
+ showToast('Finished ' + label);
1232
+ await refreshOverview();
1233
+ } catch (err) {
1234
+ box.innerHTML = '<div class="callout" data-tone="danger"><p>' + esc(err.message) + '</p></div>';
1235
+ setText('quick-run-status', 'The run did not complete.');
1236
+ throw err;
1237
+ }
1238
+ }
1239
+
1240
+ function setLane(lane) {
1241
+ state.lane = lane || 'all';
1242
+ document.querySelectorAll('#catalog-lanes button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.lane === state.lane)); });
1243
+ }
1244
+ function toolTile(t) {
1245
+ var cost = Number(t.creditCost) > 0 ? fmt(t.creditCost) + ' credits' : 'Included';
1246
+ var provider = t.providerName || (t.providerSlug ? t.providerSlug.charAt(0).toUpperCase() + t.providerSlug.slice(1) : '');
1247
+ return '<article class="tool-tile"><div class="tool-meta">' + laneBadge(t.lane) + riskBadge(t.riskLevel) + '</div>' +
1248
+ '<h3>' + esc(t.name) + '</h3>' + (provider ? '<p class="tool-slug">' + esc(provider) + '</p>' : '') + '<p class="tool-desc">' + esc(t.description) + '</p>' +
1249
+ '<div class="tool-foot"><span>' + esc(cost) + '</span><button class="btn btn-sm" data-action="run-tool" data-slug="' + esc(t.slug) + '">Plan and run</button></div></article>';
1250
+ }
1251
+ async function loadCatalog() {
1252
+ var requestId = ++catalogRequestId;
1253
+ var q = byId('catalog-search').value.trim();
1254
+ var grid = byId('catalog-grid');
1255
+ byId('clear-catalog-search').hidden = !q;
1256
+ grid.setAttribute('aria-busy', 'true');
1257
+ setText('catalog-status', 'Loading tools…');
1258
+ try {
1259
+ var data = await api('/api/v1/tools?query=' + encodeURIComponent(q) + '&lane=' + encodeURIComponent(state.lane) + '&limit=48');
1260
+ if (requestId !== catalogRequestId) return;
1261
+ var tools = data.tools || [];
1262
+ setText('nav-catalog-count', data.total ? fmt(data.total) : '');
1263
+ setText('catalog-status', fmt(data.total != null ? data.total : tools.length) + ' tools' + (q ? ' matching "' + q + '"' : '') + (tools.length < (data.total || 0) ? ', showing ' + tools.length : ''));
1264
+ grid.innerHTML = tools.length ? tools.map(toolTile).join('') : '<div class="empty-state catalog-empty"><span class="action-icon">${uiIcon("search")}</span><h2>No matching tools</h2><p>Try another search or reset your filters.</p><button class="btn" type="button" data-action="reset-catalog">Reset filters</button></div>';
1265
+ } catch (err) {
1266
+ if (requestId !== catalogRequestId) return;
1267
+ setText('catalog-status', 'Tools could not be loaded.');
1268
+ grid.innerHTML = '<div class="empty-state catalog-empty"><span class="action-icon">${uiIcon("refresh")}</span><h2>Tools couldn’t be loaded</h2><p>Your workspace is unchanged. Try again when you’re ready.</p><button class="btn" type="button" data-action="retry-catalog">Try again</button></div>';
1269
+ throw err;
1270
+ } finally {
1271
+ if (requestId === catalogRequestId) grid.removeAttribute('aria-busy');
1272
+ }
1273
+ }
1274
+ function resetCatalog(keepLane) {
1275
+ byId('catalog-search').value = '';
1276
+ byId('clear-catalog-search').hidden = true;
1277
+ if (!keepLane) setLane('all');
1278
+ byId('catalog-search').focus();
1279
+ return loadCatalog();
1280
+ }
1281
+
1282
+ async function openExecuteModal(slug) {
1283
+ var tool = await api('/api/v1/tools/' + encodeURIComponent(slug));
1284
+ currentTool = tool;
1285
+ setText('modal-tool-name', tool.name);
1286
+ var costLabel = Number(tool.creditCost) > 0 ? fmt(tool.creditCost) + ' credits' : 'Included';
1287
+ setText('modal-tool-slug', [LANE_LABELS[tool.lane] || tool.lane, (tool.riskLevel || 'read') + ' access', costLabel].filter(Boolean).join(' · '));
1288
+ setText('modal-tool-desc', tool.description);
1289
+ var sample = {};
1290
+ var defs = state.activeMemoryDefaults || {};
1291
+ var props = (tool.inputSchema && tool.inputSchema.properties) || {};
1292
+ Object.keys(props).forEach(function (k) {
1293
+ var v = props[k] || {};
1294
+ if (k === 'region' && defs.defaultRegion) sample[k] = defs.defaultRegion;
1295
+ else if (k === 'owner' && defs.defaultGithubOwner) sample[k] = defs.defaultGithubOwner;
1296
+ else if ((k === 'repo' || k === 'repository') && defs.defaultGithubRepo) sample[k] = defs.defaultGithubRepo;
1297
+ else if (k === 'channel' && defs.defaultSlackChannel) sample[k] = defs.defaultSlackChannel;
1298
+ else if ((k === 'domain' || k === 'hostname') && defs.defaultDomain) sample[k] = defs.defaultDomain;
1299
+ else if ((k === 'to' || k === 'recipient' || k === 'email') && defs.defaultEmail) sample[k] = defs.defaultEmail;
1300
+ else if (v.default !== undefined) sample[k] = v.default;
1301
+ else if (k === 'query') sample[k] = 'Muxo cloud infrastructure and connected apps';
1302
+ else if (k === 'domain') sample[k] = 'muxo.ai';
1303
+ else if (k === 'owner') sample[k] = 'cloudflare';
1304
+ else if (k === 'repo') sample[k] = 'workerd';
1305
+ else if (k === 'url') sample[k] = defs.defaultDomain ? ('https://' + defs.defaultDomain) : 'https://muxo.ai';
1306
+ else if (k === 'resource_name') sample[k] = tool.providerSlug + '-primary';
1307
+ });
1308
+ byId('modal-tool-input').value = json(sample);
1309
+ byId('modal-oauth-box').hidden = true;
1310
+ byId('modal-result').hidden = true;
1311
+ pendingModalPlan = null;
1312
+ pendingModalHandoff = null;
1313
+ openDialog('exec-modal');
1314
+ }
1315
+ function closeExecuteModal() { closeDialog('exec-modal'); }
1316
+ function readModalInput() {
1317
+ try { return JSON.parse(byId('modal-tool-input').value || '{}'); }
1318
+ catch (e) { throw new Error('Input must be valid JSON.'); }
1319
+ }
1320
+ function showModalResult(text) {
1321
+ var el = byId('modal-result');
1322
+ el.hidden = false;
1323
+ el.textContent = text;
1324
+ }
1325
+ function renderModalOAuthHandoff(planned) {
1326
+ var box = byId('modal-oauth-box');
1327
+ if (planned && planned.authHandoff && planned.authHandoff.required) {
1328
+ pendingModalPlan = planned.plan;
1329
+ pendingModalHandoff = planned.authHandoff;
1330
+ var provider = planned.authHandoff.provider || 'this app';
1331
+ setText('modal-oauth-title', 'Connect ' + provider + ' to continue');
1332
+ setText('modal-oauth-msg', planned.authHandoff.instructions || 'Approve access in your browser, then come back and continue. The plan is saved.');
1333
+ byId('modal-oauth-link').href = planned.authHandoff.redirectUrl || '#';
1334
+ box.hidden = false;
1335
+ return true;
1336
+ }
1337
+ box.hidden = true;
1338
+ return false;
1339
+ }
1340
+ async function waitAndResumeModalPlan() {
1341
+ if (!pendingModalHandoff || !pendingModalPlan) return;
1342
+ showModalResult('Waiting for ' + pendingModalHandoff.provider + ' to confirm the connection…');
1343
+ try {
1344
+ var waited = await post('/api/v1/connections/wait', { provider: pendingModalHandoff.provider, externalAccountId: pendingModalHandoff.externalAccountId, timeoutMs: 12000, pollIntervalMs: 1500 });
1345
+ if (!waited.connected) {
1346
+ showModalResult(json(sanitizeForDisplay(waited)));
1347
+ showToast('Still waiting for ' + pendingModalHandoff.provider + ' (' + waited.status + ')');
1348
+ return;
1349
+ }
1350
+ byId('modal-oauth-box').hidden = true;
1351
+ if (pendingModalPlan.approvalStatus === 'pending_approval') await post('/api/v1/plans/' + pendingModalPlan.id + '/approve');
1352
+ var executed = await post('/api/v1/plans/' + pendingModalPlan.id + '/execute');
1353
+ showModalResult(json(sanitizeForDisplay({ connection: waited, execution: executed })));
1354
+ showToast('Connected and ran the plan');
1355
+ await refreshOverview();
1356
+ } catch (err) {
1357
+ showModalResult('Error: ' + err.message);
1358
+ }
1359
+ }
1360
+ async function completeCallbackAndResumeModalPlan() {
1361
+ if (!pendingModalHandoff || !pendingModalPlan) return;
1362
+ await fetch(callbackUrl(pendingModalHandoff.provider, pendingModalHandoff.externalAccountId));
1363
+ showToast('Recorded the ' + pendingModalHandoff.provider + ' handoff');
1364
+ await waitAndResumeModalPlan();
1365
+ }
1366
+ function callbackUrl(provider, externalAccountId) {
1367
+ return '/oauth/callback?format=json&workspace_id=' + encodeURIComponent(currentWorkspaceId) +
1368
+ '&provider=' + encodeURIComponent(provider) +
1369
+ '&connected_account_id=' + encodeURIComponent(externalAccountId || ('ca_' + Date.now())) +
1370
+ '&status=success';
1371
+ }
1372
+ async function submitPlanOnly() {
1373
+ try {
1374
+ var input = readModalInput();
1375
+ var planned = await post('/api/v1/plans', { toolSlug: currentTool.slug, input: input, reason: byId('modal-tool-reason').value });
1376
+ renderModalOAuthHandoff(planned);
1377
+ showModalResult(json(sanitizeForDisplay(planned)));
1378
+ showToast('Plan created. Review it in Activity.');
1379
+ await refreshOverview();
1380
+ } catch (err) {
1381
+ showModalResult('Error: ' + err.message);
1382
+ }
1383
+ }
1384
+ async function submitPlanAndExecute() {
1385
+ showModalResult('Planning and running ' + (currentTool.name || currentTool.slug) + '…');
1386
+ try {
1387
+ var input = readModalInput();
1388
+ var planned = await post('/api/v1/plans', { toolSlug: currentTool.slug, input: input, reason: byId('modal-tool-reason').value });
1389
+ if (renderModalOAuthHandoff(planned)) {
1390
+ showModalResult(json(sanitizeForDisplay(planned)));
1391
+ showToast('Connect ' + planned.authHandoff.provider + ' to continue');
1392
+ await refreshOverview();
1393
+ return;
1394
+ }
1395
+ if (planned.plan.approvalStatus === 'pending_approval') await post('/api/v1/plans/' + planned.plan.id + '/approve');
1396
+ var executed = await post('/api/v1/plans/' + planned.plan.id + '/execute');
1397
+ showModalResult(json(runSummary(planned, executed)));
1398
+ showToast('Run ' + executed.run.status);
1399
+ await refreshOverview();
1400
+ } catch (err) {
1401
+ showModalResult('Error: ' + err.message);
1402
+ }
1403
+ }
1404
+
1405
+ async function loadResources() {
1406
+ var data = await api('/api/v1/resources');
1407
+ var list = data.resources || [];
1408
+ setCount('nav-res-count', countActive(list));
1409
+ var tbody = byId('resources-tbody');
1410
+ if (tbody) tbody.innerHTML = list.length ? list.map(function (r) {
1411
+ var serviceLabel = String(r.serviceSlug || 'Service').replace(/[_-]+/g, ' ');
1412
+ var envCount = (r.envVarKeys || []).length;
1413
+ var secretCell = envCount ? pill(envCount + (envCount === 1 ? ' secret in vault' : ' secrets in vault'), 'ok') : pill('Encrypted in vault', 'ok');
1414
+ return '<tr><td><strong>' + esc(r.resourceName) + '</strong></td><td>' + esc(r.providerName) + '</td>' +
1415
+ '<td class="small muted" style="text-transform:capitalize">' + esc(serviceLabel) + '</td><td>' + statusBadge(r.status) + '</td>' +
1416
+ '<td>' + secretCell + '</td>' +
1417
+ '<td class="cell-actions"><button class="btn btn-sm" data-action="rotate" data-name="' + esc(r.resourceName) + '">Rotate keys</button></td></tr>';
1418
+ }).join('') : emptyRow(6, 'Nothing provisioned yet. Choose "Provision a service" to add a database, cache, or storage.');
1419
+ loadProvisioningOnboardingStatus().catch(function () {});
1420
+ }
1421
+
1422
+ var stripeConnectScriptPromise = null;
1423
+ function loadStripeConnectJs() {
1424
+ if (window.StripeConnect && typeof window.StripeConnect.init === 'function') {
1425
+ return Promise.resolve(window.StripeConnect);
1426
+ }
1427
+ if (stripeConnectScriptPromise) return stripeConnectScriptPromise;
1428
+ stripeConnectScriptPromise = new Promise(function (resolve, reject) {
1429
+ var s = document.createElement('script');
1430
+ s.src = 'https://connect-js.stripe.com/v1.0/connect.js';
1431
+ s.async = true;
1432
+ s.onload = function () { resolve(window.StripeConnect); };
1433
+ s.onerror = function () {
1434
+ stripeConnectScriptPromise = null;
1435
+ reject(new Error('Could not load Stripe Connect.js'));
1436
+ };
1437
+ document.head.appendChild(s);
1438
+ });
1439
+ return stripeConnectScriptPromise;
1440
+ }
1441
+
1442
+ async function loadProvisioningOnboardingStatus() {
1443
+ var badge = byId('stripe-connect-account-badge');
1444
+ var meta = byId('stripe-connect-account-meta');
1445
+ if (!badge || !meta) return;
1446
+ var res = await api('/api/v1/provisioning/connected_accounts');
1447
+ if (!res.connected || !res.account) {
1448
+ badge.textContent = 'Not onboarded yet';
1449
+ delete badge.dataset.tone;
1450
+ meta.textContent = 'Complete verification to provision managed databases and cloud resources in your workspace.';
1451
+ return;
1452
+ }
1453
+ var acct = res.account;
1454
+ var projStatus = acct.configuration && acct.configuration.developer && acct.configuration.developer.capabilities && acct.configuration.developer.capabilities.projects
1455
+ ? acct.configuration.developer.capabilities.projects.status
1456
+ : 'active';
1457
+ var dueList = (acct.requirements && (acct.requirements.currently_due || acct.requirements.entries)) || [];
1458
+ badge.textContent = projStatus === 'active' ? 'Verified' : 'Verification (' + projStatus + ')';
1459
+ badge.dataset.tone = projStatus === 'active' && dueList.length === 0 ? 'ok' : 'warn';
1460
+ meta.textContent = dueList.length > 0
1461
+ ? dueList.length + ' verification requirement(s) currently due. Complete onboarding below.'
1462
+ : 'Cloud resource provisioning is ' + projStatus + ' for this workspace.';
1463
+ }
1464
+
1465
+ async function openEmbeddedOnboarding() {
1466
+ var box = byId('stripe-embedded-onboarding-box');
1467
+ var mount = byId('stripe-embedded-onboarding-mount');
1468
+ if (!box || !mount) return;
1469
+ box.hidden = false;
1470
+ mount.innerHTML = '<p class="empty-note">Preparing verification session…</p>';
1471
+ var session = await post('/api/v1/provisioning/connected_account_sessions', {
1472
+ auto_create: true,
1473
+ enable_account_management: true,
1474
+ enable_notification_banner: true,
1475
+ fields: 'currently_due',
1476
+ future_requirements: 'include'
1477
+ });
1478
+ await loadProvisioningOnboardingStatus();
1479
+ if (session.publishable_key) {
1480
+ try {
1481
+ await loadStripeConnectJs();
1482
+ var instance = window.StripeConnect.init({
1483
+ publishableKey: session.publishable_key,
1484
+ fetchClientSecret: async function () {
1485
+ var refreshed = await post('/api/v1/provisioning/connected_account_sessions', {
1486
+ account: session.account,
1487
+ enable_account_management: true,
1488
+ enable_notification_banner: true,
1489
+ fields: 'currently_due',
1490
+ future_requirements: 'include'
1491
+ });
1492
+ return refreshed.client_secret;
1493
+ }
1494
+ });
1495
+ var bannerEl = instance.create('notification-banner');
1496
+ var onboardingEl = instance.create('account-onboarding');
1497
+ if (typeof onboardingEl.setCollectionOptions === 'function') {
1498
+ onboardingEl.setCollectionOptions({
1499
+ fields: 'currently_due',
1500
+ futureRequirements: 'include'
1501
+ });
1502
+ }
1503
+ if (typeof onboardingEl.setOnExit === 'function') {
1504
+ onboardingEl.setOnExit(function () {
1505
+ showToast('Verification step saved');
1506
+ loadProvisioningOnboardingStatus().catch(function () {});
1507
+ });
1508
+ }
1509
+ mount.innerHTML = '';
1510
+ mount.appendChild(bannerEl);
1511
+ mount.appendChild(onboardingEl);
1512
+ showToast('Verification form ready');
1513
+ return;
1514
+ } catch (err) {
1515
+ console.error(err);
1516
+ }
1517
+ }
1518
+ mount.innerHTML = '<div class="result-card">' +
1519
+ '<div class="result-head"><div><strong>Account verification session ready</strong>' +
1520
+ '<div class="result-meta">Cloud resource provisioning verification</div></div>' +
1521
+ pill('Session active', 'ok') + '</div>' +
1522
+ '<div class="result-body">' +
1523
+ '<div class="result-kv"><span>Status</span><strong>Ready for verification</strong></div>' +
1524
+ '<div class="result-kv"><span>Flow</span><strong>' + (session.publishable_key ? 'Embedded verification' : 'Hosted verification link') + '</strong></div>' +
1525
+ '</div>' +
1526
+ '<div class="result-actions" style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">' +
1527
+ '<button class="btn btn-sm btn-primary" type="button" data-action="open-hosted-onboarding">Open verification link</button>' +
1528
+ '<button class="btn btn-sm" type="button" data-action="refresh-onboarding-status">Refresh status</button>' +
1529
+ '</div></div>';
1530
+ showToast('Verification session ready');
1531
+ }
1532
+
1533
+ async function openHostedOnboardingLink() {
1534
+ var link = await post('/api/v1/provisioning/connected_account_links', {
1535
+ auto_create: true,
1536
+ type: 'account_onboarding',
1537
+ fields: 'currently_due',
1538
+ future_requirements: 'include'
1539
+ });
1540
+ await loadProvisioningOnboardingStatus();
1541
+ if (link && link.url) {
1542
+ showDetail('Verification link', 'Complete verification to provision cloud resources', {
1543
+ onboardingUrl: link.url,
1544
+ expiresAt: link.expires_at
1545
+ });
1546
+ showToast('Verification link ready');
1547
+ }
1548
+ }
1549
+ async function rotateResource(name) {
1550
+ var ok = await confirmAction({ title: 'Rotate credentials for ' + name + '?', message: 'Apps using the current credentials will stop working until they pick up the new ones from your vault.', confirm: 'Rotate keys', danger: true });
1551
+ if (!ok) return;
1552
+ var r = await planAndRun('resource.projects.rotate_credentials', { resource_name: name }, 'Rotate credentials from the console');
1553
+ showDetail('Credentials rotated', name, runSummary(r.planned, r.executed));
1554
+ await loadResources();
1555
+ }
1556
+ async function pullWorkspaceEnv() {
1557
+ var ok = await confirmAction({ title: 'Show decrypted secrets?', message: 'Connection strings and keys will be visible on screen. Make sure no one else can see it.', confirm: 'Show secrets' });
1558
+ if (!ok) return;
1559
+ var data = await api('/api/v1/resources/env');
1560
+ byId('env-export-box').hidden = false;
1561
+ setText('env-export-content', data.dotenv || '# No secrets stored in the vault yet.');
1562
+ }
1563
+
1564
+ async function loadConnections(syncLive) {
1565
+ var tbody = byId('connections-tbody');
1566
+ if (tbody && state.view === 'connections' && syncLive) tbody.innerHTML = emptyRow(5, 'Checking each provider…');
1567
+ var data = await api('/api/v1/connections?sync=' + (syncLive ? 'true' : 'false'));
1568
+ var list = data.connections || [];
1569
+ var activeConns = countActive(list);
1570
+ setCount('nav-conn-count', activeConns);
1571
+ if (state.boot) setText('kpi-connections', fmt(activeConns + countActive(state.boot.resources)));
1572
+ if (!tbody) return;
1573
+ tbody.innerHTML = list.length ? list.map(function (c) {
1574
+ var acct = esc(c.externalAccountId || '');
1575
+ var prov = esc(c.providerSlug);
1576
+ var connectedInfo = c.connectedAt ? timeAgo(c.connectedAt) : (c.status === 'active' ? 'Verified' : 'Awaiting approval');
1577
+ var actions = c.status === 'active'
1578
+ ? '<span class="ok-text">Connected</span>'
1579
+ : (c.redirectUrl ? '<a class="btn btn-sm btn-primary" href="' + esc(c.redirectUrl) + '" target="_blank" rel="noopener">Authorize</a> ' : '') +
1580
+ '<button class="btn btn-sm" data-action="wait-connection" data-provider="' + prov + '" data-account="' + acct + '">Check status</button>';
1581
+ return '<tr><td><strong>' + esc(c.providerName || prov) + '</strong></td><td>' + laneBadge(c.lane) + '</td>' +
1582
+ '<td>' + statusBadge(c.status) + '</td><td class="small muted">' + esc(connectedInfo) + '</td><td class="cell-actions">' + actions + '</td></tr>';
1583
+ }).join('') : emptyRow(5, 'No apps connected yet. Connect one above.');
1584
+ }
1585
+ async function startCustomProviderConnection() {
1586
+ var input = byId('custom-provider-input');
1587
+ var val = (input && input.value ? input.value : '').trim().toLowerCase();
1588
+ if (!val) { showToast('Enter an app name, like linear or jira'); if (input) input.focus(); return; }
1589
+ input.value = '';
1590
+ await startAppConnection(val);
1591
+ }
1592
+ async function waitConnectionInUi(provider, externalAccountId) {
1593
+ showToast('Checking ' + provider + '…');
1594
+ var waited = await post('/api/v1/connections/wait', { provider: provider, externalAccountId: externalAccountId || undefined, timeoutMs: 8000, pollIntervalMs: 1500 });
1595
+ showToast(waited.message || (provider + ': ' + waited.status));
1596
+ await loadConnections(false);
1597
+ }
1598
+ async function completeCallbackInUi(provider, externalAccountId) {
1599
+ await fetch(callbackUrl(provider, externalAccountId));
1600
+ showToast('Recorded the ' + provider + ' handoff');
1601
+ await loadConnections(false);
1602
+ }
1603
+ async function startAppConnection(provider) {
1604
+ var res = await post('/api/v1/connections', { provider: provider, lane: 'composio_sdk' });
1605
+ var conn = res.connection || {};
1606
+ if (conn.redirectUrl) window.open(conn.redirectUrl, '_blank', 'noopener');
1607
+ showToast(conn.redirectUrl ? 'Finish connecting ' + provider + ' in the new tab' : (conn.message || 'Started connecting ' + provider));
1608
+ await refreshOverview();
1609
+ }
1610
+
1611
+ function setFactsMode(mode) {
1612
+ state.factsMode = mode === 'history' ? 'history' : 'current';
1613
+ document.querySelectorAll('#facts-mode button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.mode === state.factsMode)); });
1614
+ renderFacts();
1615
+ }
1616
+ function renderFacts(facts) {
1617
+ if (facts) state.facts = facts;
1618
+ var f = state.facts || { activeFacts: [], historicalFacts: [] };
1619
+ var active = f.activeFacts || [];
1620
+ var historical = f.historicalFacts || [];
1621
+ var list = state.factsMode === 'history' ? (f.facts || active.concat(historical)) : active;
1622
+ setText('facts-summary', fmt(active.length) + ' current · ' + fmt(historical.length) + ' replaced');
1623
+ byId('facts-tbody').innerHTML = list.length ? list.map(function (x) {
1624
+ var current = x.validTo == null;
1625
+ var relation = String(x.predicate || '').replace(/_/g, ' ');
1626
+ return '<tr><td><strong>' + esc(x.subject) + '</strong></td><td class="small muted">' + esc(relation) + '</td><td>' + esc(x.object) + '</td>' +
1627
+ '<td class="small muted">' + esc(x.validFrom ? new Date(x.validFrom).toLocaleString() : '') + '</td>' +
1628
+ '<td>' + (current ? pill('Current', 'ok', true) : pill('Replaced ' + timeAgo(x.validTo).toLowerCase())) + '</td></tr>';
1629
+ }).join('') : emptyRow(5, state.factsMode === 'history' ? 'No facts recorded yet.' : 'No current facts. Save a memory to record one.');
1630
+ }
1631
+ function factText(v) {
1632
+ if (v == null) return '';
1633
+ if (typeof v !== 'object') return String(v);
1634
+ return String(v.memory || v.content || v.text || v.fact || v.value || v.summary || v.title || JSON.stringify(v));
1635
+ }
1636
+ function renderActiveMemoryDefaults(defaults, guardrails) {
1637
+ var box = byId('memory-active-defaults-box');
1638
+ if (!box) return;
1639
+ var defs = defaults || {};
1640
+ var keys = Object.keys(defs).filter(function (k) { return defs[k] != null && defs[k] !== ''; });
1641
+ var rules = (guardrails || []).filter(Boolean);
1642
+ if (!keys.length && !rules.length) {
1643
+ box.innerHTML = '<p class="empty-note">No saved workspace defaults.</p>';
1644
+ return;
1645
+ }
1646
+ var html = [];
1647
+ if (keys.length) {
1648
+ html.push('<div class="recall-group"><h3>Auto-filled into matching tools</h3><div class="rows">' + keys.map(function (k) {
1649
+ var label = DEFAULT_PARAM_LABELS[k] || k;
1650
+ return '<div class="row"><div><strong>' + esc(label) + '</strong><small>Automatically applied when an agent omits this parameter</small></div>' + pill(String(defs[k]), 'accent') + '</div>';
1651
+ }).join('') + '</div></div>');
1652
+ }
1653
+ if (rules.length) {
1654
+ html.push('<div class="recall-group"><h3>Remembered workspace guardrails</h3><div class="rows">' + rules.map(function (r) {
1655
+ return '<div class="row"><div><strong>' + esc(r) + '</strong><small>Applies to this workspace</small></div>' + pill('Active rule', 'ok', true) + '</div>';
1656
+ }).join('') + '</div></div>');
1657
+ }
1658
+ box.innerHTML = html.join('');
1659
+ }
1660
+ async function teachMemoryFromUi(chipPrompt) {
1661
+ var input = byId('memory-teach-input');
1662
+ var promptText = (typeof chipPrompt === 'string' ? chipPrompt : (input && input.value) || '').trim();
1663
+ if (!promptText) { showToast('Tell Muxo what preference or default to remember'); if (input) input.focus(); return; }
1664
+ if (input) input.value = promptText;
1665
+ var res = await post('/api/v1/do', { prompt: promptText });
1666
+ if (input && typeof chipPrompt !== 'string') input.value = '';
1667
+ showToast(res.humanMessage || 'Saved preference to workspace memory');
1668
+ await loadMemory();
1669
+ await refreshOverview();
1670
+ }
1671
+ function renderRecall(ctx) {
1672
+ function group(title, rows) { return '<div class="recall-group"><h3>' + esc(title) + '</h3><div class="rows">' + rows.join('') + '</div></div>'; }
1673
+ function line(main, sub) { return '<div class="row"><div><strong>' + esc(main) + '</strong>' + (sub ? '<small>' + esc(sub) + '</small>' : '') + '</div></div>'; }
1674
+ var facts = (ctx.staticFacts || []).map(factText).filter(Boolean);
1675
+ var dynamic = (ctx.dynamicContext || []).map(factText).filter(Boolean);
1676
+ var recalled = ctx.recalledMemories || [];
1677
+ var activeDefs = ctx.activeDefaults || {};
1678
+ var defKeys = Object.keys(activeDefs).filter(function (k) { return activeDefs[k] != null && activeDefs[k] !== ''; });
1679
+ var groups = [];
1680
+ if (defKeys.length) {
1681
+ groups.push(group('Active auto-fill defaults', defKeys.map(function (k) {
1682
+ return line((DEFAULT_PARAM_LABELS[k] || k) + ': ' + String(activeDefs[k]), 'Auto-filled into matching tool plans');
1683
+ })));
1684
+ }
1685
+ if (recalled.length) groups.push(group('Recalled memories', recalled.map(function (m) { return line(m.title || 'Memory', m.snippet || ''); })));
1686
+ if (facts.length) groups.push(group('Known facts', facts.map(function (f) { return line(f); })));
1687
+ if (dynamic.length) groups.push(group('Current context', dynamic.map(function (f) { return line(f); })));
1688
+ byId('memory-context-box').innerHTML = groups.length ? groups.join('') : '<p class="empty-note">Nothing to recall yet. Save a memory or run a tool, then try again.</p>';
1689
+ setText('memory-prompt-box', ctx.formattedPromptContext || 'No prompt context yet.');
1690
+ }
1691
+ async function loadMemory() {
1692
+ var q = byId('memory-query').value.trim();
1693
+ var results = await Promise.all([
1694
+ api('/api/v1/memory' + (q ? '?query=' + encodeURIComponent(q) : '')),
1695
+ api('/api/v1/memory/facts?historical=true&limit=100').catch(function () { return null; })
1696
+ ]);
1697
+ var data = results[0];
1698
+ var ctx = data.context || {};
1699
+ var memories = data.recentMemories || data.records || data.memories || [];
1700
+ var wsName = (state.boot && state.boot.organization && state.boot.organization.name) || 'Active workspace';
1701
+ setText('mem-namespace', wsName);
1702
+ setCount('nav-mem-count', memories.length);
1703
+ state.activeMemoryDefaults = data.activeDefaults || ctx.activeDefaults || state.activeMemoryDefaults || {};
1704
+ renderActiveMemoryDefaults(state.activeMemoryDefaults, data.activeGuardrails || ctx.activeGuardrails || []);
1705
+ renderRecall(ctx);
1706
+ renderFacts(results[1] || { activeFacts: data.temporalFacts || [], historicalFacts: data.historicalFacts || [] });
1707
+ byId('memories-tbody').innerHTML = memories.length ? memories.map(function (m) {
1708
+ return '<tr><td><strong>' + esc(m.title) + '</strong></td><td>' + pill(String(m.kind || 'fact').replace(/_/g, ' ')) + '</td>' +
1709
+ '<td class="small muted">' + esc(m.content) + '</td><td class="small muted">' + esc(timeAgo(m.createdAt)) + '</td>' +
1710
+ '<td class="cell-actions"><button class="btn btn-sm" data-action="delete-memory" data-id="' + esc(m.id) + '">Delete</button></td></tr>';
1711
+ }).join('') : emptyRow(5, 'No memories yet. Save a fact above, or run a tool and Muxo will remember what happened.');
1712
+ }
1713
+ async function submitNewMemory() {
1714
+ var title = byId('memory-title').value.trim() || 'Workspace fact';
1715
+ var content = byId('memory-content').value.trim();
1716
+ if (!content) { showToast('Write what Muxo should remember'); byId('memory-content').focus(); return; }
1717
+ await post('/api/v1/memory', { title: title, kind: byId('memory-kind').value, content: content });
1718
+ byId('memory-title').value = '';
1719
+ byId('memory-content').value = '';
1720
+ showToast('Saved to workspace memory');
1721
+ await loadMemory();
1722
+ }
1723
+ async function deleteMemoryItem(id) {
1724
+ var ok = await confirmAction({ title: 'Delete this memory?', message: 'Muxo will stop recalling it for future plans.', confirm: 'Delete', danger: true });
1725
+ if (!ok) return;
1726
+ await api('/api/v1/memory/' + encodeURIComponent(id), { method: 'DELETE' });
1727
+ showToast('Memory deleted');
1728
+ await loadMemory();
1729
+ }
1730
+ async function forgetMatching() {
1731
+ var q = byId('forget-query').value.trim();
1732
+ if (!q) { showToast('Enter a word or phrase to forget'); byId('forget-query').focus(); return; }
1733
+ var ok = await confirmAction({ title: 'Forget everything about "' + q + '"?', message: 'Matching memories are deleted and matching facts are marked as replaced. This cannot be undone.', confirm: 'Forget', danger: true });
1734
+ if (!ok) return;
1735
+ var res = await post('/api/v1/memory/forget', { query: q });
1736
+ byId('forget-query').value = '';
1737
+ showToast('Deleted ' + fmt(res.deletedLocalCount) + ' memories and retired ' + fmt(res.invalidatedFactsCount) + ' facts');
1738
+ await loadMemory();
1739
+ }
1740
+
1741
+ function renderPlainEnglishGuardrails(plain, policy) {
1742
+ var box = byId('guardrails-plain-box');
1743
+ var postureBadge = byId('guardrail-posture-pill');
1744
+ if (postureBadge && plain && plain.postureLabel) {
1745
+ postureBadge.textContent = plain.postureLabel;
1746
+ postureBadge.dataset.tone = plain.postureLabel.indexOf('Hands-off') >= 0 ? 'warn' : 'ok';
1747
+ }
1748
+ if (!box) return;
1749
+ var rules = (plain && plain.rules) || [];
1750
+ var badges = (plain && plain.badges) || [];
1751
+ if (!rules.length && policy) {
1752
+ rules = [
1753
+ 'Budget cap: Max ' + fmt(policy.maxCreditsPerRun || 100) + ' credits per run.',
1754
+ policy.blockDestructiveSql !== false ? 'Database protection: Destructive SQL is blocked.' : 'Database protection: Destructive SQL guard is off.'
1755
+ ];
1756
+ }
1757
+ var badgeHtml = badges.length ? '<div class="chips" style="margin-top:0;margin-bottom:12px">' + badges.map(function (b) {
1758
+ var tone = b.tone === 'safe' ? 'ok' : b.tone === 'strict' ? 'accent' : 'warn';
1759
+ return pill(b.label, tone, true);
1760
+ }).join(' ') + '</div>' : '';
1761
+ var rowsHtml = rules.map(function (r) {
1762
+ var isCustom = r.indexOf('Custom guardrail:') === 0;
1763
+ return '<div class="row"><div><strong>' + esc(r) + '</strong></div>' + (isCustom ? pill('Plain-English rule', 'accent') : pill('Enforced', 'ok', true)) + '</div>';
1764
+ }).join('');
1765
+ box.innerHTML = badgeHtml + '<div class="rows">' + rowsHtml + '</div>';
1766
+ }
1767
+ async function applyGuardrailFromUi(chipPrompt) {
1768
+ var input = byId('guardrail-input');
1769
+ var promptText = (typeof chipPrompt === 'string' ? chipPrompt : (input && input.value) || '').trim();
1770
+ if (!promptText) { showToast('Write a rule in plain English (e.g. "Don\'t spend over 25 credits per run")'); if (input) input.focus(); return; }
1771
+ if (input) input.value = promptText;
1772
+ var res = await post('/api/v1/guardrails', { prompt: promptText });
1773
+ if (input && typeof chipPrompt !== 'string') input.value = '';
1774
+ if (res.plainEnglish) {
1775
+ state.plainEnglishGuardrails = res.plainEnglish;
1776
+ renderPlainEnglishGuardrails(res.plainEnglish, res.workspace && res.workspace.policy);
1777
+ }
1778
+ if (res.workspace && res.workspace.policy) syncPolicyForm(res.workspace.policy);
1779
+ showToast(res.humanMessage || 'Guardrail applied');
1780
+ await loadPlans();
1781
+ await refreshOverview();
1782
+ }
1783
+ async function resetGuardrailsFromUi() {
1784
+ var res = await post('/api/v1/guardrails', { reset: true });
1785
+ if (res.plainEnglish) {
1786
+ state.plainEnglishGuardrails = res.plainEnglish;
1787
+ renderPlainEnglishGuardrails(res.plainEnglish, res.workspace && res.workspace.policy);
1788
+ }
1789
+ if (res.workspace && res.workspace.policy) syncPolicyForm(res.workspace.policy);
1790
+ showToast(res.humanMessage || 'Reset guardrails to Safe Default');
1791
+ await loadPlans();
1792
+ await refreshOverview();
1793
+ }
1794
+ function renderGovernance(g, policy, plainEnglish) {
1795
+ renderIdentity(g);
1796
+ var ks = g.killSwitch || {};
1797
+ var paused = Boolean(ks.active);
1798
+ var status = byId('gov-kill-status');
1799
+ if (status) {
1800
+ status.textContent = paused ? 'Paused' : 'Running';
1801
+ status.dataset.tone = paused ? 'danger' : 'ok';
1802
+ }
1803
+ setText('gov-kill-sub', paused ? 'Emergency stop is on' : 'Agents can act within policy');
1804
+ var kp = byId('kill-pill');
1805
+ if (kp) {
1806
+ kp.textContent = paused ? 'Paused' : 'Running';
1807
+ kp.dataset.tone = paused ? 'danger' : 'ok';
1808
+ }
1809
+ if (byId('kill-active-box')) byId('kill-active-box').hidden = !paused;
1810
+ setText('kill-reason', ks.reason ? 'Reason: ' + ks.reason : '');
1811
+ if (byId('kill-reason-input')) byId('kill-reason-input').hidden = paused;
1812
+ if (byId('kill-activate-btn')) byId('kill-activate-btn').hidden = paused;
1813
+ if (byId('kill-resume-btn')) byId('kill-resume-btn').hidden = !paused;
1814
+ var ts = g.trustScore;
1815
+ if (ts && ts.overall != null) {
1816
+ var score = ts.overall <= 1 ? Math.round(ts.overall * 1000) : Math.round(ts.overall);
1817
+ setText('gov-trust', fmt(score));
1818
+ setText('gov-trust-sub', 'of 1,000' + (ts.tier ? ' · ' + String(ts.tier).replace(/_/g, ' ') : ''));
1819
+ }
1820
+ var chain = g.auditChain || {};
1821
+ var chainOk = g.auditChainIntegrity != null ? g.auditChainIntegrity : g.auditIntegrityValid != null ? g.auditIntegrityValid : chain.chainVerified !== false;
1822
+ var count = g.auditEntriesCount != null ? g.auditEntriesCount : (chain.entriesCount || 0);
1823
+ var badge = byId('gov-chain-badge');
1824
+ if (badge) {
1825
+ badge.textContent = chainOk ? 'Verified' : 'Check needed';
1826
+ badge.dataset.tone = chainOk ? 'ok' : 'danger';
1827
+ }
1828
+ setText('gov-chain-sub', fmt(count) + ' signed entries');
1829
+ if (byId('gov-modules')) byId('gov-modules').innerHTML = (g.modules || []).map(function (m) { return pill(MODULE_LABELS[m] || m, 'ok', true); }).join('');
1830
+ if (policy) syncPolicyForm(policy);
1831
+ if (plainEnglish || state.plainEnglishGuardrails) renderPlainEnglishGuardrails(plainEnglish || state.plainEnglishGuardrails, policy);
1832
+ }
1833
+ async function loadPlans() {
1834
+ var results = await Promise.all([api('/api/v1/plans'), api('/api/v1/governance').catch(function () { return null; })]);
1835
+ var plans = results[0].plans || [];
1836
+ var gov = results[1];
1837
+ if (gov && gov.governance) {
1838
+ if (gov.plainEnglishGuardrails) state.plainEnglishGuardrails = gov.plainEnglishGuardrails;
1839
+ renderGovernance(gov.governance, gov.workspacePolicy, gov.plainEnglishGuardrails);
1840
+ }
1841
+ var pending = plans.filter(function (p) { return p.approvalStatus === 'pending_approval'; }).length;
1842
+ setText('gov-pending', fmt(pending));
1843
+ setCount('nav-plan-count', pending);
1844
+ var tbody = byId('plans-tbody');
1845
+ if (tbody) tbody.innerHTML = plans.length ? plans.map(function (p) {
1846
+ var verdict = p.decisionRecord ? p.decisionRecord.verdict : 'allow';
1847
+ var verdictLabel = verdict === 'deny' ? 'Blocked' : verdict === 'require_approval' ? 'Needs approval' : 'Allowed';
1848
+ var id = esc(p.id);
1849
+ var inspectBtn = '<button class="btn btn-sm" type="button" data-action="inspect-plan" data-id="' + id + '">Inspect</button>';
1850
+ var action = p.approvalStatus === 'pending_approval'
1851
+ ? inspectBtn + ' <button class="btn btn-sm" type="button" data-action="reject-plan" data-id="' + id + '">Reject</button> <button class="btn btn-sm btn-primary" type="button" data-action="approve-plan" data-id="' + id + '">Approve and run</button>'
1852
+ : (p.approvalStatus === 'approved' || p.approvalStatus === 'auto_approved')
1853
+ ? inspectBtn + ' <button class="btn btn-sm btn-primary" type="button" data-action="execute-plan" data-id="' + id + '">Run</button>'
1854
+ : inspectBtn;
1855
+ return '<tr id="plan-row-' + id + '"><td class="mono small">' + esc(shortId(p.id)) + '</td><td><strong>' + esc(formatToolLabel(p.toolSlug)) + '</strong></td>' +
1856
+ '<td>' + riskBadge(p.riskLevel) + ' ' + pill(verdictLabel, verdict === 'deny' ? 'danger' : '') + '</td>' +
1857
+ '<td>' + statusBadge(p.approvalStatus) + '</td><td class="cell-actions">' + action + '</td></tr>';
1858
+ }).join('') : emptyRow(5, 'Nothing waiting. Plans appear here when an agent asks to run a tool.');
1859
+ var focusPlan = new URLSearchParams(window.location.search).get('plan');
1860
+ if (focusPlan && /^[A-Za-z0-9_-]+$/.test(focusPlan)) {
1861
+ var row = byId('plan-row-' + focusPlan);
1862
+ if (row) {
1863
+ row.style.background = 'rgba(99, 102, 241, 0.14)';
1864
+ row.scrollIntoView({ behavior: 'smooth', block: 'center' });
1865
+ showToast('This plan is waiting for your review.');
1866
+ }
1867
+ }
1868
+ }
1869
+ async function inspectPlan(planId) {
1870
+ var data = await api('/api/v1/plans/' + encodeURIComponent(planId));
1871
+ var p = data.plan || data;
1872
+ showDetail('Action plan', shortId(planId), p,
1873
+ riskBadge(p.riskLevel) + ' ' + statusBadge(p.approvalStatus) + ' ' +
1874
+ pill((p.creditCost != null ? fmt(p.creditCost) : '0') + ' credits'));
1875
+ }
1876
+ async function rejectPlan(planId) {
1877
+ await post('/api/v1/plans/' + encodeURIComponent(planId) + '/approve', { decision: 'rejected' });
1878
+ showToast('Plan rejected. Nothing was executed.');
1879
+ await loadPlans();
1880
+ await refreshOverview();
1881
+ }
1882
+ async function approveAndRun(planId) {
1883
+ await post('/api/v1/plans/' + planId + '/approve');
1884
+ await executeExistingPlan(planId);
1885
+ }
1886
+ async function executeExistingPlan(planId) {
1887
+ var res = await post('/api/v1/plans/' + planId + '/execute');
1888
+ showToast('Plan ran: ' + res.run.status);
1889
+ await loadPlans();
1890
+ await refreshOverview();
1891
+ }
1892
+ async function activateKillSwitch() {
1893
+ var reason = byId('kill-reason-input').value.trim() || 'Paused from the Muxo console';
1894
+ var ok = await confirmAction({ title: 'Pause all agent actions?', message: 'Every agent using this workspace stops immediately and stays stopped until you resume.', confirm: 'Pause all actions', danger: true });
1895
+ if (!ok) return;
1896
+ await post('/api/v1/governance/kill-switch', { action: 'quarantine', reason: reason });
1897
+ byId('kill-reason-input').value = '';
1898
+ showToast('All agent actions are paused');
1899
+ await loadPlans();
1900
+ }
1901
+ async function resumeKillSwitch() {
1902
+ await post('/api/v1/governance/kill-switch', { action: 'resume' });
1903
+ showToast('Agents can act again');
1904
+ await loadPlans();
1905
+ }
1906
+ async function scanContextFromUi() {
1907
+ var content = byId('scan-content').value.trim();
1908
+ if (!content) { showToast('Paste some text to check'); byId('scan-content').focus(); return; }
1909
+ var res = await post('/api/v1/governance/scan-context', { content: content, sessionId: 'console' });
1910
+ var s = res.scan || {};
1911
+ var safe = s.safe !== false;
1912
+ var box = byId('scan-result');
1913
+ box.hidden = false;
1914
+ box.dataset.tone = safe ? 'ok' : 'danger';
1915
+ var findings = s.findings || [];
1916
+ box.innerHTML = '<h3>' + (safe ? 'No prompt injection found' : 'Risk level: ' + esc(s.riskLevel || 'high')) + '</h3>' +
1917
+ (findings.length
1918
+ ? '<ul>' + findings.map(function (f) { return '<li><strong>' + esc(f.patternName || f.patternId) + '</strong> · ' + esc(f.severity || '') + '<br><span>' + esc(f.description || '') + '</span></li>'; }).join('') + '</ul>'
1919
+ : '<p>This text looks safe to store or pass to an agent.</p>');
1920
+ }
1921
+ async function savePolicy() {
1922
+ var requireApprovalFor = [];
1923
+ document.querySelectorAll('#policy-risks input:checked').forEach(function (cb) { requireApprovalFor.push(cb.value); });
1924
+ await post('/api/v1/policy', {
1925
+ mode: byId('policy-mode').value,
1926
+ requireApprovalFor: requireApprovalFor,
1927
+ executionRing: Number(byId('policy-ring').value),
1928
+ blockDestructiveSql: byId('policy-block-sql').checked
1929
+ });
1930
+ showToast('Policy saved');
1931
+ await refreshOverview();
1932
+ }
1933
+
1934
+ async function loadRuns() {
1935
+ var data = await api('/api/v1/runs');
1936
+ var runs = data.runs || [];
1937
+ setCount('nav-run-count', runs.length);
1938
+ var tbody = byId('runs-tbody');
1939
+ if (tbody) tbody.innerHTML = runs.length ? runs.map(function (r) {
1940
+ return '<tr><td><span class="mono small">' + esc(shortId(r.id)) + '</span><br><span class="small muted">' + esc(timeAgo(r.createdAt || r.startedAt)) + '</span></td>' +
1941
+ '<td><strong>' + esc(formatToolLabel(r.toolSlug)) + '</strong><br>' + laneBadge(r.lane) + '</td>' +
1942
+ '<td>' + riskBadge(r.riskLevel) + '</td><td>' + statusBadge(r.status) + '</td>' +
1943
+ '<td class="mono small">' + esc(r.durationMs || 0) + ' ms</td>' +
1944
+ '<td>' + (r.receiptId ? pill('Signed', 'ok', true) : pill('No record')) + '</td>' +
1945
+ '<td class="cell-actions"><button class="btn btn-sm" data-action="inspect-receipt" data-id="' + esc(r.receiptId || r.id) + '">Inspect</button></td></tr>';
1946
+ }).join('') : emptyRow(7, 'No runs yet. Every tool run will appear here with its audit record.');
1947
+ }
1948
+ async function inspectReceipt(receiptId) {
1949
+ var data = await api('/api/v1/receipts/' + encodeURIComponent(receiptId));
1950
+ var sigValid = data.signatureValid != null ? data.signatureValid : (data.valid != null ? data.valid : true);
1951
+ var govValid = data.governanceSignatureValid != null ? data.governanceSignatureValid : (data.verified != null ? data.verified : true);
1952
+ showDetail('Audit record', shortId(receiptId), data.receipt || data,
1953
+ pill(sigValid ? 'Receipt signature valid' : 'Receipt signature invalid', sigValid ? 'ok' : 'danger', true) + ' ' +
1954
+ pill(govValid ? 'Policy decision verified' : 'Policy decision not verified', govValid ? 'ok' : 'danger', true));
1955
+ }
1956
+
1957
+ function renderSettlement(s) {
1958
+ if (!s) return;
1959
+ var by = s.bySupplier || {};
1960
+ setText('billing-retail', '$' + ((s.totalUserRetailSpendCents || 0) / 100).toFixed(2));
1961
+ setText('billing-wholesale', fmt(s.totalCreditsConsumed || 0) + ' credits');
1962
+ function lane(key, label) {
1963
+ var x = by[key] || {};
1964
+ var runsCount = Number(x.runsCount || 0);
1965
+ return '<div class="row"><div><strong>' + label + '</strong><small>' + fmt(runsCount) + (runsCount === 1 ? ' run' : ' runs') + '</small></div>' + pill(runsCount ? 'Active' : 'No runs yet', runsCount ? 'ok' : '') + '</div>';
1966
+ }
1967
+ var summaryBox = byId('settlement-summary-box');
1968
+ if (summaryBox) summaryBox.innerHTML = '<div class="rows">' + lane('composio_instant', 'Instant tools') + lane('composio_sdk', 'Connected apps') + lane('stripe_projects', 'Cloud services') + '</div>' +
1969
+ '<p class="small muted" style="margin-top:12px">' + fmt(s.totalCreditsConsumed || 0) + ' total credits used across all providers.</p>';
1970
+ }
1971
+ async function loadLedger() {
1972
+ var data = await api('/api/v1/billing/ledger');
1973
+ renderSettlement(data.settlementSummary);
1974
+ var entries = data.entries || [];
1975
+ var tbody = byId('ledger-tbody');
1976
+ if (tbody) tbody.innerHTML = entries.length ? entries.map(function (e) {
1977
+ var delta = Number(e.creditsDelta || 0);
1978
+ return '<tr><td class="small muted">' + esc(new Date(e.createdAt).toLocaleString()) + '</td><td>' + esc(e.description) + '</td>' +
1979
+ '<td class="mono" style="color:' + (delta >= 0 ? 'var(--color-ok)' : 'var(--color-text)') + '">' + (delta >= 0 ? '+' : '') + esc(fmt(delta)) + '</td>' +
1980
+ '<td class="mono">' + esc(fmt(e.balanceAfter)) + '</td></tr>';
1981
+ }).join('') : emptyRow(4, 'No billing activity yet.');
1982
+ await renderPlanCards();
1983
+ await loadByokKeys();
1984
+ }
1985
+ async function loadByokKeys() {
1986
+ var data = await api('/api/v1/byok');
1987
+ var keys = (data.keys || []).filter(function (k) { return k.configured !== false && (k.keyPreview || k.keyHint); });
1988
+ var hasSupplier = keys.some(function (k) { return k.category === 'supplier'; });
1989
+ var badge = byId('byok-mode-badge');
1990
+ if (badge) {
1991
+ badge.textContent = hasSupplier ? 'Your keys · 0 credits' : 'Muxo credits';
1992
+ if (hasSupplier) badge.dataset.tone = 'accent'; else delete badge.dataset.tone;
1993
+ }
1994
+ var tbody = byId('byok-keys-tbody');
1995
+ if (tbody) tbody.innerHTML = keys.length ? keys.map(function (k) {
1996
+ var slug = k.provider || k.providerSlug || '';
1997
+ return '<tr><td><strong>' + esc(k.providerName || k.displayName || slug) + '</strong></td>' +
1998
+ '<td>' + pill(k.category === 'llm' ? 'Language model' : 'Provider', k.category === 'llm' ? 'accent' : '') + '</td>' +
1999
+ '<td class="mono small">' + esc(k.keyHint || k.keyPreview || '••••') + '</td>' +
2000
+ '<td class="cell-actions"><button class="btn btn-sm" data-action="delete-byok" data-provider="' + esc(slug) + '">Remove</button></td></tr>';
2001
+ }).join('') : emptyRow(4, 'No keys saved. Runs are paid with Muxo credits.');
2002
+ }
2003
+ async function saveByokKeyFromUi() {
2004
+ var provider = byId('byok-provider-select').value;
2005
+ var secretKey = byId('byok-key-input').value.trim();
2006
+ if (!secretKey) { showToast('Paste a key first'); byId('byok-key-input').focus(); return; }
2007
+ await post('/api/v1/byok', { provider: provider, secretKey: secretKey, apiKey: secretKey });
2008
+ byId('byok-key-input').value = '';
2009
+ showToast('Saved your ' + provider + ' key. It is encrypted in your vault.');
2010
+ await loadByokKeys();
2011
+ }
2012
+ async function deleteByokKeyFromUi(provider) {
2013
+ var ok = await confirmAction({ title: 'Remove your ' + provider + ' key?', message: 'Runs that used it will be paid with Muxo credits instead.', confirm: 'Remove', danger: true });
2014
+ if (!ok) return;
2015
+ await api('/api/v1/byok/' + encodeURIComponent(provider), { method: 'DELETE' });
2016
+ showToast('Removed the ' + provider + ' key');
2017
+ await loadByokKeys();
2018
+ }
2019
+
2020
+ async function fillAndRunNl(promptText) {
2021
+ byId('nl-prompt-input').value = promptText;
2022
+ await runNaturalLanguagePrompt();
2023
+ }
2024
+ async function runNaturalLanguagePrompt() {
2025
+ var input = byId('nl-prompt-input');
2026
+ var promptText = input.value.trim();
2027
+ if (!promptText) { showToast('Type what you want Muxo to do'); input.focus(); return; }
2028
+ var box = byId('quick-run-output');
2029
+ setText('quick-run-status', 'Working on "' + promptText + '"…');
2030
+ box.innerHTML = '<p class="empty-note">Selecting the right tool and checking workspace policy for "' + esc(promptText) + '"…</p>';
2031
+ try {
2032
+ var res = await post('/api/v1/do', { prompt: promptText, autoApprove: true, approveRisky: true });
2033
+ if (res.status === 'guardrail_applied') {
2034
+ renderQuickResult({
2035
+ title: 'Guardrail updated',
2036
+ subtitle: res.humanMessage || promptText,
2037
+ badgeHtml: statusBadge('guardrail_applied'),
2038
+ kv: [
2039
+ ['Rule summary', (res.guardrail && res.guardrail.summary) || 'Saved to workspace guardrails'],
2040
+ ['Posture', (res.plainEnglishGuardrails && res.plainEnglishGuardrails.postureLabel) || 'Active']
2041
+ ],
2042
+ output: res.plainEnglishGuardrails || res.workspacePolicy,
2043
+ raw: res
2044
+ });
2045
+ setText('quick-run-status', res.humanMessage || 'Guardrail saved');
2046
+ showToast(res.humanMessage || 'Guardrail saved');
2047
+ await refreshOverview();
2048
+ return;
2049
+ }
2050
+ if (res.status === 'memory_saved') {
2051
+ renderQuickResult({
2052
+ title: 'Saved to workspace memory',
2053
+ subtitle: res.humanMessage || promptText,
2054
+ badgeHtml: statusBadge('memory_saved'),
2055
+ kv: [
2056
+ ['Memory', (res.memoryRecord && res.memoryRecord.title) || 'Workspace preference'],
2057
+ ['Auto-fill defaults', Object.keys(res.activeDefaults || {}).map(function (k) { return k + '=' + res.activeDefaults[k]; }).join(', ') || 'Updated']
2058
+ ],
2059
+ output: { activeDefaults: res.activeDefaults, facts: res.temporalFacts },
2060
+ raw: res
2061
+ });
2062
+ setText('quick-run-status', res.humanMessage || 'Saved to workspace memory');
2063
+ showToast(res.humanMessage || 'Saved to workspace memory');
2064
+ await refreshOverview();
2065
+ return;
2066
+ }
2067
+ if ((res.status === 'awaiting_approval' || res.status === 'awaiting_oauth_connection') && res.humanHandoff) {
2068
+ var handoff = res.humanHandoff;
2069
+ var handoffUrl = handoff.url || (res.authHandoff && res.authHandoff.redirectUrl) || '';
2070
+ var handoffSlug = (res.plan && res.plan.toolSlug) || '';
2071
+ renderQuickResult({
2072
+ title: handoff.headline || (res.status === 'awaiting_approval' ? 'Approval needed' : 'Connect app to continue'),
2073
+ subtitle: res.humanMessage || promptText,
2074
+ badgeHtml: statusBadge(res.status),
2075
+ kv: [
2076
+ ['Action', handoffSlug ? formatToolLabel(handoffSlug) : null],
2077
+ ['Plan', res.plan ? shortId(res.plan.id) : null],
2078
+ ['Next step', handoff.instructions || null]
2079
+ ],
2080
+ actionsHtml: handoffUrl ? '<a class="btn btn-primary" href="' + esc(handoffUrl) + '" target="_blank" rel="noopener">' + esc(handoff.ctaLabel || 'Open handoff') + '</a>' : '',
2081
+ output: res.llmGuidance || handoff,
2082
+ raw: res
2083
+ });
2084
+ setText('quick-run-status', handoff.headline || 'This action needs you');
2085
+ showToast(handoff.headline || 'This action needs you');
2086
+ await refreshOverview();
2087
+ return;
2088
+ }
2089
+ var intent = res.intent || res.resolution || {};
2090
+ var toolSlug = intent.toolSlug || (intent.tool && intent.tool.slug) || (res.plan && res.plan.toolSlug) || 'Tool';
2091
+ var toolLabel = (intent.tool && intent.tool.name) || formatToolLabel(toolSlug);
2092
+ var status = res.status || (res.executed ? 'succeeded' : 'planned');
2093
+ var creditCost = res.plan && res.plan.creditCost != null ? res.plan.creditCost : 0;
2094
+ var outPayload = res.run ? res.run.output : (res.authHandoff || res.humanMessage || res.message);
2095
+ var autoFilled = (res.plan && res.plan.memoryContext && res.plan.memoryContext.autoFilledInput) || {};
2096
+ var autoFilledStr = Object.keys(autoFilled).map(function (k) { return k + '=' + autoFilled[k]; }).join(', ');
2097
+ var kvRows = [
2098
+ ['Credits used', creditCost > 0 ? fmt(creditCost) + ' credits' : 'Included (0 credits)'],
2099
+ ['Audit record', res.receipt ? shortId(res.receipt.id) : (res.plan ? shortId(res.plan.id) : 'Recorded')]
2100
+ ];
2101
+ if (autoFilledStr) kvRows.push(['Auto-filled from Memory', autoFilledStr]);
2102
+ renderQuickResult({
2103
+ title: toolLabel,
2104
+ subtitle: res.prompt || promptText,
2105
+ badgeHtml: statusBadge(status),
2106
+ kv: kvRows,
2107
+ output: outPayload,
2108
+ raw: {
2109
+ prompt: res.prompt || promptText,
2110
+ tool: toolSlug,
2111
+ status: status,
2112
+ creditCost: creditCost,
2113
+ autoFilledFromMemory: autoFilledStr ? autoFilled : undefined,
2114
+ planId: res.plan ? res.plan.id : null,
2115
+ runId: res.run ? res.run.id : null,
2116
+ auditRecordId: res.receipt ? res.receipt.id : null,
2117
+ output: outPayload
2118
+ }
2119
+ });
2120
+ setText('quick-run-status', toolLabel + ' · ' + (STATUS_LABELS[status] || status));
2121
+ showToast('Finished ' + toolLabel);
2122
+ await refreshOverview();
2123
+ } catch (err) {
2124
+ box.innerHTML = '<div class="callout" data-tone="danger"><p>' + esc(err.message) + '</p></div>';
2125
+ setText('quick-run-status', 'Muxo could not complete that request.');
2126
+ throw err;
2127
+ }
2128
+ }
2129
+ async function startCheckout(kind) {
2130
+ showToast('Opening Stripe Checkout…');
2131
+ var credits = Math.max(100, Number(byId('topup-credits-input').value || 1000));
2132
+ var res = await post('/api/v1/billing/checkout', { kind: kind, credits: credits });
2133
+ if (res.url) window.location.href = res.url;
2134
+ }
2135
+
2136
+ var FALLBACK_PLANS = [
2137
+ { 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'] },
2138
+ { 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'] },
2139
+ { 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'] }
2140
+ ];
2141
+ var TIER_ORDER = { free: 0, starter: 1, pro: 2 };
2142
+
2143
+ async function planCatalog() {
2144
+ if (!state.planCatalog) {
2145
+ try { state.planCatalog = await api('/api/v1/billing/plans', { anonymous: true }); } catch (e) { state.planCatalog = { plans: FALLBACK_PLANS }; }
2146
+ }
2147
+ return state.planCatalog;
2148
+ }
2149
+
2150
+ async function renderPlanCards() {
2151
+ var box = byId('plan-cards');
2152
+ if (!box) return;
2153
+ var catalog = await planCatalog();
2154
+ var org = (state.boot && state.boot.organization) || {};
2155
+ var billing = (state.boot && state.boot.billing) || {};
2156
+ var tier = String(org.tier || 'free');
2157
+ var interval = state.billingInterval;
2158
+ box.innerHTML = (catalog.plans || FALLBACK_PLANS).map(function (p) {
2159
+ var current = tier === p.tier;
2160
+ var price = p.prices[interval] || p.prices.month;
2161
+ var priceLine = p.tier === 'free'
2162
+ ? '<p class="price">$0 <span>forever</span></p>'
2163
+ : '<p class="price">' + esc(price.label) + ' <span>' + (interval === 'year' ? 'per year · ' + esc(price.perMonthLabel) + '/mo' : 'per month') + '</span></p>';
2164
+ var action;
2165
+ if (current) action = pill('Current plan', 'ok');
2166
+ 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>';
2167
+ else action = '<button class="btn" type="button" data-action="change-plan" data-tier="' + esc(p.tier) + '">Switch to ' + esc(p.name) + '</button>';
2168
+ return '<div class="panel"><div class="panel-header"><div><h2>' + esc(p.name) + '</h2><p>' + esc(p.tagline || '') + '</p></div></div>' +
2169
+ priceLine +
2170
+ '<ul class="plan-list">' + (p.features || []).map(function (f) { return '<li>' + esc(f) + '</li>'; }).join('') + '</ul>' +
2171
+ '<div class="page-actions">' + action + '</div></div>';
2172
+ }).join('');
2173
+ var manage = byId('manage-billing-btn');
2174
+ if (manage) manage.hidden = !billing.canManageBilling;
2175
+ var note = [];
2176
+ if (billing.status === 'comped') note.push('Complimentary plan');
2177
+ else if (billing.status && billing.status !== 'none') note.push('Subscription ' + String(billing.status).replace(/_/g, ' '));
2178
+ if (billing.cancelAtPeriodEnd && billing.renewsAt) note.push('ends ' + String(billing.renewsAt).slice(0, 10));
2179
+ else if (billing.renewsAt && billing.status !== 'comped') note.push('renews ' + String(billing.renewsAt).slice(0, 10));
2180
+ if (billing.paymentFailed) note.push('Payment failed — update your card in Manage billing');
2181
+ if (billing.usage && billing.usage.resetsAt) note.push('Usage resets ' + String(billing.usage.resetsAt).slice(0, 10));
2182
+ setText('billing-status-note', note.join(' · '));
2183
+ }
2184
+
2185
+ function setBillingInterval(interval) {
2186
+ state.billingInterval = interval === 'year' ? 'year' : 'month';
2187
+ document.querySelectorAll('[data-action="billing-interval"]').forEach(function (b) {
2188
+ b.setAttribute('aria-pressed', String(b.dataset.interval === state.billingInterval));
2189
+ });
2190
+ renderPlanCards().catch(showError);
2191
+ }
2192
+
2193
+ async function changePlan(tier) {
2194
+ showToast('Talking to Stripe…');
2195
+ try {
2196
+ var res = await post('/api/v1/billing/upgrade', { tier: tier, interval: state.billingInterval });
2197
+ if (res.portalUrl || (res.action === 'billing_portal' && res.url)) { window.location.href = res.portalUrl || res.url; return; }
2198
+ if (res.url) { window.location.href = res.url; return; }
2199
+ showToast(res.message || 'Plan is unchanged.');
2200
+ await refreshOverview();
2201
+ await renderPlanCards();
2202
+ } catch (err) {
2203
+ var data = err && err.data;
2204
+ if (data && (data.checkoutUrl || data.url)) { window.location.href = data.checkoutUrl || data.url; return; }
2205
+ showError(err);
2206
+ }
2207
+ }
2208
+
2209
+ async function manageBilling() {
2210
+ showToast('Opening the Stripe billing portal…');
2211
+ var res = await post('/api/v1/billing/portal', {});
2212
+ if (res.url) window.location.href = res.url;
2213
+ }
2214
+
2215
+ async function checkCheckoutReturn() {
2216
+ var params = new URLSearchParams(window.location.search);
2217
+ var clean = false;
2218
+ var sessionId = params.get('session_id');
2219
+ if (params.get('checkout') === 'success' && sessionId) {
2220
+ clean = true;
2221
+ if (isSignedIn()) {
2222
+ try {
2223
+ var res = await post('/api/v1/billing/verify-session', { sessionId: sessionId });
2224
+ showToast(res.fulfilled ? 'Payment confirmed. Your workspace has been updated.' : 'Payment is processing. Credits appear as soon as Stripe confirms it.');
2225
+ } catch (err) { showToast(err.message, 'danger'); }
2226
+ } else {
2227
+ showToast('Payment received. Sign in to see your updated balance.');
2228
+ }
2229
+ }
2230
+ if (params.get('checkout') === 'cancelled') { clean = true; showToast('Checkout cancelled. You were not charged.'); }
2231
+ if (params.get('plan') === 'updated') { clean = true; showToast('Your plan change is confirmed.'); }
2232
+ if (params.get('connected')) { clean = true; showToast(params.get('connected') + ' is connected'); }
2233
+ // Keys are never accepted from the URL: a crafted link could sign a person into someone else's workspace.
2234
+ if (params.get('api_key')) clean = true;
2235
+ if (params.get('claimed')) { clean = true; showToast('You now own this workspace (' + params.get('claimed') + ')'); }
2236
+ if (clean) window.history.replaceState({}, '', window.location.pathname);
2237
+ }
2238
+
2239
+ var ACTIONS = {
2240
+ 'toggle-theme': toggleTheme,
2241
+ 'mobile-menu': openMobileMenu,
2242
+ 'sign-out': function () { closeDialog('mobile-menu'); return signOut(); },
2243
+ 'refresh-overview': refreshOverview,
2244
+ 'switch-workspace': switchExternalWorkspacePrompt,
2245
+ 'select-workspace': function (el) { return selectWorkspaceFromUi(el.dataset.id); },
2246
+ 'spawn-agent': spawnAutonomousAgent,
2247
+ 'create-key': generateApiKey,
2248
+ 'revoke-key': function (el) { return revokeApiKey(el.dataset.id, el.dataset.name); },
2249
+ 'use-spawned': useLatestSpawnedKey,
2250
+ 'claim-link': createClaimLinkPrompt,
2251
+ 'workspace-create': function () { return openWorkspaceForm('create'); },
2252
+ 'workspace-join': function () { return openWorkspaceForm('join'); },
2253
+ 'copy': function (el) { return copyFrom(el.dataset.target); },
2254
+ 'dismiss-key': function () {
2255
+ byId('new-key-banner').hidden = true;
2256
+ setText('new-key-value', '');
2257
+ latestSpawnedKey = '';
2258
+ },
2259
+ 'run-tool': function (el) { return openExecuteModal(el.dataset.slug); },
2260
+ 'quick-run': function (el) { return quickRun(el.dataset.slug, JSON.parse(el.dataset.input || '{}')); },
2261
+ 'ask-suggest': function (el) { return fillAndRunNl(el.dataset.prompt); },
2262
+ 'lane': function (el) { setLane(el.dataset.lane); return loadCatalog(); },
2263
+ 'browse-lane': function (el) { setLane(el.dataset.lane); navigate('catalog'); },
2264
+ 'reset-catalog': function () { return resetCatalog(false); },
2265
+ 'clear-search': function () { return resetCatalog(true); },
2266
+ 'retry-catalog': loadCatalog,
2267
+ 'connect-app': function (el) { return startAppConnection(el.dataset.provider); },
2268
+ 'sync-connections': function () { return loadConnections(true); },
2269
+ 'wait-connection': function (el) { return waitConnectionInUi(el.dataset.provider, el.dataset.account); },
2270
+ 'complete-handoff': function (el) { return completeCallbackInUi(el.dataset.provider, el.dataset.account); },
2271
+ 'provision': function () { setLane('stripe_projects'); navigate('catalog'); },
2272
+ 'export-env': pullWorkspaceEnv,
2273
+ 'close-env': function () { byId('env-export-box').hidden = true; setText('env-export-content', ''); },
2274
+ 'open-embedded-onboarding': openEmbeddedOnboarding,
2275
+ 'open-hosted-onboarding': openHostedOnboardingLink,
2276
+ 'close-embedded-onboarding': function () { byId('stripe-embedded-onboarding-box').hidden = true; byId('stripe-embedded-onboarding-mount').innerHTML = ''; },
2277
+ 'refresh-onboarding-status': loadProvisioningOnboardingStatus,
2278
+ 'rotate': function (el) { return rotateResource(el.dataset.name); },
2279
+ 'refresh-memory': loadMemory,
2280
+ 'delete-memory': function (el) { return deleteMemoryItem(el.dataset.id); },
2281
+ 'facts-mode': function (el) { setFactsMode(el.dataset.mode); },
2282
+ 'teach-chip': function (el) { return teachMemoryFromUi(el.dataset.prompt); },
2283
+ 'guardrail-chip': function (el) { return applyGuardrailFromUi(el.dataset.prompt); },
2284
+ 'guardrail-reset': resetGuardrailsFromUi,
2285
+ 'inspect-plan': function (el) { return inspectPlan(el.dataset.id); },
2286
+ 'reject-plan': function (el) { return rejectPlan(el.dataset.id); },
2287
+ 'approve-plan': function (el) { return approveAndRun(el.dataset.id); },
2288
+ 'billing-interval': function (el) { setBillingInterval(el.dataset.interval); },
2289
+ 'change-plan': function (el) { return changePlan(el.dataset.tier); },
2290
+ 'manage-billing': function () { return manageBilling(); },
2291
+ 'execute-plan': function (el) { return executeExistingPlan(el.dataset.id); },
2292
+ 'kill-resume': resumeKillSwitch,
2293
+ 'refresh-governance': loadPlans,
2294
+ 'refresh-runs': function () { return Promise.all([loadRuns(), loadPlans()]); },
2295
+ 'inspect-receipt': function (el) { return inspectReceipt(el.dataset.id); },
2296
+ 'checkout': function (el) { return startCheckout(el.dataset.kind); },
2297
+ 'delete-byok': function (el) { return deleteByokKeyFromUi(el.dataset.provider); },
2298
+ 'plan-only': submitPlanOnly,
2299
+ 'plan-run': submitPlanAndExecute,
2300
+ 'wait-resume': waitAndResumeModalPlan,
2301
+ 'callback-resume': completeCallbackAndResumeModalPlan,
2302
+ 'close-dialog': function (el) { var d = el.closest('dialog'); if (d) d.close(); },
2303
+ 'auth-mode': function (el) { navigate(el.dataset.mode === 'signup' ? 'signup' : 'login'); }
2304
+ };
2305
+
2306
+ function onClick(e) {
2307
+ var nav = e.target.closest('a[data-nav]');
2308
+ if (nav && e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {
2309
+ e.preventDefault();
2310
+ navigate(nav.dataset.nav);
2311
+ return;
2312
+ }
2313
+ var el = e.target.closest('[data-action]');
2314
+ if (!el) return;
2315
+ if (el.tagName === 'A' && (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)) return;
2316
+ var fn = ACTIONS[el.dataset.action];
2317
+ if (!fn) return;
2318
+ e.preventDefault();
2319
+ withBusy(el, function () { return fn(el, e); }).catch(showError);
2320
+ }
2321
+ function onSubmit(id, fn) {
2322
+ var form = byId(id);
2323
+ if (!form) return;
2324
+ form.addEventListener('submit', function (e) {
2325
+ e.preventDefault();
2326
+ var btn = e.submitter || form.querySelector('[type=submit]');
2327
+ withBusy(btn, function () { return fn(e); }).catch(showError);
2328
+ });
2329
+ }
2330
+
2331
+ async function boot(initial) {
2332
+ await loadAuth();
2333
+ try { await checkCheckoutReturn(); } catch (err) { console.error(err); }
2334
+ var clerkAuth = authConfig.provider === 'clerk';
2335
+ if (initial === 'login' || initial === 'signup') {
2336
+ if (clerkAuth && isSignedIn()) navigate('overview');
2337
+ else if (clerkAuth) mountClerkAuth(initial);
2338
+ return;
2339
+ }
2340
+ if (clerkAuth && !isSignedIn()) { navigate('login', { focus: false }); return; }
2341
+ try {
2342
+ await refreshOverview();
2343
+ } catch (err) {
2344
+ if (err.status === 401 || err.status === 403) return;
2345
+ showError(err);
2346
+ }
2347
+ if (initial !== 'overview' && initial !== 'setup' && initial !== 'workspaces') loadView(initial);
2348
+ loadByokKeys().catch(function () {});
2349
+ }
2350
+ function init() {
2351
+ applyTheme(document.documentElement.dataset.theme === 'light' ? 'light' : 'dark');
2352
+ document.querySelectorAll('dialog.sheet').forEach(function (d) {
2353
+ d.addEventListener('click', function (e) { if (e.target === d) d.close(); });
2354
+ });
2355
+ byId('mobile-menu').addEventListener('close', function () { byId('mobile-menu-toggle').setAttribute('aria-expanded', 'false'); });
2356
+ onSubmit('nl-form', runNaturalLanguagePrompt);
2357
+ onSubmit('auth-form', submitAuth);
2358
+ onSubmit('custom-provider-form', startCustomProviderConnection);
2359
+ onSubmit('memory-search-form', loadMemory);
2360
+ onSubmit('memory-form', submitNewMemory);
2361
+ onSubmit('memory-teach-form', function () { return teachMemoryFromUi(); });
2362
+ onSubmit('forget-form', forgetMatching);
2363
+ onSubmit('guardrail-form', function () { return applyGuardrailFromUi(); });
2364
+ onSubmit('kill-form', activateKillSwitch);
2365
+ onSubmit('policy-form', savePolicy);
2366
+ onSubmit('scan-form', scanContextFromUi);
2367
+ onSubmit('byok-form', saveByokKeyFromUi);
2368
+ onSubmit('join-workspace-form', joinWorkspaceFromForm);
2369
+ onSubmit('create-workspace-form', createWorkspaceFromForm);
2370
+ onSubmit('team-onboard-form', onboardTeamFromForm);
2371
+ var searchTimer = null;
2372
+ var searchInput = byId('catalog-search');
2373
+ if (searchInput) {
2374
+ searchInput.addEventListener('input', function () {
2375
+ byId('clear-catalog-search').hidden = !searchInput.value.trim();
2376
+ clearTimeout(searchTimer);
2377
+ searchTimer = setTimeout(function () { loadCatalog().catch(showError); }, 280);
2378
+ });
2379
+ }
2380
+ document.addEventListener('click', onClick);
2381
+ window.addEventListener('popstate', function () {
2382
+ var next = viewForPath(location.pathname);
2383
+ // Clerk's hash routing fires popstate inside the sign-in view; only react to real page changes.
2384
+ if (next === state.view) return;
2385
+ navigate(next, { push: false, focus: false });
2386
+ });
2387
+ window.addEventListener('storage', function (e) { if (e.key === THEME_KEY && (e.newValue === 'dark' || e.newValue === 'light')) applyTheme(e.newValue); });
2388
+ window.addEventListener('message', function (e) {
2389
+ if (!e || !e.data || e.data.type !== 'muxo:oauth_complete') return;
2390
+ var prov = e.data.providerName || e.data.provider || 'App';
2391
+ if (pendingModalHandoff && pendingModalPlan) {
2392
+ waitAndResumeModalPlan();
2393
+ } else {
2394
+ showToast(prov + ' connected to Muxo');
2395
+ refreshOverview();
2396
+ }
2397
+ });
2398
+ try {
2399
+ var connectedParam = new URLSearchParams(location.search || '').get('connected');
2400
+ if (connectedParam) {
2401
+ showToast('Connected ' + connectedParam + ' to Muxo');
2402
+ if (history && history.replaceState) history.replaceState({}, '', location.pathname);
2403
+ }
2404
+ } catch (e) {}
2405
+ var initial = viewForPath(location.pathname);
2406
+ navigate(initial, { push: false, focus: false, skipLoad: true });
2407
+ boot(initial);
2408
+ }
2409
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();
2410
+ `;
2411
+ function navItem(view, label, icon, countId) {
2412
+ const path = Object.entries(CONSOLE_ROUTES).find(([, v]) => v === view)?.[0] ?? "/";
2413
+ return `<a class="nav-item" href="${path}" data-nav="${view}">${uiIcon(icon)}<span>${label}</span>${countId ? `<span id="${countId}" class="count"></span>` : ""}</a>`;
2414
+ }
2415
+ function consoleNavigation(withCounts = true) {
2416
+ return `<nav class="nav-group" aria-label="Console">
2417
+ <p>Workspace</p>
2418
+ ${navItem("overview", "Overview", "home")}
2419
+ ${navItem("catalog", "Tools", "grid")}
2420
+ ${navItem("activity", "Activity", "activity", withCounts ? "nav-run-count" : undefined)}
2421
+ </nav>
2422
+ <nav class="nav-group" aria-label="Manage">
2423
+ <p>Manage</p>
2424
+ ${navItem("setup", "API access", "key")}
2425
+ ${navItem("workspaces", "Workspaces", "users")}
2426
+ ${navItem("billing", "Billing", "wallet")}
2427
+ </nav>
2428
+ <nav class="nav-group" aria-label="Resources">
2429
+ <p>Resources</p>
2430
+ <a class="nav-item" href="${DOCS_URL}/quickstart">${uiIcon("book")}<span>Documentation</span>${uiIcon("external")}</a>
2431
+ </nav>`;
2432
+ }
2433
+ export function renderConsoleHtml() {
2434
+ const scriptPrelude = `var ROUTES = ${JSON.stringify(CONSOLE_ROUTES)};` +
2435
+ `var THEME_KEY = ${JSON.stringify(THEME_STORAGE_KEY)};` +
2436
+ `var THEME_BG = ${JSON.stringify({ light: muxoThemes.light.bg, dark: muxoThemes.dark.bg })};`;
2437
+ return `<!DOCTYPE html>
2438
+ <html lang="en" data-theme="dark">
2439
+ <head>
2440
+ <meta charset="utf-8" />
2441
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
2442
+ <title>Muxo Console</title>
2443
+ <meta name="description" content="Manage the tools, approvals, memory, and billing your agents use through Muxo." />
2444
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
2445
+ <meta name="color-scheme" content="dark light" />
2446
+ <meta name="robots" content="noindex" />
2447
+ <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
2448
+ <link rel="apple-touch-icon" href="https://muxo.ai/apple-touch-icon.png" />
2449
+ <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
2450
+ <style>${muxoBaseCss}${consoleCss}</style>
2451
+ </head>
2452
+ <body>
2453
+ <a class="skip-link" href="#main">Skip to content</a>
2454
+ <header class="console-topbar">
2455
+ <div class="topbar-in">
2456
+ <button id="mobile-menu-toggle" class="icon-button mobile-menu-toggle" type="button" data-action="mobile-menu" aria-label="Open navigation" aria-haspopup="dialog" aria-controls="mobile-menu" aria-expanded="false">${uiIcon("menu")}</button>
2457
+ <a class="brand" href="/" data-nav="overview" aria-label="Muxo console home">${muxoMark}<span>Muxo</span><span class="brand-section">Console</span></a>
2458
+ <button class="workspace-chip" type="button" data-action="switch-workspace" aria-label="Switch workspace" aria-haspopup="dialog">
2459
+ <span id="workspace-name">Workspace</span><span id="workspace-tier" class="tier">Free plan</span>${uiIcon("chevron")}
2460
+ </button>
2461
+ <nav class="topbar-links" aria-label="Muxo">
2462
+ <a class="nav-link" href="${DOCS_URL}">Docs</a>
2463
+ <button id="theme-toggle" class="icon-button" type="button" data-action="toggle-theme" aria-pressed="true" aria-label="Switch to light mode">${uiIcon("sun", "theme-sun")}${uiIcon("moon", "theme-moon")}</button>
2464
+ <a id="account-signin" class="btn btn-primary" href="/login" data-nav="login">Sign in</a>
2465
+ <div id="account-signed-in" class="account" hidden>
2466
+ <span id="account-label" class="account-label"></span>
2467
+ <button class="btn" type="button" data-action="sign-out">Sign out</button>
2468
+ </div>
2469
+ </nav>
2470
+ </div>
2471
+ </header>
2472
+
2473
+ <div id="console-shell" class="console-layout">
2474
+ <aside class="console-sidebar" aria-label="Console">
2475
+ ${consoleNavigation()}
2476
+ <div class="sidebar-usage">
2477
+ <div class="usage-row"><span>Monthly actions</span><strong id="meter-actions-text">0 / 1,000</strong></div>
2478
+ <div class="meter" role="presentation"><span id="meter-actions-bar" style="width:0%"></span></div>
2479
+ <div class="usage-row"><span>Credits</span><strong id="meter-credits-text">0</strong></div>
2480
+ <a href="/billing" data-nav="billing">Add credits</a>
2481
+ </div>
2482
+ </aside>
2483
+
2484
+ <main id="main" class="console-main" tabindex="-1">
2485
+ <section class="page" data-view="overview" aria-labelledby="h-overview">
2486
+ <header class="page-header overview-header">
2487
+ <div class="overview-title">
2488
+ <h1 id="h-overview">Overview</h1>
2489
+ <div class="page-actions">
2490
+ <button class="icon-button" type="button" data-action="refresh-overview" aria-label="Refresh overview" title="Refresh overview">${uiIcon("refresh")}</button>
2491
+ <a class="btn btn-primary" href="/catalog" data-nav="catalog">${uiIcon("grid")}Browse tools</a>
2492
+ </div>
2493
+ </div>
2494
+ <p class="lede">Monitor agent activity and approvals.</p>
2495
+ </header>
2496
+ <div class="stack">
2497
+ <div class="metrics">
2498
+ <a class="metric metric-link" href="/activity" data-nav="activity"><p class="metric-label">Monthly actions ${uiIcon("arrow")}</p><p class="metric-value" id="kpi-actions">—</p><p class="metric-sub" id="kpi-actions-sub">Monthly usage</p></a>
2499
+ <a class="metric metric-link" href="/activity" data-nav="activity"><p class="metric-label">Needs your review ${uiIcon("arrow")}</p><p class="metric-value" id="kpi-pending">—</p><p class="metric-sub" id="kpi-pending-sub">Waiting for your review</p></a>
2500
+ <a class="metric metric-link" href="/catalog" data-nav="catalog"><p class="metric-label">Connected services ${uiIcon("arrow")}</p><p class="metric-value" id="kpi-connections">—</p><p class="metric-sub">Apps &amp; cloud resources</p></a>
2501
+ <a class="metric metric-link" href="/billing" data-nav="billing"><p class="metric-label">Available credits ${uiIcon("arrow")}</p><p class="metric-value" id="kpi-credits">—</p><p class="metric-sub" id="kpi-tier-badge">Workspace balance</p></a>
2502
+ </div>
2503
+
2504
+ <div id="approval-notice" class="approval-notice" hidden>
2505
+ <div><strong id="approval-notice-title"></strong><p>Review the plan before it runs.</p></div>
2506
+ <a class="btn btn-sm" href="/activity" data-nav="activity">Review ${uiIcon("arrow")}</a>
2507
+ </div>
2508
+
2509
+ <div class="dashboard-layout">
2510
+ <div class="panel">
2511
+ <div class="panel-header">
2512
+ <div><h2>Recent governed actions</h2><p>Tool activity and its audit trail.</p></div>
2513
+ <a class="btn btn-quiet btn-sm" href="/activity" data-nav="activity">View all ${uiIcon("arrow")}</a>
2514
+ </div>
2515
+ <div id="recent-runs" aria-live="polite"><p class="empty-note">Loading activity…</p></div>
2516
+ </div>
2517
+ <div class="dashboard-aside">
2518
+ <div class="panel">
2519
+ <div class="panel-header">
2520
+ <div><h2 id="recent-tools-title">Tools</h2></div>
2521
+ <a class="docs-link" href="/catalog" data-nav="catalog">Browse ${uiIcon("arrow")}</a>
2522
+ </div>
2523
+ <div id="recent-tools"><p class="empty-note">Loading tools…</p></div>
2524
+ </div>
2525
+ <div class="panel">
2526
+ <div class="workspace-summary"><span class="workspace-avatar">${uiIcon("users")}</span><div><h2 id="overview-workspace-name">Workspace</h2><p id="overview-workspace-tier"></p></div></div>
2527
+ <div class="summary-links">
2528
+ <a class="summary-link" href="/workspaces" data-nav="workspaces">${uiIcon("users")}Manage workspace &amp; team${uiIcon("chevron")}</a>
2529
+ <a class="summary-link" href="/keys" data-nav="setup">${uiIcon("key")}API access${uiIcon("chevron")}</a>
2530
+ <a class="summary-link" href="${DOCS_URL}/quickstart">${uiIcon("book")}Agent setup docs${uiIcon("external")}</a>
2531
+ </div>
2532
+ </div>
2533
+ </div>
2534
+ </div>
2535
+ </div>
2536
+ </section>
2537
+
2538
+ <section class="page" data-view="catalog" aria-labelledby="h-catalog" hidden>
2539
+ <header class="page-header">
2540
+ <div><h1 id="h-catalog">Tools</h1><p class="lede">Find tools for your agents. Every action follows your workspace guardrails.</p></div>
2541
+ <a class="btn" href="${DOCS_URL}">${uiIcon("book")}Documentation</a>
2542
+ </header>
2543
+ <div class="toolbar catalog-toolbar">
2544
+ <div class="search-field">${uiIcon("search")}<input id="catalog-search" type="search" aria-label="Search tools" placeholder="Search tools or providers…" autocomplete="off" /><button id="clear-catalog-search" class="icon-button clear-search" type="button" data-action="clear-search" aria-label="Clear tool search" hidden>${uiIcon("close")}</button></div>
2545
+ <div id="catalog-lanes" class="segmented" role="group" aria-label="Tool category">
2546
+ <button type="button" data-action="lane" data-lane="all" aria-pressed="true">All tools</button>
2547
+ <button type="button" data-action="lane" data-lane="composio_instant" aria-pressed="false">Instant tools</button>
2548
+ <button type="button" data-action="lane" data-lane="composio_sdk" aria-pressed="false">Connected apps</button>
2549
+ <button type="button" data-action="lane" data-lane="stripe_projects" aria-pressed="false">Cloud services</button>
2550
+ </div>
2551
+ </div>
2552
+ <p id="catalog-status" class="toolbar-note" role="status"></p>
2553
+ <div id="catalog-grid" class="tool-grid"></div>
2554
+ </section>
2555
+
2556
+ <section class="page" data-view="setup" aria-labelledby="h-setup" hidden>
2557
+ <header class="page-header">
2558
+ <div>
2559
+ <h1 id="h-setup">API access</h1>
2560
+ <p class="lede">Manage credentials for your agents and apps.</p>
2561
+ </div>
2562
+ <a class="btn" href="${DOCS_URL}/quickstart">${uiIcon("book")}Setup docs</a>
2563
+ </header>
2564
+ <div class="stack">
2565
+ <div class="workspace-context">
2566
+ <div class="workspace-summary"><span class="workspace-avatar">${uiIcon("key")}</span><div><p class="section-label">Active workspace</p><h2 id="access-workspace-name">Workspace</h2></div></div>
2567
+ <a class="docs-link" href="/workspaces" data-nav="workspaces">Manage workspaces ${uiIcon("arrow")}</a>
2568
+ </div>
2569
+ <div class="access-layout">
2570
+ <div class="panel">
2571
+ <div class="panel-header">
2572
+ <div><h2>Workspace keys</h2><p>New keys are shown once. Store them securely.</p></div>
2573
+ <button class="btn btn-primary" type="button" data-action="create-key">${uiIcon("plus")}Create key</button>
2574
+ </div>
2575
+ <div id="new-key-banner" class="callout" data-tone="ok" style="margin-bottom:12px" hidden>
2576
+ <button class="icon-button key-dismiss" type="button" data-action="dismiss-key" aria-label="Hide new key" title="Hide this key after saving it">${uiIcon("close")}</button>
2577
+ <p id="new-key-title"><strong>Copy this key now.</strong></p>
2578
+ <code id="new-key-value" class="key-value"></code>
2579
+ <div class="callout-actions">
2580
+ <button class="btn btn-sm" type="button" data-action="copy" data-target="new-key-value">${uiIcon("copy")}Copy</button>
2581
+ <button id="use-spawned-key-btn" class="btn btn-sm" type="button" data-action="use-spawned" hidden>Open this workspace</button>
2582
+ <button class="btn btn-sm btn-quiet" type="button" data-action="claim-link">Create owner claim link</button>
2583
+ </div>
2584
+ </div>
2585
+ <div id="api-keys-list" class="rows"></div>
2586
+ </div>
2587
+ <div class="panel">
2588
+ <div class="panel-header">
2589
+ <div><h2>Endpoints</h2><p>Use with your workspace key.</p></div>
2590
+ </div>
2591
+ <div class="endpoint"><div><strong>REST API</strong><button class="btn btn-quiet btn-sm" type="button" data-action="copy" data-target="api-base-url" aria-label="Copy REST API endpoint">${uiIcon("copy")}</button></div><code id="api-base-url"></code></div>
2592
+ <div class="endpoint"><div><strong>MCP</strong><button class="btn btn-quiet btn-sm" type="button" data-action="copy" data-target="api-mcp-url" aria-label="Copy MCP endpoint">${uiIcon("copy")}</button></div><code id="api-mcp-url"></code></div>
2593
+ <div class="summary-links">
2594
+ <a class="summary-link" href="${DOCS_URL}/quickstart">${uiIcon("book")}Agent &amp; SDK setup${uiIcon("external")}</a>
2595
+ <a class="summary-link" href="/workspaces" data-nav="workspaces">${uiIcon("users")}Team access${uiIcon("chevron")}</a>
2596
+ </div>
2597
+ </div>
2598
+ </div>
2599
+ </div>
2600
+ </section>
2601
+
2602
+ <section class="page" data-view="workspaces" aria-labelledby="h-workspaces" hidden>
2603
+ <header class="page-header">
2604
+ <div><h1 id="h-workspaces">Workspaces</h1><p class="lede">Manage your workspaces and give your team access.</p></div>
2605
+ <div class="page-actions">
2606
+ <button class="btn" type="button" data-action="workspace-join">${uiIcon("link")}Join workspace</button>
2607
+ <button class="btn btn-primary" type="button" data-action="workspace-create">${uiIcon("plus")}New workspace</button>
2608
+ </div>
2609
+ </header>
2610
+ <div class="stack">
2611
+ <div class="workspace-context">
2612
+ <div class="workspace-summary"><span class="workspace-avatar">${uiIcon("users")}</span><div><p class="section-label">Active workspace</p><h2 id="workspace-slug">Workspace</h2></div></div>
2613
+ <span id="workspace-auth-badge" class="pill">Signed in</span>
2614
+ </div>
2615
+ <div class="panel">
2616
+ <div class="panel-header"><div><h2>Your workspaces</h2><p id="workspace-count"></p></div></div>
2617
+ <div id="workspaces-list" class="rows"><p class="empty-note">Loading workspaces…</p></div>
2618
+ <div class="row" id="reset-workspace-row" hidden><div><strong>Using an API key</strong><small>Remove this key from your browser when you’re finished.</small></div><button id="reset-workspace-btn" class="btn btn-sm" type="button" data-action="sign-out">Forget key</button></div>
2619
+ </div>
2620
+ <div class="panel">
2621
+ <div class="panel-header">
2622
+ <div><h2>Onboard a new team</h2><p>Create a team key for this workspace or a separate workspace.</p></div>
2623
+ </div>
2624
+ <form id="team-onboard-form" class="team-form">
2625
+ <label class="field"><span>Team name</span><input id="team-onboard-name" type="text" placeholder="e.g. Platform Engineering" autocomplete="off" required /></label>
2626
+ <label class="field"><span>Workspace</span>
2627
+ <select id="team-onboard-mode" aria-describedby="team-workspace-help">
2628
+ <option value="shared_workspace">Use this workspace</option>
2629
+ <option value="dedicated_workspace">Create a separate workspace</option>
2630
+ </select>
2631
+ <small id="team-workspace-help" class="field-help">A separate workspace keeps tools, connections, and memory isolated.</small>
2632
+ </label>
2633
+ <label class="field"><span>Role</span>
2634
+ <select id="team-onboard-role">
2635
+ <option value="operator">Operator</option>
2636
+ <option value="admin">Admin</option>
2637
+ <option value="member">Member</option>
2638
+ </select>
2639
+ <small class="field-help">Operators run tools. Admins also manage keys and guardrails.</small>
2640
+ </label>
2641
+ <div class="team-form-footer"><p>Share the key privately. You can revoke it in API access.</p><button class="btn btn-primary" type="submit">${uiIcon("plus")}Create team access</button></div>
2642
+ </form>
2643
+ <div id="team-kit-box" class="callout" data-tone="ok" style="margin-top:16px" hidden>
2644
+ <p><strong id="team-kit-title">Team access is ready</strong></p>
2645
+ <p id="team-kit-workspace" class="small muted"></p>
2646
+ <p class="small muted" style="margin-top:8px">Copy this key now. It won’t be shown again.</p>
2647
+ <div class="team-kit-secret"><code id="team-kit-key" class="mono"></code><button class="btn btn-sm" type="button" data-action="copy" data-target="team-kit-key">${uiIcon("copy")}Copy key</button></div>
2648
+ <details class="recall-raw" style="margin-top:10px">
2649
+ <summary>Share setup details</summary>
2650
+ <div class="team-kit-secret"><code id="team-kit-cli" class="mono"></code><button class="btn btn-sm" type="button" data-action="copy" data-target="team-kit-cli">${uiIcon("copy")}Copy command</button></div>
2651
+ <pre id="team-kit-instructions" class="code" style="margin-top:8px"></pre>
2652
+ <div class="callout-actions" style="margin-top:8px">
2653
+ <button class="btn btn-sm" type="button" data-action="copy" data-target="team-kit-instructions">${uiIcon("copy")}Copy instructions</button>
2654
+ </div>
2655
+ </details>
2656
+ </div>
2657
+ </div>
2658
+ </div>
2659
+ </section>
2660
+
2661
+ <section class="page" data-view="activity" aria-labelledby="h-activity" hidden>
2662
+ <header class="page-header">
2663
+ <div>
2664
+ <h1 id="h-activity">Activity</h1>
2665
+ <p class="lede">Review actions, approvals, and signed audit records.</p>
2666
+ </div>
2667
+ <div class="page-actions"><button class="btn" type="button" data-action="refresh-runs">${uiIcon("refresh")}Refresh</button></div>
2668
+ </header>
2669
+ <div class="stack">
2670
+ <div class="metrics">
2671
+ <div class="metric"><p class="metric-label">Agents</p><p class="metric-value" id="gov-kill-status" data-tone="ok">Running</p><p class="metric-sub" id="gov-kill-sub">Agents can act within policy</p></div>
2672
+ <div class="metric"><p class="metric-label">Trust score</p><p class="metric-value" id="gov-trust">—</p><p class="metric-sub" id="gov-trust-sub">of 1,000</p></div>
2673
+ <div class="metric"><p class="metric-label">Audit log</p><p class="metric-value" id="gov-chain-badge" data-tone="ok">Verified</p><p class="metric-sub" id="gov-chain-sub">Signed entries</p></div>
2674
+ <div class="metric"><p class="metric-label">Needs your review</p><p class="metric-value" id="gov-pending">0</p><p class="metric-sub">Plans held by guardrails</p></div>
2675
+ </div>
2676
+
2677
+ <div class="table-wrap">
2678
+ <table>
2679
+ <thead><tr><th>Run</th><th>Tool</th><th>Risk</th><th>Status</th><th>Duration</th><th>Audit record</th><th class="right"><span class="sr-only">Actions</span></th></tr></thead>
2680
+ <tbody id="runs-tbody"></tbody>
2681
+ </table>
2682
+ </div>
2683
+
2684
+ <div class="split even">
2685
+ <div class="panel">
2686
+ <div class="panel-header"><div><h2>Approval queue</h2><p>Plans held by your workspace guardrails awaiting owner approval.</p></div></div>
2687
+ <div class="table-wrap">
2688
+ <table>
2689
+ <thead><tr><th>Plan</th><th>Tool</th><th>Risk</th><th>Status</th><th class="right">Actions</th></tr></thead>
2690
+ <tbody id="plans-tbody"></tbody>
2691
+ </table>
2692
+ </div>
2693
+ </div>
2694
+ <div class="panel">
2695
+ <div class="panel-header">
2696
+ <div><h2>Guardrails &amp; memory</h2><p>Rules and defaults for this workspace.</p></div>
2697
+ <span id="guardrail-posture-pill" class="pill dot" data-tone="ok">Safe default</span>
2698
+ </div>
2699
+ <div id="guardrails-plain-box" class="recall-box" style="min-height:0" aria-live="polite"><p class="empty-note">Loading active guardrails…</p></div>
2700
+ <div id="memory-active-defaults-box" class="recall-box" style="min-height:0;margin-top:12px" aria-live="polite"></div>
2701
+ <div class="rows" style="margin-top:12px">
2702
+ <div class="row"><div><strong>Safety modules on every run</strong><div id="gov-modules" class="module-list" style="margin-top:8px"></div></div></div>
2703
+ </div>
2704
+ </div>
2705
+ </div>
2706
+ </div>
2707
+ </section>
2708
+
2709
+ <section class="page" data-view="billing" aria-labelledby="h-billing" hidden>
2710
+ <header class="page-header">
2711
+ <div>
2712
+ <h1 id="h-billing">Billing and credits</h1>
2713
+ <p class="lede">Pay with Muxo credits, or bring your own provider keys and those runs cost you nothing on Muxo.</p>
2714
+ </div>
2715
+ <div class="page-actions"><a class="btn" href="${PRICING_URL}">${uiIcon("external")}Pricing details</a></div>
2716
+ </header>
2717
+ <div class="stack">
2718
+ <div class="metrics">
2719
+ <div class="metric"><p class="metric-label">Plan</p><p class="metric-value" id="billing-plan">Free plan</p><p class="metric-sub">Change anytime</p></div>
2720
+ <div class="metric"><p class="metric-label">Credits</p><p class="metric-value" id="billing-credits">0</p><p class="metric-sub">$0.01 each</p></div>
2721
+ <div class="metric"><p class="metric-label">Spent with Muxo</p><p class="metric-value" id="billing-retail">$0.00</p><p class="metric-sub">Credits used on runs</p></div>
2722
+ <div class="metric"><p class="metric-label">Credits used</p><p class="metric-value" id="billing-wholesale">0 credits</p><p class="metric-sub">Consolidated on one bill</p></div>
2723
+ </div>
2724
+ <div class="split even">
2725
+ <div class="panel" style="grid-column:1/-1">
2726
+ <div class="panel-header">
2727
+ <div><h2>Plans</h2><p>Every plan includes approvals, the audit log, and workspace memory. Yearly billing gives you 2 months free.</p></div>
2728
+ <div class="row-actions" role="group" aria-label="Billing period">
2729
+ <button class="chip" type="button" data-action="billing-interval" data-interval="month" aria-pressed="true">Monthly</button>
2730
+ <button class="chip" type="button" data-action="billing-interval" data-interval="year" aria-pressed="false">Yearly · 2 months free</button>
2731
+ </div>
2732
+ </div>
2733
+ <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>
2734
+ <div class="page-actions" style="margin-top:16px;align-items:center">
2735
+ <button class="btn" type="button" data-action="manage-billing" id="manage-billing-btn" hidden>Manage billing in Stripe</button>
2736
+ <small class="muted" id="billing-status-note"></small>
2737
+ </div>
2738
+ </div>
2739
+ <div class="panel">
2740
+ <div class="panel-header"><div><h2>Add credits</h2><p>Credits pay for instant tools, connected apps, and provisioning.</p></div></div>
2741
+ <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>
2742
+ <div class="page-actions" style="margin-top:16px">
2743
+ <button class="btn btn-primary" type="button" data-action="checkout" data-kind="credit_topup">Pay with Stripe</button>
2744
+ </div>
2745
+ </div>
2746
+ </div>
2747
+ <div class="panel">
2748
+ <div class="panel-header"><div><h2>Bring your own keys</h2><p>Use your own provider keys. They’re stored securely in your vault.</p></div><span id="byok-mode-badge" class="pill">Muxo credits</span></div>
2749
+ <form id="byok-form" class="form-row provider-key-form">
2750
+ <label class="field"><span>Provider</span>
2751
+ <select id="byok-provider-select">
2752
+ <option value="openai">OpenAI (plain-English routing)</option>
2753
+ <option value="anthropic">Anthropic (plain-English routing)</option>
2754
+ <option value="openrouter">OpenRouter (plain-English routing)</option>
2755
+ <option value="gemini">Google Gemini (plain-English routing)</option>
2756
+ <option value="composio">Apps and instant tools (0 credits)</option>
2757
+ <option value="stripe">Cloud infrastructure (0 credits)</option>
2758
+ <option value="supermemory">Workspace memory</option>
2759
+ <option value="firecrawl">Firecrawl (scrape and crawl)</option>
2760
+ <option value="exa">Exa (search)</option>
2761
+ </select>
2762
+ </label>
2763
+ <label class="field"><span>Key</span><input id="byok-key-input" type="password" placeholder="Paste the key" autocomplete="off" /></label>
2764
+ <button class="btn btn-primary" type="submit" style="min-height:40px">Save key</button>
2765
+ </form>
2766
+ <div class="table-wrap">
2767
+ <table>
2768
+ <thead><tr><th>Provider</th><th>Used for</th><th>Key</th><th class="right"><span class="sr-only">Actions</span></th></tr></thead>
2769
+ <tbody id="byok-keys-tbody"></tbody>
2770
+ </table>
2771
+ </div>
2772
+ </div>
2773
+ <div class="split even">
2774
+ <div class="panel">
2775
+ <div class="panel-header"><div><h2>Usage by category</h2><p>All provider usage is settled through your workspace balance.</p></div></div>
2776
+ <div id="settlement-summary-box"><p class="empty-note">Loading…</p></div>
2777
+ </div>
2778
+ <div class="table-wrap">
2779
+ <table>
2780
+ <thead><tr><th>Time</th><th>Event</th><th>Credits</th><th>Balance</th></tr></thead>
2781
+ <tbody id="ledger-tbody"></tbody>
2782
+ </table>
2783
+ </div>
2784
+ </div>
2785
+ </div>
2786
+ </section>
2787
+ </main>
2788
+ </div>
2789
+
2790
+ <div id="auth-view" class="auth-view" hidden>
2791
+ <div class="auth-card oauth-card">
2792
+ <div class="oauth-card-top">
2793
+ <span class="oauth-brand">${muxoMark}<span>Muxo</span></span>
2794
+ <span class="oauth-step-tag">Workspace access</span>
2795
+ </div>
2796
+ <div class="oauth-card-body">
2797
+ <h1 id="auth-title">Sign in to Muxo</h1>
2798
+ <p id="auth-lede" class="lede">Manage tools, approvals, memory, and billing for your agents.</p>
2799
+ <div id="auth-tabs" class="segmented" role="group" aria-label="Account">
2800
+ <button type="button" data-action="auth-mode" data-mode="login" aria-pressed="true">Sign in</button>
2801
+ <button type="button" data-action="auth-mode" data-mode="signup" aria-pressed="false">Create account</button>
2802
+ </div>
2803
+ <form id="auth-form" class="auth-form" novalidate>
2804
+ <label id="auth-name-field" class="field" hidden><span>Name</span><input id="auth-name" type="text" autocomplete="name" /></label>
2805
+ <label class="field"><span>Email</span><input id="auth-email" type="email" autocomplete="email" required /></label>
2806
+ <label class="field"><span>Password</span><input id="auth-password" type="password" autocomplete="current-password" minlength="6" required /></label>
2807
+ <label id="auth-workspace-field" class="field" hidden><span>Workspace name <em>optional</em></span><input id="auth-workspace" type="text" autocomplete="organization" /></label>
2808
+ <p id="auth-error" class="field-error" role="alert"></p>
2809
+ <button id="auth-submit" class="btn btn-primary btn-lg btn-block" type="submit">Sign in</button>
2810
+ </form>
2811
+ <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>
2812
+ <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>
2813
+ <div class="auth-alt">
2814
+ <button class="btn btn-quiet btn-sm" type="button" data-action="switch-workspace">${uiIcon("key")}Use an API key instead</button>
2815
+ <a class="docs-link" href="${DOCS_URL}/quickstart">Setup guide ${uiIcon("external")}</a>
2816
+ </div>
2817
+ </div>
2818
+ <div class="oauth-card-foot">
2819
+ <span>Secured by <strong>Muxo</strong></span>
2820
+ <a href="/" data-nav="overview">Back to console</a>
2821
+ </div>
2822
+ </div>
2823
+ </div>
2824
+
2825
+ <dialog id="mobile-menu" class="sheet mobile-menu" aria-labelledby="mobile-menu-title">
2826
+ <div class="mobile-menu-content">
2827
+ <div class="sheet-head"><h2 id="mobile-menu-title">Navigation</h2><button class="icon-button" type="button" data-action="close-dialog" aria-label="Close navigation">${uiIcon("close")}</button></div>
2828
+ ${consoleNavigation(false)}
2829
+ <div id="mobile-account-actions" class="mobile-menu-account" hidden>
2830
+ <p id="mobile-account-label"></p>
2831
+ <button class="btn" type="button" data-action="sign-out">Sign out</button>
2832
+ </div>
2833
+ </div>
2834
+ </dialog>
2835
+
2836
+ <dialog id="exec-modal" class="sheet" aria-labelledby="modal-tool-name">
2837
+ <div class="sheet-inner">
2838
+ <div class="sheet-head">
2839
+ <div><h2 id="modal-tool-name">Run a tool</h2><p id="modal-tool-slug" class="tool-slug"></p></div>
2840
+ <button class="icon-button" type="button" data-action="close-dialog" aria-label="Close">${uiIcon("close")}</button>
2841
+ </div>
2842
+ <p id="modal-tool-desc" class="muted" style="font-size:14px"></p>
2843
+ <label class="field"><span>Input (JSON)</span><textarea id="modal-tool-input" class="mono" rows="6" spellcheck="false">{}</textarea></label>
2844
+ <label class="field"><span>Reason <em>recorded in the audit log</em></span><input id="modal-tool-reason" type="text" value="Run from the Muxo console" /></label>
2845
+ <div class="sheet-actions">
2846
+ <button class="btn" type="button" data-action="plan-only">Plan only</button>
2847
+ <button class="btn btn-primary" type="button" data-action="plan-run">Plan and run</button>
2848
+ </div>
2849
+ <div id="modal-oauth-box" class="callout" data-tone="warn" hidden>
2850
+ <h3 id="modal-oauth-title">Connect this app to continue</h3>
2851
+ <p id="modal-oauth-msg"></p>
2852
+ <div class="callout-actions">
2853
+ <a id="modal-oauth-link" class="btn btn-sm btn-primary" href="#" target="_blank" rel="noopener">Authorize in browser ${uiIcon("external")}</a>
2854
+ <button class="btn btn-sm" type="button" data-action="wait-resume">I've authorized, continue</button>
2855
+ </div>
2856
+ </div>
2857
+ <pre id="modal-result" class="code" aria-live="polite" hidden></pre>
2858
+ </div>
2859
+ </dialog>
2860
+
2861
+ <dialog id="detail-dialog" class="sheet" aria-labelledby="detail-title">
2862
+ <div class="sheet-inner">
2863
+ <div class="sheet-head">
2864
+ <div><h2 id="detail-title">Details</h2><p id="detail-sub" class="tool-slug"></p></div>
2865
+ <button class="icon-button" type="button" data-action="close-dialog" aria-label="Close">${uiIcon("close")}</button>
2866
+ </div>
2867
+ <div id="detail-summary" class="module-list" hidden></div>
2868
+ <pre id="detail-body" class="code"></pre>
2869
+ <div class="sheet-actions">
2870
+ <button class="btn" type="button" data-action="copy" data-target="detail-body">${uiIcon("copy")}Copy</button>
2871
+ <button class="btn btn-primary" type="button" data-action="close-dialog">Done</button>
2872
+ </div>
2873
+ </div>
2874
+ </dialog>
2875
+
2876
+ <dialog id="workspace-dialog" class="sheet" aria-labelledby="workspace-dialog-title">
2877
+ <div class="sheet-inner">
2878
+ <div class="sheet-head">
2879
+ <div><h2 id="workspace-dialog-title">Workspaces</h2><p class="tool-slug">Switch, join, or create a workspace.</p></div>
2880
+ <button class="icon-button" type="button" data-action="close-dialog" aria-label="Close">${uiIcon("close")}</button>
2881
+ </div>
2882
+ <div>
2883
+ <h3 style="font-size:14px;margin:0 0 8px">Your workspaces</h3>
2884
+ <div id="workspace-dialog-list" class="rows"></div>
2885
+ </div>
2886
+ <form id="join-workspace-form" class="form-grid" style="margin-top:16px;padding-top:16px;border-top:1px solid var(--color-border)" novalidate>
2887
+ <label class="field">
2888
+ <span>Join with an API key</span>
2889
+ <span class="field-help">Use a workspace key shared by your teammate.</span>
2890
+ <div class="inline-form" style="margin-top:6px">
2891
+ <input id="join-workspace-key-input" type="password" placeholder="mk_live_..." autocomplete="off" />
2892
+ <button class="btn btn-primary" type="submit">Join workspace</button>
2893
+ </div>
2894
+ </label>
2895
+ </form>
2896
+ <form id="create-workspace-form" class="form-grid" style="margin-top:14px;padding-top:14px;border-top:1px solid var(--color-border)" novalidate>
2897
+ <div class="form-row">
2898
+ <label class="field"><span>New workspace name</span><input id="create-workspace-name-input" type="text" placeholder="e.g. Production" autocomplete="off" /></label>
2899
+ <label class="field"><span>Team name <em>optional</em></span><input id="create-workspace-team-input" type="text" placeholder="e.g. Engineering" autocomplete="off" /></label>
2900
+ </div>
2901
+ <div class="sheet-actions" style="margin-top:6px">
2902
+ <button class="btn" type="button" data-action="close-dialog">Close</button>
2903
+ <button class="btn btn-primary" type="submit">Create workspace</button>
2904
+ </div>
2905
+ </form>
2906
+ </div>
2907
+ </dialog>
2908
+
2909
+ <dialog id="ask-dialog" class="sheet narrow" aria-labelledby="ask-title">
2910
+ <form id="ask-form" class="sheet-inner" novalidate>
2911
+ <div class="sheet-head"><h2 id="ask-title">Continue</h2></div>
2912
+ <p id="ask-message" class="muted" style="font-size:14px"></p>
2913
+ <label id="ask-field" class="field"><span id="ask-label"></span><input id="ask-input" type="text" autocomplete="off" /></label>
2914
+ <div class="sheet-actions">
2915
+ <button class="btn" type="button" data-action="close-dialog">Cancel</button>
2916
+ <button id="ask-confirm" class="btn btn-primary" type="submit">Continue</button>
2917
+ </div>
2918
+ </form>
2919
+ </dialog>
2920
+
2921
+ <div id="toast" class="toast" role="status" aria-live="polite"></div>
2922
+
2923
+ <script>${scriptPrelude}${consoleScript}</script>
2924
+ </body>
2925
+ </html>`;
2926
+ }
2927
+ const landingCss = `
2928
+ .site-header{position:sticky;top:0;z-index:30;background:var(--color-navigation);border-bottom:1px solid var(--color-border);backdrop-filter:saturate(180%) blur(20px);-webkit-backdrop-filter:saturate(180%) blur(20px)}
2929
+ .site-header-in{display:flex;align-items:center;justify-content:space-between;gap:16px;max-width:var(--grid-width);min-height:64px;margin-inline:auto;padding:8px var(--page-gutter)}
2930
+ .brand{display:inline-flex;align-items:center;gap:8px;min-height:44px;color:var(--color-text);text-decoration:none;font-size:18px;font-weight:600;letter-spacing:var(--tracking-heading)}
2931
+ .brand svg{width:26px;height:26px}
2932
+ .site-links{display:flex;align-items:center;gap:20px}
2933
+ .site-links a:not(.btn){color:var(--color-muted);font-size:13px;text-decoration:none}
2934
+ .site-links a:not(.btn):hover{color:var(--color-text)}
2935
+ .btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;padding:7px 18px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);background:transparent;color:var(--color-text);font-size:13px;text-decoration:none;white-space:nowrap}
2936
+ .btn:hover{background:var(--color-surface-2)}
2937
+ .btn-primary{border-color:transparent;background:var(--color-button-primary);color:var(--color-button-text)}
2938
+ .btn-primary:hover{background:var(--color-button-primary-hover)}
2939
+ .btn-lg{min-height:44px;padding:11px 22px;font-size:16px}
2940
+ .hero{max-width:var(--content-width);margin:0 auto;padding:var(--section-space) var(--page-gutter) 48px;text-align:center}
2941
+ .kicker{color:var(--color-muted);font-size:14px;font-weight:500}
2942
+ .hero h1{margin:16px auto 20px;max-width:16ch;font-size:var(--text-display);line-height:var(--leading-title);letter-spacing:var(--tracking-display)}
2943
+ .hero .lede{max-width:56ch;margin:0 auto;color:var(--color-muted);font-size:var(--text-lead);line-height:var(--leading-body)}
2944
+ .actions{display:flex;justify-content:center;flex-wrap:wrap;gap:12px;margin:32px 0 48px}
2945
+ .snippet{max-width:760px;margin:0 auto;overflow:hidden;border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-surface);text-align:left}
2946
+ .snippet-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 8px 6px 20px;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);color:var(--color-muted);font-size:12px}
2947
+ .snippet-bar button{min-height:32px;padding:4px 12px;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-muted);font-size:12px}
2948
+ .snippet-bar button:hover{background:var(--color-bg);color:var(--color-text)}
2949
+ .snippet pre{margin:0;padding:24px;font-size:13.5px;line-height:1.8;white-space:pre-wrap;overflow-wrap:anywhere}
2950
+ .snippet-foot{padding:12px 24px;border-top:1px solid var(--color-border);color:var(--color-muted);font:12px/1.6 var(--font-mono);overflow-wrap:anywhere}
2951
+ .section{max-width:var(--grid-width);margin:0 auto;padding:var(--space-12) var(--page-gutter)}
2952
+ .section-head{display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:16px;margin-bottom:24px}
2953
+ .section-head h2{font-size:var(--text-section);line-height:1.2}
2954
+ .section-head p{margin-top:6px;color:var(--color-muted);font-size:15px}
2955
+ .grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border-top:1px solid var(--color-border);border-left:1px solid var(--color-border)}
2956
+ .tile{display:flex;flex-direction:column;gap:8px;min-width:0;min-height:220px;padding:clamp(20px,2.2vw,32px);border-right:1px solid var(--color-border);border-bottom:1px solid var(--color-border);background:var(--color-bg);color:var(--color-text);text-decoration:none;transition:background-color var(--motion-fast)}
2957
+ a.tile:hover{background:var(--color-surface)}
2958
+ .tile-badge{color:var(--color-muted);font:400 11px/1.5 var(--font-mono)}
2959
+ .tile-name{margin-top:auto;font-size:var(--text-lg);font-weight:600;line-height:1.3;letter-spacing:var(--tracking-heading)}
2960
+ .tile-slug{color:var(--color-accent);font:12px/1.5 var(--font-mono);overflow-wrap:anywhere}
2961
+ .tile-sub{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;color:var(--color-muted);font-size:var(--text-sm);line-height:var(--leading-body)}
2962
+ .filter{display:flex;align-items:center;gap:10px;width:300px;max-width:100%;min-height:44px;padding-left:14px;border:1px solid var(--color-control-border);border-radius:var(--radius-field);background:var(--color-bg);color:var(--color-muted)}
2963
+ .filter:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 4px var(--color-accent-soft)}
2964
+ .filter input{flex:1;min-width:0;padding:11px 0;border:0;background:none;color:var(--color-text);font-size:14px;outline:none}
2965
+ .filter .ui-icon{width:16px;height:16px}
2966
+ .empty{grid-column:1/-1;padding:48px 24px;color:var(--color-muted);text-align:center}
2967
+ .site-footer{border-top:1px solid var(--color-border)}
2968
+ .site-footer-in{display:flex;align-items:center;flex-wrap:wrap;gap:24px;max-width:var(--grid-width);min-height:80px;margin-inline:auto;padding:16px var(--page-gutter);color:var(--color-muted)}
2969
+ .site-footer nav{display:flex;flex-wrap:wrap;gap:24px;margin-left:auto}
2970
+ .site-footer a{color:var(--color-muted);font-size:12px;text-decoration:none}
2971
+ .site-footer a:hover{color:var(--color-text)}
2972
+ .icon-button{display:grid;place-items:center;width:40px;height:40px;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-muted)}
2973
+ .icon-button:hover{background:var(--color-surface-2);color:var(--color-text)}
2974
+ .theme-sun{display:none}html[data-theme=dark] .theme-sun{display:inline-block}html[data-theme=dark] .theme-moon{display:none}
2975
+ @media (max-width:1100px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
2976
+ @media (max-width:640px){.site-links a:not(.btn){display:none}.grid{grid-template-columns:1fr}.snippet pre{padding:18px;font-size:12.5px}}
2977
+ `;
2978
+ const landingScript = String.raw `
2979
+ var ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
2980
+ var LANES = { composio_instant: 'Instant tool', composio_sdk: 'Connected app', stripe_projects: 'Cloud service' };
2981
+ var searchTimer = null;
2982
+ function esc(v) { return String(v == null ? '' : v).replace(/[&<>"']/g, function (c) { return ESC[c]; }); }
2983
+ async function filterLandingCatalog() {
2984
+ var q = document.getElementById('landing-search').value || '';
2985
+ var grid = document.getElementById('landing-grid');
2986
+ try {
2987
+ var res = await fetch('/api/v1/tools?query=' + encodeURIComponent(q) + '&limit=16');
2988
+ var data = await res.json();
2989
+ var tools = data.tools || [];
2990
+ grid.innerHTML = tools.length ? tools.map(function (t) {
2991
+ var provider = t.providerName || (t.providerSlug ? t.providerSlug.charAt(0).toUpperCase() + t.providerSlug.slice(1) : '');
2992
+ return '<a class="tile" href="/catalog"><span class="tile-badge">' + esc(LANES[t.lane] || t.lane) + ' · ' + esc(t.riskLevel) + '</span>' +
2993
+ '<span class="tile-name">' + esc(t.name) + '</span>' + (provider ? '<span class="tile-slug">' + esc(provider) + '</span>' : '') +
2994
+ '<span class="tile-sub">' + esc(t.description) + '</span></a>';
2995
+ }).join('') : '<p class="empty">No tools match that search.</p>';
2996
+ } catch (e) {
2997
+ grid.innerHTML = '<p class="empty">The catalog could not be loaded right now.</p>';
2998
+ }
2999
+ }
3000
+ document.getElementById('landing-search').addEventListener('input', function () {
3001
+ clearTimeout(searchTimer);
3002
+ searchTimer = setTimeout(filterLandingCatalog, 250);
3003
+ });
3004
+ document.getElementById('copy-install').addEventListener('click', function () {
3005
+ var text = document.getElementById('install-snippet').textContent || '';
3006
+ var btn = this;
3007
+ navigator.clipboard.writeText(text).then(function () { btn.textContent = 'Copied'; setTimeout(function () { btn.textContent = 'Copy'; }, 1600); });
3008
+ });
3009
+ document.getElementById('theme-toggle').addEventListener('click', function () {
3010
+ var next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
3011
+ document.documentElement.dataset.theme = next;
3012
+ try { localStorage.setItem(${JSON.stringify(THEME_STORAGE_KEY)}, next); } catch (e) {}
3013
+ });
3014
+ (function updateOriginUrls() {
3015
+ var origin = window.location.origin;
3016
+ var el = document.getElementById('landing-mcp-urls');
3017
+ if (el) el.textContent = 'MCP ' + origin + '/mcp · OAuth callback ' + origin + '/oauth/callback · Skill ' + origin + '/skill.md';
3018
+ })();
3019
+ filterLandingCatalog();
3020
+ `;
3021
+ export function renderLandingHtml() {
3022
+ const pillars = [
3023
+ ["Connected apps", "Connected apps and instant tools", "Managed sign-in for GitHub, Gmail, Slack, Notion, and Linear, plus search, scraping, SEO, and DNS tools that work with no setup."],
3024
+ ["Cloud infrastructure", "Muxo cloud infrastructure", "Provision Neon, Supabase, Turso, Upstash, Cloudflare, and Vercel. Credentials stay encrypted in your workspace vault."],
3025
+ ["Workspace memory", "Muxo workspace memory", "Facts and preferences for each workspace, recalled before every plan, with secrets removed before anything is stored."],
3026
+ ["Safety & audit", "Muxo guardrails and audit log", "Prompt-injection scanning, risk-based approvals, an emergency stop, and a signed record of every run."],
3027
+ ];
3028
+ return `<!DOCTYPE html>
3029
+ <html lang="en" data-theme="dark">
3030
+ <head>
3031
+ <meta charset="utf-8" />
3032
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
3033
+ <title>Muxo — your tools, in your agent</title>
3034
+ <meta name="description" content="Connect apps, cloud services, memory, and safety controls to the agent you already use." />
3035
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3036
+ <meta name="color-scheme" content="dark light" />
3037
+ <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
3038
+ <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
3039
+ <style>${muxoBaseCss}${landingCss}</style>
3040
+ </head>
3041
+ <body>
3042
+ <header class="site-header">
3043
+ <div class="site-header-in">
3044
+ <a class="brand" href="/landing" aria-label="Muxo home">${muxoMark}<span>Muxo</span></a>
3045
+ <nav class="site-links" aria-label="Primary">
3046
+ <a href="${DOCS_URL}">Docs</a>
3047
+ <a href="${PRICING_URL}">Pricing</a>
3048
+ <a href="/skill.md">Agent skill</a>
3049
+ <a class="btn" href="/login">Sign in</a>
3050
+ <a class="btn btn-primary" href="/">Open console</a>
3051
+ </nav>
3052
+ </div>
3053
+ </header>
3054
+ <main>
3055
+ <section class="hero">
3056
+ <p class="kicker">One key. One MCP endpoint.</p>
3057
+ <h1>Your tools, in your agent.</h1>
3058
+ <p class="lede">Connect apps, cloud services, memory, and safety controls to the agent you already use. You choose the account, review the action, and keep a record of every run.</p>
3059
+ <div class="actions">
3060
+ <a class="btn btn-primary btn-lg" href="/signup">Get started</a>
3061
+ <a class="btn btn-lg" href="${DOCS_URL}/quickstart">Read the quickstart</a>
3062
+ </div>
3063
+ <div class="snippet">
3064
+ <div class="snippet-bar"><span>Install</span><button id="copy-install" type="button">Copy</button></div>
3065
+ <pre id="install-snippet"># Claude Code
3066
+ claude plugin marketplace add https://api.muxo.ai/.claude-plugin/marketplace.json
3067
+ claude plugin install muxo@muxo
3068
+
3069
+ # Any agent with a terminal (Node.js 22+)
3070
+ npx -y @muxoai/cli@latest login --signup --name "my-agent"
3071
+ npx -y @muxoai/cli@latest link github --wait</pre>
3072
+ <p class="snippet-foot" id="landing-mcp-urls">MCP https://api.muxo.ai/mcp</p>
3073
+ </div>
3074
+ </section>
3075
+
3076
+ <section id="pillars" class="section" aria-labelledby="pillars-title">
3077
+ <div class="section-head"><div><h2 id="pillars-title">What your agent gets</h2><p>Four capabilities behind one connection.</p></div></div>
3078
+ <div class="grid">
3079
+ ${pillars
3080
+ .map(([tag, title, body]) => `<div class="tile"><span class="tile-badge">${tag}</span><span class="tile-name">${title}</span><span class="tile-sub">${body}</span></div>`)
3081
+ .join("")}
3082
+ </div>
3083
+ </section>
3084
+
3085
+ <section id="canvas" class="section" aria-labelledby="canvas-title">
3086
+ <div class="section-head">
3087
+ <div><h2 id="canvas-title">Interactive unified capability canvas</h2><p>Live results from the Muxo catalog.</p></div>
3088
+ <label class="filter">${uiIcon("search")}<span class="sr-only">Filter tools</span><input id="landing-search" type="search" placeholder="Filter tools: neon, github, search" autocomplete="off" /></label>
3089
+ </div>
3090
+ <div id="landing-grid" class="grid"></div>
3091
+ </section>
3092
+ </main>
3093
+ <footer class="site-footer">
3094
+ <div class="site-footer-in">
3095
+ <a class="brand" href="/landing" aria-label="Muxo home">${muxoMark}<span>Muxo</span></a>
3096
+ <nav aria-label="Footer">
3097
+ <a href="${DOCS_URL}">Docs</a>
3098
+ <a href="${DOCS_URL}/support">Support</a>
3099
+ <a href="${DOCS_URL}/privacy">Privacy</a>
3100
+ <a href="${DOCS_URL}/terms">Terms</a>
3101
+ </nav>
3102
+ <button id="theme-toggle" class="icon-button" type="button" aria-label="Switch theme">${uiIcon("sun", "theme-sun")}${uiIcon("moon", "theme-moon")}</button>
3103
+ </div>
3104
+ </footer>
3105
+ <script>${landingScript}</script>
3106
+ </body>
3107
+ </html>`;
3108
+ }
3109
+ function escapeHtml(value) {
3110
+ return value.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
3111
+ }
3112
+ /** JSON that is safe to place inside an inline <script> element. */
3113
+ function inlineJson(value) {
3114
+ return JSON.stringify(value)
3115
+ .replace(/</g, "\\u003c")
3116
+ .replace(/>/g, "\\u003e")
3117
+ .replace(/&/g, "\\u0026")
3118
+ .replace(/\u2028/g, "\\u2028")
3119
+ .replace(/\u2029/g, "\\u2029");
3120
+ }
3121
+ const consentCss = `
3122
+ .consent-list{display:grid;gap:8px;margin:20px 0 0;padding:0;list-style:none;text-align:left;font-size:14px}
3123
+ .consent-list li{display:flex;gap:10px;align-items:flex-start}
3124
+ .consent-list li::before{content:"";flex:none;width:6px;height:6px;margin-top:8px;border-radius:50%;background:var(--color-accent)}
3125
+ .consent-redirect{margin-top:16px;color:var(--color-muted);font-size:13px;overflow-wrap:anywhere}
3126
+ .consent-redirect strong{color:var(--color-text);font-family:var(--font-mono);font-weight:500}
3127
+ .consent-state{margin-top:24px}
3128
+ .consent-account{color:var(--color-muted);font-size:14px}
3129
+ .consent-account strong{color:var(--color-text)}
3130
+ .consent-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:16px}
3131
+ .consent-switch{margin-top:10px}
3132
+ `;
3133
+ const consentScript = String.raw `
3134
+ (function () {
3135
+ var C = window.MUXO_CONSENT;
3136
+ function byId(id) { return document.getElementById(id); }
3137
+ function show(id, on) { var el = byId(id); if (el) el.hidden = !on; }
3138
+ function fail(msg) { show('consent-loading', false); byId('consent-error').textContent = msg || ''; }
3139
+ if (!C.clerk) { fail('Sign-in is not configured on this Muxo server, so this connection cannot be approved here.'); return; }
3140
+ if (!/^https:\/\/[a-z0-9.-]+$/i.test(C.clerk.frontendApi)) { fail('Sign-in is misconfigured on this server.'); return; }
3141
+ var clerk = null;
3142
+ var mounted = false;
3143
+ var busy = false;
3144
+ var here = location.href.split('#')[0];
3145
+ function appearance() {
3146
+ var cs = getComputedStyle(document.documentElement);
3147
+ function v(n) { return cs.getPropertyValue(n).trim(); }
3148
+ return {
3149
+ variables: { colorPrimary: v('--color-button-primary'), colorText: v('--color-text'), colorTextSecondary: v('--color-muted'), colorBackground: v('--color-surface'), colorInputBackground: v('--color-bg'), colorInputText: v('--color-text'), colorDanger: v('--color-danger'), borderRadius: '12px', fontFamily: v('--font-sans'), fontSize: '15px' },
3150
+ elements: { rootBox: { width: '100%' }, cardBox: { width: '100%', boxShadow: 'none', border: '0' }, card: { boxShadow: 'none', background: 'transparent', padding: '4px 0 0' }, header: { display: 'none' }, footer: { background: 'transparent' } }
3151
+ };
3152
+ }
3153
+ function render() {
3154
+ show('consent-loading', false);
3155
+ if (clerk.session && clerk.user) {
3156
+ if (mounted) { try { clerk.unmountSignIn(byId('clerk-auth')); } catch (e) {} mounted = false; }
3157
+ show('consent-signin', false);
3158
+ show('consent-approve', true);
3159
+ var u = clerk.user;
3160
+ byId('consent-email').textContent = (u.primaryEmailAddress && u.primaryEmailAddress.emailAddress) || u.username || 'your account';
3161
+ return;
3162
+ }
3163
+ show('consent-approve', false);
3164
+ show('consent-signin', true);
3165
+ if (!mounted) {
3166
+ clerk.mountSignIn(byId('clerk-auth'), { routing: 'hash', forceRedirectUrl: here, signUpForceRedirectUrl: here, appearance: appearance() });
3167
+ mounted = true;
3168
+ }
3169
+ }
3170
+ async function decide(decision) {
3171
+ if (busy) return;
3172
+ if (decision === 'deny' && C.cancelUrl) { location.assign(C.cancelUrl); return; }
3173
+ busy = true;
3174
+ fail('');
3175
+ byId('consent-allow').disabled = true;
3176
+ byId('consent-deny').disabled = true;
3177
+ try {
3178
+ var token = await clerk.session.getToken();
3179
+ var res = await fetch(C.action || '/oauth/authorize', {
3180
+ method: 'POST',
3181
+ headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
3182
+ body: JSON.stringify(Object.assign({}, C.request, { decision: decision }))
3183
+ });
3184
+ var data = await res.json().catch(function () { return {}; });
3185
+ if (!res.ok || !data.redirect) throw new Error(data.error || 'Muxo could not finish this request. Try again.');
3186
+ byId(decision === 'allow' ? 'consent-allow' : 'consent-deny').textContent = decision === 'allow' ? (C.action ? 'Opening…' : 'Returning…') : 'Cancelling…';
3187
+ location.assign(data.redirect);
3188
+ } catch (err) {
3189
+ fail(err.message);
3190
+ busy = false;
3191
+ byId('consent-allow').disabled = false;
3192
+ byId('consent-deny').disabled = false;
3193
+ }
3194
+ }
3195
+ byId('consent-allow').addEventListener('click', function () { decide('allow'); });
3196
+ byId('consent-deny').addEventListener('click', function () { decide('deny'); });
3197
+ byId('consent-switch').addEventListener('click', function () { clerk.signOut({ redirectUrl: here }); });
3198
+ var s = document.createElement('script');
3199
+ s.src = C.clerk.frontendApi + '/npm/@clerk/clerk-js@5/dist/clerk.browser.js';
3200
+ s.async = true;
3201
+ s.crossOrigin = 'anonymous';
3202
+ s.setAttribute('data-clerk-publishable-key', C.clerk.publishableKey);
3203
+ s.onerror = function () { fail('Sign-in could not load. Check your connection and reload.'); };
3204
+ s.onload = function () {
3205
+ if (!window.Clerk) { fail('Sign-in could not load.'); return; }
3206
+ window.Clerk.load({ appearance: appearance() }).then(function () {
3207
+ clerk = window.Clerk;
3208
+ clerk.addListener(function () { render(); });
3209
+ render();
3210
+ }).catch(function () { fail('Sign-in could not load. Reload to try again.'); });
3211
+ };
3212
+ document.head.appendChild(s);
3213
+ })();
3214
+ `;
3215
+ /** Consent screen for MCP clients (ChatGPT, Claude, CLIs) asking to use a Muxo workspace. */
3216
+ export function renderOAuthConsentHtml(opts) {
3217
+ const clientName = escapeHtml(opts.clientName || "An app");
3218
+ const clientNameMidSentence = /^(A|An) /.test(clientName) ? clientName[0].toLowerCase() + clientName.slice(1) : clientName;
3219
+ const claim = opts.claim;
3220
+ const stepTag = opts.error ? "Connection error" : claim ? "Workspace claim" : "Agent sign-in";
3221
+ const head = `<!DOCTYPE html>
3222
+ <html lang="en" data-theme="dark">
3223
+ <head>
3224
+ <meta charset="utf-8" />
3225
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
3226
+ <title>${opts.error ? (opts.claim ? "Claim problem" : "Connection problem") : opts.claim ? `Claim ${escapeHtml(opts.claim.workspaceName)}` : `Connect ${clientNameMidSentence}`} · Muxo</title>
3227
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3228
+ <meta name="color-scheme" content="dark light" />
3229
+ <meta name="robots" content="noindex" />
3230
+ <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
3231
+ <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
3232
+ <style>${muxoBaseCss}${consoleCss}${consentCss}</style>
3233
+ </head>
3234
+ <body>`;
3235
+ if (opts.error) {
3236
+ return `${head}
3237
+ <main class="auth-view">
3238
+ <div class="auth-card oauth-card">
3239
+ <div class="oauth-card-top">
3240
+ <span class="oauth-brand">${muxoMark}<span>Muxo</span></span>
3241
+ <span class="oauth-step-tag">${stepTag}</span>
3242
+ </div>
3243
+ <div class="oauth-card-body">
3244
+ <h1>${opts.claim ? "This claim link isn't valid" : "This connection link isn't valid"}</h1>
3245
+ <p class="lede">${escapeHtml(opts.error)}</p>
3246
+ </div>
3247
+ <div class="oauth-card-foot">
3248
+ <span>${opts.claim ? "Ask the agent for a new claim link" : "Start the connection again from your app"}</span>
3249
+ <a href="${DOCS_URL}">Read the setup guide</a>
3250
+ </div>
3251
+ </div>
3252
+ </main>
3253
+ </body>
3254
+ </html>`;
3255
+ }
3256
+ const config = {
3257
+ clerk: opts.clerk || null,
3258
+ request: opts.request || {},
3259
+ action: claim?.action,
3260
+ cancelUrl: claim?.cancelUrl,
3261
+ };
3262
+ const intro = claim
3263
+ ? `<h1>Claim ${escapeHtml(claim.workspaceName)}</h1>
3264
+ <p class="lede">An agent created this Muxo workspace and invited you to own it.</p>
3265
+ <ul class="consent-list">
3266
+ <li>You become the owner of this workspace and its credits</li>
3267
+ <li>You manage its keys, safety rules, memory, and billing</li>
3268
+ <li>The agent keeps working with its key until you revoke it</li>
3269
+ </ul>
3270
+ <p class="consent-redirect">Sign in as <strong>${escapeHtml(claim.targetEmail)}</strong> to accept.</p>`
3271
+ : `<h1>Connect ${clientNameMidSentence} to Muxo</h1>
3272
+ <p class="lede">${clientName} wants to use your Muxo workspace.</p>
3273
+ <ul class="consent-list">
3274
+ <li>Find tools and plan actions in your workspace</li>
3275
+ <li>Run tools under your workspace's safety rules and approvals</li>
3276
+ <li>Read the status of connections, plans, and runs</li>
3277
+ </ul>
3278
+ <p class="consent-redirect">After you decide, you'll return to <strong>${escapeHtml(opts.redirectOrigin || "")}</strong></p>`;
3279
+ return `${head}
3280
+ <main class="auth-view">
3281
+ <div class="auth-card oauth-card">
3282
+ <div class="oauth-card-top">
3283
+ <span class="oauth-brand">${muxoMark}<span>Muxo</span></span>
3284
+ <span class="oauth-step-tag">${stepTag}</span>
3285
+ </div>
3286
+ <div class="oauth-card-body">
3287
+ ${intro}
3288
+ <div class="consent-state">
3289
+ <p id="consent-loading" class="muted">Checking your sign-in…</p>
3290
+ <div id="consent-signin" hidden>
3291
+ <p class="consent-account">Sign in to Muxo to continue.</p>
3292
+ <div id="clerk-auth" class="clerk-auth"></div>
3293
+ </div>
3294
+ <div id="consent-approve" hidden>
3295
+ <p class="consent-account">Signed in as <strong id="consent-email"></strong></p>
3296
+ <div class="consent-actions">
3297
+ <button id="consent-deny" class="btn btn-lg" type="button">${claim ? "Not now" : "Cancel"}</button>
3298
+ <button id="consent-allow" class="btn btn-primary btn-lg" type="button">${claim ? "Claim workspace" : "Allow"}</button>
3299
+ </div>
3300
+ <p class="consent-switch"><button id="consent-switch" class="btn btn-quiet btn-sm" type="button">Use a different account</button></p>
3301
+ </div>
3302
+ <p id="consent-error" class="field-error" role="alert"></p>
3303
+ </div>
3304
+ </div>
3305
+ <div class="oauth-card-foot">
3306
+ <span>${claim ? "Only the invited email can claim this workspace, and the link works once." : "You can remove this connection anytime in the Muxo console."}</span>
3307
+ </div>
3308
+ </div>
3309
+ </main>
3310
+ <script>window.MUXO_CONSENT = ${inlineJson(config)};${consentScript}</script>
3311
+ </body>
3312
+ </html>`;
3313
+ }
3314
+ const approvalCss = `
3315
+ .approval-facts{display:grid;gap:8px;margin:18px 0 0;padding:0;list-style:none;text-align:left;font-size:14px}
3316
+ .approval-facts li{display:flex;justify-content:space-between;gap:14px;padding:9px 12px;border:1px solid var(--color-border);border-radius:10px;background:var(--color-bg)}
3317
+ .approval-facts .k{color:var(--color-muted);flex:none}
3318
+ .approval-facts .v{color:var(--color-text);font-weight:500;text-align:right;overflow-wrap:anywhere}
3319
+ .approval-input{margin:14px 0 0;padding:12px;border:1px solid var(--color-border);border-radius:10px;background:var(--color-bg);font-family:var(--font-mono);font-size:12px;text-align:left;white-space:pre-wrap;overflow-wrap:anywhere;max-height:180px;overflow:auto}
3320
+ .approval-risk{display:inline-block;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;text-transform:capitalize}
3321
+ .approval-risk[data-tone="ok"]{background:var(--color-ok-bg);color:var(--color-ok)}
3322
+ .approval-risk[data-tone="warn"]{background:var(--color-warn-bg);color:var(--color-warn)}
3323
+ .approval-risk[data-tone="strict"]{background:var(--color-danger-bg);color:var(--color-danger)}
3324
+ .approval-note{margin-top:14px;color:var(--color-muted);font-size:13px}
3325
+ .approval-done{margin-top:18px;text-align:left;font-size:14px}
3326
+ `;
3327
+ const approvalScript = String.raw `
3328
+ (function () {
3329
+ var C = window.MUXO_APPROVAL;
3330
+ function byId(id) { return document.getElementById(id); }
3331
+ function show(id, on) { var el = byId(id); if (el) el.hidden = !on; }
3332
+ function fail(msg) { var el = byId('approval-error'); if (el) el.textContent = msg || ''; }
3333
+ function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
3334
+ var clerk = null;
3335
+ var mounted = false;
3336
+ var busy = false;
3337
+ var here = location.href.split('#')[0];
3338
+ var plan = null;
3339
+ function storedKey() { try { return localStorage.getItem('muxo_external_api_key') || ''; } catch (e) { return ''; } }
3340
+ async function bearerToken() {
3341
+ var key = storedKey();
3342
+ if (key) return key;
3343
+ if (clerk && clerk.session) { var t = await clerk.session.getToken(); if (t) return t; }
3344
+ return '';
3345
+ }
3346
+ async function api(path, method, body) {
3347
+ var token = await bearerToken();
3348
+ if (!token) { var e = new Error('sign_in'); e.code = 'sign_in'; throw e; }
3349
+ var res = await fetch(path, { method: method || 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + token }, body: body ? JSON.stringify(body) : undefined });
3350
+ var data; try { data = await res.json(); } catch (e) { data = { ok: false, error: 'Unexpected response from Muxo (HTTP ' + res.status + ')' }; }
3351
+ if (res.status === 401) { var e2 = new Error('sign_in'); e2.code = 'sign_in'; throw e2; }
3352
+ if (!res.ok || data.ok === false) throw new Error(data.error || data.message || ('HTTP ' + res.status));
3353
+ return data;
3354
+ }
3355
+ function riskTone(risk) { return risk === 'read' ? 'ok' : (risk === 'write' || risk === 'admin') ? 'warn' : 'strict'; }
3356
+ function renderPlan() {
3357
+ show('approval-loading', false);
3358
+ show('approval-review', true);
3359
+ byId('approval-tool').textContent = plan.toolSlug || '';
3360
+ byId('approval-risk').textContent = plan.riskLevel + ' risk';
3361
+ byId('approval-risk').setAttribute('data-tone', riskTone(plan.riskLevel));
3362
+ byId('approval-cost').textContent = String(plan.creditCost != null ? plan.creditCost : 0) + ' credits';
3363
+ byId('approval-reason').textContent = plan.reason || '—';
3364
+ byId('approval-input').textContent = JSON.stringify(plan.input || {}, null, 2);
3365
+ var already = plan.approvalStatus === 'approved' || plan.approvalStatus === 'auto_approved';
3366
+ var executed = plan.approvalStatus === 'executed';
3367
+ if (executed) { show('approval-review', false); done('This plan was already executed.', null); return; }
3368
+ if (already) {
3369
+ byId('approval-approve-only').disabled = true;
3370
+ byId('approval-reject').disabled = true;
3371
+ byId('approval-state-note').textContent = 'Already approved. You can run it now.';
3372
+ }
3373
+ }
3374
+ function done(message, result) {
3375
+ show('approval-loading', false);
3376
+ show('approval-review', false);
3377
+ show('approval-done', true);
3378
+ var html = '<p>' + esc(message) + '</p>';
3379
+ if (result && result.run) {
3380
+ html += '<ul class="approval-facts">'
3381
+ + '<li><span class="k">Run</span><span class="v">' + esc(result.run.id) + ' · ' + esc(result.run.status) + '</span></li>'
3382
+ + (result.receipt ? '<li><span class="k">Receipt</span><span class="v">' + esc(result.receipt.id) + '</span></li>' : '')
3383
+ + '</ul>';
3384
+ }
3385
+ html += '<p class="approval-note">You can close this window, or <a href="/activity">open the console activity log</a>.</p>';
3386
+ byId('approval-done').innerHTML = html;
3387
+ }
3388
+ async function load() {
3389
+ fail('');
3390
+ try {
3391
+ var data = await api('/api/v1/plans/' + encodeURIComponent(C.planId));
3392
+ plan = data.plan;
3393
+ renderPlan();
3394
+ } catch (err) {
3395
+ show('approval-loading', false);
3396
+ if (err && err.code === 'sign_in') { renderAuth(); return; }
3397
+ show('approval-review', false);
3398
+ fail(err.message || 'Muxo could not load this plan.');
3399
+ }
3400
+ }
3401
+ async function decide(mode) {
3402
+ if (busy || !plan) return;
3403
+ busy = true;
3404
+ fail('');
3405
+ byId('approval-approve-run').disabled = true;
3406
+ byId('approval-approve-only').disabled = true;
3407
+ byId('approval-reject').disabled = true;
3408
+ try {
3409
+ if (mode === 'reject') {
3410
+ await api('/api/v1/plans/' + encodeURIComponent(C.planId) + '/approve', 'POST', { decision: 'rejected' });
3411
+ done('Plan rejected. Nothing was executed.', null);
3412
+ return;
3413
+ }
3414
+ if (plan.approvalStatus === 'pending_approval') {
3415
+ await api('/api/v1/plans/' + encodeURIComponent(C.planId) + '/approve', 'POST');
3416
+ }
3417
+ if (mode === 'run') {
3418
+ var result = await api('/api/v1/plans/' + encodeURIComponent(C.planId) + '/execute', 'POST');
3419
+ done('Approved and executed.', result);
3420
+ } else {
3421
+ done('Plan approved. Your agent can now execute it.', null);
3422
+ }
3423
+ } catch (err) {
3424
+ fail(err.message || 'Muxo could not complete that action.');
3425
+ busy = false;
3426
+ byId('approval-approve-run').disabled = false;
3427
+ byId('approval-approve-only').disabled = false;
3428
+ byId('approval-reject').disabled = false;
3429
+ }
3430
+ }
3431
+ function renderAuth() {
3432
+ show('approval-loading', false);
3433
+ show('approval-signin', true);
3434
+ if (storedKey()) {
3435
+ try { localStorage.removeItem('muxo_external_api_key'); } catch (e) {}
3436
+ fail('Your saved console key was rejected. Sign in below or paste a valid workspace API key.');
3437
+ }
3438
+ if (!C.clerk) {
3439
+ byId('approval-signin-msg').textContent = 'Paste your workspace or team API key below, or sign in through the Muxo console.';
3440
+ show('approval-console-link', true);
3441
+ return;
3442
+ }
3443
+ if (clerk && !mounted) {
3444
+ clerk.mountSignIn(byId('clerk-auth'), { routing: 'hash', forceRedirectUrl: here, signUpForceRedirectUrl: here, appearance: appearance() });
3445
+ mounted = true;
3446
+ }
3447
+ }
3448
+ function appearance() {
3449
+ var cs = getComputedStyle(document.documentElement);
3450
+ function v(n) { return cs.getPropertyValue(n).trim(); }
3451
+ return {
3452
+ variables: { colorPrimary: v('--color-button-primary'), colorText: v('--color-text'), colorTextSecondary: v('--color-muted'), colorBackground: v('--color-surface'), colorInputBackground: v('--color-bg'), colorInputText: v('--color-text'), colorDanger: v('--color-danger'), borderRadius: '12px', fontFamily: v('--font-sans'), fontSize: '15px' },
3453
+ elements: { rootBox: { width: '100%' }, cardBox: { width: '100%', boxShadow: 'none', border: '0' }, card: { boxShadow: 'none', background: 'transparent', padding: '4px 0 0' }, header: { display: 'none' }, footer: { background: 'transparent' } }
3454
+ };
3455
+ }
3456
+ var keyForm = byId('approval-key-form');
3457
+ if (keyForm) {
3458
+ keyForm.addEventListener('submit', async function (e) {
3459
+ e.preventDefault();
3460
+ var input = byId('approval-key-input');
3461
+ var key = (input && input.value ? input.value : '').trim();
3462
+ if (!key) return;
3463
+ fail('');
3464
+ try {
3465
+ var res = await fetch('/api/v1/workspaces/join', {
3466
+ method: 'POST',
3467
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
3468
+ body: JSON.stringify({ apiKey: key })
3469
+ });
3470
+ var data = await res.json();
3471
+ if (!res.ok || data.ok === false) throw new Error(data.error || data.message || 'Invalid API key');
3472
+ var tokenToStore = data.sessionToken || key;
3473
+ try { localStorage.setItem('muxo_external_api_key', tokenToStore); } catch (err) {}
3474
+ show('approval-signin', false);
3475
+ show('approval-loading', true);
3476
+ await load();
3477
+ } catch (err) {
3478
+ fail(err.message || 'Could not verify that API key.');
3479
+ }
3480
+ });
3481
+ }
3482
+ byId('approval-approve-run').addEventListener('click', function () { decide('run'); });
3483
+ byId('approval-approve-only').addEventListener('click', function () { decide('approve'); });
3484
+ byId('approval-reject').addEventListener('click', function () { decide('reject'); });
3485
+ if (storedKey() || !C.clerk) { load(); return; }
3486
+ if (!/^https:\/\/[a-z0-9.-]+$/i.test(C.clerk.frontendApi)) { fail('Sign-in is misconfigured on this server.'); return; }
3487
+ var s = document.createElement('script');
3488
+ s.src = C.clerk.frontendApi + '/npm/@clerk/clerk-js@5/dist/clerk.browser.js';
3489
+ s.async = true;
3490
+ s.crossOrigin = 'anonymous';
3491
+ s.setAttribute('data-clerk-publishable-key', C.clerk.publishableKey);
3492
+ s.onerror = function () { fail('Sign-in could not load. Check your connection and reload.'); };
3493
+ s.onload = function () {
3494
+ if (!window.Clerk) { fail('Sign-in could not load.'); return; }
3495
+ window.Clerk.load({ appearance: appearance() }).then(function () {
3496
+ clerk = window.Clerk;
3497
+ clerk.addListener(function () { if (clerk.session && clerk.user && !plan) load(); });
3498
+ if (clerk.session && clerk.user) load(); else renderAuth();
3499
+ }).catch(function () { fail('Sign-in could not load. Reload to try again.'); });
3500
+ };
3501
+ document.head.appendChild(s);
3502
+ })();
3503
+ `;
3504
+ /** One-click approval screen for plans held by workspace guardrails (`/approve/:planId`). */
3505
+ export function renderApprovalPageHtml(opts) {
3506
+ const planId = String(opts.planId || "");
3507
+ const config = { planId, clerk: opts.clerk || null };
3508
+ return `<!DOCTYPE html>
3509
+ <html lang="en" data-theme="dark">
3510
+ <head>
3511
+ <meta charset="utf-8" />
3512
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
3513
+ <title>Review action · Muxo</title>
3514
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3515
+ <meta name="color-scheme" content="dark light" />
3516
+ <meta name="robots" content="noindex" />
3517
+ <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
3518
+ <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
3519
+ <style>${muxoBaseCss}${consoleCss}${consentCss}${approvalCss}</style>
3520
+ </head>
3521
+ <body>
3522
+ <main class="auth-view">
3523
+ <div class="auth-card oauth-card">
3524
+ <div class="oauth-card-top">
3525
+ <span class="oauth-brand">${muxoMark}<span>Muxo</span></span>
3526
+ <span class="oauth-step-tag">Action review</span>
3527
+ </div>
3528
+ <div class="oauth-card-body">
3529
+ <h1>Review this action</h1>
3530
+ <p class="lede">Muxo held this action because your guardrails require your approval before it can run.</p>
3531
+ <div class="consent-state">
3532
+ <p id="approval-loading" class="muted">Loading plan ${escapeHtml(planId)}…</p>
3533
+ <div id="approval-signin" hidden>
3534
+ <p id="approval-signin-msg" class="consent-account">Sign in to Muxo to review this action.</p>
3535
+ <div id="clerk-auth" class="clerk-auth"></div>
3536
+ <form id="approval-key-form" class="inline-form" style="margin-top:12px" novalidate>
3537
+ <input id="approval-key-input" type="password" placeholder="Or paste workspace / team key (mk_live_...)" autocomplete="off" />
3538
+ <button class="btn btn-primary" type="submit">Continue</button>
3539
+ </form>
3540
+ <p id="approval-console-link" style="margin-top:10px" hidden><a href="/">Open the Muxo console</a></p>
3541
+ </div>
3542
+ <div id="approval-review" hidden>
3543
+ <ul class="approval-facts">
3544
+ <li><span class="k">Action</span><span class="v" id="approval-tool"></span></li>
3545
+ <li><span class="k">Risk</span><span class="v"><span id="approval-risk" class="approval-risk" data-tone="warn"></span></span></li>
3546
+ <li><span class="k">Cost</span><span class="v" id="approval-cost"></span></li>
3547
+ <li><span class="k">Reason</span><span class="v" id="approval-reason"></span></li>
3548
+ </ul>
3549
+ <pre id="approval-input" class="approval-input" aria-label="Tool input"></pre>
3550
+ <p id="approval-state-note" class="approval-note">Nothing runs until you approve.</p>
3551
+ <div class="consent-actions">
3552
+ <button id="approval-reject" class="btn btn-lg" type="button">Reject</button>
3553
+ <button id="approval-approve-only" class="btn btn-lg" type="button">Approve only</button>
3554
+ </div>
3555
+ <div class="consent-actions" style="grid-template-columns:1fr;margin-top:10px">
3556
+ <button id="approval-approve-run" class="btn btn-primary btn-lg" type="button">Approve and run</button>
3557
+ </div>
3558
+ </div>
3559
+ <div id="approval-done" class="approval-done" hidden></div>
3560
+ <p id="approval-error" class="field-error" role="alert"></p>
3561
+ </div>
3562
+ </div>
3563
+ <div class="oauth-card-foot">
3564
+ <span>Muxo checks every action against your guardrails and workspace memory before it reaches you.</span>
3565
+ </div>
3566
+ </div>
3567
+ </main>
3568
+ <script>window.MUXO_APPROVAL = ${inlineJson(config)};${approvalScript}</script>
3569
+ </body>
3570
+ </html>`;
3571
+ }
3572
+ const oauthConnectCss = `
3573
+ .connect-bridge{display:flex;align-items:center;justify-content:center;gap:12px;margin:0 auto 18px}
3574
+ .connect-node{display:inline-flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:14px;border:1px solid var(--color-border);background:var(--color-bg);box-shadow:0 2px 8px rgba(0,0,0,.04);margin:0}
3575
+ .connect-node svg{width:24px;height:24px}
3576
+ .connect-node-monogram{font-size:18px;font-weight:700;letter-spacing:-0.02em;color:var(--color-text)}
3577
+ .connect-connector{display:inline-flex;align-items:center;gap:6px;color:var(--color-muted)}
3578
+ .connect-line{width:22px;height:1px;background:var(--color-border-strong)}
3579
+ .connect-lock{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:999px;border:1px solid var(--color-border);background:var(--color-surface-2);color:var(--color-text)}
3580
+ .connect-lock svg{width:13px;height:13px}
3581
+ .connect-lock[data-tone="ok"]{border-color:var(--color-ok-border);background:var(--color-ok-bg);color:var(--color-ok)}
3582
+ .connect-badge-row{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px;margin-bottom:14px}
3583
+ .connect-pill{display:inline-flex;align-items:center;gap:6px;padding:4px 11px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid var(--color-border);background:var(--color-bg);color:var(--color-text)}
3584
+ .connect-pill-subtle{font-weight:500;color:var(--color-muted)}
3585
+ .connect-pill-status[data-tone="ok"]{border-color:var(--color-ok-border);background:var(--color-ok-bg);color:var(--color-ok)}
3586
+ .connect-pill-status[data-tone="warn"]{border-color:var(--color-warn-border);background:var(--color-warn-bg);color:var(--color-warn)}
3587
+ .connect-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:16px 0 6px;text-align:left}
3588
+ .connect-step{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--color-border);border-radius:10px;background:var(--color-bg);font-size:12px;line-height:1.3;color:var(--color-muted)}
3589
+ .connect-step-num{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;background:var(--color-surface-2);color:var(--color-text);font-size:11px;font-weight:700;flex:none}
3590
+ .connect-field{margin-top:12px;text-align:left}
3591
+ .connect-field label{display:block;font-size:13px;font-weight:600;margin-bottom:4px;color:var(--color-text)}
3592
+ .connect-field small{display:block;font-size:12px;color:var(--color-muted);margin-bottom:6px}
3593
+ .connect-field input{width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid var(--color-border);border-radius:8px;background:var(--color-bg);color:var(--color-text);font-size:14px}
3594
+ @media (max-width:520px){.connect-steps{grid-template-columns:1fr}}
3595
+ `;
3596
+ function renderProviderBadge(slug, name) {
3597
+ const s = (slug || "").toLowerCase().replace(/[^a-z0-9]/g, "");
3598
+ if (s === "github") {
3599
+ return `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.58 2 12.23c0 4.52 2.87 8.35 6.84 9.7.5.09.68-.22.68-.49 0-.24-.01-.89-.01-1.74-2.78.62-3.37-1.37-3.37-1.37-.45-1.18-1.11-1.49-1.11-1.49-.91-.64.07-.63.07-.63 1 .07 1.53 1.05 1.53 1.05.9 1.57 2.35 1.12 2.92.85.09-.67.35-1.12.63-1.38-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.03 1.03-2.75-.1-.26-.45-1.3.1-2.71 0 0 .84-.28 2.75 1.05A9.36 9.36 0 0 1 12 6.84c.85 0 1.71.12 2.51.35 1.91-1.33 2.75-1.05 2.75-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.63 1.03 2.75 0 3.94-2.34 4.81-4.57 5.06.36.32.68.94.68 1.9 0 1.37-.01 2.48-.01 2.82 0 .27.18.59.69.49A10.25 10.25 0 0 0 22 12.23C22 6.58 17.52 2 12 2Z"/></svg>`;
3600
+ }
3601
+ if (s === "slack") {
3602
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="13" y="2" width="3" height="8" rx="1.5"/><path d="M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5"/><rect x="8" y="14" width="3" height="8" rx="1.5"/><path d="M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5"/><rect x="14" y="13" width="8" height="3" rx="1.5"/><path d="M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5"/><rect x="2" y="8" width="8" height="3" rx="1.5"/><path d="M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5"/></svg>`;
3603
+ }
3604
+ if (s === "gmail" || s.startsWith("google")) {
3605
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg>`;
3606
+ }
3607
+ if (s === "notion") {
3608
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h12l4 4v12H6l-2-2V4Z"/><path d="M9 9v7l5-7v7"/></svg>`;
3609
+ }
3610
+ if (s === "linear") {
3611
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="m5.5 15.5 7 3M4.5 11.5l8 8M7.5 6.5l10 10"/></svg>`;
3612
+ }
3613
+ const initial = escapeHtml((name || slug || "A").trim().charAt(0).toUpperCase() || "A");
3614
+ return `<span class="connect-node-monogram">${initial}</span>`;
3615
+ }
3616
+ const oauthConnectScript = String.raw `
3617
+ (function () {
3618
+ var C = window.MUXO_CONNECT || {};
3619
+ var form = document.getElementById('muxo-connect-form');
3620
+ var btn = document.getElementById('muxo-connect-submit');
3621
+ var errEl = document.getElementById('muxo-connect-error');
3622
+ if (!form || !btn) return;
3623
+ form.addEventListener('submit', async function (e) {
3624
+ e.preventDefault();
3625
+ if (errEl) errEl.textContent = '';
3626
+ btn.disabled = true;
3627
+ var originalText = btn.textContent;
3628
+ btn.textContent = 'Connecting to ' + (C.toolkitName || 'provider') + '…';
3629
+ try {
3630
+ var input = {};
3631
+ var fields = form.querySelectorAll('input[name]');
3632
+ for (var i = 0; i < fields.length; i++) {
3633
+ var f = fields[i];
3634
+ if (f.name && f.value !== '') input[f.name] = f.value;
3635
+ }
3636
+ var res = await fetch(form.action, {
3637
+ method: 'POST',
3638
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
3639
+ body: JSON.stringify({ input: input })
3640
+ });
3641
+ var data = await res.json().catch(function () { return {}; });
3642
+ var target = data.redirectUrl || data.redirect_url;
3643
+ if (!res.ok || data.ok === false || !target) {
3644
+ throw new Error(data.error || data.message || 'Could not start authorization handoff.');
3645
+ }
3646
+ window.location.href = target;
3647
+ } catch (err) {
3648
+ if (errEl) errEl.textContent = (err && err.message) ? err.message : 'Could not start authorization handoff.';
3649
+ btn.disabled = false;
3650
+ btn.textContent = originalText;
3651
+ }
3652
+ });
3653
+ })();
3654
+ `;
3655
+ /** Muxo-branded OAuth connection handoff screen (`/oauth/connect/:token`). */
3656
+ export function renderOAuthConnectPageHtml(opts) {
3657
+ const toolkitName = escapeHtml(opts.toolkitName || "Connected App");
3658
+ const toolkitSlug = escapeHtml(opts.toolkitSlug || "app");
3659
+ const workspaceId = opts.workspaceId ? escapeHtml(opts.workspaceId) : "";
3660
+ const authModeLabel = escapeHtml((opts.authMode || "OAUTH2").toUpperCase());
3661
+ const fields = Array.isArray(opts.expectedInputFields) ? opts.expectedInputFields : [];
3662
+ const querySuffix = (() => {
3663
+ const params = new URLSearchParams();
3664
+ if (opts.workspaceId)
3665
+ params.set("workspace_id", opts.workspaceId);
3666
+ if (opts.toolkitSlug)
3667
+ params.set("provider", opts.toolkitSlug);
3668
+ if (opts.callbackUrl)
3669
+ params.set("callback_url", opts.callbackUrl);
3670
+ const qs = params.toString();
3671
+ return qs ? `?${qs}` : "";
3672
+ })();
3673
+ const actionUrl = `/oauth/connect/${encodeURIComponent(opts.token)}${querySuffix}`;
3674
+ const fieldsHtml = fields
3675
+ .map((f) => {
3676
+ const inputType = f.type === "password" || /secret|token|key|password/i.test(f.name)
3677
+ ? "password"
3678
+ : "text";
3679
+ return `<div class="connect-field">
3680
+ <label for="field-${escapeHtml(f.name)}">${escapeHtml(f.label || f.name)}</label>
3681
+ ${f.description ? `<small>${escapeHtml(f.description)}</small>` : ""}
3682
+ <input id="field-${escapeHtml(f.name)}" name="${escapeHtml(f.name)}" type="${inputType}" value="${escapeHtml(f.default || "")}" ${f.required !== false ? "required" : ""} autocomplete="off" />
3683
+ </div>`;
3684
+ })
3685
+ .join("");
3686
+ const config = {
3687
+ token: opts.token,
3688
+ toolkitSlug: opts.toolkitSlug,
3689
+ toolkitName: opts.toolkitName,
3690
+ workspaceId: opts.workspaceId || null,
3691
+ };
3692
+ return `<!DOCTYPE html>
3693
+ <html lang="en" data-theme="dark">
3694
+ <head>
3695
+ <meta charset="utf-8" />
3696
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
3697
+ <title>Muxo wants to connect to your ${toolkitName} · Muxo</title>
3698
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3699
+ <meta name="color-scheme" content="dark light" />
3700
+ <meta name="robots" content="noindex" />
3701
+ <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
3702
+ <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
3703
+ <style>${muxoBaseCss}${consoleCss}${consentCss}${approvalCss}${oauthConnectCss}</style>
3704
+ </head>
3705
+ <body>
3706
+ <main class="auth-view">
3707
+ <div class="auth-card oauth-card">
3708
+ <div class="oauth-card-top">
3709
+ <span class="oauth-brand">${muxoMark}<span>Muxo</span></span>
3710
+ <span class="oauth-step-tag">App consent</span>
3711
+ </div>
3712
+ <div class="oauth-card-body">
3713
+ <div class="connect-bridge" aria-hidden="true">
3714
+ <div class="auth-mark connect-node" title="Muxo">${muxoMark}</div>
3715
+ <div class="connect-connector">
3716
+ <span class="connect-line"></span>
3717
+ <span class="connect-lock">${uiIcon("shield")}</span>
3718
+ <span class="connect-line"></span>
3719
+ </div>
3720
+ <div class="connect-node" title="${toolkitName}">${renderProviderBadge(opts.toolkitSlug, opts.toolkitName)}</div>
3721
+ </div>
3722
+ <div class="connect-badge-row">
3723
+ <span class="connect-pill">Muxo Connected Apps · ${toolkitSlug}</span>
3724
+ <span class="connect-pill connect-pill-subtle">${authModeLabel}</span>
3725
+ </div>
3726
+ <h1>Muxo wants to connect to your ${toolkitName}</h1>
3727
+ <p class="lede">Authorize ${toolkitName} so your agent can run governed actions in ${workspaceId ? `workspace <code>${workspaceId}</code>` : "your Muxo workspace"}.</p>
3728
+ <div class="connect-steps" aria-label="Connection steps">
3729
+ <div class="connect-step"><span class="connect-step-num">1</span><span>Authorize ${toolkitName}</span></div>
3730
+ <div class="connect-step"><span class="connect-step-num">2</span><span>Sealed in Muxo Vault</span></div>
3731
+ <div class="connect-step"><span class="connect-step-num">3</span><span>Resume in ChatGPT / Agent</span></div>
3732
+ </div>
3733
+ <ul class="consent-list">
3734
+ <li>Credentials are sealed in your isolated Muxo workspace vault</li>
3735
+ <li>Every action is checked against your workspace safety guardrails</li>
3736
+ <li>All tool runs emit a signed Ed25519 receipt and can be revoked anytime</li>
3737
+ </ul>
3738
+ <div class="consent-state">
3739
+ <form id="muxo-connect-form" method="POST" action="${escapeHtml(actionUrl)}">
3740
+ ${fieldsHtml}
3741
+ <div class="consent-actions" style="grid-template-columns:1fr;margin-top:16px">
3742
+ <button id="muxo-connect-submit" data-testid="connect-button" class="btn btn-primary btn-lg" type="submit">Connect ${toolkitName} Account</button>
3743
+ </div>
3744
+ </form>
3745
+ <p id="muxo-connect-error" class="field-error" role="alert">${opts.error ? escapeHtml(opts.error) : ""}</p>
3746
+ </div>
3747
+ </div>
3748
+ <div class="oauth-card-foot">
3749
+ <span>Secured by <strong>Muxo</strong> · Governed Agent Execution</span>
3750
+ </div>
3751
+ </div>
3752
+ </main>
3753
+ <script>window.MUXO_CONNECT = ${inlineJson(config)};${oauthConnectScript}</script>
3754
+ </body>
3755
+ </html>`;
3756
+ }
3757
+ const oauthCallbackScript = String.raw `
3758
+ (function () {
3759
+ var C = window.MUXO_OAUTH_CALLBACK || {};
3760
+ try {
3761
+ if (window.opener && !window.opener.closed) {
3762
+ window.opener.postMessage({
3763
+ type: 'muxo:oauth_complete',
3764
+ provider: C.providerSlug,
3765
+ providerName: C.providerName,
3766
+ status: C.status,
3767
+ workspaceId: C.workspaceId,
3768
+ externalAccountId: C.externalAccountId
3769
+ }, '*');
3770
+ }
3771
+ } catch (e) {}
3772
+ var closeBtn = document.getElementById('oauth-close-window');
3773
+ var hintEl = document.getElementById('oauth-close-hint');
3774
+ if (closeBtn) {
3775
+ closeBtn.addEventListener('click', function () {
3776
+ try { window.close(); } catch (e) {}
3777
+ if (hintEl) hintEl.hidden = false;
3778
+ });
3779
+ }
3780
+ })();
3781
+ `;
3782
+ /** Muxo-branded OAuth callback completion screen (`/oauth/callback`). */
3783
+ export function renderOAuthCallbackPageHtml(opts) {
3784
+ const providerSlug = escapeHtml(opts.providerSlug || "app");
3785
+ const providerName = escapeHtml(opts.providerName || "Connected App");
3786
+ const statusUpper = escapeHtml(String(opts.status || "active").toUpperCase());
3787
+ const isSuccess = String(opts.status || "active").toLowerCase() === "active";
3788
+ const workspaceId = escapeHtml(opts.workspaceId || "ws_default");
3789
+ const externalAccountId = opts.externalAccountId ? escapeHtml(opts.externalAccountId) : "";
3790
+ const consoleHref = `/?connected=${encodeURIComponent(opts.providerSlug || "app")}`;
3791
+ const config = {
3792
+ providerSlug: opts.providerSlug,
3793
+ providerName: opts.providerName,
3794
+ status: opts.status,
3795
+ workspaceId: opts.workspaceId,
3796
+ externalAccountId: opts.externalAccountId || null,
3797
+ };
3798
+ return `<!DOCTYPE html>
3799
+ <html lang="en" data-theme="dark">
3800
+ <head>
3801
+ <meta charset="utf-8" />
3802
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
3803
+ <title>Muxo OAuth Handoff Complete · Muxo</title>
3804
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3805
+ <meta name="color-scheme" content="dark light" />
3806
+ <meta name="robots" content="noindex" />
3807
+ <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
3808
+ <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
3809
+ <style>${muxoBaseCss}${consoleCss}${consentCss}${approvalCss}${oauthConnectCss}</style>
3810
+ </head>
3811
+ <body>
3812
+ <main class="auth-view">
3813
+ <div class="auth-card oauth-card">
3814
+ <div class="oauth-card-top">
3815
+ <span class="oauth-brand">${muxoMark}<span>Muxo</span></span>
3816
+ <span class="oauth-step-tag">${isSuccess ? "Connected" : "Connection update"}</span>
3817
+ </div>
3818
+ <div class="oauth-card-body">
3819
+ <div class="connect-bridge" aria-hidden="true">
3820
+ <div class="auth-mark connect-node" title="Muxo">${muxoMark}</div>
3821
+ <div class="connect-connector">
3822
+ <span class="connect-line"></span>
3823
+ <span class="connect-lock" data-tone="${isSuccess ? "ok" : "warn"}">${uiIcon("check")}</span>
3824
+ <span class="connect-line"></span>
3825
+ </div>
3826
+ <div class="connect-node" title="${providerName}">${renderProviderBadge(opts.providerSlug, opts.providerName)}</div>
3827
+ </div>
3828
+ <div class="connect-badge-row">
3829
+ <span class="connect-pill connect-pill-status" data-tone="${isSuccess ? "ok" : "warn"}">OAuth Handoff Complete (${statusUpper})</span>
3830
+ </div>
3831
+ <h1>${providerName} Connected to Muxo</h1>
3832
+ <p class="lede">Workspace <code>${workspaceId}</code> is now linked${externalAccountId ? ` (<code>${externalAccountId}</code>)` : ""}. You can close this window and return to ChatGPT, Codex, Claude Code, Droid, or your terminal.</p>
3833
+ <ul class="approval-facts">
3834
+ <li><span class="k">Connected app</span><span class="v">${providerName} (${providerSlug})</span></li>
3835
+ <li><span class="k">Vault status</span><span class="v">${isSuccess ? "Active · Sealed in Muxo Vault" : statusUpper}</span></li>
3836
+ <li><span class="k">Workspace</span><span class="v"><code>${workspaceId}</code></span></li>
3837
+ ${externalAccountId ? `<li><span class="k">Connection ID</span><span class="v"><code>${externalAccountId}</code></span></li>` : ""}
3838
+ </ul>
3839
+ <div class="consent-state">
3840
+ <div class="consent-actions" style="grid-template-columns:1fr;margin-top:16px">
3841
+ <button id="oauth-close-window" class="btn btn-primary btn-lg" type="button">Done — Return to ChatGPT / Agent</button>
3842
+ </div>
3843
+ <div class="consent-actions" style="grid-template-columns:1fr;margin-top:10px">
3844
+ <a class="btn btn-lg" href="${escapeHtml(consoleHref)}">Open Muxo Console</a>
3845
+ </div>
3846
+ <p id="oauth-close-hint" class="approval-note" hidden>You can safely close this browser tab now and tell ChatGPT or your agent to continue.</p>
3847
+ </div>
3848
+ </div>
3849
+ <div class="oauth-card-foot">
3850
+ <span>Secured by <strong>Muxo</strong> · Governed Agent Execution</span>
3851
+ </div>
3852
+ </div>
3853
+ </main>
3854
+ <script>window.MUXO_OAUTH_CALLBACK = ${inlineJson(config)};${oauthCallbackScript}</script>
3855
+ </body>
3856
+ </html>`;
3857
+ }