feat(cli): add mesh login command with beautiful capybara success page#2367
feat(cli): add mesh login command with beautiful capybara success page#2367
Conversation
- Add CLI mesh login command with browser-based OAuth flow - Add beautiful cli-callback page with capybara coding animation - Add /api/auth/custom/cli-token endpoint for creating CLI API keys - Add mesh session and client utilities for CLI authentication - Update runtime OAuth to show beautiful success page for standalone MCPs The CLI login flow opens a browser for authentication, then displays a beautiful success page featuring the capybara coding animation while completing the callback via hidden iframe. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsShould a new version be published when this PR is merged? React with an emoji to vote on the release type:
Current version: Deployment
|
There was a problem hiding this comment.
7 issues found across 7 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/runtime/src/oauth.ts">
<violation number="1" location="packages/runtime/src/oauth.ts:295">
P2: The success page never performs a top-level redirect, so custom-scheme redirect URIs (allowed by isValidRedirectUri) will likely fail because browsers block custom schemes inside iframes. Add a top-level redirect fallback for non-http(s) protocols before creating the iframe to keep native/CLI callbacks working.</violation>
</file>
<file name="packages/cli/src/lib/mesh-client.ts">
<violation number="1" location="packages/cli/src/lib/mesh-client.ts:1058">
P2: Nested `JSON.parse(part.input)` can throw and silently drop the entire tool-call event. Unlike `parseToolCalls` which gracefully falls back to raw input on parse failure, this streaming version loses the event entirely. Consider wrapping this parse in its own try-catch with fallback.</violation>
</file>
<file name="packages/cli/src/commands/auth/mesh-login.ts">
<violation number="1" location="packages/cli/src/commands/auth/mesh-login.ts:171">
P2: Invalid state or missing token returns without rejecting/closing the server, leaving the login flow hanging indefinitely. The promise should be rejected and the server/timeout cleaned up on these error paths.</violation>
<violation number="2" location="packages/cli/src/commands/auth/mesh-login.ts:392">
P1: The callback page loads third‑party JS while the OAuth token is still in the URL query. This can leak the token via script access or the Referer header. Strip the query before loading external scripts or avoid third‑party scripts on this page.</violation>
</file>
<file name="apps/mesh/src/api/routes/auth.ts">
<violation number="1" location="apps/mesh/src/api/routes/auth.ts:110">
P1: This endpoint creates a new API key but uses GET instead of POST. State-changing operations should use POST to prevent unintended key creation via browser prefetching, CSRF attacks, or caching. GET requests are meant to be idempotent.</violation>
</file>
<file name="apps/mesh/src/web/routes/cli-callback.tsx">
<violation number="1" location="apps/mesh/src/web/routes/cli-callback.tsx:68">
P0: Validate the CLI callback origin/host before appending the API token. As written, any `callback` URL from query params receives the token via hidden iframe, which can leak the API key to an attacker-controlled origin.</violation>
<violation number="2" location="apps/mesh/src/web/routes/cli-callback.tsx:73">
P2: `btoa` will throw on non‑Latin1 characters (e.g., user names with Unicode). Encode the JSON as UTF‑8 bytes before base64 so the callback doesn't fail for non‑ASCII users.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| } | ||
|
|
||
| // Build callback URL with token and user data | ||
| const callbackUrl = new URL(callback); |
There was a problem hiding this comment.
P0: Validate the CLI callback origin/host before appending the API token. As written, any callback URL from query params receives the token via hidden iframe, which can leak the API key to an attacker-controlled origin.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/routes/cli-callback.tsx, line 68:
<comment>Validate the CLI callback origin/host before appending the API token. As written, any `callback` URL from query params receives the token via hidden iframe, which can leak the API key to an attacker-controlled origin.</comment>
<file context>
@@ -0,0 +1,236 @@
+ }
+
+ // Build callback URL with token and user data
+ const callbackUrl = new URL(callback);
+ callbackUrl.searchParams.set("token", data.token);
+
</file context>
| </div> | ||
| </div> | ||
|
|
||
| <script src="https://cdn.jsdelivr.net/gh/hiunicornstudio/unicornstudio.js@v2.0.0/dist/unicornStudio.umd.js"></script> |
There was a problem hiding this comment.
P1: The callback page loads third‑party JS while the OAuth token is still in the URL query. This can leak the token via script access or the Referer header. Strip the query before loading external scripts or avoid third‑party scripts on this page.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/auth/mesh-login.ts, line 392:
<comment>The callback page loads third‑party JS while the OAuth token is still in the URL query. This can leak the token via script access or the Referer header. Strip the query before loading external scripts or avoid third‑party scripts on this page.</comment>
<file context>
@@ -0,0 +1,515 @@
+ </div>
+ </div>
+
+ <script src="https://cdn.jsdelivr.net/gh/hiunicornstudio/unicornstudio.js@v2.0.0/dist/unicornStudio.umd.js"></script>
+ <script>
+ if (window.UnicornStudio) {
</file context>
| * | ||
| * Route: GET /api/auth/custom/cli-token | ||
| */ | ||
| app.get("/cli-token", async (c) => { |
There was a problem hiding this comment.
P1: This endpoint creates a new API key but uses GET instead of POST. State-changing operations should use POST to prevent unintended key creation via browser prefetching, CSRF attacks, or caching. GET requests are meant to be idempotent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/auth.ts, line 110:
<comment>This endpoint creates a new API key but uses GET instead of POST. State-changing operations should use POST to prevent unintended key creation via browser prefetching, CSRF attacks, or caching. GET requests are meant to be idempotent.</comment>
<file context>
@@ -98,4 +98,70 @@ app.get("/config", async (c) => {
+ *
+ * Route: GET /api/auth/custom/cli-token
+ */
+app.get("/cli-token", async (c) => {
+ try {
+ // Get session from cookie using the global auth instance
</file context>
| app.get("/cli-token", async (c) => { | |
| app.post("/cli-token", async (c) => { |
| var iframe = document.createElement('iframe'); | ||
| iframe.src = callbackUrl; | ||
| container.appendChild(iframe); |
There was a problem hiding this comment.
P2: The success page never performs a top-level redirect, so custom-scheme redirect URIs (allowed by isValidRedirectUri) will likely fail because browsers block custom schemes inside iframes. Add a top-level redirect fallback for non-http(s) protocols before creating the iframe to keep native/CLI callbacks working.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/runtime/src/oauth.ts, line 295:
<comment>The success page never performs a top-level redirect, so custom-scheme redirect URIs (allowed by isValidRedirectUri) will likely fail because browsers block custom schemes inside iframes. Add a top-level redirect fallback for non-http(s) protocols before creating the iframe to keep native/CLI callbacks working.</comment>
<file context>
@@ -60,6 +60,256 @@ interface PendingAuthState {
+
+ // Create a visible iframe to show the client callback
+ // This lets the user see both our animation and the client's response
+ var iframe = document.createElement('iframe');
+ iframe.src = callbackUrl;
+ container.appendChild(iframe);
</file context>
| var iframe = document.createElement('iframe'); | |
| iframe.src = callbackUrl; | |
| container.appendChild(iframe); | |
| try { | |
| var protocol = new URL(callbackUrl).protocol; | |
| if (protocol !== 'http:' && protocol !== 'https:') { | |
| window.location.href = callbackUrl; | |
| return; | |
| } | |
| } catch (err) { | |
| window.location.href = callbackUrl; | |
| return; | |
| } | |
| var iframe = document.createElement('iframe'); | |
| iframe.src = callbackUrl; | |
| container.appendChild(iframe); |
| type: "tool-call-end", | ||
| toolCallId: part.toolCallId || "", | ||
| toolName: part.toolName || "", | ||
| args: part.input ? JSON.parse(part.input) : {}, |
There was a problem hiding this comment.
P2: Nested JSON.parse(part.input) can throw and silently drop the entire tool-call event. Unlike parseToolCalls which gracefully falls back to raw input on parse failure, this streaming version loses the event entirely. Consider wrapping this parse in its own try-catch with fallback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/lib/mesh-client.ts, line 1058:
<comment>Nested `JSON.parse(part.input)` can throw and silently drop the entire tool-call event. Unlike `parseToolCalls` which gracefully falls back to raw input on parse failure, this streaming version loses the event entirely. Consider wrapping this parse in its own try-catch with fallback.</comment>
<file context>
@@ -0,0 +1,1222 @@
+ type: "tool-call-end",
+ toolCallId: part.toolCallId || "",
+ toolName: part.toolName || "",
+ args: part.input ? JSON.parse(part.input) : {},
+ });
+ }
</file context>
| return; | ||
| } | ||
|
|
||
| if (receivedState !== state) { |
There was a problem hiding this comment.
P2: Invalid state or missing token returns without rejecting/closing the server, leaving the login flow hanging indefinitely. The promise should be rejected and the server/timeout cleaned up on these error paths.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/auth/mesh-login.ts, line 171:
<comment>Invalid state or missing token returns without rejecting/closing the server, leaving the login flow hanging indefinitely. The promise should be rejected and the server/timeout cleaned up on these error paths.</comment>
<file context>
@@ -0,0 +1,515 @@
+ return;
+ }
+
+ if (receivedState !== state) {
+ res.writeHead(400, { "Content-Type": "text/html" });
+ res.end(`<html><body><h1>Invalid State</h1></body></html>`);
</file context>
|
|
||
| // Include user info so CLI doesn't need to fetch again | ||
| if (data.user) { | ||
| callbackUrl.searchParams.set("user", btoa(JSON.stringify(data.user))); |
There was a problem hiding this comment.
P2: btoa will throw on non‑Latin1 characters (e.g., user names with Unicode). Encode the JSON as UTF‑8 bytes before base64 so the callback doesn't fail for non‑ASCII users.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/routes/cli-callback.tsx, line 73:
<comment>`btoa` will throw on non‑Latin1 characters (e.g., user names with Unicode). Encode the JSON as UTF‑8 bytes before base64 so the callback doesn't fail for non‑ASCII users.</comment>
<file context>
@@ -0,0 +1,236 @@
+
+ // Include user info so CLI doesn't need to fetch again
+ if (data.user) {
+ callbackUrl.searchParams.set("user", btoa(JSON.stringify(data.user)));
+ }
+ if (data.expiresAt) {
</file context>
The CLI login flow opens a browser for authentication, then displays a beautiful success page featuring the capybara coding animation while completing the callback via hidden iframe.
What is this contribution about?
Screenshots/Demonstration
Review Checklist
Summary by cubic
Adds a browser-based OAuth login flow for the Mesh CLI with a polished capybara success page, plus a secure CLI token endpoint and session/client utilities to enable authenticated CLI tooling. This simplifies CLI auth and unlocks LLM/gateway operations from the terminal.
Written for commit 83bebab. Summary will update on new commits.