clawfire 0.4.4 → 0.5.0

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/dev.cjs CHANGED
@@ -1494,7 +1494,23 @@ async function checkCli(projectDir) {
1494
1494
  }
1495
1495
  return result;
1496
1496
  }
1497
- async function fetchFirebaseSdkConfig(projectDir) {
1497
+ async function fetchFirebaseSdkConfig(projectDir, appId) {
1498
+ if (appId) {
1499
+ try {
1500
+ const output = await execWithTimeout(
1501
+ "firebase",
1502
+ ["apps:sdkconfig", "web", appId, "--json"],
1503
+ projectDir,
1504
+ 15e3
1505
+ );
1506
+ const config = parseSdkConfigOutput(output);
1507
+ if (config) return config;
1508
+ } catch (err) {
1509
+ const msg = err instanceof Error ? err.message : "Unknown error";
1510
+ throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);
1511
+ }
1512
+ throw new Error("Could not parse Firebase SDK config from CLI output");
1513
+ }
1498
1514
  try {
1499
1515
  const output = await execWithTimeout(
1500
1516
  "firebase",
@@ -1530,11 +1546,11 @@ async function fetchFirebaseSdkConfig(projectDir) {
1530
1546
  "No web app registered in this Firebase project. Go to Firebase Console > Project Settings > General > Your apps > Add app (Web) to create one, then click Auto-fill again."
1531
1547
  );
1532
1548
  }
1533
- const appId = webApps[0].appId;
1549
+ const firstAppId = webApps[0].appId;
1534
1550
  try {
1535
1551
  const output = await execWithTimeout(
1536
1552
  "firebase",
1537
- ["apps:sdkconfig", "web", appId, "--json"],
1553
+ ["apps:sdkconfig", "web", firstAppId, "--json"],
1538
1554
  projectDir,
1539
1555
  15e3
1540
1556
  );
@@ -1542,7 +1558,7 @@ async function fetchFirebaseSdkConfig(projectDir) {
1542
1558
  if (config) return config;
1543
1559
  } catch (err) {
1544
1560
  const msg = err instanceof Error ? err.message : "Unknown error";
1545
- throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);
1561
+ throw new Error(`Failed to fetch config for web app ${firstAppId}: ${msg}`);
1546
1562
  }
1547
1563
  throw new Error("Could not parse Firebase SDK config from CLI output");
1548
1564
  }
@@ -1611,17 +1627,20 @@ function generateDashboardHtml(options) {
1611
1627
 
1612
1628
  <!-- Step indicators -->
1613
1629
  <div id="setup-steps" style="display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;">
1614
- <div id="step-ind-1" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#1a1a2e;color:#f97316;border-right:1px solid #2a2a2a;">
1615
- 1. CLI Install
1630
+ <div id="step-ind-1" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#1a1a2e;color:#f97316;border-right:1px solid #2a2a2a;">
1631
+ 1. CLI
1616
1632
  </div>
1617
- <div id="step-ind-2" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
1633
+ <div id="step-ind-2" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
1618
1634
  2. Login
1619
1635
  </div>
1620
- <div id="step-ind-3" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
1621
- 3. Select Project
1636
+ <div id="step-ind-3" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
1637
+ 3. Project
1638
+ </div>
1639
+ <div id="step-ind-4" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
1640
+ 4. Web App
1622
1641
  </div>
1623
- <div id="step-ind-4" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;">
1624
- 4. Auto-fill
1642
+ <div id="step-ind-5" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;">
1643
+ 5. Auto-fill
1625
1644
  </div>
1626
1645
  </div>
1627
1646
 
@@ -1696,16 +1715,54 @@ function generateDashboardHtml(options) {
1696
1715
  </div>
1697
1716
  </div>
1698
1717
 
1699
- <!-- Step 4: Done / Auto-fill -->
1718
+ <!-- Step 4: Web App -->
1700
1719
  <div id="step-4" style="display:none;">
1701
1720
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1702
1721
  <span id="step4-icon" style="font-size:20px;">&#9898;</span>
1722
+ <div>
1723
+ <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Web App</div>
1724
+ <div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Checking web apps...</div>
1725
+ </div>
1726
+ </div>
1727
+ <div id="step4-action" style="display:none;">
1728
+ <!-- Existing web apps list -->
1729
+ <div id="webapp-list" style="display:none;">
1730
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
1731
+ <select id="webapp-select" style="padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:13px;font-family:monospace;min-width:280px;outline:none;">
1732
+ <option value="">Loading web apps...</option>
1733
+ </select>
1734
+ <button id="select-webapp-btn" onclick="selectWebApp()" style="padding:8px 20px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1735
+ Use This App
1736
+ </button>
1737
+ <button onclick="showCreateWebApp()" style="padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;">
1738
+ + Create New
1739
+ </button>
1740
+ </div>
1741
+ </div>
1742
+ <!-- Create new web app form -->
1743
+ <div id="webapp-create" style="display:none;">
1744
+ <div style="font-size:13px;color:#a3a3a3;margin-bottom:8px;">No web app found. Create one to get Firebase SDK config:</div>
1745
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
1746
+ <input id="webapp-name" type="text" placeholder="My Web App" style="padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:13px;min-width:220px;outline:none;" />
1747
+ <button id="create-webapp-btn" onclick="createWebApp()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1748
+ Create Web App
1749
+ </button>
1750
+ </div>
1751
+ </div>
1752
+ <div id="webapp-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1753
+ </div>
1754
+ </div>
1755
+
1756
+ <!-- Step 5: Done / Auto-fill -->
1757
+ <div id="step-5" style="display:none;">
1758
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1759
+ <span id="step5-icon" style="font-size:20px;">&#9898;</span>
1703
1760
  <div>
1704
1761
  <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Auto-fill Config</div>
1705
- <div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
1762
+ <div id="step5-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
1706
1763
  </div>
1707
1764
  </div>
1708
- <div id="step4-action">
1765
+ <div id="step5-action">
1709
1766
  <button id="autofill-wizard-btn" onclick="autoFillConfig()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1710
1767
  Auto-fill Config Now
1711
1768
  </button>
@@ -1850,7 +1907,7 @@ function generateDashboardHtml(options) {
1850
1907
  // \u2500\u2500\u2500 Setup Wizard \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\u2500
1851
1908
 
1852
1909
  function setStepIndicator(activeStep) {
1853
- for (var i = 1; i <= 4; i++) {
1910
+ for (var i = 1; i <= 5; i++) {
1854
1911
  var el = document.getElementById('step-ind-' + i);
1855
1912
  if (i < activeStep) {
1856
1913
  el.style.background = '#0a1a0a';
@@ -1872,7 +1929,7 @@ function generateDashboardHtml(options) {
1872
1929
  var CIRCLE = '\\u26AA';
1873
1930
 
1874
1931
  // Hide all steps first
1875
- for (var i = 1; i <= 4; i++) {
1932
+ for (var i = 1; i <= 5; i++) {
1876
1933
  document.getElementById('step-' + i).style.display = 'none';
1877
1934
  }
1878
1935
  document.getElementById('setup-done').style.display = 'none';
@@ -1882,10 +1939,13 @@ function generateDashboardHtml(options) {
1882
1939
 
1883
1940
  if (status.nextStep === 'done') {
1884
1941
  // All done!
1885
- setStepIndicator(5);
1942
+ setStepIndicator(6);
1886
1943
  document.getElementById('setup-done').style.display = 'block';
1887
- document.getElementById('setup-done-detail').textContent =
1888
- 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
1944
+ var detail = 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
1945
+ if (status.webApp && status.webApp.displayName) {
1946
+ detail += ' | App: ' + status.webApp.displayName;
1947
+ }
1948
+ document.getElementById('setup-done-detail').textContent = detail;
1889
1949
  return;
1890
1950
  }
1891
1951
 
@@ -1952,11 +2012,32 @@ function generateDashboardHtml(options) {
1952
2012
  return;
1953
2013
  }
1954
2014
 
1955
- // Step 4: Auto-fill
2015
+ // Step 4: Web App
1956
2016
  var step4 = document.getElementById('step-4');
1957
2017
  step4.style.display = 'block';
1958
- document.getElementById('step4-icon').textContent = CIRCLE;
1959
- setStepIndicator(4);
2018
+ if (status.webApp && status.webApp.appId) {
2019
+ document.getElementById('step4-icon').textContent = GREEN;
2020
+ document.getElementById('step4-detail').textContent = 'Web app: ' + (status.webApp.displayName || status.webApp.appId);
2021
+ document.getElementById('step4-action').style.display = 'block';
2022
+ // Show list with current selection
2023
+ loadWebAppList(status.webApp.appId);
2024
+ } else {
2025
+ document.getElementById('step4-icon').textContent = YELLOW;
2026
+ document.getElementById('step4-detail').textContent = 'No web app selected';
2027
+ document.getElementById('step4-action').style.display = 'block';
2028
+ loadWebAppList('');
2029
+ }
2030
+
2031
+ if (status.nextStep === 'create-web-app') {
2032
+ setStepIndicator(4);
2033
+ return;
2034
+ }
2035
+
2036
+ // Step 5: Auto-fill
2037
+ var step5 = document.getElementById('step-5');
2038
+ step5.style.display = 'block';
2039
+ document.getElementById('step5-icon').textContent = CIRCLE;
2040
+ setStepIndicator(5);
1960
2041
  }
1961
2042
 
1962
2043
  // \u2500\u2500\u2500 Step 1: Install CLI \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
@@ -2151,6 +2232,148 @@ function generateDashboardHtml(options) {
2151
2232
  });
2152
2233
  };
2153
2234
 
2235
+ // \u2500\u2500\u2500 Step 4: Web App \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
2236
+ function loadWebAppList(currentAppId, retryCount) {
2237
+ retryCount = retryCount || 0;
2238
+ var listDiv = document.getElementById('webapp-list');
2239
+ var createDiv = document.getElementById('webapp-create');
2240
+ var select = document.getElementById('webapp-select');
2241
+
2242
+ fetch(API + '/__dev/setup/web-apps')
2243
+ .then(function(r) { return r.json(); })
2244
+ .then(function(data) {
2245
+ if (data.error) {
2246
+ if (retryCount < 2) {
2247
+ setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
2248
+ return;
2249
+ }
2250
+ createDiv.style.display = 'block';
2251
+ listDiv.style.display = 'none';
2252
+ document.getElementById('webapp-create').querySelector('div').textContent = 'Error loading apps: ' + data.error;
2253
+ return;
2254
+ }
2255
+ if (!data.apps || data.apps.length === 0) {
2256
+ // No web apps \u2014 show create form
2257
+ createDiv.style.display = 'block';
2258
+ listDiv.style.display = 'none';
2259
+ return;
2260
+ }
2261
+
2262
+ // Show list
2263
+ listDiv.style.display = 'block';
2264
+ createDiv.style.display = 'none';
2265
+ select.innerHTML = '<option value="">-- Select a web app --</option>';
2266
+ data.apps.forEach(function(app) {
2267
+ var opt = document.createElement('option');
2268
+ opt.value = app.appId;
2269
+ opt.setAttribute('data-name', app.displayName || '');
2270
+ opt.textContent = (app.displayName || 'Unnamed') + ' (' + app.appId.slice(-12) + ')';
2271
+ if (app.appId === currentAppId) opt.selected = true;
2272
+ select.appendChild(opt);
2273
+ });
2274
+ select.disabled = false;
2275
+ })
2276
+ .catch(function() {
2277
+ if (retryCount < 2) {
2278
+ setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
2279
+ return;
2280
+ }
2281
+ createDiv.style.display = 'block';
2282
+ listDiv.style.display = 'none';
2283
+ });
2284
+ }
2285
+
2286
+ window.showCreateWebApp = function() {
2287
+ document.getElementById('webapp-list').style.display = 'none';
2288
+ document.getElementById('webapp-create').style.display = 'block';
2289
+ document.getElementById('webapp-name').focus();
2290
+ };
2291
+
2292
+ window.selectWebApp = function() {
2293
+ var select = document.getElementById('webapp-select');
2294
+ var status = document.getElementById('webapp-status');
2295
+ var appId = select.value;
2296
+ if (!appId) {
2297
+ status.textContent = 'Please select a web app first.';
2298
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
2299
+ return;
2300
+ }
2301
+ var selectedOption = select.options[select.selectedIndex];
2302
+ var displayName = selectedOption.getAttribute('data-name') || '';
2303
+
2304
+ var btn = document.getElementById('select-webapp-btn');
2305
+ btn.disabled = true;
2306
+ btn.textContent = 'Saving...';
2307
+
2308
+ fetch(API + '/__dev/setup/select-web-app', {
2309
+ method: 'POST',
2310
+ headers: { 'Content-Type': 'application/json' },
2311
+ body: JSON.stringify({ appId: appId, displayName: displayName })
2312
+ })
2313
+ .then(function(r) { return r.json(); })
2314
+ .then(function(data) {
2315
+ if (data.success) {
2316
+ status.textContent = data.message;
2317
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2318
+ setTimeout(refreshSetupStatus, 1000);
2319
+ } else {
2320
+ status.textContent = data.message;
2321
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2322
+ btn.disabled = false;
2323
+ btn.textContent = 'Use This App';
2324
+ }
2325
+ })
2326
+ .catch(function(err) {
2327
+ status.textContent = 'Error: ' + err.message;
2328
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2329
+ btn.disabled = false;
2330
+ btn.textContent = 'Use This App';
2331
+ });
2332
+ };
2333
+
2334
+ window.createWebApp = function() {
2335
+ var nameInput = document.getElementById('webapp-name');
2336
+ var btn = document.getElementById('create-webapp-btn');
2337
+ var status = document.getElementById('webapp-status');
2338
+ var displayName = nameInput.value.trim();
2339
+
2340
+ if (!displayName) {
2341
+ status.textContent = 'Please enter a name for the web app.';
2342
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
2343
+ return;
2344
+ }
2345
+
2346
+ btn.disabled = true;
2347
+ btn.textContent = 'Creating...';
2348
+ status.textContent = 'Creating web app... This may take a moment.';
2349
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';
2350
+
2351
+ fetch(API + '/__dev/setup/create-web-app', {
2352
+ method: 'POST',
2353
+ headers: { 'Content-Type': 'application/json' },
2354
+ body: JSON.stringify({ displayName: displayName })
2355
+ })
2356
+ .then(function(r) { return r.json(); })
2357
+ .then(function(data) {
2358
+ if (data.success) {
2359
+ status.textContent = data.message;
2360
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2361
+ setTimeout(refreshSetupStatus, 1000);
2362
+ } else {
2363
+ status.textContent = data.message;
2364
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2365
+ btn.disabled = false;
2366
+ btn.textContent = 'Create Web App';
2367
+ }
2368
+ })
2369
+ .catch(function(err) {
2370
+ status.textContent = 'Error: ' + err.message;
2371
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2372
+ btn.disabled = false;
2373
+ btn.textContent = 'Create Web App';
2374
+ });
2375
+ };
2376
+
2154
2377
  // \u2500\u2500\u2500 Refresh Setup Status \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
2155
2378
  function refreshSetupStatus() {
2156
2379
  fetch(API + '/__dev/setup/status')
@@ -2545,15 +2768,34 @@ var import_node_path4 = require("path");
2545
2768
  var import_node_os = require("os");
2546
2769
  var FirebaseSetup = class {
2547
2770
  projectDir;
2771
+ stateFilePath;
2548
2772
  constructor(projectDir) {
2549
2773
  this.projectDir = projectDir;
2774
+ this.stateFilePath = (0, import_node_path4.resolve)(projectDir, ".clawfire-setup.json");
2775
+ }
2776
+ // ─── State Persistence ────────────────────────────────────────────
2777
+ loadState() {
2778
+ try {
2779
+ if ((0, import_node_fs4.existsSync)(this.stateFilePath)) {
2780
+ return JSON.parse((0, import_node_fs4.readFileSync)(this.stateFilePath, "utf-8"));
2781
+ }
2782
+ } catch {
2783
+ }
2784
+ return {};
2785
+ }
2786
+ saveState(partial) {
2787
+ const current = this.loadState();
2788
+ const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
2789
+ (0, import_node_fs4.writeFileSync)(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2550
2790
  }
2551
2791
  // ─── Status Check ──────────────────────────────────────────────────
2552
2792
  async getStatus() {
2793
+ const savedState = this.loadState();
2553
2794
  const status = {
2554
2795
  cli: { installed: false, version: "" },
2555
2796
  auth: { authenticated: false, user: "" },
2556
2797
  project: { id: "", hasFirebaserc: false },
2798
+ webApp: { appId: savedState.webAppId || "", displayName: savedState.webAppDisplayName || "" },
2557
2799
  ready: false,
2558
2800
  nextStep: "install-cli"
2559
2801
  };
@@ -2595,6 +2837,10 @@ var FirebaseSetup = class {
2595
2837
  status.nextStep = "select-project";
2596
2838
  return status;
2597
2839
  }
2840
+ if (!status.webApp.appId) {
2841
+ status.nextStep = "create-web-app";
2842
+ return status;
2843
+ }
2598
2844
  status.ready = true;
2599
2845
  status.nextStep = "done";
2600
2846
  return status;
@@ -2748,6 +2994,12 @@ var FirebaseSetup = class {
2748
2994
  if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {
2749
2995
  return { success: false, message: "Invalid project ID format." };
2750
2996
  }
2997
+ const savedState = this.loadState();
2998
+ if (savedState.projectId && savedState.projectId !== projectId) {
2999
+ this.saveState({ projectId, webAppId: void 0, webAppDisplayName: void 0 });
3000
+ } else {
3001
+ this.saveState({ projectId });
3002
+ }
2751
3003
  try {
2752
3004
  await this.execTimeout(
2753
3005
  "firebase",
@@ -2783,6 +3035,52 @@ var FirebaseSetup = class {
2783
3035
  }
2784
3036
  }
2785
3037
  }
3038
+ // ─── Web App Management ───────────────────────────────────────────
3039
+ async listWebApps() {
3040
+ try {
3041
+ const output = await this.execTimeout(
3042
+ "firebase",
3043
+ ["apps:list", "--json"],
3044
+ 15e3
3045
+ );
3046
+ const data = JSON.parse(output);
3047
+ const allApps = data?.result || [];
3048
+ const webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
3049
+ appId: String(a.appId || ""),
3050
+ displayName: String(a.displayName || "")
3051
+ })).filter((a) => a.appId);
3052
+ return { apps: webApps };
3053
+ } catch (err) {
3054
+ const msg = err instanceof Error ? err.message : "Unknown error";
3055
+ return { apps: [], error: msg };
3056
+ }
3057
+ }
3058
+ async createWebApp(displayName) {
3059
+ if (!displayName || displayName.length < 1) {
3060
+ return { success: false, message: "Display name is required." };
3061
+ }
3062
+ try {
3063
+ const output = await this.execTimeout(
3064
+ "firebase",
3065
+ ["apps:create", "web", displayName, "--json"],
3066
+ 3e4
3067
+ );
3068
+ const data = JSON.parse(output);
3069
+ const appId = data?.result?.appId || "";
3070
+ if (appId) {
3071
+ this.saveState({ webAppId: appId, webAppDisplayName: displayName });
3072
+ return { success: true, appId, message: `Web app "${displayName}" created successfully.` };
3073
+ }
3074
+ return { success: false, message: "App created but could not retrieve app ID." };
3075
+ } catch (err) {
3076
+ const msg = err instanceof Error ? err.message : "Unknown error";
3077
+ return { success: false, message: `Failed to create web app: ${msg}` };
3078
+ }
3079
+ }
3080
+ selectWebApp(appId, displayName) {
3081
+ this.saveState({ webAppId: appId, webAppDisplayName: displayName });
3082
+ return { success: true, message: `Web app "${displayName}" selected.` };
3083
+ }
2786
3084
  // ─── Helpers ───────────────────────────────────────────────────────
2787
3085
  execTimeout(command, args, timeoutMs) {
2788
3086
  return new Promise((resolve7, reject) => {
@@ -3672,7 +3970,8 @@ ${liveReloadScript}
3672
3970
  return;
3673
3971
  }
3674
3972
  if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
3675
- fetchFirebaseSdkConfig(this.options.projectDir).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
3973
+ const savedState = this.firebaseSetup.loadState();
3974
+ fetchFirebaseSdkConfig(this.options.projectDir, savedState.webAppId).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
3676
3975
  return;
3677
3976
  }
3678
3977
  if (url.pathname === "/__dev/env" && req.method === "GET") {
@@ -3757,6 +4056,53 @@ ${liveReloadScript}
3757
4056
  });
3758
4057
  return;
3759
4058
  }
4059
+ if (url.pathname === "/__dev/setup/web-apps" && req.method === "GET") {
4060
+ this.firebaseSetup.listWebApps().then((result) => sendJson(result)).catch((err) => sendJson({ apps: [], error: err instanceof Error ? err.message : "Failed" }, 500));
4061
+ return;
4062
+ }
4063
+ if (url.pathname === "/__dev/setup/create-web-app" && req.method === "POST") {
4064
+ let body = "";
4065
+ req.on("data", (chunk) => {
4066
+ body += chunk;
4067
+ });
4068
+ req.on("end", () => {
4069
+ try {
4070
+ const data = JSON.parse(body);
4071
+ if (!data.displayName) {
4072
+ sendJson({ success: false, message: "displayName is required" }, 400);
4073
+ return;
4074
+ }
4075
+ this.firebaseSetup.createWebApp(data.displayName).then((result) => {
4076
+ clearFirebaseStatusCache();
4077
+ sendJson(result);
4078
+ }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4079
+ } catch {
4080
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
4081
+ }
4082
+ });
4083
+ return;
4084
+ }
4085
+ if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
4086
+ let body = "";
4087
+ req.on("data", (chunk) => {
4088
+ body += chunk;
4089
+ });
4090
+ req.on("end", () => {
4091
+ try {
4092
+ const data = JSON.parse(body);
4093
+ if (!data.appId) {
4094
+ sendJson({ success: false, message: "appId is required" }, 400);
4095
+ return;
4096
+ }
4097
+ const result = this.firebaseSetup.selectWebApp(data.appId, data.displayName || "");
4098
+ clearFirebaseStatusCache();
4099
+ sendJson(result);
4100
+ } catch {
4101
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
4102
+ }
4103
+ });
4104
+ return;
4105
+ }
3760
4106
  res.writeHead(404);
3761
4107
  res.end("Not found");
3762
4108
  }