voicecc 1.1.20 → 1.1.22
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.
- package/dashboard/routes/auth.ts +47 -25
- package/package.json +1 -1
package/dashboard/routes/auth.ts
CHANGED
|
@@ -118,23 +118,19 @@ function spawnLoginProcess(): Promise<string> {
|
|
|
118
118
|
|
|
119
119
|
let output = "";
|
|
120
120
|
let resolved = false;
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
let loginSent = false;
|
|
124
|
-
let enterDebounce: ReturnType<typeof setTimeout> | null = null;
|
|
121
|
+
let enterCount = 0;
|
|
125
122
|
|
|
126
123
|
child.onData((data: string) => {
|
|
127
124
|
output += data;
|
|
128
125
|
const clean = data.replace(/\x1b\[[^m]*m/g, "").replace(/\r/g, "").trim();
|
|
129
|
-
if (clean) console.debug(`[auth/login]
|
|
126
|
+
if (clean) console.debug(`[auth/login] data: ${clean.slice(0, 200)}`);
|
|
130
127
|
|
|
131
128
|
if (resolved) return;
|
|
132
129
|
|
|
133
|
-
//
|
|
130
|
+
// Check for OAuth URL on every data event
|
|
134
131
|
const urlMatch = output.match(/(https:\/\/claude\.ai\/oauth\/authorize\S+)/);
|
|
135
132
|
if (urlMatch) {
|
|
136
133
|
resolved = true;
|
|
137
|
-
step = 4;
|
|
138
134
|
console.debug("[auth/login] URL captured");
|
|
139
135
|
pendingLogin = {
|
|
140
136
|
pty: child,
|
|
@@ -145,26 +141,41 @@ function spawnLoginProcess(): Promise<string> {
|
|
|
145
141
|
return;
|
|
146
142
|
}
|
|
147
143
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
144
|
+
// Detect REPL prompt — means user is already authenticated.
|
|
145
|
+
// The REPL shows "◐ medium · /effort" or similar model/effort indicator.
|
|
146
|
+
const stripped = output.replace(/\x1b\[[^m]*m/g, "");
|
|
147
|
+
if (enterCount >= 1 && !resolved) {
|
|
148
|
+
const replIndicators = [
|
|
149
|
+
/\/effort/, // effort command shown in REPL status
|
|
150
|
+
/◐.*medium/, // model indicator in REPL
|
|
151
|
+
/\$\d+\.\d+/, // cost like $0.00
|
|
152
|
+
/what can i help/i,
|
|
153
|
+
];
|
|
154
|
+
for (const pattern of replIndicators) {
|
|
155
|
+
if (pattern.test(stripped)) {
|
|
156
|
+
resolved = true;
|
|
157
|
+
console.debug("[auth/login] REPL detected — already authenticated");
|
|
158
|
+
child.kill();
|
|
159
|
+
reject(new Error("ALREADY_AUTHENTICATED"));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
164
163
|
}
|
|
165
164
|
});
|
|
166
165
|
|
|
166
|
+
// Press Enter every 2s to advance through setup prompts
|
|
167
|
+
// (trust, theme, login method, etc.) until the OAuth URL appears.
|
|
168
|
+
// Some prompts render ❯ via ANSI cursor positioning which is
|
|
169
|
+
// impossible to detect reliably, so periodic Enter is simplest.
|
|
170
|
+
const enterInterval = setInterval(() => {
|
|
171
|
+
if (resolved) { clearInterval(enterInterval); return; }
|
|
172
|
+
enterCount++;
|
|
173
|
+
console.debug(`[auth/login] pressing Enter (periodic #${enterCount})`);
|
|
174
|
+
child.write("\r");
|
|
175
|
+
}, 2000);
|
|
176
|
+
|
|
167
177
|
child.onExit(({ exitCode }: { exitCode: number }) => {
|
|
178
|
+
clearInterval(enterInterval);
|
|
168
179
|
console.debug("[auth/login] PTY exited with code", exitCode);
|
|
169
180
|
if (!resolved) {
|
|
170
181
|
resolved = true;
|
|
@@ -178,10 +189,11 @@ function spawnLoginProcess(): Promise<string> {
|
|
|
178
189
|
// Timeout
|
|
179
190
|
setTimeout(() => {
|
|
180
191
|
if (!resolved) {
|
|
192
|
+
clearInterval(enterInterval);
|
|
181
193
|
resolved = true;
|
|
182
194
|
child.kill();
|
|
183
195
|
const clean = output.replace(/\x1b\[[^m]*m/g, "").slice(-500);
|
|
184
|
-
reject(new Error(`Login timed out
|
|
196
|
+
reject(new Error(`Login timed out. Last output: ${clean}`));
|
|
185
197
|
}
|
|
186
198
|
}, LOGIN_TIMEOUT_MS);
|
|
187
199
|
});
|
|
@@ -289,11 +301,21 @@ export function authRoutes(): Hono {
|
|
|
289
301
|
* Spawns interactive claude via PTY, navigates to /login, returns URL.
|
|
290
302
|
*/
|
|
291
303
|
app.post("/oauth/start", async (c) => {
|
|
304
|
+
// Pre-check: if already authenticated, don't spawn PTY
|
|
305
|
+
const status = await getAuthStatus();
|
|
306
|
+
if (status.authenticated) {
|
|
307
|
+
return c.json({ error: "Already authenticated", alreadyAuthenticated: true }, 400);
|
|
308
|
+
}
|
|
309
|
+
|
|
292
310
|
try {
|
|
293
311
|
const url = await spawnLoginProcess();
|
|
294
312
|
return c.json({ url });
|
|
295
313
|
} catch (err) {
|
|
296
|
-
|
|
314
|
+
const msg = (err as Error).message;
|
|
315
|
+
if (msg === "ALREADY_AUTHENTICATED") {
|
|
316
|
+
return c.json({ error: "Already authenticated", alreadyAuthenticated: true }, 400);
|
|
317
|
+
}
|
|
318
|
+
return c.json({ error: msg }, 500);
|
|
297
319
|
}
|
|
298
320
|
});
|
|
299
321
|
|