clawfire 0.4.3 → 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-PAI4XU2S.js");
212
+ const { startDevServer } = await import("./dev-server-5ATZVQJT.js");
213
213
  await startDevServer({
214
214
  projectDir,
215
215
  port,
@@ -1082,39 +1082,106 @@ async function checkCli(projectDir) {
1082
1082
  }
1083
1083
  return result;
1084
1084
  }
1085
- async function fetchFirebaseSdkConfig(projectDir) {
1086
- const output = await execWithTimeout(
1087
- "firebase",
1088
- ["apps:sdkconfig", "web", "--json"],
1089
- projectDir,
1090
- 15e3
1091
- );
1092
- const data = JSON.parse(output);
1093
- if (data?.result?.sdkConfig) {
1094
- return data.result.sdkConfig;
1095
- }
1096
- if (data?.result?.fileContents) {
1097
- const contents = data.result.fileContents;
1098
- const config = {};
1099
- const extract = (key) => {
1100
- const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
1101
- return match ? match[1] : void 0;
1102
- };
1103
- config.apiKey = extract("apiKey");
1104
- config.authDomain = extract("authDomain");
1105
- config.projectId = extract("projectId");
1106
- config.storageBucket = extract("storageBucket");
1107
- config.messagingSenderId = extract("messagingSenderId");
1108
- config.appId = extract("appId");
1109
- return config;
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
+ }
1102
+ try {
1103
+ const output = await execWithTimeout(
1104
+ "firebase",
1105
+ ["apps:sdkconfig", "web", "--json"],
1106
+ projectDir,
1107
+ 15e3
1108
+ );
1109
+ const config = parseSdkConfigOutput(output);
1110
+ if (config) return config;
1111
+ } catch {
1112
+ }
1113
+ let webApps = [];
1114
+ try {
1115
+ const appsOutput = await execWithTimeout(
1116
+ "firebase",
1117
+ ["apps:list", "--json"],
1118
+ projectDir,
1119
+ 15e3
1120
+ );
1121
+ const appsData = JSON.parse(appsOutput);
1122
+ const allApps = appsData?.result || [];
1123
+ webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
1124
+ appId: String(a.appId || ""),
1125
+ displayName: String(a.displayName || "")
1126
+ })).filter((a) => a.appId);
1127
+ } catch {
1128
+ throw new Error(
1129
+ "Failed to fetch Firebase SDK config. Make sure you are logged in and have an active project selected."
1130
+ );
1131
+ }
1132
+ if (webApps.length === 0) {
1133
+ throw new Error(
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."
1135
+ );
1136
+ }
1137
+ const firstAppId = webApps[0].appId;
1138
+ try {
1139
+ const output = await execWithTimeout(
1140
+ "firebase",
1141
+ ["apps:sdkconfig", "web", firstAppId, "--json"],
1142
+ projectDir,
1143
+ 15e3
1144
+ );
1145
+ const config = parseSdkConfigOutput(output);
1146
+ if (config) return config;
1147
+ } catch (err) {
1148
+ const msg = err instanceof Error ? err.message : "Unknown error";
1149
+ throw new Error(`Failed to fetch config for web app ${firstAppId}: ${msg}`);
1110
1150
  }
1111
1151
  throw new Error("Could not parse Firebase SDK config from CLI output");
1112
1152
  }
