Skip to content

feat(cli): add mesh login command with beautiful capybara success page#2367

Open
vibegui wants to merge 1 commit intomainfrom
feat/cli-mesh-login
Open

feat(cli): add mesh login command with beautiful capybara success page#2367
vibegui wants to merge 1 commit intomainfrom
feat/cli-mesh-login

Conversation

@vibegui
Copy link
Contributor

@vibegui vibegui commented Jan 30, 2026

  • 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.

What is this contribution about?

Describe your changes and why they're needed.

Screenshots/Demonstration

Add screenshots or a Loom video if your changes affect the UI.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

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.

  • New Features
    • CLI: new mesh login command opens the browser, completes via a local callback (port 3458), and stores a 90-day API key.
    • Web: /cli-callback route fetches the CLI token and completes the callback via a hidden iframe while showing the capybara animation.
    • API: GET /api/auth/custom/cli-token creates a long-lived API key from the authenticated session.
    • Runtime: OAuth callback now returns a success page with embedded callback support for standalone MCPs.
    • CLI libs: mesh-session and mesh-client handle session storage (~/.deco_mesh_session.json), org scoping via headers, and tool/gateway/LLM calls (including streaming).

Written for commit 83bebab. Summary will update on new commits.

- 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>
@github-actions
Copy link
Contributor

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Contributor

Release Options

Should a new version be published when this PR is merged?

React with an emoji to vote on the release type:

Reaction Type Next Version
👍 Prerelease 2.62.1-alpha.1
🎉 Patch 2.62.1
❤️ Minor 2.63.0
🚀 Major 3.0.0

Current version: 2.62.0

Deployment

  • Deploy to production (triggers ArgoCD sync after Docker image is published)

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

</div>
</div>

<script src="https://cdn.jsdelivr.net/gh/hiunicornstudio/unicornstudio.js@v2.0.0/dist/unicornStudio.umd.js"></script>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

*
* Route: GET /api/auth/custom/cli-token
*/
app.get("/cli-token", async (c) => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
app.get("/cli-token", async (c) => {
app.post("/cli-token", async (c) => {
Fix with Cubic

Comment on lines +295 to +297
var iframe = document.createElement('iframe');
iframe.src = callbackUrl;
container.appendChild(iframe);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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);
Fix with Cubic

type: "tool-call-end",
toolCallId: part.toolCallId || "",
toolName: part.toolName || "",
args: part.input ? JSON.parse(part.input) : {},
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

return;
}

if (receivedState !== state) {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic


// Include user info so CLI doesn't need to fetch again
if (data.user) {
callbackUrl.searchParams.set("user", btoa(JSON.stringify(data.user)));
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant