Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-cli-login.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"emdash": patch
---

Fix CLI login against remote Cloudflare-deployed instances by unwrapping API response envelope and adding admin scope
7 changes: 5 additions & 2 deletions packages/core/src/cli/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ async function pollForToken(
});

if (res.ok) {
return (await res.json()) as TokenResponse;
const body = (await res.json()) as { data: TokenResponse };
return body.data;
}

const body = (await res.json()) as { error?: string; interval?: number };
Comment on lines 94 to 99
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

In the non-ok branch, the token endpoint can return standard API errors shaped like { error: { code, message } } (e.g. invalid device_code / unsupported grant_type), but this code parses { error?: string }. That leads to unhelpful messages like Token exchange failed: [object Object] and skips the server-provided message. Consider handling both shapes (RFC 8628 flat { error: string } and the standard { error: { message } }) when building the thrown Error.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -269,6 +270,7 @@ export const loginCommand = defineCommand({
},
body: JSON.stringify({
client_id: "emdash-cli",
scope: "admin",
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

scope: "admin" requests only the admin scope. On the server, device-flow authorization clamps scopes by intersection (see packages/auth/src/rbac.ts), so non-admin users will end up with zero effective scopes and the device authorization step will fail with INSUFFICIENT_ROLE. If the intent is "admin for admins, otherwise clamped", request the full default scope set plus admin (space-separated) so non-admin roles still retain permitted scopes.

Suggested change
scope: "admin",
scope: "read write admin",

Copilot uses AI. Check for mistakes.
}),
});

Expand All @@ -277,7 +279,8 @@ export const loginCommand = defineCommand({
process.exit(2);
}

const deviceCode = (await codeRes.json()) as DeviceCodeResponse;
const deviceCodeBody = (await codeRes.json()) as { data: DeviceCodeResponse };
const deviceCode = deviceCodeBody.data;

// Step 3: Display instructions
console.log();
Expand Down
Loading