1153
+ function parseSdkConfigOutput(output) {
1154
+ try {
1155
+ const data = JSON.parse(output);
1156
+ if (data?.result?.sdkConfig) {
1157
+ return data.result.sdkConfig;
1158
+ }
1159
+ if (data?.result?.fileContents) {
1160
+ const contents = data.result.fileContents;
1161
+ const config = {};
1162
+ const extract = (key) => {
1163
+ const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
1164
+ return match ? match[1] : void 0;
1165
+ };
1166
+ config.apiKey = extract("apiKey");
1167
+ config.authDomain = extract("authDomain");
1168
+ config.projectId = extract("projectId");
1169
+ config.storageBucket = extract("storageBucket");
1170
+ config.messagingSenderId = extract("messagingSenderId");
1171
+ config.appId = extract("appId");
1172
+ if (config.apiKey || config.projectId) return config;
1173
+ }
1174
+ } catch {
1175
+ }
1176
+ return null;
1177
+ }
1113
1178
  function execWithTimeout(command, args, cwd, timeoutMs) {
1114
1179
  return new Promise((resolve6, reject) => {
1115
- const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
1180
+ const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout, stderr) => {
1116
1181
  if (err) {
1117
- reject(err);
1182
+ const detail = stderr?.trim() || stdout?.trim() || "";
1183
+ const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
1184
+ reject(enriched);
1118
1185
  } else {
1119
1186
  resolve6(stdout);
1120
1187
  }
@@ -1148,17 +1215,20 @@ function generateDashboardHtml(options) {
1148
1215
 
1149
1216
  <!-- Step indicators -->
1150
1217
  <div id="setup-steps" style="display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;">
1151
- <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;">
1152
- 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
1153
1220
  </div>
1154
- <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;">
1155
1222
  2. Login
1156
1223
  </div>
1157
- <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;">
1158
- 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
1159
1226
  </div>
1160
- <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;">
1161
- 4. Auto-fill
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
1229
+ </div>
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
1162
1232
  </div>
1163
1233
  </div>
1164
1234
 
@@ -1233,16 +1303,54 @@ function generateDashboardHtml(options) {
1233
1303
  </div>
1234
1304
  </div>
1235
1305
 
1236
- <!-- Step 4: Done / Auto-fill -->
1306
+ <!-- Step 4: Web App -->
1237
1307
  <div id="step-4" style="display:none;">
1238
1308
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1239
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>
1240
1348
  <div>
1241
1349
  <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Auto-fill Config</div>
1242
- <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>
1243
1351
  </div>
1244
1352
  </div>
1245
- <div id="step4-action">
1353
+ <div id="step5-action">
1246
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;">
1247
1355
  Auto-fill Config Now
1248
1356
  </button>
@@ -1387,7 +1495,7 @@ function generateDashboardHtml(options) {
1387
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
1388
1496
 
1389
1497
  function setStepIndicator(activeStep) {
1390
- for (var i = 1; i <= 4; i++) {
1498
+ for (var i = 1; i <= 5; i++) {
1391
1499
  var el = document.getElementById('step-ind-' + i);
1392
1500
  if (i < activeStep) {
1393
1501
  el.style.background = '#0a1a0a';
@@ -1409,7 +1517,7 @@ function generateDashboardHtml(options) {
1409
1517
  var CIRCLE = '\\u26AA';
1410
1518
 
1411
1519
  // Hide all steps first
1412
- for (var i = 1; i <= 4; i++) {
1520
+ for (var i = 1; i <= 5; i++) {
1413
1521
  document.getElementById('step-' + i).style.display = 'none';
1414
1522
  }
1415
1523
  document.getElementById('setup-done').style.display = 'none';
@@ -1419,10 +1527,13 @@ function generateDashboardHtml(options) {
1419
1527
 
1420
1528
  if (status.nextStep === 'done') {
1421
1529
  // All done!
1422
- setStepIndicator(5);
1530
+ setStepIndicator(6);
1423
1531
  document.getElementById('setup-done').style.display = 'block';
1424
- document.getElementById('setup-done-detail').textContent =
1425
- '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;
1426
1537
  return;
1427
1538
  }
1428
1539
 
@@ -1489,11 +1600,32 @@ function generateDashboardHtml(options) {
1489
1600
  return;
1490
1601
  }
1491
1602
 
1492
- // Step 4: Auto-fill
1603
+ // Step 4: Web App
1493
1604
  var step4 = document.getElementById('step-4');
1494
1605
  step4.style.display = 'block';
1495
- document.getElementById('step4-icon').textContent = CIRCLE;
1496
- 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);
1497
1629
  }
1498
1630
 
1499
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
@@ -1688,6 +1820,148 @@ function generateDashboardHtml(options) {
1688
1820
  });
1689
1821
  };
1690
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
+
1691
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
1692
1966
  function refreshSetupStatus() {
1693
1967
  fetch(API + '/__dev/setup/status')
@@ -1863,7 +2137,12 @@ function generateDashboardHtml(options) {
1863
2137
  if (wizStatus) wizStatus.style.display = 'none';
1864
2138
 
1865
2139
  fetch(API + '/__dev/firebase-sdk-config')
1866
- .then(function(r) { return r.json(); })
2140
+ .then(function(r) {
2141
+ if (!r.ok) {
2142
+ return r.json().catch(function() { return { error: 'Server error (' + r.status + ')' }; });
2143
+ }
2144
+ return r.json();
2145
+ })
1867
2146
  .then(function(data) {
1868
2147
  if (data.error) {
1869
2148
  showAutoFillResult(false, data.error);
@@ -2077,15 +2356,34 @@ import { resolve as resolve4, join as join3 } from "path";
2077
2356
  import { tmpdir, platform } from "os";
2078
2357
  var FirebaseSetup = class {
2079
2358
  projectDir;
2359
+ stateFilePath;
2080
2360
  constructor(projectDir) {
2081
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");
2082
2378
  }
2083
2379
  // ─── Status Check ──────────────────────────────────────────────────
2084
2380
  async getStatus() {
2381
+ const savedState = this.loadState();
2085
2382
  const status = {
2086
2383
  cli: { installed: false, version: "" },
2087
2384
  auth: { authenticated: false, user: "" },
2088
2385
  project: { id: "", hasFirebaserc: false },
2386
+ webApp: { appId: savedState.webAppId || "", displayName: savedState.webAppDisplayName || "" },
2089
2387
  ready: false,
2090
2388
  nextStep: "install-cli"
2091
2389
  };
@@ -2127,6 +2425,10 @@ var FirebaseSetup = class {
2127
2425
  status.nextStep = "select-project";
2128
2426
  return status;
2129
2427
  }
2428
+ if (!status.webApp.appId) {
2429
+ status.nextStep = "create-web-app";
2430
+ return status;
2431
+ }
2130
2432
  status.ready = true;
2131
2433
  status.nextStep = "done";
2132
2434
  return status;
@@ -2280,6 +2582,12 @@ var FirebaseSetup = class {
2280
2582
  if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {
2281
2583
  return { success: false, message: "Invalid project ID format." };
2282
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
+ }
2283
2591
  try {
2284
2592
  await this.execTimeout(
2285
2593
  "firebase",
@@ -2315,6 +2623,52 @@ var FirebaseSetup = class {
2315
2623
  }
2316
2624
  }
2317
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
+ }
2318
2672
  // ─── Helpers ───────────────────────────────────────────────────────
2319
2673
  execTimeout(command, args, timeoutMs) {
2320
2674
  return new Promise((resolve6, reject) => {
@@ -3204,7 +3558,8 @@ ${liveReloadScript}
3204
3558
  return;
3205
3559
  }
3206
3560
  if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
3207
- 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));
3208
3563
  return;
3209
3564
  }
3210
3565
  if (url.pathname === "/__dev/env" && req.method === "GET") {
@@ -3289,6 +3644,53 @@ ${liveReloadScript}
3289
3644
  });
3290
3645
  return;
3291
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
+ }
3292
3694
  res.writeHead(404);
3293
3695
  res.end("Not found");
3294
3696
  }