clawfire 0.4.1 → 0.4.3

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-DRZ52WIA.js");
212
+ const { startDevServer } = await import("./dev-server-PAI4XU2S.js");
213
213
  await startDevServer({
214
214
  projectDir,
215
215
  port,
@@ -1195,34 +1195,15 @@ function generateDashboardHtml(options) {
1195
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
- <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;">
1199
- Cancel
1200
- </button>
1201
1198
  </div>
1202
- <!-- Login progress / auth URL -->
1203
- <div id="login-progress" style="display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;">
1199
+ <!-- Login waiting message -->
1200
+ <div id="login-waiting" style="display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;">
1204
1201
  <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 id="login-spinner-text" style="color:#e5e5e5;font-size:13px;font-weight:600;">Starting login...</span>
1207
- </div>
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>
1202
+ <span 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>
1203
+ <span style="color:#e5e5e5;font-size:13px;font-weight:600;">Waiting for login...</span>
1212
1204
  </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>
1205
+ <div style="font-size:13px;color:#a3a3a3;">A terminal window has been opened. Please complete the Firebase login there.</div>
1206
+ <div style="font-size:12px;color:#666;margin-top:6px;">This page will update automatically when login is detected.</div>
1226
1207
  </div>
1227
1208
  <div id="login-result" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1228
1209
  </div>
@@ -1432,6 +1413,9 @@ function generateDashboardHtml(options) {
1432
1413
  document.getElementById('step-' + i).style.display = 'none';
1433
1414
  }
1434
1415
  document.getElementById('setup-done').style.display = 'none';
1416
+ // Reset login UI state from previous interactions
1417
+ document.getElementById('login-waiting').style.display = 'none';
1418
+ document.getElementById('login-result').style.display = 'none';
1435
1419
 
1436
1420
  if (status.nextStep === 'done') {
1437
1421
  // All done!
@@ -1469,14 +1453,14 @@ function generateDashboardHtml(options) {
1469
1453
  document.getElementById('step2-action').style.display = 'block';
1470
1454
  document.getElementById('login-btn').style.display = 'none';
1471
1455
  document.getElementById('reauth-btn').style.display = 'inline-block';
1472
- document.getElementById('cancel-login-btn').style.display = 'none';
1456
+ document.getElementById('login-waiting').style.display = 'none';
1473
1457
  } else {
1474
1458
  document.getElementById('step2-icon').textContent = RED;
1475
1459
  document.getElementById('step2-detail').textContent = 'Not logged in to Firebase';
1476
1460
  document.getElementById('step2-action').style.display = 'block';
1477
1461
  document.getElementById('login-btn').style.display = 'inline-block';
1478
1462
  document.getElementById('reauth-btn').style.display = 'none';
1479
- document.getElementById('cancel-login-btn').style.display = 'none';
1463
+ document.getElementById('login-waiting').style.display = 'none';
1480
1464
  }
1481
1465
 
1482
1466
  if (status.nextStep === 'login') {
@@ -1545,22 +1529,17 @@ function generateDashboardHtml(options) {
1545
1529
  });
1546
1530
  };
1547
1531
 
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
1532
+ // \u2500\u2500\u2500 Step 2: Login (opens terminal window) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1549
1533
  window.startFirebaseLogin = function(reauth) {
1550
1534
  var loginBtn = document.getElementById('login-btn');
1551
1535
  var reauthBtn = document.getElementById('reauth-btn');
1552
- var cancelBtn = document.getElementById('cancel-login-btn');
1553
- var progress = document.getElementById('login-progress');
1536
+ var waiting = document.getElementById('login-waiting');
1554
1537
  var result = document.getElementById('login-result');
1555
1538
 
1556
1539
  loginBtn.style.display = 'none';
1557
1540
  reauthBtn.style.display = 'none';
1558
- cancelBtn.style.display = 'inline-block';
1559
- progress.style.display = 'block';
1541
+ waiting.style.display = 'block';
1560
1542
  result.style.display = 'none';
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...';
1564
1543
 
1565
1544
  fetch(API + '/__dev/setup/login', {
1566
1545
  method: 'POST',
@@ -1568,131 +1547,53 @@ function generateDashboardHtml(options) {
1568
1547
  body: JSON.stringify({ reauth: !!reauth })
1569
1548
  })
1570
1549
  .then(function(r) { return r.json(); })
1571
- .then(function() {
1572
- // Start polling login state
1550
+ .then(function(data) {
1551
+ if (!data.success) {
1552
+ waiting.style.display = 'none';
1553
+ result.textContent = data.message;
1554
+ result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1555
+ loginBtn.style.display = 'inline-block';
1556
+ return;
1557
+ }
1558
+ // Poll setup status until login is detected
1573
1559
  startLoginPoll();
1574
1560
  })
1575
1561
  .catch(function(err) {
1576
- progress.style.display = 'none';
1577
- result.textContent = 'Failed to start login: ' + err.message;
1562
+ waiting.style.display = 'none';
1563
+ result.textContent = 'Failed: ' + err.message;
1578
1564
  result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1579
1565
  loginBtn.style.display = 'inline-block';
1580
- cancelBtn.style.display = 'none';
1581
1566
  });
1582
1567
  };
1583
1568
 
1584
1569
  function startLoginPoll() {
1585
1570
  if (loginPollTimer) clearInterval(loginPollTimer);
1586
1571
  loginPollTimer = setInterval(function() {
1587
- fetch(API + '/__dev/setup/login-state')
1572
+ fetch(API + '/__dev/setup/status')
1588
1573
  .then(function(r) { return r.json(); })
1589
- .then(function(state) {
1590
- // Show auth URL if available
1591
- if (state.authUrl) {
1592
- var urlBox = document.getElementById('login-url-box');
1593
- var urlLink = document.getElementById('login-url-link');
1594
- var urlRaw = document.getElementById('login-url-raw');
1595
- urlBox.style.display = 'block';
1596
- urlLink.href = 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';
1605
- }
1606
-
1607
- // Show output log
1608
- if (state.output) {
1609
- var outputEl = document.getElementById('login-output');
1610
- outputEl.style.display = 'block';
1611
- outputEl.textContent = state.output.slice(-500);
1612
- }
1613
-
1614
- // Check if done
1615
- if (state.completed) {
1574
+ .then(function(status) {
1575
+ if (status.auth.authenticated) {
1616
1576
  clearInterval(loginPollTimer);
1617
1577
  loginPollTimer = null;
1618
- document.getElementById('login-progress').style.display = 'none';
1619
- document.getElementById('cancel-login-btn').style.display = 'none';
1578
+ document.getElementById('login-waiting').style.display = 'none';
1620
1579
  var result = document.getElementById('login-result');
1621
- result.textContent = 'Login successful!';
1580
+ result.textContent = 'Login successful! Logged in as ' + status.auth.user;
1622
1581
  result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
1623
- // Refresh wizard after short delay
1624
- setTimeout(refreshSetupStatus, 1500);
1625
- }
1626
-
1627
- if (state.error) {
1628
- clearInterval(loginPollTimer);
1629
- loginPollTimer = null;
1630
- document.getElementById('login-progress').style.display = 'none';
1631
- document.getElementById('cancel-login-btn').style.display = 'none';
1632
- var result2 = document.getElementById('login-result');
1633
- result2.textContent = state.error;
1634
- result2.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1635
- document.getElementById('login-btn').style.display = 'inline-block';
1636
- }
1637
-
1638
- if (!state.active && !state.completed && !state.error) {
1639
- clearInterval(loginPollTimer);
1640
- loginPollTimer = null;
1582
+ // Wait for token to fully settle, then refresh wizard + force-load projects
1583
+ setTimeout(function() {
1584
+ refreshSetupStatus();
1585
+ // Extra delay for project list \u2014 Firebase CLI needs time after fresh login
1586
+ setTimeout(function() { loadProjectList(''); }, 2500);
1587
+ }, 2000);
1641
1588
  }
1642
1589
  })
1643
1590
  .catch(function() {});
1644
- }, 1500);
1591
+ }, 3000);
1645
1592
  }
1646
1593
 
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
-
1686
- window.cancelFirebaseLogin = function() {
1687
- if (loginPollTimer) { clearInterval(loginPollTimer); loginPollTimer = null; }
1688
- fetch(API + '/__dev/setup/cancel-login', { method: 'POST' }).catch(function() {});
1689
- document.getElementById('login-progress').style.display = 'none';
1690
- document.getElementById('cancel-login-btn').style.display = 'none';
1691
- document.getElementById('login-btn').style.display = 'inline-block';
1692
- };
1693
-
1694
1594
  // \u2500\u2500\u2500 Step 3: Project Selection \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
1695
- function loadProjectList(currentProjectId) {
1595
+ function loadProjectList(currentProjectId, retryCount) {
1596
+ retryCount = retryCount || 0;
1696
1597
  var select = document.getElementById('project-select');
1697
1598
  select.innerHTML = '<option value="">Loading projects...</option>';
1698
1599
  select.disabled = true;
@@ -1702,10 +1603,21 @@ function generateDashboardHtml(options) {
1702
1603
  .then(function(data) {
1703
1604
  select.innerHTML = '';
1704
1605
  if (data.error) {
1606
+ // Auto-retry up to 2 times on error (token might not be ready yet after fresh login)
1607
+ if (retryCount < 2) {
1608
+ select.innerHTML = '<option value="">Loading projects... (retry)</option>';
1609
+ setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);
1610
+ return;
1611
+ }
1705
1612
  select.innerHTML = '<option value="">Error: ' + escHtml(data.error) + '</option>';
1706
1613
  return;
1707
1614
  }
1708
1615
  if (!data.projects || data.projects.length === 0) {
1616
+ if (retryCount < 2) {
1617
+ select.innerHTML = '<option value="">Loading projects... (retry)</option>';
1618
+ setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);
1619
+ return;
1620
+ }
1709
1621
  select.innerHTML = '<option value="">No projects found</option>';
1710
1622
  return;
1711
1623
  }
@@ -1721,6 +1633,11 @@ function generateDashboardHtml(options) {
1721
1633
  select.disabled = false;
1722
1634
  })
1723
1635
  .catch(function(err) {
1636
+ if (retryCount < 2) {
1637
+ select.innerHTML = '<option value="">Loading projects... (retry)</option>';
1638
+ setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);
1639
+ return;
1640
+ }
1724
1641
  select.innerHTML = '<option value="">Failed to load</option>';
1725
1642
  });
1726
1643
  }
@@ -2155,21 +2072,11 @@ function generateDashboardHtml(options) {
2155
2072
 
2156
2073
  // src/dev/firebase-setup.ts
2157
2074
  import { execFile as execFile2, spawn } from "child_process";
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";
2075
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2076
+ import { resolve as resolve4, join as join3 } from "path";
2077
+ import { tmpdir, platform } from "os";
2161
2078
  var FirebaseSetup = class {
2162
2079
  projectDir;
2163
- loginProcess = null;
2164
- loginSession = {
2165
- active: false,
2166
- authUrl: null,
2167
- waitingForCode: false,
2168
- completed: false,
2169
- error: null,
2170
- output: ""
2171
- };
2172
- loginTimeout = null;
2173
2080
  constructor(projectDir) {
2174
2081
  this.projectDir = projectDir;
2175
2082
  }
@@ -2249,149 +2156,93 @@ var FirebaseSetup = class {
2249
2156
  return { success: false, message: `Failed to install Firebase CLI: ${msg}` };
2250
2157
  }
2251
2158
  }
2252
- // ─── Analytics Pre-config ──────────────────────────────────────────
2159
+ // ─── Login via Terminal ───────────────────────────────────────────
2253
2160
  /**
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
- }
2275
- // ─── Login Flow ────────────────────────────────────────────────────
2276
- /**
2277
- * Start login using `firebase login --no-localhost`.
2161
+ * Opens a new terminal window and runs `firebase login`.
2162
+ *
2163
+ * Firebase CLI requires a real TTY for interactive login.
2164
+ * Instead of trying to fake a TTY, we open an actual terminal.
2165
+ * The dashboard polls getStatus() to detect when login completes.
2278
2166
  *
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()
2167
+ * macOS: Creates a .command file and opens it (Terminal.app)
2168
+ * Linux: Tries common terminal emulators
2169
+ * Windows: Opens a new cmd window
2284
2170
  */
2285
- startLogin(reauth = false) {
2286
- this.cleanupLogin();
2287
- this.ensureAnalyticsConfigured();
2288
- this.loginSession = {
2289
- active: true,
2290
- authUrl: null,
2291
- waitingForCode: false,
2292
- completed: false,
2293
- error: null,
2294
- output: ""
2295
- };
2171
+ openLoginTerminal(reauth = false) {
2172
+ const cmd = reauth ? "firebase login --reauth" : "firebase login";
2173
+ const os = platform();
2296
2174
  try {
2297
- const args = reauth ? ["login", "--reauth", "--no-localhost"] : ["login", "--no-localhost"];
2298
- const proc = spawn("firebase", args, {
2299
- cwd: this.projectDir,
2300
- stdio: ["pipe", "pipe", "pipe"],
2301
- env: { ...process.env, TERM: "dumb" }
2302
- });
2303
- this.loginProcess = proc;
2304
- const appendOutput = (data) => {
2305
- const text = data.toString();
2306
- this.loginSession.output += text;
2307
- const urlMatch = text.match(/(https:\/\/accounts\.google\.com\S+)/);
2308
- if (urlMatch) {
2309
- this.loginSession.authUrl = urlMatch[1];
2310
- this.loginSession.waitingForCode = true;
2311
- }
2312
- if (text.includes("Success") || text.includes("Logged in as")) {
2313
- this.loginSession.completed = true;
2314
- this.loginSession.active = false;
2315
- this.loginSession.waitingForCode = false;
2316
- }
2317
- if (text.includes("Allow Firebase to collect") || text.includes("(Y/n)")) {
2175
+ if (os === "darwin") {
2176
+ const scriptPath = join3(tmpdir(), "clawfire-firebase-login.command");
2177
+ writeFileSync2(scriptPath, [
2178
+ "#!/bin/bash",
2179
+ `cd "${this.projectDir}"`,
2180
+ cmd,
2181
+ 'echo ""',
2182
+ 'echo "Login complete! Closing in 3 seconds..."',
2183
+ "sleep 3",
2184
+ // Spawn osascript in background to close this specific terminal window, then exit
2185
+ `(sleep 1 && osascript -e 'tell application "Terminal" to close (every window whose name contains "clawfire-firebase-login")' 2>/dev/null) &`,
2186
+ "exit 0"
2187
+ ].join("\n"), { mode: 493 });
2188
+ const child = spawn("open", [scriptPath], { detached: true, stdio: "ignore" });
2189
+ child.unref();
2190
+ } else if (os === "win32") {
2191
+ const child = spawn("cmd", ["/c", "start", "cmd", "/c", `${cmd} && timeout /t 3 >nul`], {
2192
+ cwd: this.projectDir,
2193
+ detached: true,
2194
+ stdio: "ignore"
2195
+ });
2196
+ child.unref();
2197
+ } else {
2198
+ const scriptPath = join3(tmpdir(), "clawfire-firebase-login.sh");
2199
+ writeFileSync2(scriptPath, [
2200
+ "#!/bin/bash",
2201
+ `cd "${this.projectDir}"`,
2202
+ cmd,
2203
+ 'echo ""',
2204
+ 'echo "Login complete! Closing in 3 seconds..."',
2205
+ "sleep 3",
2206
+ "exit 0"
2207
+ ].join("\n"), { mode: 493 });
2208
+ const terminals = [
2209
+ { cmd: "x-terminal-emulator", args: ["-e", scriptPath] },
2210
+ { cmd: "gnome-terminal", args: ["--", "bash", scriptPath] },
2211
+ { cmd: "konsole", args: ["-e", "bash", scriptPath] },
2212
+ { cmd: "xfce4-terminal", args: ["-e", scriptPath] },
2213
+ { cmd: "xterm", args: ["-e", scriptPath] }
2214
+ ];
2215
+ let opened = false;
2216
+ for (const t of terminals) {
2318
2217
  try {
2319
- proc.stdin?.write("n\n");
2218
+ const child = spawn(t.cmd, t.args, { detached: true, stdio: "ignore" });
2219
+ child.unref();
2220
+ child.on("error", () => {
2221
+ });
2222
+ opened = true;
2223
+ break;
2320
2224
  } catch {
2225
+ continue;
2321
2226
  }
2322
2227
  }
2323
- };
2324
- proc.stdout?.on("data", appendOutput);
2325
- proc.stderr?.on("data", appendOutput);
2326
- proc.on("close", (code) => {
2327
- this.loginSession.active = false;
2328
- this.loginSession.waitingForCode = false;
2329
- if (code === 0) {
2330
- this.loginSession.completed = true;
2331
- } else if (!this.loginSession.completed) {
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.`;
2335
- }
2336
- this.loginProcess = null;
2337
- });
2338
- proc.on("error", (err) => {
2339
- this.loginSession.active = false;
2340
- this.loginSession.error = err.message;
2341
- this.loginProcess = null;
2342
- });
2343
- this.loginTimeout = setTimeout(() => {
2344
- if (this.loginSession.active) {
2345
- this.loginSession.error = "Login timed out (5 minutes). Please try again.";
2346
- this.cleanupLogin();
2228
+ if (!opened) {
2229
+ return {
2230
+ success: false,
2231
+ message: `Could not find a terminal emulator. Please run "${cmd}" manually in your terminal.`
2232
+ };
2347
2233
  }
2348
- }, 5 * 60 * 1e3);
2349
- } catch (err) {
2350
- this.loginSession.active = false;
2351
- this.loginSession.error = err instanceof Error ? err.message : "Failed to start login";
2352
- }
2353
- return this.loginSession;
2354
- }
2355
- getLoginState() {
2356
- return { ...this.loginSession };
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
- }
2377
- cancelLogin() {
2378
- this.cleanupLogin();
2379
- this.loginSession.error = "Login cancelled by user.";
2380
- }
2381
- cleanupLogin() {
2382
- if (this.loginProcess) {
2383
- try {
2384
- this.loginProcess.kill("SIGTERM");
2385
- } catch {
2386
2234
  }
2387
- this.loginProcess = null;
2388
- }
2389
- if (this.loginTimeout) {
2390
- clearTimeout(this.loginTimeout);
2391
- this.loginTimeout = null;
2235
+ return {
2236
+ success: true,
2237
+ message: "Terminal window opened. Please complete the login in the new terminal."
2238
+ };
2239
+ } catch (err) {
2240
+ const msg = err instanceof Error ? err.message : "Unknown error";
2241
+ return {
2242
+ success: false,
2243
+ message: `Failed to open terminal: ${msg}. Please run "${cmd}" manually in your terminal.`
2244
+ };
2392
2245
  }
2393
- this.loginSession.active = false;
2394
- this.loginSession.waitingForCode = false;
2395
2246
  }
2396
2247
  // ─── Project Listing ───────────────────────────────────────────────
2397
2248
  async listProjects() {
@@ -2485,7 +2336,6 @@ var FirebaseSetup = class {
2485
2336
  }
2486
2337
  /** Cleanup resources */
2487
2338
  destroy() {
2488
- this.cleanupLogin();
2489
2339
  }
2490
2340
  };
2491
2341
 
@@ -3408,36 +3258,8 @@ ${liveReloadScript}
3408
3258
  reauth = !!data.reauth;
3409
3259
  } catch {
3410
3260
  }
3411
- const session = this.firebaseSetup.startLogin(reauth);
3412
- sendJson(session);
3413
- });
3414
- return;
3415
- }
3416
- if (url.pathname === "/__dev/setup/login-state" && req.method === "GET") {
3417
- sendJson(this.firebaseSetup.getLoginState());
3418
- return;
3419
- }
3420
- if (url.pathname === "/__dev/setup/cancel-login" && req.method === "POST") {
3421
- this.firebaseSetup.cancelLogin();
3422
- sendJson({ ok: true });
3423
- return;
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
- }
3261
+ const result = this.firebaseSetup.openLoginTerminal(reauth);
3262
+ sendJson(result);
3441
3263
  });
3442
3264
  return;
3443
3265
  }