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/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-XLMLGSQP.js");
212
+ const { startDevServer } = await import("./dev-server-5ATZVQJT.js");
213
213
  await startDevServer({
214
214
  projectDir,
215
215
  port,
@@ -1082,7 +1082,23 @@ async function checkCli(projectDir) {
1082
1082
  }
1083
1083
  return result;
1084
1084
  }
1085
- async function fetchFirebaseSdkConfig(projectDir) {
1085
+ async function fetchFirebaseSdkConfig(projectDir, appId) {
1086
+ if (appId) {
1087
+ try {
1088
+ const output = await execWithTimeout(
1089
+ "firebase",
1090
+ ["apps:sdkconfig", "web", appId, "--json"],
1091
+ projectDir,
1092
+ 15e3
1093
+ );
1094
+ const config = parseSdkConfigOutput(output);
1095
+ if (config) return config;
1096
+ } catch (err) {
1097
+ const msg = err instanceof Error ? err.message : "Unknown error";
1098
+ throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);
1099
+ }
1100
+ throw new Error("Could not parse Firebase SDK config from CLI output");
1101
+ }
1086
1102
  try {
1087
1103
  const output = await execWithTimeout(
1088
1104
  "firebase",
@@ -1118,11 +1134,11 @@ async function fetchFirebaseSdkConfig(projectDir) {
1118
1134
  "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."
1119
1135
  );
1120
1136
  }
1121
- const appId = webApps[0].appId;
1137
+ const firstAppId = webApps[0].appId;
1122
1138
  try {
1123
1139
  const output = await execWithTimeout(
1124
1140
  "firebase",
1125
- ["apps:sdkconfig", "web", appId, "--json"],
1141
+ ["apps:sdkconfig", "web", firstAppId, "--json"],
1126
1142
  projectDir,
1127
1143
  15e3
1128
1144
  );
@@ -1130,7 +1146,7 @@ async function fetchFirebaseSdkConfig(projectDir) {
1130
1146
  if (config) return config;
1131
1147
  } catch (err) {
1132
1148
  const msg = err instanceof Error ? err.message : "Unknown error";
1133
- throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);
1149
+ throw new Error(`Failed to fetch config for web app ${firstAppId}: ${msg}`);
1134
1150
  }
1135
1151
  throw new Error("Could not parse Firebase SDK config from CLI output");
1136
1152
  }
@@ -1199,17 +1215,20 @@ function generateDashboardHtml(options) {
1199
1215
 
1200
1216
  <!-- Step indicators -->
1201
1217
  <div id="setup-steps" style="display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;">
1202
- <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;">
1203
- 1. CLI Install
1218
+ <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;">
1219
+ 1. CLI
1204
1220
  </div>
1205
- <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;">
1221
+ <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;">
1206
1222
  2. Login
1207
1223
  </div>
1208
- <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;">
1209
- 3. Select Project
1224
+ <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;">
1225
+ 3. Project
1226
+ </div>
1227
+ <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;">
1228
+ 4. Web App
1210
1229
  </div>
1211
- <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;">
1212
- 4. Auto-fill
1230
+ <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;">
1231
+ 5. Auto-fill
1213
1232
  </div>
1214
1233
  </div>
1215
1234
 
@@ -1284,16 +1303,54 @@ function generateDashboardHtml(options) {
1284
1303
  </div>
1285
1304
  </div>
1286
1305
 
1287
- <!-- Step 4: Done / Auto-fill -->
1306
+ <!-- Step 4: Web App -->
1288
1307
  <div id="step-4" style="display:none;">
1289
1308
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1290
1309
  <span id="step4-icon" style="font-size:20px;">&#9898;</span>
1310
+ <div>
1311
+ <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Web App</div>
1312
+ <div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Checking web apps...</div>
1313
+ </div>
1314
+ </div>
1315
+ <div id="step4-action" style="display:none;">
1316
+ <!-- Existing web apps list -->
1317
+ <div id="webapp-list" style="display:none;">
1318
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
1319
+ <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;">
1320
+ <option value="">Loading web apps...</option>
1321
+ </select>
1322
+ <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;">
1323
+ Use This App
1324
+ </button>
1325
+ <button onclick="showCreateWebApp()" style="padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;">
1326
+ + Create New
1327
+ </button>
1328
+ </div>
1329
+ </div>
1330
+ <!-- Create new web app form -->
1331
+ <div id="webapp-create" style="display:none;">
1332
+ <div style="font-size:13px;color:#a3a3a3;margin-bottom:8px;">No web app found. Create one to get Firebase SDK config:</div>
1333
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
1334
+ <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;" />
1335
+ <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;">
1336
+ Create Web App
1337
+ </button>
1338
+ </div>
1339
+ </div>
1340
+ <div id="webapp-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1341
+ </div>
1342
+ </div>
1343
+
1344
+ <!-- Step 5: Done / Auto-fill -->
1345
+ <div id="step-5" style="display:none;">
1346
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1347
+ <span id="step5-icon" style="font-size:20px;">&#9898;</span>
1291
1348
  <div>
1292
1349
  <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Auto-fill Config</div>
1293
- <div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
1350
+ <div id="step5-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
1294
1351
  </div>
1295
1352
  </div>
1296
- <div id="step4-action">
1353
+ <div id="step5-action">
1297
1354
  <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;">
1298
1355
  Auto-fill Config Now
1299
1356
  </button>
@@ -1438,7 +1495,7 @@ function generateDashboardHtml(options) {
1438
1495
  // \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
1439
1496
 
1440
1497
  function setStepIndicator(activeStep) {
1441
- for (var i = 1; i <= 4; i++) {
1498
+ for (var i = 1; i <= 5; i++) {
1442
1499
  var el = document.getElementById('step-ind-' + i);
1443
1500
  if (i < activeStep) {
1444
1501
  el.style.background = '#0a1a0a';
@@ -1460,7 +1517,7 @@ function generateDashboardHtml(options) {
1460
1517
  var CIRCLE = '\\u26AA';
1461
1518
 
1462
1519
  // Hide all steps first
1463
- for (var i = 1; i <= 4; i++) {
1520
+ for (var i = 1; i <= 5; i++) {
1464
1521
  document.getElementById('step-' + i).style.display = 'none';
1465
1522
  }
1466
1523
  document.getElementById('setup-done').style.display = 'none';
@@ -1470,10 +1527,13 @@ function generateDashboardHtml(options) {
1470
1527
 
1471
1528
  if (status.nextStep === 'done') {
1472
1529
  // All done!
1473
- setStepIndicator(5);
1530
+ setStepIndicator(6);
1474
1531
  document.getElementById('setup-done').style.display = 'block';
1475
- document.getElementById('setup-done-detail').textContent =
1476
- 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
1532
+ var detail = 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
1533
+ if (status.webApp && status.webApp.displayName) {
1534
+ detail += ' | App: ' + status.webApp.displayName;
1535
+ }
1536
+ document.getElementById('setup-done-detail').textContent = detail;
1477
1537
  return;
1478
1538
  }
1479
1539
 
@@ -1540,11 +1600,32 @@ function generateDashboardHtml(options) {
1540
1600
  return;
1541
1601
  }
1542
1602
 
1543
- // Step 4: Auto-fill
1603
+ // Step 4: Web App
1544
1604
  var step4 = document.getElementById('step-4');
1545
1605
  step4.style.display = 'block';
1546
- document.getElementById('step4-icon').textContent = CIRCLE;
1547
- setStepIndicator(4);
1606
+ if (status.webApp && status.webApp.appId) {
1607
+ document.getElementById('step4-icon').textContent = GREEN;
1608
+ document.getElementById('step4-detail').textContent = 'Web app: ' + (status.webApp.displayName || status.webApp.appId);
1609
+ document.getElementById('step4-action').style.display = 'block';
1610
+ // Show list with current selection
1611
+ loadWebAppList(status.webApp.appId);
1612
+ } else {
1613
+ document.getElementById('step4-icon').textContent = YELLOW;
1614
+ document.getElementById('step4-detail').textContent = 'No web app selected';
1615
+ document.getElementById('step4-action').style.display = 'block';
1616
+ loadWebAppList('');
1617
+ }
1618
+
1619
+ if (status.nextStep === 'create-web-app') {
1620
+ setStepIndicator(4);
1621
+ return;
1622
+ }
1623
+
1624
+ // Step 5: Auto-fill
1625
+ var step5 = document.getElementById('step-5');
1626
+ step5.style.display = 'block';
1627
+ document.getElementById('step5-icon').textContent = CIRCLE;
1628
+ setStepIndicator(5);
1548
1629
  }
1549
1630
 
1550
1631
  // \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
@@ -1739,6 +1820,148 @@ function generateDashboardHtml(options) {
1739
1820
  });
1740
1821
  };
1741
1822
 
1823
+ // \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
1824
+ function loadWebAppList(currentAppId, retryCount) {
1825
+ retryCount = retryCount || 0;
1826
+ var listDiv = document.getElementById('webapp-list');
1827
+ var createDiv = document.getElementById('webapp-create');
1828
+ var select = document.getElementById('webapp-select');
1829
+
1830
+ fetch(API + '/__dev/setup/web-apps')
1831
+ .then(function(r) { return r.json(); })
1832
+ .then(function(data) {
1833
+ if (data.error) {
1834
+ if (retryCount < 2) {
1835
+ setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
1836
+ return;
1837
+ }
1838
+ createDiv.style.display = 'block';
1839
+ listDiv.style.display = 'none';
1840
+ document.getElementById('webapp-create').querySelector('div').textContent = 'Error loading apps: ' + data.error;
1841
+ return;
1842
+ }
1843
+ if (!data.apps || data.apps.length === 0) {
1844
+ // No web apps \u2014 show create form
1845
+ createDiv.style.display = 'block';
1846
+ listDiv.style.display = 'none';
1847
+ return;
1848
+ }
1849
+
1850
+ // Show list
1851
+ listDiv.style.display = 'block';
1852
+ createDiv.style.display = 'none';
1853
+ select.innerHTML = '<option value="">-- Select a web app --</option>';
1854
+ data.apps.forEach(function(app) {
1855
+ var opt = document.createElement('option');
1856
+ opt.value = app.appId;
1857
+ opt.setAttribute('data-name', app.displayName || '');
1858
+ opt.textContent = (app.displayName || 'Unnamed') + ' (' + app.appId.slice(-12) + ')';
1859
+ if (app.appId === currentAppId) opt.selected = true;
1860
+ select.appendChild(opt);
1861
+ });
1862
+ select.disabled = false;
1863
+ })
1864
+ .catch(function() {
1865
+ if (retryCount < 2) {
1866
+ setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
1867
+ return;
1868
+ }
1869
+ createDiv.style.display = 'block';
1870
+ listDiv.style.display = 'none';
1871
+ });
1872
+ }
1873
+
1874
+ window.showCreateWebApp = function() {
1875
+ document.getElementById('webapp-list').style.display = 'none';
1876
+ document.getElementById('webapp-create').style.display = 'block';
1877
+ document.getElementById('webapp-name').focus();
1878
+ };
1879
+
1880
+ window.selectWebApp = function() {
1881
+ var select = document.getElementById('webapp-select');
1882
+ var status = document.getElementById('webapp-status');
1883
+ var appId = select.value;
1884
+ if (!appId) {
1885
+ status.textContent = 'Please select a web app first.';
1886
+ 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;';
1887
+ return;
1888
+ }
1889
+ var selectedOption = select.options[select.selectedIndex];
1890
+ var displayName = selectedOption.getAttribute('data-name') || '';
1891
+
1892
+ var btn = document.getElementById('select-webapp-btn');
1893
+ btn.disabled = true;
1894
+ btn.textContent = 'Saving...';
1895
+
1896
+ fetch(API + '/__dev/setup/select-web-app', {
1897
+ method: 'POST',
1898
+ headers: { 'Content-Type': 'application/json' },
1899
+ body: JSON.stringify({ appId: appId, displayName: displayName })
1900
+ })
1901
+ .then(function(r) { return r.json(); })
1902
+ .then(function(data) {
1903
+ if (data.success) {
1904
+ status.textContent = data.message;
1905
+ 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;';
1906
+ setTimeout(refreshSetupStatus, 1000);
1907
+ } else {
1908
+ status.textContent = data.message;
1909
+ 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;';
1910
+ btn.disabled = false;
1911
+ btn.textContent = 'Use This App';
1912
+ }
1913
+ })
1914
+ .catch(function(err) {
1915
+ status.textContent = 'Error: ' + err.message;
1916
+ 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;';
1917
+ btn.disabled = false;
1918
+ btn.textContent = 'Use This App';
1919
+ });
1920
+ };
1921
+
1922
+ window.createWebApp = function() {
1923
+ var nameInput = document.getElementById('webapp-name');
1924
+ var btn = document.getElementById('create-webapp-btn');
1925
+ var status = document.getElementById('webapp-status');
1926
+ var displayName = nameInput.value.trim();
1927
+
1928
+ if (!displayName) {
1929
+ status.textContent = 'Please enter a name for the web app.';
1930
+ 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;';
1931
+ return;
1932
+ }
1933
+
1934
+ btn.disabled = true;
1935
+ btn.textContent = 'Creating...';
1936
+ status.textContent = 'Creating web app... This may take a moment.';
1937
+ 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;';
1938
+
1939
+ fetch(API + '/__dev/setup/create-web-app', {
1940
+ method: 'POST',
1941
+ headers: { 'Content-Type': 'application/json' },
1942
+ body: JSON.stringify({ displayName: displayName })
1943
+ })
1944
+ .then(function(r) { return r.json(); })
1945
+ .then(function(data) {
1946
+ if (data.success) {
1947
+ status.textContent = data.message;
1948
+ 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;';
1949
+ setTimeout(refreshSetupStatus, 1000);
1950
+ } else {
1951
+ status.textContent = data.message;
1952
+ 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;';
1953
+ btn.disabled = false;
1954
+ btn.textContent = 'Create Web App';
1955
+ }
1956
+ })
1957
+ .catch(function(err) {
1958
+ status.textContent = 'Error: ' + err.message;
1959
+ 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;';
1960
+ btn.disabled = false;
1961
+ btn.textContent = 'Create Web App';
1962
+ });
1963
+ };
1964
+
1742
1965
  // \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
1743
1966
  function refreshSetupStatus() {
1744
1967
  fetch(API + '/__dev/setup/status')
@@ -2133,15 +2356,34 @@ import { resolve as resolve4, join as join3 } from "path";
2133
2356
  import { tmpdir, platform } from "os";
2134
2357
  var FirebaseSetup = class {
2135
2358
  projectDir;
2359
+ stateFilePath;
2136
2360
  constructor(projectDir) {
2137
2361
  this.projectDir = projectDir;
2362
+ this.stateFilePath = resolve4(projectDir, ".clawfire-setup.json");
2363
+ }
2364
+ // ─── State Persistence ────────────────────────────────────────────
2365
+ loadState() {
2366
+ try {
2367
+ if (existsSync5(this.stateFilePath)) {
2368
+ return JSON.parse(readFileSync4(this.stateFilePath, "utf-8"));
2369
+ }
2370
+ } catch {
2371
+ }
2372
+ return {};
2373
+ }
2374
+ saveState(partial) {
2375
+ const current = this.loadState();
2376
+ const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
2377
+ writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2138
2378
  }
2139
2379
  // ─── Status Check ──────────────────────────────────────────────────
2140
2380
  async getStatus() {
2381
+ const savedState = this.loadState();
2141
2382
  const status = {
2142
2383
  cli: { installed: false, version: "" },
2143
2384
  auth: { authenticated: false, user: "" },
2144
2385
  project: { id: "", hasFirebaserc: false },
2386
+ webApp: { appId: savedState.webAppId || "", displayName: savedState.webAppDisplayName || "" },
2145
2387
  ready: false,
2146
2388
  nextStep: "install-cli"
2147
2389
  };
@@ -2183,6 +2425,10 @@ var FirebaseSetup = class {
2183
2425
  status.nextStep = "select-project";
2184
2426
  return status;
2185
2427
  }
2428
+ if (!status.webApp.appId) {
2429
+ status.nextStep = "create-web-app";
2430
+ return status;
2431
+ }
2186
2432
  status.ready = true;
2187
2433
  status.nextStep = "done";
2188
2434
  return status;
@@ -2336,6 +2582,12 @@ var FirebaseSetup = class {
2336
2582
  if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {
2337
2583
  return { success: false, message: "Invalid project ID format." };
2338
2584
  }
2585
+ const savedState = this.loadState();
2586
+ if (savedState.projectId && savedState.projectId !== projectId) {
2587
+ this.saveState({ projectId, webAppId: void 0, webAppDisplayName: void 0 });
2588
+ } else {
2589
+ this.saveState({ projectId });
2590
+ }
2339
2591
  try {
2340
2592
  await this.execTimeout(
2341
2593
  "firebase",
@@ -2371,6 +2623,52 @@ var FirebaseSetup = class {
2371
2623
  }
2372
2624
  }
2373
2625
  }
2626
+ // ─── Web App Management ───────────────────────────────────────────
2627
+ async listWebApps() {
2628
+ try {
2629
+ const output = await this.execTimeout(
2630
+ "firebase",
2631
+ ["apps:list", "--json"],
2632
+ 15e3
2633
+ );
2634
+ const data = JSON.parse(output);
2635
+ const allApps = data?.result || [];
2636
+ const webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
2637
+ appId: String(a.appId || ""),
2638
+ displayName: String(a.displayName || "")
2639
+ })).filter((a) => a.appId);
2640
+ return { apps: webApps };
2641
+ } catch (err) {
2642
+ const msg = err instanceof Error ? err.message : "Unknown error";
2643
+ return { apps: [], error: msg };
2644
+ }
2645
+ }
2646
+ async createWebApp(displayName) {
2647
+ if (!displayName || displayName.length < 1) {
2648
+ return { success: false, message: "Display name is required." };
2649
+ }
2650
+ try {
2651
+ const output = await this.execTimeout(
2652
+ "firebase",
2653
+ ["apps:create", "web", displayName, "--json"],
2654
+ 3e4
2655
+ );
2656
+ const data = JSON.parse(output);
2657
+ const appId = data?.result?.appId || "";
2658
+ if (appId) {
2659
+ this.saveState({ webAppId: appId, webAppDisplayName: displayName });
2660
+ return { success: true, appId, message: `Web app "${displayName}" created successfully.` };
2661
+ }
2662
+ return { success: false, message: "App created but could not retrieve app ID." };
2663
+ } catch (err) {
2664
+ const msg = err instanceof Error ? err.message : "Unknown error";
2665
+ return { success: false, message: `Failed to create web app: ${msg}` };
2666
+ }
2667
+ }
2668
+ selectWebApp(appId, displayName) {
2669
+ this.saveState({ webAppId: appId, webAppDisplayName: displayName });
2670
+ return { success: true, message: `Web app "${displayName}" selected.` };
2671
+ }
2374
2672
  // ─── Helpers ───────────────────────────────────────────────────────
2375
2673
  execTimeout(command, args, timeoutMs) {
2376
2674
  return new Promise((resolve6, reject) => {
@@ -3260,7 +3558,8 @@ ${liveReloadScript}
3260
3558
  return;
3261
3559
  }
3262
3560
  if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
3263
- fetchFirebaseSdkConfig(this.options.projectDir).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
3561
+ const savedState = this.firebaseSetup.loadState();
3562
+ 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));
3264
3563
  return;
3265
3564
  }
3266
3565
  if (url.pathname === "/__dev/env" && req.method === "GET") {
@@ -3345,6 +3644,53 @@ ${liveReloadScript}
3345
3644
  });
3346
3645
  return;
3347
3646
  }
3647
+ if (url.pathname === "/__dev/setup/web-apps" && req.method === "GET") {
3648
+ this.firebaseSetup.listWebApps().then((result) => sendJson(result)).catch((err) => sendJson({ apps: [], error: err instanceof Error ? err.message : "Failed" }, 500));
3649
+ return;
3650
+ }
3651
+ if (url.pathname === "/__dev/setup/create-web-app" && req.method === "POST") {
3652
+ let body = "";
3653
+ req.on("data", (chunk) => {
3654
+ body += chunk;
3655
+ });
3656
+ req.on("end", () => {
3657
+ try {
3658
+ const data = JSON.parse(body);
3659
+ if (!data.displayName) {
3660
+ sendJson({ success: false, message: "displayName is required" }, 400);
3661
+ return;
3662
+ }
3663
+ this.firebaseSetup.createWebApp(data.displayName).then((result) => {
3664
+ clearFirebaseStatusCache();
3665
+ sendJson(result);
3666
+ }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
3667
+ } catch {
3668
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
3669
+ }
3670
+ });
3671
+ return;
3672
+ }
3673
+ if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
3674
+ let body = "";
3675
+ req.on("data", (chunk) => {
3676
+ body += chunk;
3677
+ });
3678
+ req.on("end", () => {
3679
+ try {
3680
+ const data = JSON.parse(body);
3681
+ if (!data.appId) {
3682
+ sendJson({ success: false, message: "appId is required" }, 400);
3683
+ return;
3684
+ }
3685
+ const result = this.firebaseSetup.selectWebApp(data.appId, data.displayName || "");
3686
+ clearFirebaseStatusCache();
3687
+ sendJson(result);
3688
+ } catch {
3689
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
3690
+ }
3691
+ });
3692
+ return;
3693
+ }
3348
3694
  res.writeHead(404);
3349
3695
  res.end("Not found");
3350
3696
  }