clawfire 0.4.0 → 0.4.1

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/dist/cli.js CHANGED
@@ -209,7 +209,7 @@ async function runDevServer() {
209
209
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
210
210
  const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
211
211
  const noHotReload = args.includes("--no-hot-reload");
212
- const { startDevServer } = await import("./dev-server-MMWHTL5T.js");
212
+ const { startDevServer } = await import("./dev-server-DRZ52WIA.js");
213
213
  await startDevServer({
214
214
  projectDir,
215
215
  port,
@@ -1189,10 +1189,10 @@ function generateDashboardHtml(options) {
1189
1189
  </div>
1190
1190
  </div>
1191
1191
  <div id="step2-action" style="display:none;">
1192
- <button id="login-btn" onclick="startFirebaseLogin()" style="padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1192
+ <button id="login-btn" onclick="startFirebaseLogin(false)" style="padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1193
1193
  Login to Firebase
1194
1194
  </button>
1195
- <button id="reauth-btn" onclick="startFirebaseLogin()" style="display:none;padding:8px 20px;background:#eab308;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1195
+ <button id="reauth-btn" onclick="startFirebaseLogin(true)" style="display:none;padding:8px 20px;background:#eab308;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1196
1196
  Re-authenticate
1197
1197
  </button>
1198
1198
  <button id="cancel-login-btn" onclick="cancelFirebaseLogin()" style="display:none;padding:8px 20px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;margin-left:8px;">
@@ -1202,14 +1202,27 @@ function generateDashboardHtml(options) {
1202
1202
  <!-- Login progress / auth URL -->
1203
1203
  <div id="login-progress" style="display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;">
1204
1204
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
1205
- <span id="login-spinner" style="display:inline-block;width:14px;height:14px;border:2px solid #3b82f6;border-top-color:transparent;border-radius:50;animation:spin 1s linear infinite;"></span>
1206
- <span style="color:#e5e5e5;font-size:13px;font-weight:600;">Waiting for authentication...</span>
1205
+ <span id="login-spinner" style="display:inline-block;width:14px;height:14px;border:2px solid #3b82f6;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></span>
1206
+ <span id="login-spinner-text" style="color:#e5e5e5;font-size:13px;font-weight:600;">Starting login...</span>
1207
1207
  </div>
1208
- <div id="login-url-box" style="display:none;margin-top:8px;">
1209
- <div style="font-size:12px;color:#a3a3a3;margin-bottom:4px;">If the browser didn't open automatically, click the link below:</div>
1210
- <a id="login-url-link" href="#" target="_blank" rel="noopener" style="color:#3b82f6;font-size:13px;word-break:break-all;font-family:monospace;"></a>
1208
+ <div id="login-url-box" style="display:none;margin-top:12px;">
1209
+ <div style="font-size:13px;color:#e5e5e5;margin-bottom:6px;font-weight:600;">Step 1: Open this link to authenticate</div>
1210
+ <a id="login-url-link" href="#" target="_blank" rel="noopener" style="display:inline-block;padding:8px 16px;background:#3b82f6;color:#fff;border-radius:6px;font-size:13px;text-decoration:none;margin-bottom:8px;">Open Google Login</a>
1211
+ <div style="font-size:11px;color:#666;margin-top:4px;word-break:break-all;font-family:monospace;" id="login-url-raw"></div>
1211
1212
  </div>
1212
- <div id="login-output" style="display:none;margin-top:8px;max-height:100px;overflow-y:auto;padding:8px;background:#000;border-radius:4px;font-family:monospace;font-size:11px;color:#666;white-space:pre-wrap;"></div>
1213
+ <div id="auth-code-box" style="display:none;margin-top:12px;">
1214
+ <div style="font-size:13px;color:#e5e5e5;margin-bottom:6px;font-weight:600;">Step 2: Paste the authorization code</div>
1215
+ <div style="font-size:12px;color:#a3a3a3;margin-bottom:8px;">After signing in, Google will show an authorization code. Copy it and paste below.</div>
1216
+ <div style="display:flex;gap:8px;">
1217
+ <input id="auth-code-input" type="text" placeholder="Paste authorization code here"
1218
+ style="flex:1;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;" />
1219
+ <button id="auth-code-submit" onclick="submitAuthCode()" style="padding:8px 16px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;">
1220
+ Submit Code
1221
+ </button>
1222
+ </div>
1223
+ <div id="auth-code-error" style="display:none;margin-top:6px;font-size:12px;color:#ef4444;"></div>
1224
+ </div>
1225
+ <div id="login-output" style="display:none;margin-top:8px;max-height:80px;overflow-y:auto;padding:8px;background:#000;border-radius:4px;font-family:monospace;font-size:11px;color:#666;white-space:pre-wrap;"></div>
1213
1226
  </div>
1214
1227
  <div id="login-result" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1215
1228
  </div>
@@ -1260,7 +1273,7 @@ function generateDashboardHtml(options) {
1260
1273
  <div id="setup-done" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
1261
1274
  <div style="font-size:16px;color:#22c55e;font-weight:700;margin-bottom:4px;">Setup Complete</div>
1262
1275
  <div id="setup-done-detail" style="font-size:13px;color:#a3a3a3;"></div>
1263
- <button onclick="startFirebaseLogin()" style="margin-top:8px;padding:6px 14px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:12px;cursor:pointer;">Re-authenticate</button>
1276
+ <button onclick="startFirebaseLogin(true)" style="margin-top:8px;padding:6px 14px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:12px;cursor:pointer;">Re-authenticate</button>
1264
1277
  </div>
1265
1278
  </div>
1266
1279
  </div>
@@ -1533,7 +1546,7 @@ function generateDashboardHtml(options) {
1533
1546
  };
1534
1547
 
1535
1548
  // \u2500\u2500\u2500 Step 2: Login \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1536
- window.startFirebaseLogin = function() {
1549
+ window.startFirebaseLogin = function(reauth) {
1537
1550
  var loginBtn = document.getElementById('login-btn');
1538
1551
  var reauthBtn = document.getElementById('reauth-btn');
1539
1552
  var cancelBtn = document.getElementById('cancel-login-btn');
@@ -1546,8 +1559,14 @@ function generateDashboardHtml(options) {
1546
1559
  progress.style.display = 'block';
1547
1560
  result.style.display = 'none';
1548
1561
  document.getElementById('login-url-box').style.display = 'none';
1562
+ document.getElementById('auth-code-box').style.display = 'none';
1563
+ document.getElementById('login-spinner-text').textContent = 'Starting login...';
1549
1564
 
1550
- fetch(API + '/__dev/setup/login', { method: 'POST' })
1565
+ fetch(API + '/__dev/setup/login', {
1566
+ method: 'POST',
1567
+ headers: { 'Content-Type': 'application/json' },
1568
+ body: JSON.stringify({ reauth: !!reauth })
1569
+ })
1551
1570
  .then(function(r) { return r.json(); })
1552
1571
  .then(function() {
1553
1572
  // Start polling login state
@@ -1572,9 +1591,17 @@ function generateDashboardHtml(options) {
1572
1591
  if (state.authUrl) {
1573
1592
  var urlBox = document.getElementById('login-url-box');
1574
1593
  var urlLink = document.getElementById('login-url-link');
1594
+ var urlRaw = document.getElementById('login-url-raw');
1575
1595
  urlBox.style.display = 'block';
1576
1596
  urlLink.href = state.authUrl;
1577
- urlLink.textContent = state.authUrl.length > 80 ? state.authUrl.slice(0, 80) + '...' : state.authUrl;
1597
+ urlLink.textContent = 'Open Google Login';
1598
+ if (urlRaw) urlRaw.textContent = state.authUrl;
1599
+ document.getElementById('login-spinner-text').textContent = 'Waiting for authentication...';
1600
+ }
1601
+
1602
+ // Show auth code input when waiting for code
1603
+ if (state.waitingForCode) {
1604
+ document.getElementById('auth-code-box').style.display = 'block';
1578
1605
  }
1579
1606
 
1580
1607
  // Show output log
@@ -1617,6 +1644,45 @@ function generateDashboardHtml(options) {
1617
1644
  }, 1500);
1618
1645
  }
1619
1646
 
1647
+ window.submitAuthCode = function() {
1648
+ var input = document.getElementById('auth-code-input');
1649
+ var errEl = document.getElementById('auth-code-error');
1650
+ var btn = document.getElementById('auth-code-submit');
1651
+ var code = input ? input.value.trim() : '';
1652
+ if (!code) {
1653
+ errEl.textContent = 'Please paste the authorization code.';
1654
+ errEl.style.display = 'block';
1655
+ return;
1656
+ }
1657
+ errEl.style.display = 'none';
1658
+ btn.disabled = true;
1659
+ btn.textContent = 'Submitting...';
1660
+
1661
+ fetch(API + '/__dev/setup/submit-auth-code', {
1662
+ method: 'POST',
1663
+ headers: { 'Content-Type': 'application/json' },
1664
+ body: JSON.stringify({ code: code })
1665
+ })
1666
+ .then(function(r) { return r.json(); })
1667
+ .then(function(data) {
1668
+ if (data.success) {
1669
+ document.getElementById('login-spinner-text').textContent = 'Verifying authorization...';
1670
+ document.getElementById('auth-code-box').style.display = 'none';
1671
+ } else {
1672
+ errEl.textContent = data.message || 'Failed to submit code.';
1673
+ errEl.style.display = 'block';
1674
+ }
1675
+ btn.disabled = false;
1676
+ btn.textContent = 'Submit Code';
1677
+ })
1678
+ .catch(function(err) {
1679
+ errEl.textContent = 'Error: ' + err.message;
1680
+ errEl.style.display = 'block';
1681
+ btn.disabled = false;
1682
+ btn.textContent = 'Submit Code';
1683
+ });
1684
+ };
1685
+
1620
1686
  window.cancelFirebaseLogin = function() {
1621
1687
  if (loginPollTimer) { clearInterval(loginPollTimer); loginPollTimer = null; }
1622
1688
  fetch(API + '/__dev/setup/cancel-login', { method: 'POST' }).catch(function() {});
@@ -2089,14 +2155,16 @@ function generateDashboardHtml(options) {
2089
2155
 
2090
2156
  // src/dev/firebase-setup.ts
2091
2157
  import { execFile as execFile2, spawn } from "child_process";
2092
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2093
- import { resolve as resolve4 } from "path";
2158
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync } from "fs";
2159
+ import { resolve as resolve4, join as join3, dirname as dirname2 } from "path";
2160
+ import { homedir } from "os";
2094
2161
  var FirebaseSetup = class {
2095
2162
  projectDir;
2096
2163
  loginProcess = null;
2097
2164
  loginSession = {
2098
2165
  active: false,
2099
2166
  authUrl: null,
2167
+ waitingForCode: false,
2100
2168
  completed: false,
2101
2169
  error: null,
2102
2170
  output: ""
@@ -2164,7 +2232,7 @@ var FirebaseSetup = class {
2164
2232
  return { success: true, message: "Firebase CLI is already installed." };
2165
2233
  } catch {
2166
2234
  }
2167
- const output = await this.execTimeout(
2235
+ await this.execTimeout(
2168
2236
  "npm",
2169
2237
  ["install", "-g", "firebase-tools"],
2170
2238
  12e4
@@ -2181,21 +2249,56 @@ var FirebaseSetup = class {
2181
2249
  return { success: false, message: `Failed to install Firebase CLI: ${msg}` };
2182
2250
  }
2183
2251
  }
2252
+ // ─── Analytics Pre-config ──────────────────────────────────────────
2253
+ /**
2254
+ * Pre-configure Firebase analytics preference to avoid the interactive
2255
+ * "Allow Firebase to collect..." prompt that breaks non-TTY spawns.
2256
+ */
2257
+ ensureAnalyticsConfigured() {
2258
+ try {
2259
+ const home = homedir();
2260
+ const configPath = join3(home, ".config", "configstore", "firebase-tools.json");
2261
+ if (existsSync5(configPath)) {
2262
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
2263
+ if (config.usage === void 0) {
2264
+ config.usage = false;
2265
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
2266
+ }
2267
+ } else {
2268
+ const dir = dirname2(configPath);
2269
+ mkdirSync(dir, { recursive: true });
2270
+ writeFileSync2(configPath, JSON.stringify({ usage: false }, null, 2), "utf-8");
2271
+ }
2272
+ } catch {
2273
+ }
2274
+ }
2184
2275
  // ─── Login Flow ────────────────────────────────────────────────────
2185
- startLogin() {
2276
+ /**
2277
+ * Start login using `firebase login --no-localhost`.
2278
+ *
2279
+ * This mode is designed for non-TTY environments:
2280
+ * 1. Prints a Google OAuth URL
2281
+ * 2. User opens URL in browser and authenticates
2282
+ * 3. Google shows an authorization code on screen
2283
+ * 4. User pastes code back → call submitAuthCode()
2284
+ */
2285
+ startLogin(reauth = false) {
2186
2286
  this.cleanupLogin();
2287
+ this.ensureAnalyticsConfigured();
2187
2288
  this.loginSession = {
2188
2289
  active: true,
2189
2290
  authUrl: null,
2291
+ waitingForCode: false,
2190
2292
  completed: false,
2191
2293
  error: null,
2192
2294
  output: ""
2193
2295
  };
2194
2296
  try {
2195
- const proc = spawn("firebase", ["login", "--reauth"], {
2297
+ const args = reauth ? ["login", "--reauth", "--no-localhost"] : ["login", "--no-localhost"];
2298
+ const proc = spawn("firebase", args, {
2196
2299
  cwd: this.projectDir,
2197
2300
  stdio: ["pipe", "pipe", "pipe"],
2198
- env: { ...process.env }
2301
+ env: { ...process.env, TERM: "dumb" }
2199
2302
  });
2200
2303
  this.loginProcess = proc;
2201
2304
  const appendOutput = (data) => {
@@ -2204,16 +2307,14 @@ var FirebaseSetup = class {
2204
2307
  const urlMatch = text.match(/(https:\/\/accounts\.google\.com\S+)/);
2205
2308
  if (urlMatch) {
2206
2309
  this.loginSession.authUrl = urlMatch[1];
2310
+ this.loginSession.waitingForCode = true;
2207
2311
  }
2208
- const localhostMatch = text.match(/(http:\/\/localhost:\d+\S*)/);
2209
- if (localhostMatch && !this.loginSession.authUrl) {
2210
- this.loginSession.authUrl = localhostMatch[1];
2211
- }
2212
- if (text.includes("Success") || text.includes("Logged in as") || text.includes("\u2714")) {
2312
+ if (text.includes("Success") || text.includes("Logged in as")) {
2213
2313
  this.loginSession.completed = true;
2214
2314
  this.loginSession.active = false;
2315
+ this.loginSession.waitingForCode = false;
2215
2316
  }
2216
- if (text.includes("Allow Firebase to collect") || text.includes("analytics")) {
2317
+ if (text.includes("Allow Firebase to collect") || text.includes("(Y/n)")) {
2217
2318
  try {
2218
2319
  proc.stdin?.write("n\n");
2219
2320
  } catch {
@@ -2224,10 +2325,13 @@ var FirebaseSetup = class {
2224
2325
  proc.stderr?.on("data", appendOutput);
2225
2326
  proc.on("close", (code) => {
2226
2327
  this.loginSession.active = false;
2328
+ this.loginSession.waitingForCode = false;
2227
2329
  if (code === 0) {
2228
2330
  this.loginSession.completed = true;
2229
2331
  } else if (!this.loginSession.completed) {
2230
- this.loginSession.error = `Login process exited with code ${code}`;
2332
+ const lines = this.loginSession.output.trim().split("\n").filter((l) => l.trim());
2333
+ const lastLines = lines.slice(-3).join(" | ");
2334
+ this.loginSession.error = `Login failed (exit code ${code}).` + (lastLines ? ` Details: ${lastLines}` : "") + ` Try running "firebase login" in your terminal.`;
2231
2335
  }
2232
2336
  this.loginProcess = null;
2233
2337
  });
@@ -2251,6 +2355,25 @@ var FirebaseSetup = class {
2251
2355
  getLoginState() {
2252
2356
  return { ...this.loginSession };
2253
2357
  }
2358
+ /**
2359
+ * Submit the authorization code that the user copied from the browser.
2360
+ * This writes the code to the Firebase CLI process stdin.
2361
+ */
2362
+ submitAuthCode(code) {
2363
+ if (!this.loginProcess || !this.loginProcess.stdin) {
2364
+ return { success: false, message: "No active login process. Please start login again." };
2365
+ }
2366
+ if (!code || !code.trim()) {
2367
+ return { success: false, message: "Authorization code is required." };
2368
+ }
2369
+ try {
2370
+ this.loginProcess.stdin.write(code.trim() + "\n");
2371
+ this.loginSession.waitingForCode = false;
2372
+ return { success: true, message: "Authorization code submitted. Waiting for verification..." };
2373
+ } catch (err) {
2374
+ return { success: false, message: "Failed to submit code: " + (err instanceof Error ? err.message : "unknown error") };
2375
+ }
2376
+ }
2254
2377
  cancelLogin() {
2255
2378
  this.cleanupLogin();
2256
2379
  this.loginSession.error = "Login cancelled by user.";
@@ -2268,6 +2391,7 @@ var FirebaseSetup = class {
2268
2391
  this.loginTimeout = null;
2269
2392
  }
2270
2393
  this.loginSession.active = false;
2394
+ this.loginSession.waitingForCode = false;
2271
2395
  }
2272
2396
  // ─── Project Listing ───────────────────────────────────────────────
2273
2397
  async listProjects() {
@@ -2275,7 +2399,8 @@ var FirebaseSetup = class {
2275
2399
  const output = await this.execTimeout(
2276
2400
  "firebase",
2277
2401
  ["projects:list", "--json"],
2278
- 15e3
2402
+ 3e4
2403
+ // 30s — can be slow on first call
2279
2404
  );
2280
2405
  const data = JSON.parse(output);
2281
2406
  if (data?.result && Array.isArray(data.result)) {
@@ -2290,8 +2415,11 @@ var FirebaseSetup = class {
2290
2415
  return { projects: [], error: "Unexpected response format" };
2291
2416
  } catch (err) {
2292
2417
  const msg = err instanceof Error ? err.message : "Unknown error";
2293
- if (msg.includes("authenticate") || msg.includes("login")) {
2294
- return { projects: [], error: "Not authenticated. Please login first." };
2418
+ if (msg.includes("authenticate") || msg.includes("login") || msg.includes("credential")) {
2419
+ return { projects: [], error: "Session expired. Please re-authenticate first." };
2420
+ }
2421
+ if (msg.includes("Timeout")) {
2422
+ return { projects: [], error: "Request timed out. Please try again." };
2295
2423
  }
2296
2424
  return { projects: [], error: msg };
2297
2425
  }
@@ -2322,7 +2450,7 @@ var FirebaseSetup = class {
2322
2450
  rc.projects.default = projectId;
2323
2451
  writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2324
2452
  return { success: true, message: `Active project set to "${projectId}".` };
2325
- } catch (err) {
2453
+ } catch {
2326
2454
  try {
2327
2455
  const firebasercPath = resolve4(this.projectDir, ".firebaserc");
2328
2456
  const rc = {
@@ -2331,7 +2459,7 @@ var FirebaseSetup = class {
2331
2459
  writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2332
2460
  return { success: true, message: `Active project set to "${projectId}" (via .firebaserc).` };
2333
2461
  } catch (writeErr) {
2334
- const msg = err instanceof Error ? err.message : "Unknown error";
2462
+ const msg = writeErr instanceof Error ? writeErr.message : "Unknown error";
2335
2463
  return { success: false, message: `Failed to set project: ${msg}` };
2336
2464
  }
2337
2465
  }
@@ -2339,9 +2467,11 @@ var FirebaseSetup = class {
2339
2467
  // ─── Helpers ───────────────────────────────────────────────────────
2340
2468
  execTimeout(command, args, timeoutMs) {
2341
2469
  return new Promise((resolve6, reject) => {
2342
- const proc = execFile2(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout) => {
2470
+ const proc = execFile2(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
2343
2471
  if (err) {
2344
- reject(err);
2472
+ const detail = stderr?.trim() || stdout?.trim() || "";
2473
+ const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
2474
+ reject(enriched);
2345
2475
  } else {
2346
2476
  resolve6(stdout);
2347
2477
  }
@@ -3267,8 +3397,20 @@ ${liveReloadScript}
3267
3397
  return;
3268
3398
  }
3269
3399
  if (url.pathname === "/__dev/setup/login" && req.method === "POST") {
3270
- const session = this.firebaseSetup.startLogin();
3271
- sendJson(session);
3400
+ let body = "";
3401
+ req.on("data", (chunk) => {
3402
+ body += chunk;
3403
+ });
3404
+ req.on("end", () => {
3405
+ let reauth = false;
3406
+ try {
3407
+ const data = JSON.parse(body);
3408
+ reauth = !!data.reauth;
3409
+ } catch {
3410
+ }
3411
+ const session = this.firebaseSetup.startLogin(reauth);
3412
+ sendJson(session);
3413
+ });
3272
3414
  return;
3273
3415
  }
3274
3416
  if (url.pathname === "/__dev/setup/login-state" && req.method === "GET") {
@@ -3280,6 +3422,25 @@ ${liveReloadScript}
3280
3422
  sendJson({ ok: true });
3281
3423
  return;
3282
3424
  }
3425
+ if (url.pathname === "/__dev/setup/submit-auth-code" && req.method === "POST") {
3426
+ let body = "";
3427
+ req.on("data", (chunk) => {
3428
+ body += chunk;
3429
+ });
3430
+ req.on("end", () => {
3431
+ try {
3432
+ const data = JSON.parse(body);
3433
+ if (!data.code) {
3434
+ sendJson({ success: false, message: "code is required" }, 400);
3435
+ return;
3436
+ }
3437
+ sendJson(this.firebaseSetup.submitAuthCode(data.code));
3438
+ } catch {
3439
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
3440
+ }
3441
+ });
3442
+ return;
3443
+ }
3283
3444
  if (url.pathname === "/__dev/setup/projects" && req.method === "GET") {
3284
3445
  this.firebaseSetup.listProjects().then((result) => sendJson(result)).catch((err) => sendJson({ projects: [], error: err instanceof Error ? err.message : "Failed" }, 500));
3285
3446
  return;