@muxoai/platform 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/README.md +1 -1
  4. package/dist/cli.js +6 -3
  5. package/dist/composio-catalog.d.ts +177 -1
  6. package/dist/composio-catalog.js +1427 -7
  7. package/dist/config.d.ts +6 -0
  8. package/dist/config.js +11 -1
  9. package/dist/engine.d.ts +18 -1
  10. package/dist/engine.js +259 -35
  11. package/dist/mcp-schema.d.ts +162 -1
  12. package/dist/mcp-schema.js +1523 -40
  13. package/dist/mcp.d.ts +2 -2
  14. package/dist/mcp.js +412 -23
  15. package/dist/policy.d.ts +108 -1
  16. package/dist/policy.js +406 -1
  17. package/dist/providers/composio-instant.js +38 -3
  18. package/dist/providers/composio-sdk.d.ts +59 -2
  19. package/dist/providers/composio-sdk.js +273 -59
  20. package/dist/providers/stripe-projects.d.ts +4 -0
  21. package/dist/providers/stripe-projects.js +548 -126
  22. package/dist/server.js +531 -61
  23. package/dist/store.js +2 -0
  24. package/dist/types.d.ts +15 -0
  25. package/dist/ui/console-html.d.ts +31 -1
  26. package/dist/ui/console-html.js +922 -368
  27. package/dist/ui/design-tokens.js +1 -1
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/package.json +3 -2
  31. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  32. package/plugins/muxo/.codex-plugin/plugin.json +15 -8
  33. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  34. package/plugins/muxo/hooks/session-start.sh +15 -3
  35. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  36. package/plugins/muxo/mcp.json +0 -10
  37. package/plugins/muxo/plugin.json +4 -4
  38. package/plugins/muxo/skills/muxo/SKILL.md +25 -16
  39. package/plugins/muxo/skills/muxo/references/cli.md +5 -1
  40. package/plugins/muxo/skills/muxo/references/mcp.md +11 -6
  41. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  42. package/plugins/muxo/skills/setup/SKILL.md +1 -1
  43. package/src/catalogues/stripe-projects-catalog.json +140 -0
@@ -4,11 +4,13 @@ export const CONSOLE_ROUTES = Object.freeze({
4
4
  "/": "overview",
5
5
  "/console": "overview",
6
6
  "/dashboard": "overview",
7
- "/catalog": "overview",
7
+ "/catalog": "catalog",
8
+ "/tools": "catalog",
8
9
  "/connections": "overview",
9
10
  "/infrastructure": "overview",
10
11
  "/keys": "setup",
11
12
  "/setup": "setup",
13
+ "/workspaces": "workspaces",
12
14
  "/activity": "activity",
13
15
  "/receipts": "activity",
14
16
  "/governance": "activity",
@@ -39,6 +41,7 @@ const consoleCss = `
39
41
  .workspace-chip:hover{background:var(--color-surface-2)}
40
42
  .workspace-chip #workspace-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
41
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)}
42
45
  .topbar-links{display:flex;align-items:center;gap:20px;margin-left:auto}
43
46
  .nav-link{display:inline-flex;align-items:center;min-height:44px;color:var(--color-muted);font-size:13px;text-decoration:none}
44
47
  .nav-link:hover{color:var(--color-text)}
@@ -47,6 +50,13 @@ const consoleCss = `
47
50
  .theme-sun{display:none}html[data-theme=dark] .theme-sun{display:inline-block}html[data-theme=dark] .theme-moon{display:none}
48
51
  .account{display:flex;align-items:center;gap:12px}
49
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}
50
60
 
51
61
  .console-layout{display:grid;grid-template-columns:var(--sidebar-width) minmax(0,1fr);min-height:calc(100dvh - var(--header-height))}
52
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)}
@@ -54,6 +64,7 @@ const consoleCss = `
54
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}
55
65
  .nav-item:hover{color:var(--color-text);background:var(--color-surface-2)}
56
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)}
57
68
  .nav-item .ui-icon{width:18px;height:18px}
58
69
  .nav-item .count{margin-left:auto;color:var(--color-muted);font-size:12px;font-weight:400;font-variant-numeric:tabular-nums}
59
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)}
@@ -70,7 +81,10 @@ const consoleCss = `
70
81
  .page-header h1{font-size:clamp(28px,3vw,34px);line-height:1.15}
71
82
  .lede{max-width:62ch;margin-top:8px;color:var(--color-muted);font-size:15px;text-wrap:pretty}
72
83
  .page-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
73
- .stack{display:grid;gap:20px}
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}
74
88
  .split{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);gap:20px}
75
89
  .split.even{grid-template-columns:repeat(2,minmax(0,1fr))}
76
90
  .panel{min-width:0;padding:24px;border:1px solid var(--color-border);border-radius:18px;background:var(--color-surface)}
@@ -78,6 +92,60 @@ const consoleCss = `
78
92
  .panel-header h2{font-size:17px;line-height:1.3}
79
93
  .panel-header p{margin-top:4px;color:var(--color-muted);font-size:13px}
80
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}
81
149
 
82
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)}
83
151
  .metric{min-width:0;padding:20px 24px;border-left:1px solid var(--color-border)}
@@ -87,6 +155,12 @@ const consoleCss = `
87
155
  .metric-value[data-tone=ok]{color:var(--color-ok)}
88
156
  .metric-value[data-tone=danger]{color:var(--color-danger)}
89
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)}
90
164
 
91
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)}
92
166
  .btn:not(:disabled):hover{background:var(--color-surface-2);text-decoration:none}
@@ -116,6 +190,7 @@ input[type=search]::-webkit-search-cancel-button{display:none}
116
190
  input[type=checkbox]{width:16px;height:16px;margin:0;accent-color:var(--color-button-primary)}
117
191
  .form-grid{display:grid;gap:14px}
118
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}
119
194
  .inline-form{display:flex;align-items:center;gap:8px}
120
195
  .inline-form input{flex:1}
121
196
  .checks{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
@@ -136,6 +211,13 @@ input[type=checkbox]{width:16px;height:16px;margin:0;accent-color:var(--color-bu
136
211
  .search-field input{border:0;padding-left:0;background:transparent}
137
212
  .search-field input:focus{box-shadow:none}
138
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}
139
221
 
140
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}
141
223
  .pill.dot::before{content:"";width:6px;height:6px;flex:none;border-radius:50%;background:currentColor}
@@ -165,7 +247,7 @@ input[type=checkbox]{width:16px;height:16px;margin:0;accent-color:var(--color-bu
165
247
  .empty-note{padding:20px 0;color:var(--color-muted);font-size:13px}
166
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}
167
249
 
168
- .table-wrap{overflow-x:auto;border:1px solid var(--color-border);border-radius:18px;background:var(--color-surface)}
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)}
169
251
  .panel .table-wrap{border-radius:var(--radius-md)}
170
252
  table{width:100%;border-collapse:collapse;text-align:left;font-size:13px;font-variant-numeric:tabular-nums}
171
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}
@@ -185,6 +267,9 @@ th.right,.cell-actions{text-align:right}
185
267
  .row strong{display:block;font-size:14px;font-weight:500;overflow-wrap:anywhere}
186
268
  .row small{display:block;margin-top:2px;color:var(--color-muted);font-size:12px;overflow-wrap:anywhere}
187
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}
188
273
 
189
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)}
190
275
  .ask:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 4px var(--color-accent-soft)}
@@ -235,50 +320,105 @@ body:has(dialog[open]){overflow:hidden}
235
320
  .toast[data-tone=danger]{background:var(--color-button-danger);color:#fff}
236
321
 
237
322
  .auth-view{display:grid;place-items:center;min-height:calc(100dvh - var(--header-height));padding:48px 20px}
238
- .auth-card{width:min(440px,100%);padding:40px;border:1px solid var(--color-border);border-radius:24px;background:var(--color-surface);text-align:center}
239
- .auth-mark{display:inline-grid;place-items:center;width:56px;height:56px;margin-bottom:16px;border-radius:16px;background:var(--color-surface-2);color:var(--color-text)}
240
- .auth-mark svg{width:30px;height:30px}
241
- .auth-card h1{font-size:28px;line-height:1.2}
242
- .auth-card .lede{margin:8px auto 0}
243
- .auth-card .segmented{margin-top:24px}
244
- .auth-form{display:grid;gap:14px;margin-top:24px;text-align:left}
245
- .auth-alt{margin-top:12px}
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)}
246
345
  .row-actions{display:inline-flex;align-items:center;gap:8px;flex:none}
247
- .clerk-auth{display:flex;justify-content:center;min-height:120px;margin-top:20px;text-align:left}
248
- .clerk-loading{display:flex;align-items:center;gap:10px;align-self:center;margin:0;color:var(--color-muted);font-size:15px}
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}
249
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}
250
349
  @keyframes muxo-spin{to{transform:rotate(360deg)}}
251
350
  @media (prefers-reduced-motion:reduce){.spinner{animation:none}}
252
- .auth-switch{margin-top:4px}
351
+ .auth-switch{margin-top:8px}
253
352
  .auth-switch:has([hidden]){display:none}
254
- .auth-foot{margin-top:16px;color:var(--color-muted);font-size:12px}
353
+ .auth-foot{margin-top:16px;color:var(--color-muted);font-size:12px;line-height:1.5}
255
354
  .auth-foot code{font-size:11.5px}
256
355
 
257
- @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{grid-template-columns:1fr}}
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))}}
258
357
  @media (max-width:900px){
259
358
  .console-layout{grid-template-columns:1fr}
260
- .console-sidebar{top:var(--header-height);z-index:30;flex-direction:row;gap:4px;height:auto;overflow-x:auto;padding:8px 16px;border-right:0;border-bottom:1px solid var(--color-border);background:var(--color-navigation);backdrop-filter:saturate(180%) blur(20px);-webkit-backdrop-filter:saturate(180%) blur(20px)}
261
- .nav-group{display:contents}.nav-group p,.sidebar-usage,.nav-item .count{display:none}
262
- .nav-item{flex:none;margin:0;white-space:nowrap}
359
+ .console-sidebar{display:none}
360
+ .mobile-menu-toggle{display:grid}
263
361
  .console-main{padding:28px 20px 80px}
264
- .topbar-links .nav-link,.workspace-chip,.account-label{display:none}
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)}
265
370
  .checks{grid-template-columns:repeat(2,minmax(0,1fr))}
266
371
  }
267
- @media (max-width:560px){.metrics{grid-template-columns:1fr}.metric{border-left:0}.metric+.metric{border-top:1px solid var(--color-border)}.form-row{grid-template-columns:1fr}.panel{padding:20px}.auth-card{padding:28px 22px}.brand-section{display:none}.inline-form{flex-wrap:wrap}}
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
+ }
268
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}}
269
400
  `;
270
401
  const consoleScript = String.raw `
271
- var VIEW_PATHS = { overview: '/', setup: '/keys', activity: '/activity', billing: '/billing', login: '/login', signup: '/signup' };
272
- var VIEW_TITLES = { overview: 'Harness onboarding', setup: 'API keys and access', activity: 'History and audit', billing: 'Billing and credits', login: 'Sign in', signup: 'Create account' };
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' };
273
404
  var LANE_LABELS = { composio_instant: 'Instant tool', composio_sdk: 'Connected app', stripe_projects: 'Cloud service' };
