Skip to content

Commit 885528c

Browse files
authored
Merge pull request #7 from betegon/opencode/clever-eagle
feat(auth): add QR code display for mobile OAuth flow
2 parents 38201ac + a8d44b3 commit 885528c

File tree

4 files changed

+90
-11
lines changed

4 files changed

+90
-11
lines changed

bun.lock

Lines changed: 13 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
"devDependencies": {
2020
"@types/bun": "latest",
2121
"@types/node": "^20",
22+
"@types/qrcode-terminal": "^0.12.2",
2223
"typescript": "^5"
2324
},
2425
"dependencies": {
2526
"@ast-grep/napi": "^0.31.0",
2627
"@stricli/auto-complete": "^1.2.4",
2728
"@stricli/core": "^1.2.4",
29+
"qrcode-terminal": "^0.12.0",
2830
"zod": "^3.24.0"
2931
}
3032
}

packages/cli/src/commands/auth/login.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import {
88
setAuthToken,
99
} from "../../lib/config.js";
1010
import { completeOAuthFlow, performDeviceFlow } from "../../lib/oauth.js";
11+
import { generateQRCode } from "../../lib/qrcode.js";
1112

1213
type LoginFlags = {
1314
readonly token?: string;
1415
readonly timeout: number;
16+
readonly qr: boolean;
1517
};
1618

1719
export const loginCommand = buildCommand({
@@ -36,6 +38,11 @@ export const loginCommand = buildCommand({
3638
brief: "Timeout for OAuth flow in seconds (default: 900)",
3739
default: 900,
3840
},
41+
qr: {
42+
kind: "boolean",
43+
brief: "Show QR code for mobile scanning",
44+
default: true,
45+
},
3946
},
4047
},
4148
async func(this: SentryContext, flags: LoginFlags): Promise<void> {
@@ -81,10 +88,18 @@ export const loginCommand = buildCommand({
8188
verificationUri,
8289
verificationUriComplete
8390
) => {
84-
process.stdout.write("Opening browser...\n");
85-
process.stdout.write(
86-
`If it doesn't open, visit: ${verificationUri}\n`
87-
);
91+
process.stdout.write("Opening browser...\n\n");
92+
93+
if (flags.qr) {
94+
process.stdout.write(
95+
"Scan this QR code or visit the URL below:\n\n"
96+
);
97+
const qr = await generateQRCode(verificationUriComplete);
98+
process.stdout.write(qr);
99+
process.stdout.write("\n");
100+
}
101+
102+
process.stdout.write(`URL: ${verificationUri}\n`);
88103
process.stdout.write(`Code: ${userCode}\n\n`);
89104
process.stdout.write("Waiting for authorization...\n");
90105
await openBrowser(verificationUriComplete);

packages/cli/src/lib/qrcode.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* QR Code Utilities
3+
*
4+
* Terminal QR code generation for authentication flows.
5+
* Uses qrcode-terminal for rendering QR codes in the terminal.
6+
*/
7+
8+
import qrcodeTerminal from "qrcode-terminal";
9+
import { z } from "zod";
10+
11+
// ─────────────────────────────────────────────────────────────────────────────
12+
// Schema & Types
13+
// ─────────────────────────────────────────────────────────────────────────────
14+
15+
/**
16+
* QR code generation options schema
17+
*/
18+
export const QRCodeOptionsSchema = z.object({
19+
/**
20+
* Use compact (small) QR code rendering.
21+
* Recommended for terminal display.
22+
*/
23+
small: z.boolean().default(true),
24+
});
25+
26+
export type QRCodeOptions = z.infer<typeof QRCodeOptionsSchema>;
27+
28+
// ─────────────────────────────────────────────────────────────────────────────
29+
// Public API
30+
// ─────────────────────────────────────────────────────────────────────────────
31+
32+
/**
33+
* Generate a QR code string for terminal display
34+
*
35+
* @param data - The data to encode in the QR code (typically a URL)
36+
* @param options - QR code generation options
37+
* @returns The QR code as a string suitable for terminal output
38+
*
39+
* @example
40+
* ```ts
41+
* const qr = await generateQRCode("https://example.com/auth?code=ABC123");
42+
* process.stdout.write(qr);
43+
* ```
44+
*/
45+
export function generateQRCode(
46+
data: string,
47+
options?: Partial<QRCodeOptions>
48+
): Promise<string> {
49+
const opts = QRCodeOptionsSchema.parse(options ?? {});
50+
51+
return new Promise((resolve) => {
52+
qrcodeTerminal.generate(data, { small: opts.small }, (qrcode) => {
53+
resolve(qrcode);
54+
});
55+
});
56+
}

0 commit comments

Comments
 (0)