274
405
  var STATUS_LABELS = {
275
406
  executed: 'Completed',
276
407
  succeeded: 'Completed',
408
+ completed: 'Completed',
277
409
  guardrail_applied: 'Guardrail active',
278
410
  memory_saved: 'Saved to memory',
279
411
  planned_dry_run: 'Planned',
280
- awaiting_approval: 'Waiting for approval',
281
- awaiting_oauth_connection: 'Connect app to continue'
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'
282
422
  };
283
423
  var DEFAULT_PARAM_LABELS = {
284
424
  defaultRegion: 'Default cloud region',
@@ -332,9 +472,8 @@ function sanitizeForDisplay(val) {
332
472
  return out;
333
473
  }
334
474
 
335
- var state = { view: 'overview', boot: null, harness: 'plugin', lane: 'all', factsMode: 'current', facts: null, authMode: 'login', activeMemoryDefaults: {}, plainEnglishGuardrails: null, billingInterval: 'month', planCatalog: null };
475
+ var state = { view: 'overview', boot: null, lane: 'all', factsMode: 'current', facts: null, authMode: 'login', activeMemoryDefaults: {}, plainEnglishGuardrails: null, billingInterval: 'month', planCatalog: null };
336
476
  var currentTool = null;
337
- var activeKeyHint = 'mk_live_...';
338
477
  var externalApiKey = readToken();
339
478
  var latestSpawnedKey = '';
340
479
  var currentWorkspaceId = '';
@@ -342,6 +481,7 @@ var activeWorkspaceOverrideId = '';
342
481
  var pendingModalPlan = null;
343
482
  var pendingModalHandoff = null;
344
483
  var toastTimer = null;
484
+ var catalogRequestId = 0;
345
485
  var authConfig = { provider: 'password', clerk: null };
346
486
  var clerk = null;
347
487
  var clerkMounted = '';
@@ -374,8 +514,8 @@ function riskBadge(risk) {
374
514
  function statusBadge(status) {
375
515
  var s = String(status || 'unknown');
376
516
  var tone = /^(active|succeeded|approved|auto_approved|completed|executed|guardrail_applied|memory_saved)$/.test(s) ? 'ok'
377
- : /(fail|error|denied|rejected|blocked|revoked|expired)/.test(s) ? 'danger'
378
- : /(pending|initiated|waiting|running|queued)/.test(s) ? 'warn' : '';
517
+ : /(fail|error|denied|rejected|blocked|revoked|expired|unknown|unverified)/.test(s) ? 'danger'
518
+ : /(pending|initiated|waiting|awaiting)/.test(s) ? 'warn' : '';
379
519
  return pill(STATUS_LABELS[s] || s.replace(/_/g, ' '), tone, true);
380
520
  }
381
521
 
@@ -407,6 +547,11 @@ function applyTheme(theme) {
407
547
  b.setAttribute('aria-pressed', String(theme === 'dark'));
408
548
  b.setAttribute('aria-label', theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode');
409
549
  }
550
+ if (clerk && clerkMounted && (state.view === 'login' || state.view === 'signup')) {
551
+ var prev = clerkMounted;
552
+ unmountClerkAuth();
553
+ mountClerkAuth(prev);
554
+ }
410
555
  }
411
556
  function toggleTheme() {
412
557
  var next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
@@ -416,6 +561,10 @@ function toggleTheme() {
416
561
 
417
562
  function openDialog(id) { var d = byId(id); if (d && !d.open) { if (d.showModal) d.showModal(); else d.setAttribute('open', ''); } }
418
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
+ }
419
568
  function askInput(o) {
420
569
  return new Promise(function (resolve) {
421
570
  var d = byId('ask-dialog');
@@ -491,6 +640,8 @@ function navigate(view, opts) {
491
640
  opts = opts || {};
492
641
  if (!VIEW_PATHS[view]) view = 'overview';
493
642
  var authView = view === 'login' || view === 'signup';
643
+ closeDialog('mobile-menu');
644
+ byId('mobile-menu-toggle').hidden = authView;
494
645
  state.view = view;
495
646
  byId('console-shell').hidden = authView;
496
647
  byId('auth-view').hidden = !authView;
@@ -511,7 +662,8 @@ function navigate(view, opts) {
511
662
  }
512
663
  function loadView(view) {
513
664
  var job = null;
514
- if (view === 'overview' || view === 'setup') job = refreshOverview();
665
+ if (view === 'overview' || view === 'setup' || view === 'workspaces') job = refreshOverview();
666
+ else if (view === 'catalog') job = loadCatalog();
515
667
  else if (view === 'activity') job = Promise.all([loadRuns(), loadPlans()]);
516
668
  else if (view === 'billing') job = loadLedger();
517
669
  if (job && job.catch) job.catch(showError);
@@ -656,13 +808,18 @@ function renderWorkspaceList(workspaces) {
656
808
  }).join('') : '<p class="empty-note">Sign in or paste a workspace API key below to link a workspace.</p>';
657
809
  var dialogBox = byId('workspace-dialog-list');
658
810
  if (dialogBox) dialogBox.innerHTML = html;
659
- var setupBox = byId('setup-workspaces-list');
811
+ var setupBox = byId('workspaces-list');
660
812
  if (setupBox) setupBox.innerHTML = html;
661
813
  }
662
814
  async function switchExternalWorkspacePrompt() {
663
815
  renderWorkspaceList();
664
816
  openDialog('workspace-dialog');
665
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
+ }
666
823
  async function selectWorkspaceFromUi(workspaceId) {
667
824
  if (!workspaceId) return;
668
825
  activeWorkspaceOverrideId = workspaceId;
@@ -745,13 +902,14 @@ async function onboardTeamFromForm() {
745
902
  var box = byId('team-kit-box');
746
903
  if (box) {
747
904
  box.hidden = false;
748
- setText('team-kit-title', 'Team Onboarding Kit · ' + (kit.teamName || teamName) + ' (' + (kit.role || role) + ')');
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) || '');
749
907
  setText('team-kit-key', kit.apiKey || '');
750
908
  setText('team-kit-cli', kit.cliSetupCommand || '');
751
909
  setText('team-kit-instructions', kit.instructions || '');
752
910
  }
753
911
  if (teamInput) teamInput.value = '';
754
- showToast('Generated onboarding kit for ' + teamName);
912
+ showToast('Created team access for ' + teamName);
755
913
  await refreshOverview();
756
914
  }
757
915
  async function setExternalWorkspaceKey(key, toastMsg) {
@@ -865,6 +1023,8 @@ function renderAccount(s) {
865
1023
  byId('account-signin').hidden = signedIn;
866
1024
  byId('account-signed-in').hidden = !signedIn;
867
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;
868
1028
  var badge = byId('workspace-auth-badge');
869
1029
  if (badge) {
870
1030
  badge.textContent = usingKey ? 'API key ' + externalApiKey.slice(0, 12) + '…' : signedIn ? 'Signed in' : 'Local workspace';
@@ -881,12 +1041,7 @@ function syncPolicyForm(policy) {
881
1041
  if (byId('policy-ring')) byId('policy-ring').value = String(policy.executionRing != null ? policy.executionRing : 2);
882
1042
  if (byId('policy-block-sql')) byId('policy-block-sql').checked = policy.blockDestructiveSql !== false;
883
1043
  }
884
- function renderIdentity(g) {
885
- if (!g) return;
886
- setText('gov-did', g.agentDid || '');
887
- var pub = g.publicKeyHex || g.ed25519PublicKey || '';
888
- setText('gov-pubkey', pub ? pub.slice(0, 32) + '…' : '');
889
- }
1044
+ function renderIdentity() {}
890
1045
  function renderApiKeys(keys) {
891
1046
  byId('api-keys-list').innerHTML = keys.length ? keys.map(function (k) {
892
1047
  var revoked = Boolean(k.revokedAt);
@@ -895,16 +1050,31 @@ function renderApiKeys(keys) {
895
1050
  k.role ? pill(k.role) : '',
896
1051
  pill(k.environment === 'test' ? 'Test' : 'Live', k.environment === 'test' ? 'warn' : 'ok')
897
1052
  ].filter(Boolean).join(' ');
898
- return '<div class="row"><div><strong>' + esc(k.name) + '</strong><small class="mono">' + esc(k.keyPrefix) + '</small></div>' +
1053
+ return '<div class="row api-key-row"><div><strong>' + esc(k.name) + '</strong><small class="mono">' + esc(k.keyPrefix) + '</small></div>' +
899
1054
  (revoked ? pill('Revoked', 'danger') : '<span class="row-actions">' + metaBadges + ' ' +
900
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>';
901
- }).join('') : '<p class="empty-note">No keys yet. Create one to connect an agent or onboard a team.</p>';
1056
+ }).join('') : '<p class="empty-note">No API keys yet. Create one to connect an agent or app.</p>';
902
1057
  }
903
1058
  function renderRecentRuns(runs) {
904
- byId('recent-runs').innerHTML = runs.length ? runs.slice(0, 5).map(function (r) {
1059
+ byId('recent-runs').innerHTML = runs.length ? runs.slice(0, 6).map(function (r) {
905
1060
  var meta = [LANE_LABELS[r.lane] || '', timeAgo(r.createdAt || r.startedAt), r.durationMs != null ? r.durationMs + ' ms' : ''].filter(Boolean).join(' · ');
906
- return '<div class="row"><div><strong>' + esc(formatToolLabel(r.toolSlug)) + '</strong><small>' + esc(meta) + '</small></div>' + statusBadge(r.status) + '</div>';
907
- }).join('') : '<p class="empty-note">No runs yet. Try a live tool above.</p>';
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');
908
1078
  }
909
1079
  function countActive(list) { return (list || []).filter(function (x) { return x.status === 'active'; }).length; }
910
1080
  function setCount(id, n) { setText(id, n ? fmt(n) : ''); }
@@ -924,18 +1094,34 @@ async function refreshOverview() {
924
1094
  var pending = plans.filter(function (p) { return p.approvalStatus === 'pending_approval'; }).length;
925
1095
  var tier = String(org.tier || 'free');
926
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
+ }
927
1103
  currentWorkspaceId = org.id || '';
928
1104
  setText('workspace-name', org.name || 'Workspace');
929
1105
  setText('workspace-tier', tierLabel);
930
- setText('workspace-slug', org.slug || org.name || 'Primary workspace');
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');
931
1118
  setText('kpi-credits', fmt(credits));
932
1119
  setText('kpi-tier-badge', tierLabel);
933
1120
  setText('kpi-actions', fmt(used));
934
1121
  setText('kpi-actions-sub', 'of ' + fmt(limit) + ' included');
935
1122
  setText('kpi-connections', fmt(activeConns + activeRes));
936
- setText('kpi-runs', fmt(runs.length));
937
1123
  setText('meter-actions-text', fmt(used) + ' / ' + fmt(limit));
938
- byId('meter-actions-bar').style.width = Math.min(100, Math.max(2, Math.round((used / Math.max(1, limit)) * 100))) + '%';
1124
+ byId('meter-actions-bar').style.width = Math.min(100, Math.max(0, Math.round((used / Math.max(1, limit)) * 100))) + '%';
939
1125
  setText('meter-credits-text', fmt(credits));
940
1126
  setText('billing-plan', tierLabel);
941
1127
  setText('billing-credits', fmt(credits));
@@ -949,8 +1135,6 @@ async function refreshOverview() {
949
1135
  state.plainEnglishGuardrails = s.plainEnglishGuardrails || null;
950
1136
  renderPlainEnglishGuardrails(state.plainEnglishGuardrails, org.policy);
951
1137
  renderActiveMemoryDefaults(state.activeMemoryDefaults, s.activeGuardrails || (s.supermemory && s.supermemory.activeGuardrails) || []);
952
- activeKeyHint = externalApiKey || s.defaultApiKey || ((s.apiKeys || [])[0] ? s.apiKeys[0].keyPrefix : 'mk_live_...');
953
- setHarnessSnippet(state.harness);
954
1138
  renderIdentity(s.governance);
955
1139
  renderApiKeys(s.apiKeys || []);
956
1140
  renderWorkspaceList(s.workspaces || []);
@@ -958,78 +1142,6 @@ async function refreshOverview() {
958
1142
  return s;
959
1143
  }
960
1144
 
961
- function setHarnessSnippet(kind) {
962
- state.harness = kind || 'plugin';
963
- document.querySelectorAll('#harness-tabs button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.kind === state.harness)); });
964
- var el = byId('mcp-config-snippet');
965
- var origin = window.location.origin;
966
- var lines;
967
- if (state.harness === 'plugin') {
968
- lines = [
969
- '# Claude Code',
970
- 'claude plugin marketplace add ' + origin + '/.claude-plugin/marketplace.json',
971
- 'claude plugin install muxo@muxo',
972
- '',
973
- '# OpenAI Codex',
974
- 'codex mcp add muxo --url ' + origin + '/mcp',
975
- '',
976
- '# Any agent with a terminal (Node.js 22+)',
977
- 'npx -y @muxoai/cli@latest login --signup --name "my-agent"'
978
- ];
979
- } else if (state.harness === 'oauth') {
980
- lines = [
981
- '# Sign in from the CLI, then connect an app in your browser',
982
- 'npx -y @muxoai/cli@latest login --signup --name "my-agent" --url ' + origin,
983
- 'npx -y @muxoai/cli@latest whoami',
984
- 'npx -y @muxoai/cli@latest link github --wait',
985
- '',
986
- '# Check that your agent can reach Muxo',
987
- 'npx -y @muxoai/cli@latest harness'
988
- ];
989
- } else if (state.harness === 'sdk') {
990
- lines = [
991
- 'import { MuxoClient } from "@muxoai/sdk";',
992
- '',
993
- 'const muxo = new MuxoClient({',
994
- ' baseUrl: "' + origin + '",',
995
- ' apiKey: process.env.MUXO_API_KEY || "' + activeKeyHint + '"',
996
- '});',
997
- '',
998
- 'const result = await muxo.do("Fetch top 3 stories on Hacker News");'
999
- ];
1000
- } else if (state.harness === 'mastra') {
1001
- lines = [
1002
- 'import { MCPClient } from "@mastra/mcp";',
1003
- '',
1004
- 'export const muxo = new MCPClient({',
1005
- ' servers: {',
1006
- ' muxo: {',
1007
- ' url: new URL("' + origin + '/mcp"),',
1008
- ' requestInit: { headers: { Authorization: "Bearer ' + activeKeyHint + '" } }',
1009
- ' }',
1010
- ' }',
1011
- '});'
1012
- ];
1013
- } else if (state.harness === 'pi') {
1014
- lines = [
1015
- '# Pi coding agent',
1016
- 'curl -sL ' + origin + '/skill.md > ~/.pi/skills/muxo.md',
1017
- 'export MUXO_API_KEY="' + activeKeyHint + '"'
1018
- ];
1019
- } else if (state.harness === 'curl') {
1020
- lines = [
1021
- '# Run any request in plain English over HTTPS',
1022
- 'curl -sX POST ' + origin + '/api/v1/do \\',
1023
- ' -H "Authorization: Bearer ' + activeKeyHint + '" \\',
1024
- ' -H "Content-Type: application/json" \\',
1025
- ' -d ' + JSON.stringify(JSON.stringify({ prompt: 'Audit https://muxo.ai for technical SEO' }))
1026
- ];
1027
- } else {
1028
- lines = [json({ mcpServers: { muxo: { url: origin + '/mcp', headers: { Authorization: 'Bearer ' + activeKeyHint } } } })];
1029
- }
1030
- el.textContent = lines.join('\n');
1031
- }
1032
-
1033
1145
  function showNewKey(key, title, canSwitch) {
1034
1146
  byId('new-key-banner').hidden = false;
1035
1147
  setText('new-key-title', title);
@@ -1137,20 +1249,35 @@ function toolTile(t) {
1137
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>';
1138
1250
  }
1139
1251
  async function loadCatalog() {
1252
+ var requestId = ++catalogRequestId;
1140
1253
  var q = byId('catalog-search').value.trim();
1141
1254
  var grid = byId('catalog-grid');
1255
+ byId('clear-catalog-search').hidden = !q;
1142
1256
  grid.setAttribute('aria-busy', 'true');
1143
1257
  setText('catalog-status', 'Loading tools…');
1144
1258
  try {
1145
1259
  var data = await api('/api/v1/tools?query=' + encodeURIComponent(q) + '&lane=' + encodeURIComponent(state.lane) + '&limit=48');
1260
+ if (requestId !== catalogRequestId) return;
1146
1261
  var tools = data.tools || [];
1147
1262
  setText('nav-catalog-count', data.total ? fmt(data.total) : '');
1148
1263
  setText('catalog-status', fmt(data.total != null ? data.total : tools.length) + ' tools' + (q ? ' matching "' + q + '"' : '') + (tools.length < (data.total || 0) ? ', showing ' + tools.length : ''));
1149
- grid.innerHTML = tools.length ? tools.map(toolTile).join('') : '<div class="empty-state">No tools match that search. Try a provider name like github or neon.</div>';
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;
1150
1270
  } finally {
1151
- grid.removeAttribute('aria-busy');
1271
+ if (requestId === catalogRequestId) grid.removeAttribute('aria-busy');
1152
1272
  }
1153
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
+ }
1154
1281
 
1155
1282
  async function openExecuteModal(slug) {
1156
1283
  var tool = await api('/api/v1/tools/' + encodeURIComponent(slug));
@@ -1248,7 +1375,7 @@ async function submitPlanOnly() {
1248
1375
  var planned = await post('/api/v1/plans', { toolSlug: currentTool.slug, input: input, reason: byId('modal-tool-reason').value });
1249
1376
  renderModalOAuthHandoff(planned);
1250
1377
  showModalResult(json(sanitizeForDisplay(planned)));
1251
- showToast('Plan created. Review it in Safety and approvals.');
1378
+ showToast('Plan created. Review it in Activity.');
1252
1379
  await refreshOverview();
1253
1380
  } catch (err) {
1254
1381
  showModalResult('Error: ' + err.message);
@@ -1279,7 +1406,8 @@ async function loadResources() {
1279
1406
  var data = await api('/api/v1/resources');
1280
1407
  var list = data.resources || [];
1281
1408
  setCount('nav-res-count', countActive(list));
1282
- byId('resources-tbody').innerHTML = list.length ? list.map(function (r) {
1409
+ var tbody = byId('resources-tbody');
1410
+ if (tbody) tbody.innerHTML = list.length ? list.map(function (r) {
1283
1411
  var serviceLabel = String(r.serviceSlug || 'Service').replace(/[_-]+/g, ' ');
1284
1412
  var envCount = (r.envVarKeys || []).length;
1285
1413
  var secretCell = envCount ? pill(envCount + (envCount === 1 ? ' secret in vault' : ' secrets in vault'), 'ok') : pill('Encrypted in vault', 'ok');
@@ -1319,7 +1447,7 @@ async function loadProvisioningOnboardingStatus() {
1319
1447
  if (!res.connected || !res.account) {
1320
1448
  badge.textContent = 'Not onboarded yet';
1321
1449
  delete badge.dataset.tone;
1322
- meta.textContent = 'Start embedded onboarding in-console or open a hosted Stripe link to activate your tenant provisioning account.';
1450
+ meta.textContent = 'Complete verification to provision managed databases and cloud resources in your workspace.';
1323
1451
  return;
1324
1452
  }
1325
1453
  var acct = res.account;
@@ -1327,11 +1455,11 @@ async function loadProvisioningOnboardingStatus() {
1327
1455
  ? acct.configuration.developer.capabilities.projects.status
1328
1456
  : 'active';
1329
1457
  var dueList = (acct.requirements && (acct.requirements.currently_due || acct.requirements.entries)) || [];
1330
- badge.textContent = projStatus === 'active' ? 'Connected · ' + acct.id : 'Onboarding (' + projStatus + ') · ' + acct.id;
1458
+ badge.textContent = projStatus === 'active' ? 'Verified' : 'Verification (' + projStatus + ')';
1331
1459
  badge.dataset.tone = projStatus === 'active' && dueList.length === 0 ? 'ok' : 'warn';
1332
1460
  meta.textContent = dueList.length > 0
1333
- ? dueList.length + ' verification requirement(s) currently due. Complete embedded onboarding below.'
1334
- : 'Stripe Accounts v2 developer.projects capability is ' + projStatus + '.';
1461
+ ? dueList.length + ' verification requirement(s) currently due. Complete onboarding below.'
1462
+ : 'Cloud resource provisioning is ' + projStatus + ' for this workspace.';
1335
1463
  }
1336
1464
 
1337
1465
  async function openEmbeddedOnboarding() {
@@ -1339,7 +1467,7 @@ async function openEmbeddedOnboarding() {
1339
1467
  var mount = byId('stripe-embedded-onboarding-mount');
1340
1468
  if (!box || !mount) return;
1341
1469
  box.hidden = false;
1342
- mount.innerHTML = '<p class="empty-note">Creating Stripe Connect v2 Account Session…</p>';
1470
+ mount.innerHTML = '<p class="empty-note">Preparing verification session…</p>';
1343
1471
  var session = await post('/api/v1/provisioning/connected_account_sessions', {
1344
1472
  auto_create: true,
1345
1473
  enable_account_management: true,
@@ -1374,33 +1502,32 @@ async function openEmbeddedOnboarding() {
1374
1502
  }
1375
1503
  if (typeof onboardingEl.setOnExit === 'function') {
1376
1504
  onboardingEl.setOnExit(function () {
1377
- showToast('Finished Stripe Connect onboarding step');
1505
+ showToast('Verification step saved');
1378
1506
  loadProvisioningOnboardingStatus().catch(function () {});
1379
1507
  });
1380
1508
  }
1381
1509
  mount.innerHTML = '';
1382
1510
  mount.appendChild(bannerEl);
1383
1511
  mount.appendChild(onboardingEl);
1384
- showToast('Mounted Stripe Connect embedded onboarding & notification banner');
1512
+ showToast('Verification form ready');
1385
1513
  return;
1386
1514
  } catch (err) {
1387
1515
  console.error(err);
1388
1516
  }
1389
1517
  }
1390
1518
  mount.innerHTML = '<div class="result-card">' +
1391
- '<div class="result-head"><div><strong>Stripe Connect v2 Account Session ready</strong>' +
1392
- '<div class="result-meta">Account ' + esc(session.account) + ' · Component <span class="mono">account-onboarding</span></div></div>' +
1519
+ '<div class="result-head"><div><strong>Account verification session ready</strong>' +
1520
+ '<div class="result-meta">Cloud resource provisioning verification</div></div>' +
1393
1521
  pill('Session active', 'ok') + '</div>' +
1394
1522
  '<div class="result-body">' +
1395
- '<div class="result-kv"><span>Connected account (v2)</span><strong class="mono">' + esc(session.account) + '</strong></div>' +
1396
- '<div class="result-kv"><span>Collection options</span><strong class="mono">currently_due · future_requirements=include</strong></div>' +
1397
- '<div class="result-kv"><span>Connect.js mount</span><strong>' + (session.publishable_key ? 'Ready' : 'Set STRIPE_PUBLISHABLE_KEY for live iframe, or use Hosted Link') + '</strong></div>' +
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>' +
1398
1525
  '</div>' +
1399
1526
  '<div class="result-actions" style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">' +
1400
- '<button class="btn btn-sm btn-primary" type="button" data-action="open-hosted-onboarding">Open hosted onboarding link</button>' +
1401
- '<button class="btn btn-sm" type="button" data-action="refresh-onboarding-status">Refresh account status</button>' +
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>' +
1402
1529
  '</div></div>';
1403
- showToast('Account session created for ' + session.account);
1530
+ showToast('Verification session ready');
1404
1531
  }
1405
1532
 
1406
1533
  async function openHostedOnboardingLink() {
@@ -1412,13 +1539,11 @@ async function openHostedOnboardingLink() {
1412
1539
  });
1413
1540
  await loadProvisioningOnboardingStatus();
1414
1541
  if (link && link.url) {
1415
- showDetail('Stripe v2 Hosted Onboarding Link', link.account, {
1416
- account: link.account,
1417
- object: link.object,
1542
+ showDetail('Verification link', 'Complete verification to provision cloud resources', {
1418
1543
  onboardingUrl: link.url,
1419
1544
  expiresAt: link.expires_at
1420
1545
  });
1421
- showToast('Generated hosted onboarding link for ' + link.account);
1546
+ showToast('Verification link ready');
1422
1547
  }
1423
1548
  }
1424
1549
  async function rotateResource(name) {
@@ -1438,12 +1563,13 @@ async function pullWorkspaceEnv() {
1438
1563
 
1439
1564
  async function loadConnections(syncLive) {
1440
1565
  var tbody = byId('connections-tbody');
1441
- if (state.view === 'connections' && syncLive) tbody.innerHTML = emptyRow(5, 'Checking each provider…');
1566
+ if (tbody && state.view === 'connections' && syncLive) tbody.innerHTML = emptyRow(5, 'Checking each provider…');
1442
1567
  var data = await api('/api/v1/connections?sync=' + (syncLive ? 'true' : 'false'));
1443
1568
  var list = data.connections || [];
1444
1569
  var activeConns = countActive(list);
1445
1570
  setCount('nav-conn-count', activeConns);
1446
1571
  if (state.boot) setText('kpi-connections', fmt(activeConns + countActive(state.boot.resources)));
1572
+ if (!tbody) return;
1447
1573
  tbody.innerHTML = list.length ? list.map(function (c) {
1448
1574
  var acct = esc(c.externalAccountId || '');
1449
1575
  var prov = esc(c.providerSlug);
@@ -1458,8 +1584,8 @@ async function loadConnections(syncLive) {
1458
1584
  }
1459
1585
  async function startCustomProviderConnection() {
1460
1586
  var input = byId('custom-provider-input');
1461
- var val = (input.value || '').trim().toLowerCase();
1462
- if (!val) { showToast('Enter an app name, like linear or jira'); input.focus(); return; }
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; }
1463
1589
  input.value = '';
1464
1590
  await startAppConnection(val);
1465
1591
  }
@@ -1479,7 +1605,7 @@ async function startAppConnection(provider) {
1479
1605
  var conn = res.connection || {};
1480
1606
  if (conn.redirectUrl) window.open(conn.redirectUrl, '_blank', 'noopener');
1481
1607
  showToast(conn.redirectUrl ? 'Finish connecting ' + provider + ' in the new tab' : (conn.message || 'Started connecting ' + provider));
1482
- if (state.view !== 'connections') navigate('connections'); else await loadConnections(false);
1608
+ await refreshOverview();
1483
1609
  }
1484
1610
 
1485
1611
  function setFactsMode(mode) {
@@ -1514,7 +1640,7 @@ function renderActiveMemoryDefaults(defaults, guardrails) {
1514
1640
  var keys = Object.keys(defs).filter(function (k) { return defs[k] != null && defs[k] !== ''; });
1515
1641
  var rules = (guardrails || []).filter(Boolean);
1516
1642
  if (!keys.length && !rules.length) {
1517
- box.innerHTML = '<p class="empty-note">No active parameter defaults yet. Tell Muxo a preference above (for example, "Remember our default GitHub repo is cloudflare/workerd and region is us-east-1") and Muxo will auto-fill tool parameters automatically.</p>';
1643
+ box.innerHTML = '<p class="empty-note">No saved workspace defaults.</p>';
1518
1644
  return;
1519
1645
  }
1520
1646
  var html = [];
@@ -1526,7 +1652,7 @@ function renderActiveMemoryDefaults(defaults, guardrails) {
1526
1652
  }
1527
1653
  if (rules.length) {
1528
1654
  html.push('<div class="recall-group"><h3>Remembered workspace guardrails</h3><div class="rows">' + rules.map(function (r) {
1529
- return '<div class="row"><div><strong>' + esc(r) + '</strong><small>Enforced across plans and remembered in Supermemory</small></div>' + pill('Active rule', 'ok', true) + '</div>';
1655
+ return '<div class="row"><div><strong>' + esc(r) + '</strong><small>Applies to this workspace</small></div>' + pill('Active rule', 'ok', true) + '</div>';
1530
1656
  }).join('') + '</div></div>');
1531
1657
  }
1532
1658
  box.innerHTML = html.join('');
@@ -1538,7 +1664,7 @@ async function teachMemoryFromUi(chipPrompt) {
1538
1664
  if (input) input.value = promptText;
1539
1665
  var res = await post('/api/v1/do', { prompt: promptText });
1540
1666
  if (input && typeof chipPrompt !== 'string') input.value = '';
1541
- showToast(res.humanMessage || 'Saved preference to Supermemory');
1667
+ showToast(res.humanMessage || 'Saved preference to workspace memory');
1542
1668
  await loadMemory();
1543
1669
  await refreshOverview();
1544
1670
  }
@@ -1715,15 +1841,17 @@ async function loadPlans() {
1715
1841
  var pending = plans.filter(function (p) { return p.approvalStatus === 'pending_approval'; }).length;
1716
1842
  setText('gov-pending', fmt(pending));
1717
1843
  setCount('nav-plan-count', pending);
1718
- byId('plans-tbody').innerHTML = plans.length ? plans.map(function (p) {
1844
+ var tbody = byId('plans-tbody');
1845
+ if (tbody) tbody.innerHTML = plans.length ? plans.map(function (p) {
1719
1846
  var verdict = p.decisionRecord ? p.decisionRecord.verdict : 'allow';
1720
1847
  var verdictLabel = verdict === 'deny' ? 'Blocked' : verdict === 'require_approval' ? 'Needs approval' : 'Allowed';
1721
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>';
1722
1850
  var action = p.approvalStatus === 'pending_approval'
1723
- ? '<button class="btn btn-sm btn-primary" data-action="approve-plan" data-id="' + id + '">Approve and run</button>'
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>'
1724
1852
  : (p.approvalStatus === 'approved' || p.approvalStatus === 'auto_approved')
1725
- ? '<button class="btn btn-sm" data-action="execute-plan" data-id="' + id + '">Run</button>'
1726
- : '<span class="small muted">' + esc(p.runId ? 'Completed' : 'Done') + '</span>';
1853
+ ? inspectBtn + ' <button class="btn btn-sm btn-primary" type="button" data-action="execute-plan" data-id="' + id + '">Run</button>'
1854
+ : inspectBtn;
1727
1855
  return '<tr id="plan-row-' + id + '"><td class="mono small">' + esc(shortId(p.id)) + '</td><td><strong>' + esc(formatToolLabel(p.toolSlug)) + '</strong></td>' +
1728
1856
  '<td>' + riskBadge(p.riskLevel) + ' ' + pill(verdictLabel, verdict === 'deny' ? 'danger' : '') + '</td>' +
1729
1857
  '<td>' + statusBadge(p.approvalStatus) + '</td><td class="cell-actions">' + action + '</td></tr>';
@@ -1734,10 +1862,23 @@ async function loadPlans() {
1734
1862
  if (row) {
1735
1863
  row.style.background = 'rgba(99, 102, 241, 0.14)';
1736
1864
  row.scrollIntoView({ behavior: 'smooth', block: 'center' });
1737
- showToast('This plan is waiting for your approval.');
1865
+ showToast('This plan is waiting for your review.');
1738
1866
  }
1739
1867
  }
1740
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
+ }
1741
1882
  async function approveAndRun(planId) {
1742
1883
  await post('/api/v1/plans/' + planId + '/approve');
1743
1884
  await executeExistingPlan(planId);
@@ -1794,7 +1935,8 @@ async function loadRuns() {
1794
1935
  var data = await api('/api/v1/runs');
1795
1936
  var runs = data.runs || [];
1796
1937
  setCount('nav-run-count', runs.length);
1797
- byId('runs-tbody').innerHTML = runs.length ? runs.map(function (r) {
1938
+ var tbody = byId('runs-tbody');
1939
+ if (tbody) tbody.innerHTML = runs.length ? runs.map(function (r) {
1798
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>' +
1799
1941
  '<td><strong>' + esc(formatToolLabel(r.toolSlug)) + '</strong><br>' + laneBadge(r.lane) + '</td>' +
1800
1942
  '<td>' + riskBadge(r.riskLevel) + '</td><td>' + statusBadge(r.status) + '</td>' +
@@ -1822,14 +1964,16 @@ function renderSettlement(s) {
1822
1964
  var runsCount = Number(x.runsCount || 0);
1823
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>';
1824
1966
  }
1825
- byId('settlement-summary-box').innerHTML = '<div class="rows">' + lane('composio_instant', 'Instant tools') + lane('composio_sdk', 'Connected apps') + lane('stripe_projects', 'Cloud services') + '</div>' +
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>' +
1826
1969
  '<p class="small muted" style="margin-top:12px">' + fmt(s.totalCreditsConsumed || 0) + ' total credits used across all providers.</p>';
1827
1970
  }
1828
1971
  async function loadLedger() {
1829
1972
  var data = await api('/api/v1/billing/ledger');
1830
1973
  renderSettlement(data.settlementSummary);
1831
1974
  var entries = data.entries || [];
1832
- byId('ledger-tbody').innerHTML = entries.length ? entries.map(function (e) {
1975
+ var tbody = byId('ledger-tbody');
1976
+ if (tbody) tbody.innerHTML = entries.length ? entries.map(function (e) {
1833
1977
  var delta = Number(e.creditsDelta || 0);
1834
1978
  return '<tr><td class="small muted">' + esc(new Date(e.createdAt).toLocaleString()) + '</td><td>' + esc(e.description) + '</td>' +
1835
1979
  '<td class="mono" style="color:' + (delta >= 0 ? 'var(--color-ok)' : 'var(--color-text)') + '">' + (delta >= 0 ? '+' : '') + esc(fmt(delta)) + '</td>' +
@@ -1843,9 +1987,12 @@ async function loadByokKeys() {
1843
1987
  var keys = (data.keys || []).filter(function (k) { return k.configured !== false && (k.keyPreview || k.keyHint); });
1844
1988
  var hasSupplier = keys.some(function (k) { return k.category === 'supplier'; });
1845
1989
  var badge = byId('byok-mode-badge');
1846
- badge.textContent = hasSupplier ? 'Your keys · 0 credits' : 'Muxo credits';
1847
- if (hasSupplier) badge.dataset.tone = 'accent'; else delete badge.dataset.tone;
1848
- byId('byok-keys-tbody').innerHTML = keys.length ? keys.map(function (k) {
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) {
1849
1996
  var slug = k.provider || k.providerSlug || '';
1850
1997
  return '<tr><td><strong>' + esc(k.providerName || k.displayName || slug) + '</strong></td>' +
1851
1998
  '<td>' + pill(k.category === 'llm' ? 'Language model' : 'Provider', k.category === 'llm' ? 'accent' : '') + '</td>' +
@@ -1902,7 +2049,7 @@ async function runNaturalLanguagePrompt() {
1902
2049
  }
1903
2050
  if (res.status === 'memory_saved') {
1904
2051
  renderQuickResult({
1905
- title: 'Saved to Active Supermemory',
2052
+ title: 'Saved to workspace memory',
1906
2053
  subtitle: res.humanMessage || promptText,
1907
2054
  badgeHtml: statusBadge('memory_saved'),
1908
2055
  kv: [
@@ -1912,8 +2059,8 @@ async function runNaturalLanguagePrompt() {
1912
2059
  output: { activeDefaults: res.activeDefaults, facts: res.temporalFacts },
1913
2060
  raw: res
1914
2061
  });
1915
- setText('quick-run-status', res.humanMessage || 'Saved to Supermemory');
1916
- showToast(res.humanMessage || 'Saved to Supermemory');
2062
+ setText('quick-run-status', res.humanMessage || 'Saved to workspace memory');
2063
+ showToast(res.humanMessage || 'Saved to workspace memory');
1917
2064
  await refreshOverview();
1918
2065
  return;
1919
2066
  }
@@ -2091,7 +2238,9 @@ async function checkCheckoutReturn() {
2091
2238
 
2092
2239
  var ACTIONS = {
2093
2240
  'toggle-theme': toggleTheme,
2094
- 'sign-out': signOut,
2241
+ 'mobile-menu': openMobileMenu,
2242
+ 'sign-out': function () { closeDialog('mobile-menu'); return signOut(); },
2243
+ 'refresh-overview': refreshOverview,
2095
2244
  'switch-workspace': switchExternalWorkspacePrompt,
2096
2245
  'select-workspace': function (el) { return selectWorkspaceFromUi(el.dataset.id); },
2097
2246
  'spawn-agent': spawnAutonomousAgent,
@@ -2099,12 +2248,22 @@ var ACTIONS = {
2099
2248
  'revoke-key': function (el) { return revokeApiKey(el.dataset.id, el.dataset.name); },
2100
2249
  'use-spawned': useLatestSpawnedKey,
2101
2250
  'claim-link': createClaimLinkPrompt,
2102
- 'harness': function (el) { setHarnessSnippet(el.dataset.kind); },
2251
+ 'workspace-create': function () { return openWorkspaceForm('create'); },
2252
+ 'workspace-join': function () { return openWorkspaceForm('join'); },
2103
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
+ },
2104
2259
  'run-tool': function (el) { return openExecuteModal(el.dataset.slug); },
2105
2260
  'quick-run': function (el) { return quickRun(el.dataset.slug, JSON.parse(el.dataset.input || '{}')); },
2106
2261
  'ask-suggest': function (el) { return fillAndRunNl(el.dataset.prompt); },
2107
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,
2108
2267
  'connect-app': function (el) { return startAppConnection(el.dataset.provider); },
2109
2268
  'sync-connections': function () { return loadConnections(true); },
2110
2269
  'wait-connection': function (el) { return waitConnectionInUi(el.dataset.provider, el.dataset.account); },
@@ -2123,6 +2282,8 @@ var ACTIONS = {
2123
2282
  'teach-chip': function (el) { return teachMemoryFromUi(el.dataset.prompt); },
2124
2283
  'guardrail-chip': function (el) { return applyGuardrailFromUi(el.dataset.prompt); },
2125
2284
  'guardrail-reset': resetGuardrailsFromUi,
2285
+ 'inspect-plan': function (el) { return inspectPlan(el.dataset.id); },
2286
+ 'reject-plan': function (el) { return rejectPlan(el.dataset.id); },
2126
2287
  'approve-plan': function (el) { return approveAndRun(el.dataset.id); },
2127
2288
  'billing-interval': function (el) { setBillingInterval(el.dataset.interval); },
2128
2289
  'change-plan': function (el) { return changePlan(el.dataset.tier); },
@@ -2130,7 +2291,7 @@ var ACTIONS = {
2130
2291
  'execute-plan': function (el) { return executeExistingPlan(el.dataset.id); },
2131
2292
  'kill-resume': resumeKillSwitch,
2132
2293
  'refresh-governance': loadPlans,
2133
- 'refresh-runs': loadRuns,
2294
+ 'refresh-runs': function () { return Promise.all([loadRuns(), loadPlans()]); },
2134
2295
  'inspect-receipt': function (el) { return inspectReceipt(el.dataset.id); },
2135
2296
  'checkout': function (el) { return startCheckout(el.dataset.kind); },
2136
2297
  'delete-byok': function (el) { return deleteByokKeyFromUi(el.dataset.provider); },
@@ -2151,6 +2312,7 @@ function onClick(e) {
2151
2312
  }
2152
2313
  var el = e.target.closest('[data-action]');
2153
2314
  if (!el) return;
2315
+ if (el.tagName === 'A' && (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)) return;
2154
2316
  var fn = ACTIONS[el.dataset.action];
2155
2317
  if (!fn) return;
2156
2318
  e.preventDefault();
@@ -2182,14 +2344,15 @@ async function boot(initial) {
2182
2344
  if (err.status === 401 || err.status === 403) return;
2183
2345
  showError(err);
2184
2346
  }
2185
- if (initial !== 'overview' && initial !== 'setup') loadView(initial);
2347
+ if (initial !== 'overview' && initial !== 'setup' && initial !== 'workspaces') loadView(initial);
2186
2348
  loadByokKeys().catch(function () {});
2187
2349
  }
2188
2350
  function init() {
2189
- applyTheme(document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light');
2351
+ applyTheme(document.documentElement.dataset.theme === 'light' ? 'light' : 'dark');
2190
2352
  document.querySelectorAll('dialog.sheet').forEach(function (d) {
2191
2353
  d.addEventListener('click', function (e) { if (e.target === d) d.close(); });
2192
2354
  });
2355
+ byId('mobile-menu').addEventListener('close', function () { byId('mobile-menu-toggle').setAttribute('aria-expanded', 'false'); });
2193
2356
  onSubmit('nl-form', runNaturalLanguagePrompt);
2194
2357
  onSubmit('auth-form', submitAuth);
2195
2358
  onSubmit('custom-provider-form', startCustomProviderConnection);
@@ -2209,6 +2372,7 @@ function init() {
2209
2372
  var searchInput = byId('catalog-search');
2210
2373
  if (searchInput) {
2211
2374
  searchInput.addEventListener('input', function () {
2375
+ byId('clear-catalog-search').hidden = !searchInput.value.trim();
2212
2376
  clearTimeout(searchTimer);
2213
2377
  searchTimer = setTimeout(function () { loadCatalog().catch(showError); }, 280);
2214
2378
  });
@@ -2221,6 +2385,23 @@ function init() {
2221
2385
  navigate(next, { push: false, focus: false });
2222
2386
  });
2223
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) {}
2224
2405
  var initial = viewForPath(location.pathname);
2225
2406
  navigate(initial, { push: false, focus: false, skipLoad: true });
2226
2407
  boot(initial);
@@ -2231,19 +2412,37 @@ function navItem(view, label, icon, countId) {
2231
2412
  const path = Object.entries(CONSOLE_ROUTES).find(([, v]) => v === view)?.[0] ?? "/";
2232
2413
  return `<a class="nav-item" href="${path}" data-nav="${view}">${uiIcon(icon)}<span>${label}</span>${countId ? `<span id="${countId}" class="count"></span>` : ""}</a>`;
2233
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
+ }
2234
2433
  export function renderConsoleHtml() {
2235
2434
  const scriptPrelude = `var ROUTES = ${JSON.stringify(CONSOLE_ROUTES)};` +
2236
2435
  `var THEME_KEY = ${JSON.stringify(THEME_STORAGE_KEY)};` +
2237
2436
  `var THEME_BG = ${JSON.stringify({ light: muxoThemes.light.bg, dark: muxoThemes.dark.bg })};`;
2238
2437
  return `<!DOCTYPE html>
2239
- <html lang="en" data-theme="light">
2438
+ <html lang="en" data-theme="dark">
2240
2439
  <head>
2241
2440
  <meta charset="utf-8" />
2242
2441
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
2243
2442
  <title>Muxo Console</title>
2244
2443
  <meta name="description" content="Manage the tools, approvals, memory, and billing your agents use through Muxo." />
2245
- <meta name="theme-color" content="${muxoThemes.light.bg}" />
2246
- <meta name="color-scheme" content="light dark" />
2444
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
2445
+ <meta name="color-scheme" content="dark light" />
2247
2446
  <meta name="robots" content="noindex" />
2248
2447
  <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
2249
2448
  <link rel="apple-touch-icon" href="https://muxo.ai/apple-touch-icon.png" />
@@ -2254,15 +2453,14 @@ export function renderConsoleHtml() {
2254
2453
  <a class="skip-link" href="#main">Skip to content</a>
2255
2454
  <header class="console-topbar">
2256
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>
2257
2457
  <a class="brand" href="/" data-nav="overview" aria-label="Muxo console home">${muxoMark}<span>Muxo</span><span class="brand-section">Console</span></a>
2258
- <button class="workspace-chip" type="button" data-action="switch-workspace" title="Open another workspace">
2259
- <span id="workspace-name">Workspace</span><span id="workspace-tier" class="tier">Free plan</span>
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")}
2260
2460
  </button>
2261
2461
  <nav class="topbar-links" aria-label="Muxo">
2262
2462
  <a class="nav-link" href="${DOCS_URL}">Docs</a>
2263
- <a class="nav-link" href="${WEBSITE_URL}">Website</a>
2264
- <a class="nav-link" href="${PRICING_URL}">Pricing</a>
2265
- <button id="theme-toggle" class="icon-button" type="button" data-action="toggle-theme" aria-pressed="false" aria-label="Switch to dark mode">${uiIcon("sun", "theme-sun")}${uiIcon("moon", "theme-moon")}</button>
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>
2266
2464
  <a id="account-signin" class="btn btn-primary" href="/login" data-nav="login">Sign in</a>
2267
2465
  <div id="account-signed-in" class="account" hidden>
2268
2466
  <span id="account-label" class="account-label"></span>
@@ -2274,16 +2472,10 @@ export function renderConsoleHtml() {
2274
2472
 
2275
2473
  <div id="console-shell" class="console-layout">
2276
2474
  <aside class="console-sidebar" aria-label="Console">
2277
- <nav class="nav-group" aria-label="Console">
2278
- <p>Console</p>
2279
- ${navItem("overview", "Harness onboarding", "terminal")}
2280
- ${navItem("setup", "API keys", "key")}
2281
- ${navItem("activity", "History & audit", "activity", "nav-run-count")}
2282
- ${navItem("billing", "Billing", "wallet")}
2283
- </nav>
2475
+ ${consoleNavigation()}
2284
2476
  <div class="sidebar-usage">
2285
2477
  <div class="usage-row"><span>Monthly actions</span><strong id="meter-actions-text">0 / 1,000</strong></div>
2286
- <div class="meter" role="presentation"><span id="meter-actions-bar" style="width:2%"></span></div>
2478
+ <div class="meter" role="presentation"><span id="meter-actions-bar" style="width:0%"></span></div>
2287
2479
  <div class="usage-row"><span>Credits</span><strong id="meter-credits-text">0</strong></div>
2288
2480
  <a href="/billing" data-nav="billing">Add credits</a>
2289
2481
  </div>
@@ -2291,86 +2483,97 @@ export function renderConsoleHtml() {
2291
2483
 
2292
2484
  <main id="main" class="console-main" tabindex="-1">
2293
2485
  <section class="page" data-view="overview" aria-labelledby="h-overview">
2294
- <header class="page-header">
2295
- <div>
2296
- <h1 id="h-overview">Connect your agent</h1>
2297
- <p class="lede">Attach Muxo to Claude Code, Codex, Cursor, Droid, or your own app. Configure tools, connections, memory, and guardrails directly from your agent harness.</p>
2298
- </div>
2299
- <div class="page-actions">
2300
- <a class="btn" href="/keys" data-nav="setup">${uiIcon("key")}Manage API keys</a>
2301
- <a class="btn btn-primary" href="${DOCS_URL}/quickstart">${uiIcon("book")}Quickstart</a>
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>
2302
2493
  </div>
2494
+ <p class="lede">Monitor agent activity and approvals.</p>
2303
2495
  </header>
2304
2496
  <div class="stack">
2305
2497
  <div class="metrics">
2306
- <div class="metric"><p class="metric-label">Credits</p><p class="metric-value" id="kpi-credits">0</p><p class="metric-sub" id="kpi-tier-badge">Free plan</p></div>
2307
- <div class="metric"><p class="metric-label">Actions this month</p><p class="metric-value" id="kpi-actions">0</p><p class="metric-sub" id="kpi-actions-sub">of 1,000 included</p></div>
2308
- <div class="metric"><p class="metric-label">Connected</p><p class="metric-value" id="kpi-connections">0</p><p class="metric-sub">Apps and cloud services</p></div>
2309
- <div class="metric"><p class="metric-label">Governed runs</p><p class="metric-value" id="kpi-runs">0</p><p class="metric-sub">Each with a signed audit record</p></div>
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>
2310
2502
  </div>
2311
2503
 
2312
- <div class="panel">
2313
- <div class="panel-header">
2314
- <div><h2>Harness setup</h2><p>Commands use this console's address and your active workspace key.</p></div>
2315
- <button class="btn btn-quiet" type="button" data-action="copy" data-target="mcp-config-snippet">${uiIcon("copy")}Copy</button>
2316
- </div>
2317
- <div id="harness-tabs" class="segmented" role="group" aria-label="Agent">
2318
- <button type="button" data-action="harness" data-kind="plugin" aria-pressed="true">Claude &amp; Codex</button>
2319
- <button type="button" data-action="harness" data-kind="mcp" aria-pressed="false">MCP config</button>
2320
- <button type="button" data-action="harness" data-kind="oauth" aria-pressed="false">CLI</button>
2321
- <button type="button" data-action="harness" data-kind="sdk" aria-pressed="false">TypeScript SDK</button>
2322
- <button type="button" data-action="harness" data-kind="mastra" aria-pressed="false">Mastra</button>
2323
- <button type="button" data-action="harness" data-kind="pi" aria-pressed="false">Pi</button>
2324
- <button type="button" data-action="harness" data-kind="curl" aria-pressed="false">HTTPS API</button>
2325
- </div>
2326
- <pre id="mcp-config-snippet" class="code" style="margin-top:16px"></pre>
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>
2327
2507
  </div>
2328
2508
 
2329
- <div class="split even">
2509
+ <div class="dashboard-layout">
2330
2510
  <div class="panel">
2331
2511
  <div class="panel-header">
2332
- <div><h2>Harness-first control</h2><p>Your agent manages tools, guardrails, and memory in conversation.</p></div>
2333
- <span class="pill" data-tone="accent">MCP + CLI</span>
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>
2334
2514
  </div>
2335
- <div class="result-card" style="min-height:0">
2336
- <div class="result-body">
2337
- <dl class="result-kv">
2338
- <dt>Conditional guardrails</dt>
2339
- <dd>Ask your agent: <code>Auto-approve expenses under $50, require my approval above $50</code></dd>
2340
- <dt>Workspace memory</dt>
2341
- <dd>Ask your agent: <code>Remember our default GitHub repo is cloudflare/workerd and region is us-east-1</code></dd>
2342
- <dt>Connect apps</dt>
2343
- <dd>Run <code>npx @muxoai/cli link github --wait</code> or ask your agent to connect GitHub, Slack, or Gmail</dd>
2344
- </dl>
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>
2345
2522
  </div>
2523
+ <div id="recent-tools"><p class="empty-note">Loading tools…</p></div>
2346
2524
  </div>
2347
- </div>
2348
- <div class="panel">
2349
- <div class="panel-header">
2350
- <div><h2>Recent governed runs</h2><p>Latest tool runs recorded in this workspace.</p></div>
2351
- <a class="btn btn-quiet" href="/activity" data-nav="activity">View history</a>
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>
2352
2532
  </div>
2353
- <div id="recent-runs" class="rows"></div>
2354
2533
  </div>
2355
2534
  </div>
2356
2535
  </div>
2357
2536
  </section>
2358
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
+
2359
2556
  <section class="page" data-view="setup" aria-labelledby="h-setup" hidden>
2360
2557
  <header class="page-header">
2361
2558
  <div>
2362
- <h1 id="h-setup">API keys &amp; access</h1>
2363
- <p class="lede">Create and revoke workspace keys, switch workspaces, or mint a scoped onboarding kit for your team.</p>
2559
+ <h1 id="h-setup">API access</h1>
2560
+ <p class="lede">Manage credentials for your agents and apps.</p>
2364
2561
  </div>
2562
+ <a class="btn" href="${DOCS_URL}/quickstart">${uiIcon("book")}Setup docs</a>
2365
2563
  </header>
2366
2564
  <div class="stack">
2367
- <div class="split even">
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">
2368
2570
  <div class="panel">
2369
2571
  <div class="panel-header">
2370
- <div><h2>API keys</h2><p>Keys for this workspace. A new key is shown only once.</p></div>
2572
+ <div><h2>Workspace keys</h2><p>New keys are shown once. Store them securely.</p></div>
2371
2573
  <button class="btn btn-primary" type="button" data-action="create-key">${uiIcon("plus")}Create key</button>
2372
2574
  </div>
2373
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>
2374
2577
  <p id="new-key-title"><strong>Copy this key now.</strong></p>
2375
2578
  <code id="new-key-value" class="key-value"></code>
2376
2579
  <div class="callout-actions">
@@ -2383,48 +2586,68 @@ export function renderConsoleHtml() {
2383
2586
  </div>
2384
2587
  <div class="panel">
2385
2588
  <div class="panel-header">
2386
- <div><h2>Workspaces &amp; access</h2><p>Switch between your workspaces, join a team workspace with an API key, or create a new workspace.</p></div>
2387
- <button class="btn btn-sm" type="button" data-action="switch-workspace">Manage workspaces</button>
2589
+ <div><h2>Endpoints</h2><p>Use with your workspace key.</p></div>
2388
2590
  </div>
2389
- <div class="rows">
2390
- <div class="row"><div><strong>Current workspace</strong><small id="workspace-slug"></small></div><span id="workspace-auth-badge" class="pill">Signed in</span></div>
2391
- <div class="row"><div><strong>Join with a Team or Workspace API key</strong><small>Paste an <code>mk_live_...</code> key shared by a teammate or agent to add its workspace.</small></div><button class="btn btn-sm" type="button" data-action="switch-workspace">Join or switch</button></div>
2392
- <div class="row"><div><strong>Create an agent workspace</strong><small>Fresh workspace with 250 starter credits.</small></div><button class="btn btn-sm" type="button" data-action="spawn-agent">Create</button></div>
2393
- <div class="row" id="reset-workspace-row" hidden><div><strong>Stop using this key</strong><small>Forget the key saved in this browser.</small></div><button id="reset-workspace-btn" class="btn btn-sm" type="button" data-action="sign-out">Forget key</button></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>
2394
2596
  </div>
2395
- <div id="setup-workspaces-list" class="rows" style="margin-top:12px"></div>
2396
2597
  </div>
2397
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>
2398
2620
  <div class="panel">
2399
2621
  <div class="panel-header">
2400
- <div><h2>Onboard a team with an API key</h2><p>Mint a scoped team key so another team can join this workspace (or get their own dedicated workspace) in one command via the CLI or MCP.</p></div>
2401
- <span class="pill" data-tone="accent">Team Onboarding</span>
2622
+ <div><h2>Onboard a new team</h2><p>Create a team key for this workspace or a separate workspace.</p></div>
2402
2623
  </div>
2403
- <form id="team-onboard-form" class="form-row" style="grid-template-columns:minmax(0,1.3fr) minmax(0,1.2fr) minmax(0,0.9fr) auto;align-items:end">
2404
- <label class="field"><span>Team name</span><input id="team-onboard-name" type="text" placeholder="Platform Engineering, Growth, or Support" autocomplete="off" /></label>
2405
- <label class="field"><span>Workspace mode</span>
2406
- <select id="team-onboard-mode">
2407
- <option value="shared_workspace">Shared workspace (share tools, connections &amp; memory)</option>
2408
- <option value="dedicated_workspace">Dedicated workspace (isolated workspace + 250 credits)</option>
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>
2409
2630
  </select>
2631
+ <small id="team-workspace-help" class="field-help">A separate workspace keeps tools, connections, and memory isolated.</small>
2410
2632
  </label>
2411
2633
  <label class="field"><span>Role</span>
2412
2634
  <select id="team-onboard-role">
2413
- <option value="operator">Operator (run &amp; plan tools)</option>
2414
- <option value="admin">Admin (manage keys &amp; guardrails)</option>
2415
- <option value="member">Member (standard access)</option>
2635
+ <option value="operator">Operator</option>
2636
+ <option value="admin">Admin</option>
2637
+ <option value="member">Member</option>
2416
2638
  </select>
2639
+ <small class="field-help">Operators run tools. Admins also manage keys and guardrails.</small>
2417
2640
  </label>
2418
- <button class="btn btn-primary" type="submit" style="min-height:40px">Generate team kit</button>
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>
2419
2642
  </form>
2420
2643
  <div id="team-kit-box" class="callout" data-tone="ok" style="margin-top:16px" hidden>
2421
- <p><strong id="team-kit-title">Team Onboarding Kit</strong></p>
2422
- <div class="rows" style="margin-top:10px">
2423
- <div class="row"><div><strong>Team API Key</strong><small>Shown once. Scoped to this team so you can audit or revoke it anytime.</small></div><div class="row-actions"><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></div>
2424
- <div class="row"><div><strong>One-command CLI onboarding</strong><small>Configures Claude Code, Cursor, Codex, and local MCP automatically.</small></div><div class="row-actions"><code id="team-kit-cli" class="mono small"></code> <button class="btn btn-sm" type="button" data-action="copy" data-target="team-kit-cli">${uiIcon("copy")}Copy CLI</button></div></div>
2425
- </div>
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>
2426
2648
  <details class="recall-raw" style="margin-top:10px">
2427
- <summary>Shareable Slack / Notion onboarding instructions</summary>
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>
2428
2651
  <pre id="team-kit-instructions" class="code" style="margin-top:8px"></pre>
2429
2652
  <div class="callout-actions" style="margin-top:8px">
2430
2653
  <button class="btn btn-sm" type="button" data-action="copy" data-target="team-kit-instructions">${uiIcon("copy")}Copy instructions</button>
@@ -2438,8 +2661,8 @@ export function renderConsoleHtml() {
2438
2661
  <section class="page" data-view="activity" aria-labelledby="h-activity" hidden>
2439
2662
  <header class="page-header">
2440
2663
  <div>
2441
- <h1 id="h-activity">History &amp; audit</h1>
2442
- <p class="lede">Read-only ledger of governed runs, signed audit records, pending approvals, and active guardrails configured via your agent harness.</p>
2664
+ <h1 id="h-activity">Activity</h1>
2665
+ <p class="lede">Review actions, approvals, and signed audit records.</p>
2443
2666
  </div>
2444
2667
  <div class="page-actions"><button class="btn" type="button" data-action="refresh-runs">${uiIcon("refresh")}Refresh</button></div>
2445
2668
  </header>
@@ -2448,7 +2671,7 @@ export function renderConsoleHtml() {
2448
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>
2449
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>
2450
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>
2451
- <div class="metric"><p class="metric-label">Waiting for approval</p><p class="metric-value" id="gov-pending">0</p><p class="metric-sub">Plans held by guardrails</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>
2452
2675
  </div>
2453
2676
 
2454
2677
  <div class="table-wrap">
@@ -2470,7 +2693,7 @@ export function renderConsoleHtml() {
2470
2693
  </div>
2471
2694
  <div class="panel">
2472
2695
  <div class="panel-header">
2473
- <div><h2>Active guardrails &amp; memory</h2><p>Read-only view of rules and defaults set from your agent harness.</p></div>
2696
+ <div><h2>Guardrails &amp; memory</h2><p>Rules and defaults for this workspace.</p></div>
2474
2697
  <span id="guardrail-posture-pill" class="pill dot" data-tone="ok">Safe default</span>
2475
2698
  </div>
2476
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>
@@ -2478,13 +2701,6 @@ export function renderConsoleHtml() {
2478
2701
  <div class="rows" style="margin-top:12px">
2479
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>
2480
2703
  </div>
2481
- <details class="recall-raw">
2482
- <summary>Cryptographic verification keys</summary>
2483
- <div class="rows" style="margin-top:10px">
2484
- <div class="row"><div><strong>Signing identity</strong><small id="gov-did" class="mono"></small></div></div>
2485
- <div class="row"><div><strong>Public verification key</strong><small id="gov-pubkey" class="mono"></small></div></div>
2486
- </div>
2487
- </details>
2488
2704
  </div>
2489
2705
  </div>
2490
2706
  </div>
@@ -2529,8 +2745,8 @@ export function renderConsoleHtml() {
2529
2745
  </div>
2530
2746
  </div>
2531
2747
  <div class="panel">
2532
- <div class="panel-header"><div><h2>Bring your own keys</h2><p>Use your own model or provider keys. Runs that use them cost 0 Muxo credits. Keys are encrypted with AES-256-GCM.</p></div></div>
2533
- <form id="byok-form" class="form-row" style="grid-template-columns:minmax(0,1fr) minmax(0,1.4fr) auto;align-items:end;margin-bottom:16px">
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">
2534
2750
  <label class="field"><span>Provider</span>
2535
2751
  <select id="byok-provider-select">
2536
2752
  <option value="openai">OpenAI (plain-English routing)</option>
@@ -2572,30 +2788,51 @@ export function renderConsoleHtml() {
2572
2788
  </div>
2573
2789
 
2574
2790
  <div id="auth-view" class="auth-view" hidden>
2575
- <div class="auth-card">
2576
- <div class="auth-mark">${muxoMark}</div>
2577
- <h1 id="auth-title">Sign in to Muxo</h1>
2578
- <p id="auth-lede" class="lede">Manage tools, approvals, memory, and billing for your agents.</p>
2579
- <div id="auth-tabs" class="segmented" role="group" aria-label="Account">
2580
- <button type="button" data-action="auth-mode" data-mode="login" aria-pressed="true">Sign in</button>
2581
- <button type="button" data-action="auth-mode" data-mode="signup" aria-pressed="false">Create account</button>
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>
2582
2821
  </div>
2583
- <form id="auth-form" class="auth-form" novalidate>
2584
- <label id="auth-name-field" class="field" hidden><span>Name</span><input id="auth-name" type="text" autocomplete="name" /></label>
2585
- <label class="field"><span>Email</span><input id="auth-email" type="email" autocomplete="email" required /></label>
2586
- <label class="field"><span>Password</span><input id="auth-password" type="password" autocomplete="current-password" minlength="6" required /></label>
2587
- <label id="auth-workspace-field" class="field" hidden><span>Workspace name <em>optional</em></span><input id="auth-workspace" type="text" autocomplete="organization" /></label>
2588
- <p id="auth-error" class="field-error" role="alert"></p>
2589
- <button id="auth-submit" class="btn btn-primary btn-lg btn-block" type="submit">Sign in</button>
2590
- </form>
2591
- <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>
2592
- <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>
2593
- <div class="auth-alt"><button class="btn btn-quiet" type="button" data-action="switch-workspace">${uiIcon("key")}Use an API key instead</button></div>
2594
- <p class="auth-foot">Setting up an agent? Run <code>npx -y @muxoai/cli@latest login --signup</code></p>
2595
- <p class="auth-foot"><a href="/" data-nav="overview">Back to the console</a></p>
2596
2822
  </div>
2597
2823
  </div>
2598
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
+
2599
2836
  <dialog id="exec-modal" class="sheet" aria-labelledby="modal-tool-name">
2600
2837
  <div class="sheet-inner">
2601
2838
  <div class="sheet-head">
@@ -2639,7 +2876,7 @@ export function renderConsoleHtml() {
2639
2876
  <dialog id="workspace-dialog" class="sheet" aria-labelledby="workspace-dialog-title">
2640
2877
  <div class="sheet-inner">
2641
2878
  <div class="sheet-head">
2642
- <div><h2 id="workspace-dialog-title">Workspaces &amp; team access</h2><p class="tool-slug">Switch workspaces, join a team workspace with an API key, or create a new workspace.</p></div>
2879
+ <div><h2 id="workspace-dialog-title">Workspaces</h2><p class="tool-slug">Switch, join, or create a workspace.</p></div>
2643
2880
  <button class="icon-button" type="button" data-action="close-dialog" aria-label="Close">${uiIcon("close")}</button>
2644
2881
  </div>
2645
2882
  <div>
@@ -2648,18 +2885,18 @@ export function renderConsoleHtml() {
2648
2885
  </div>
2649
2886
  <form id="join-workspace-form" class="form-grid" style="margin-top:16px;padding-top:16px;border-top:1px solid var(--color-border)" novalidate>
2650
2887
  <label class="field">
2651
- <span>Join or open a workspace with an API key</span>
2652
- <span class="field-help">Paste a team or workspace key (<code>mk_live_...</code>) shared by your team lead or agent. Muxo links the workspace to your account and opens it immediately.</span>
2888
+ <span>Join with an API key</span>
2889
+ <span class="field-help">Use a workspace key shared by your teammate.</span>
2653
2890
  <div class="inline-form" style="margin-top:6px">
2654
- <input id="join-workspace-key-input" type="text" placeholder="mk_live_..." autocomplete="off" />
2891
+ <input id="join-workspace-key-input" type="password" placeholder="mk_live_..." autocomplete="off" />
2655
2892
  <button class="btn btn-primary" type="submit">Join workspace</button>
2656
2893
  </div>
2657
2894
  </label>
2658
2895
  </form>
2659
2896
  <form id="create-workspace-form" class="form-grid" style="margin-top:14px;padding-top:14px;border-top:1px solid var(--color-border)" novalidate>
2660
2897
  <div class="form-row">
2661
- <label class="field"><span>New workspace name</span><input id="create-workspace-name-input" type="text" placeholder="Production Infra or Growth Team" autocomplete="off" /></label>
2662
- <label class="field"><span>Team label <em>optional</em></span><input id="create-workspace-team-input" type="text" placeholder="Growth Engineering" autocomplete="off" /></label>
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>
2663
2900
  </div>
2664
2901
  <div class="sheet-actions" style="margin-top:6px">
2665
2902
  <button class="btn" type="button" data-action="close-dialog">Close</button>
@@ -2789,14 +3026,14 @@ export function renderLandingHtml() {
2789
3026
  ["Safety & audit", "Muxo guardrails and audit log", "Prompt-injection scanning, risk-based approvals, an emergency stop, and a signed record of every run."],
2790
3027
  ];
2791
3028
  return `<!DOCTYPE html>
2792
- <html lang="en" data-theme="light">
3029
+ <html lang="en" data-theme="dark">
2793
3030
  <head>
2794
3031
  <meta charset="utf-8" />
2795
3032
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
2796
3033
  <title>Muxo — your tools, in your agent</title>
2797
3034
  <meta name="description" content="Connect apps, cloud services, memory, and safety controls to the agent you already use." />
2798
- <meta name="theme-color" content="${muxoThemes.light.bg}" />
2799
- <meta name="color-scheme" content="light dark" />
3035
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3036
+ <meta name="color-scheme" content="dark light" />
2800
3037
  <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
2801
3038
  <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
2802
3039
  <style>${muxoBaseCss}${landingCss}</style>
@@ -2979,14 +3216,16 @@ const consentScript = String.raw `
2979
3216
  export function renderOAuthConsentHtml(opts) {
2980
3217
  const clientName = escapeHtml(opts.clientName || "An app");
2981
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";
2982
3221
  const head = `<!DOCTYPE html>
2983
- <html lang="en" data-theme="light">
3222
+ <html lang="en" data-theme="dark">
2984
3223
  <head>
2985
3224
  <meta charset="utf-8" />
2986
3225
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
2987
3226
  <title>${opts.error ? (opts.claim ? "Claim problem" : "Connection problem") : opts.claim ? `Claim ${escapeHtml(opts.claim.workspaceName)}` : `Connect ${clientNameMidSentence}`} · Muxo</title>
2988
- <meta name="theme-color" content="${muxoThemes.light.bg}" />
2989
- <meta name="color-scheme" content="light dark" />
3227
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3228
+ <meta name="color-scheme" content="dark light" />
2990
3229
  <meta name="robots" content="noindex" />
2991
3230
  <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
2992
3231
  <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
@@ -2996,17 +3235,24 @@ export function renderOAuthConsentHtml(opts) {
2996
3235
  if (opts.error) {
2997
3236
  return `${head}
2998
3237
  <main class="auth-view">
2999
- <div class="auth-card">
3000
- <div class="auth-mark">${muxoMark}</div>
3001
- <h1>${opts.claim ? "This claim link isn't valid" : "This connection link isn't valid"}</h1>
3002
- <p class="lede">${escapeHtml(opts.error)}</p>
3003
- <p class="auth-foot">${opts.claim ? "Ask the agent for a new claim link" : "Start the connection again from your app"}, or <a href="${DOCS_URL}">read the setup guide</a>.</p>
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>
3004
3251
  </div>
3005
3252
  </main>
3006
3253
  </body>
3007
3254
  </html>`;
3008
3255
  }
3009
- const claim = opts.claim;
3010
3256
  const config = {
3011
3257
  clerk: opts.clerk || null,
3012
3258
  request: opts.request || {},
@@ -3032,26 +3278,33 @@ export function renderOAuthConsentHtml(opts) {
3032
3278
  <p class="consent-redirect">After you decide, you'll return to <strong>${escapeHtml(opts.redirectOrigin || "")}</strong></p>`;
3033
3279
  return `${head}
3034
3280
  <main class="auth-view">
3035
- <div class="auth-card">
3036
- <div class="auth-mark">${muxoMark}</div>
3037
- ${intro}
3038
- <div class="consent-state">
3039
- <p id="consent-loading" class="muted">Checking your sign-in…</p>
3040
- <div id="consent-signin" hidden>
3041
- <p class="consent-account">Sign in to Muxo to continue.</p>
3042
- <div id="clerk-auth" class="clerk-auth"></div>
3043
- </div>
3044
- <div id="consent-approve" hidden>
3045
- <p class="consent-account">Signed in as <strong id="consent-email"></strong></p>
3046
- <div class="consent-actions">
3047
- <button id="consent-deny" class="btn btn-lg" type="button">${claim ? "Not now" : "Cancel"}</button>
3048
- <button id="consent-allow" class="btn btn-primary btn-lg" type="button">${claim ? "Claim workspace" : "Allow"}</button>
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>
3049
3293
  </div>
3050
- <p class="consent-switch"><button id="consent-switch" class="btn btn-quiet btn-sm" type="button">Use a different account</button></p>
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>
3051
3303
  </div>
3052
- <p id="consent-error" class="field-error" role="alert"></p>
3053
3304
  </div>
3054
- <p class="auth-foot">${claim ? "Only the invited email can claim this workspace, and the link works once." : "You can remove this connection anytime by deleting its key in the Muxo console."}</p>
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>
3055
3308
  </div>
3056
3309
  </main>
3057
3310
  <script>window.MUXO_CONSENT = ${inlineJson(config)};${consentScript}</script>
@@ -3065,9 +3318,9 @@ const approvalCss = `
3065
3318
  .approval-facts .v{color:var(--color-text);font-weight:500;text-align:right;overflow-wrap:anywhere}
3066
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}
3067
3320
  .approval-risk{display:inline-block;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;text-transform:capitalize}
3068
- .approval-risk[data-tone="ok"]{background:rgba(16,185,129,.14);color:var(--color-success,#0f9d6c)}
3069
- .approval-risk[data-tone="warn"]{background:rgba(245,158,11,.14);color:var(--color-warning,#b45309)}
3070
- .approval-risk[data-tone="strict"]{background:rgba(239,68,68,.14);color:var(--color-danger,#dc2626)}
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)}
3071
3324
  .approval-note{margin-top:14px;color:var(--color-muted);font-size:13px}
3072
3325
  .approval-done{margin-top:18px;text-align:left;font-size:14px}
3073
3326
  `;
@@ -3188,10 +3441,18 @@ const approvalScript = String.raw `
3188
3441
  return;
3189
3442
  }
3190
3443
  if (clerk && !mounted) {
3191
- clerk.mountSignIn(byId('clerk-auth'), { routing: 'hash', forceRedirectUrl: here, signUpForceRedirectUrl: here });
3444
+ clerk.mountSignIn(byId('clerk-auth'), { routing: 'hash', forceRedirectUrl: here, signUpForceRedirectUrl: here, appearance: appearance() });
3192
3445
  mounted = true;
3193
3446
  }
3194
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
+ }
3195
3456
  var keyForm = byId('approval-key-form');
3196
3457
  if (keyForm) {
3197
3458
  keyForm.addEventListener('submit', async function (e) {
@@ -3231,7 +3492,7 @@ const approvalScript = String.raw `
3231
3492
  s.onerror = function () { fail('Sign-in could not load. Check your connection and reload.'); };
3232
3493
  s.onload = function () {
3233
3494
  if (!window.Clerk) { fail('Sign-in could not load.'); return; }
3234
- window.Clerk.load().then(function () {
3495
+ window.Clerk.load({ appearance: appearance() }).then(function () {
3235
3496
  clerk = window.Clerk;
3236
3497
  clerk.addListener(function () { if (clerk.session && clerk.user && !plan) load(); });
3237
3498
  if (clerk.session && clerk.user) load(); else renderAuth();
@@ -3245,13 +3506,13 @@ export function renderApprovalPageHtml(opts) {
3245
3506
  const planId = String(opts.planId || "");
3246
3507
  const config = { planId, clerk: opts.clerk || null };
3247
3508
  return `<!DOCTYPE html>
3248
- <html lang="en" data-theme="light">
3509
+ <html lang="en" data-theme="dark">
3249
3510
  <head>
3250
3511
  <meta charset="utf-8" />
3251
3512
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
3252
3513
  <title>Review action · Muxo</title>
3253
- <meta name="theme-color" content="${muxoThemes.light.bg}" />
3254
- <meta name="color-scheme" content="light dark" />
3514
+ <meta name="theme-color" content="${muxoThemes.dark.bg}" />
3515
+ <meta name="color-scheme" content="dark light" />
3255
3516
  <meta name="robots" content="noindex" />
3256
3517
  <link rel="icon" href="https://muxo.ai/favicon.svg" type="image/svg+xml" />
3257
3518
  <script data-theme-bootstrap>${muxoThemeBootstrap}</script>
@@ -3259,45 +3520,338 @@ export function renderApprovalPageHtml(opts) {
3259
3520
  </head>
3260
3521
  <body>
3261
3522
  <main class="auth-view">
3262
- <div class="auth-card">
3263
- <div class="auth-mark">${muxoMark}</div>
3264
- <h1>Review this action</h1>
3265
- <p class="lede">Muxo held this action because your guardrails require your approval before it can run.</p>
3266
- <div class="consent-state">
3267
- <p id="approval-loading" class="muted">Loading plan ${escapeHtml(planId)}…</p>
3268
- <div id="approval-signin" hidden>
3269
- <p id="approval-signin-msg" class="consent-account">Sign in to Muxo to review this action.</p>
3270
- <div id="clerk-auth" class="clerk-auth"></div>
3271
- <form id="approval-key-form" class="inline-form" style="margin-top:12px" novalidate>
3272
- <input id="approval-key-input" type="password" placeholder="Or paste workspace / team key (mk_live_...)" autocomplete="off" />
3273
- <button class="btn btn-primary" type="submit">Continue</button>
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>
3274
3744
  </form>
3275
- <p id="approval-console-link" style="margin-top:10px" hidden><a href="/">Open the Muxo console</a></p>
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>
3276
3830
  </div>
3277
- <div id="approval-review" hidden>
3278
- <ul class="approval-facts">
3279
- <li><span class="k">Action</span><span class="v" id="approval-tool"></span></li>
3280
- <li><span class="k">Risk</span><span class="v"><span id="approval-risk" class="approval-risk" data-tone="warn"></span></span></li>
3281
- <li><span class="k">Cost</span><span class="v" id="approval-cost"></span></li>
3282
- <li><span class="k">Reason</span><span class="v" id="approval-reason"></span></li>
3283
- </ul>
3284
- <pre id="approval-input" class="approval-input" aria-label="Tool input"></pre>
3285
- <p id="approval-state-note" class="approval-note">Nothing runs until you approve.</p>
3286
- <div class="consent-actions">
3287
- <button id="approval-reject" class="btn btn-lg" type="button">Reject</button>
3288
- <button id="approval-approve-only" class="btn btn-lg" type="button">Approve only</button>
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>
3289
3842
  </div>
3290
3843
  <div class="consent-actions" style="grid-template-columns:1fr;margin-top:10px">
3291
- <button id="approval-approve-run" class="btn btn-primary btn-lg" type="button">Approve and run</button>
3844
+ <a class="btn btn-lg" href="${escapeHtml(consoleHref)}">Open Muxo Console</a>
3292
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>
3293
3847
  </div>
3294
- <div id="approval-done" class="approval-done" hidden></div>
3295
- <p id="approval-error" class="field-error" role="alert"></p>
3296
3848
  </div>
3297
- <p class="auth-foot">Muxo checks every action against your guardrails and workspace memory before it reaches you.</p>
3849
+ <div class="oauth-card-foot">
3850
+ <span>Secured by <strong>Muxo</strong> · Governed Agent Execution</span>
3851
+ </div>
3298
3852
  </div>
3299
3853
  </main>
3300
- <script>window.MUXO_APPROVAL = ${inlineJson(config)};${approvalScript}</script>
3854
+ <script>window.MUXO_OAUTH_CALLBACK = ${inlineJson(config)};${oauthCallbackScript}</script>
3301
3855
  </body>
3302
3856
  </html>`;
3303
3857
  }