clawfire 0.6.1 → 0.6.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
@@ -269,7 +269,7 @@ async function runDevServer() {
269
269
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
270
270
  const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
271
271
  const noHotReload = args.includes("--no-hot-reload");
272
- const { startDevServer } = await import("./dev-server-GD445Q6F.js");
272
+ const { startDevServer } = await import("./dev-server-NWB66EJT.js");
273
273
  await startDevServer({
274
274
  projectDir,
275
275
  port,
@@ -1404,7 +1404,48 @@ function generateDashboardHtml(options) {
1404
1404
  </div>
1405
1405
  </div>
1406
1406
 
1407
- <!-- Section 3: Config Overview -->
1407
+ <!-- Section 3: Database -->
1408
+ <div style="margin-bottom:32px;">
1409
+ <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Database</h2>
1410
+ <div id="db-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
1411
+ <!-- Mode Toggle -->
1412
+ <div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
1413
+ <span style="font-size:13px;color:#a3a3a3;font-weight:600;">Mode:</span>
1414
+ <label id="db-mode-memory" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('memory')">
1415
+ <span id="db-radio-memory" style="width:16px;height:16px;border-radius:50%;border:2px solid #f97316;display:inline-flex;align-items:center;justify-content:center;">
1416
+ <span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>
1417
+ </span>
1418
+ <span style="color:#e5e5e5;font-size:13px;">In-Memory</span>
1419
+ </label>
1420
+ <label id="db-mode-firestore" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('firestore')">
1421
+ <span id="db-radio-firestore" style="width:16px;height:16px;border-radius:50%;border:2px solid #666;display:inline-flex;align-items:center;justify-content:center;">
1422
+ </span>
1423
+ <span style="color:#a3a3a3;font-size:13px;">Firestore</span>
1424
+ </label>
1425
+ </div>
1426
+
1427
+ <!-- Connect to Firestore panel -->
1428
+ <div id="db-connect-panel" style="padding:16px;border-radius:8px;background:#0a0a0a;border:1px solid #2a2a2a;">
1429
+ <div style="font-weight:600;color:#e5e5e5;font-size:14px;margin-bottom:6px;">Connect to Firestore</div>
1430
+ <div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Creates database, deploys open security rules, and enables Firestore mode.</div>
1431
+ <button id="db-connect-btn" onclick="connectFirestore()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1432
+ Connect to Firestore
1433
+ </button>
1434
+ <div style="margin-top:10px;font-size:12px;color:#eab308;">
1435
+ &#9888; Open rules (allow all) for dev only. Use /clawfire-model for production rules.
1436
+ </div>
1437
+ <div id="db-connect-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
1438
+ </div>
1439
+
1440
+ <!-- Firestore Connected banner -->
1441
+ <div id="db-connected-banner" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
1442
+ <div style="font-size:14px;color:#22c55e;font-weight:700;">Firestore Connected</div>
1443
+ <div style="font-size:12px;color:#a3a3a3;margin-top:4px;">Data persists across server restarts.</div>
1444
+ </div>
1445
+ </div>
1446
+ </div>
1447
+
1448
+ <!-- Section 4: Config Overview -->
1408
1449
  <div style="margin-bottom:32px;">
1409
1450
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
1410
1451
  <h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
@@ -1500,11 +1541,13 @@ function generateDashboardHtml(options) {
1500
1541
  fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
1501
1542
  fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
1502
1543
  fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
1544
+ fetch(API + '/__dev/db-mode').then(function(r) { return r.json(); }),
1503
1545
  ]).then(function(results) {
1504
1546
  renderFirebaseStatus(results[0]);
1505
1547
  renderConfig(results[1]);
1506
1548
  renderEnvVars(results[2]);
1507
1549
  renderSetupWizard(results[3]);
1550
+ renderDbMode(results[4].mode || 'memory');
1508
1551
  document.getElementById('dash-loading').style.display = 'none';
1509
1552
  document.getElementById('dash-loaded').style.display = 'block';
1510
1553
  }).catch(function(err) {
@@ -2289,6 +2332,100 @@ function generateDashboardHtml(options) {
2289
2332
  });
2290
2333
  };
2291
2334
 
2335
+ // \u2500\u2500\u2500 Database Mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2336
+ function loadDbMode() {
2337
+ fetch(API + '/__dev/db-mode')
2338
+ .then(function(r) { return r.json(); })
2339
+ .then(function(data) { renderDbMode(data.mode || 'memory'); })
2340
+ .catch(function() { renderDbMode('memory'); });
2341
+ }
2342
+
2343
+ function renderDbMode(mode) {
2344
+ var radioMem = document.getElementById('db-radio-memory');
2345
+ var radioFs = document.getElementById('db-radio-firestore');
2346
+ var connectPanel = document.getElementById('db-connect-panel');
2347
+ var connectedBanner = document.getElementById('db-connected-banner');
2348
+ var memLabel = document.getElementById('db-mode-memory');
2349
+ var fsLabel = document.getElementById('db-mode-firestore');
2350
+
2351
+ if (mode === 'firestore') {
2352
+ radioMem.innerHTML = '';
2353
+ radioMem.style.borderColor = '#666';
2354
+ radioFs.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
2355
+ radioFs.style.borderColor = '#f97316';
2356
+ memLabel.querySelector('span:last-child').style.color = '#a3a3a3';
2357
+ fsLabel.querySelector('span:last-child').style.color = '#e5e5e5';
2358
+ connectPanel.style.display = 'none';
2359
+ connectedBanner.style.display = 'block';
2360
+ } else {
2361
+ radioMem.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
2362
+ radioMem.style.borderColor = '#f97316';
2363
+ radioFs.innerHTML = '';
2364
+ radioFs.style.borderColor = '#666';
2365
+ memLabel.querySelector('span:last-child').style.color = '#e5e5e5';
2366
+ fsLabel.querySelector('span:last-child').style.color = '#a3a3a3';
2367
+ connectPanel.style.display = 'block';
2368
+ connectedBanner.style.display = 'none';
2369
+ }
2370
+ }
2371
+
2372
+ window.switchDbMode = function(mode) {
2373
+ var status = document.getElementById('db-connect-status');
2374
+ status.style.display = 'none';
2375
+
2376
+ fetch(API + '/__dev/db-mode', {
2377
+ method: 'POST',
2378
+ headers: { 'Content-Type': 'application/json' },
2379
+ body: JSON.stringify({ mode: mode })
2380
+ })
2381
+ .then(function(r) { return r.json(); })
2382
+ .then(function(data) {
2383
+ if (data.success) {
2384
+ renderDbMode(data.mode);
2385
+ } else {
2386
+ status.textContent = data.message;
2387
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2388
+ }
2389
+ })
2390
+ .catch(function(err) {
2391
+ status.textContent = 'Error: ' + err.message;
2392
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2393
+ });
2394
+ };
2395
+
2396
+ window.connectFirestore = function() {
2397
+ var btn = document.getElementById('db-connect-btn');
2398
+ var status = document.getElementById('db-connect-status');
2399
+ btn.disabled = true;
2400
+ btn.textContent = 'Connecting...';
2401
+ status.textContent = 'Creating Firestore database... This may take a minute.';
2402
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';
2403
+
2404
+ fetch(API + '/__dev/setup/create-firestore', { method: 'POST' })
2405
+ .then(function(r) { return r.json(); })
2406
+ .then(function(data) {
2407
+ if (data.success) {
2408
+ status.textContent = data.message;
2409
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2410
+ // Switch UI to firestore mode
2411
+ setTimeout(function() { renderDbMode('firestore'); }, 1500);
2412
+ // Refresh firebase status
2413
+ fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
2414
+ } else {
2415
+ status.textContent = data.message;
2416
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2417
+ }
2418
+ btn.disabled = false;
2419
+ btn.textContent = 'Connect to Firestore';
2420
+ })
2421
+ .catch(function(err) {
2422
+ status.textContent = 'Error: ' + err.message;
2423
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2424
+ btn.disabled = false;
2425
+ btn.textContent = 'Connect to Firestore';
2426
+ });
2427
+ };
2428
+
2292
2429
  // \u2500\u2500\u2500 Environment Variables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2293
2430
  function renderEnvVars(data) {
2294
2431
  envData = data.variables || [];
@@ -2865,6 +3002,51 @@ var FirebaseSetup = class {
2865
3002
  writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2866
3003
  return { success: true, message: `${service} enabled in firebase.json.` };
2867
3004
  }
3005
+ // ─── Firestore Database Automation ──────────────────────────────────
3006
+ /**
3007
+ * Create Firestore database via CLI.
3008
+ * Handles "ALREADY_EXISTS" gracefully — returns success.
3009
+ */
3010
+ async createFirestoreDatabase(location = "nam5") {
3011
+ try {
3012
+ await this.execTimeout(
3013
+ "firebase",
3014
+ ["firestore:databases:create", "(default)", "--location", location, "--json"],
3015
+ 6e4
3016
+ );
3017
+ return { success: true, message: `Firestore database created (location: ${location}).` };
3018
+ } catch (err) {
3019
+ const msg = err instanceof Error ? err.message : "Unknown error";
3020
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3021
+ return { success: true, message: "Firestore database already exists." };
3022
+ }
3023
+ return { success: false, message: `Failed to create Firestore database: ${msg}` };
3024
+ }
3025
+ }
3026
+ /**
3027
+ * Deploy open Firestore security rules for dev testing.
3028
+ * Writes `allow read, write: if true` rules and deploys them.
3029
+ */
3030
+ async deployFirestoreRules(openForDev = true) {
3031
+ const rulesPath = resolve4(this.projectDir, "firestore.rules");
3032
+ try {
3033
+ if (openForDev) {
3034
+ writeFileSync2(
3035
+ rulesPath,
3036
+ "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
3037
+ );
3038
+ }
3039
+ await this.execTimeout(
3040
+ "firebase",
3041
+ ["deploy", "--only", "firestore:rules", "--json"],
3042
+ 6e4
3043
+ );
3044
+ return { success: true, message: "Firestore rules deployed." };
3045
+ } catch (err) {
3046
+ const msg = err instanceof Error ? err.message : "Unknown error";
3047
+ return { success: false, message: `Failed to deploy Firestore rules: ${msg}` };
3048
+ }
3049
+ }
2868
3050
  // ─── Helpers ───────────────────────────────────────────────────────
2869
3051
  execTimeout(command, args, timeoutMs) {
2870
3052
  return new Promise((resolve6, reject) => {
@@ -3201,6 +3383,19 @@ var DevServer = class {
3201
3383
  }
3202
3384
  // ─── Route Loading ─────────────────────────────────────────────────
3203
3385
  async loadRoutes() {
3386
+ const setupState = this.firebaseSetup.loadState();
3387
+ if (setupState.dbMode === "firestore") {
3388
+ const config = this.readProjectConfig();
3389
+ const firebaseConfig = {};
3390
+ for (const field of config.fields) {
3391
+ if (!field.isPlaceholder) firebaseConfig[field.key] = field.value;
3392
+ }
3393
+ if (firebaseConfig.apiKey && firebaseConfig.projectId) {
3394
+ process.env.CLAWFIRE_FIREBASE_CONFIG = JSON.stringify(firebaseConfig);
3395
+ }
3396
+ } else {
3397
+ delete process.env.CLAWFIRE_FIREBASE_CONFIG;
3398
+ }
3204
3399
  this.router.destroy();
3205
3400
  this.router = createRouter({
3206
3401
  cors: ["*"],
@@ -3911,6 +4106,69 @@ ${liveReloadScript}
3911
4106
  });
3912
4107
  return;
3913
4108
  }
4109
+ if (url.pathname === "/__dev/db-mode" && req.method === "GET") {
4110
+ const state = this.firebaseSetup.loadState();
4111
+ sendJson({ mode: state.dbMode || "memory" });
4112
+ return;
4113
+ }
4114
+ if (url.pathname === "/__dev/db-mode" && req.method === "POST") {
4115
+ let body = "";
4116
+ req.on("data", (chunk) => {
4117
+ body += chunk;
4118
+ });
4119
+ req.on("end", async () => {
4120
+ try {
4121
+ const data = JSON.parse(body);
4122
+ const mode = data.mode;
4123
+ if (mode !== "memory" && mode !== "firestore") {
4124
+ sendJson({ success: false, message: "Invalid mode. Use: memory, firestore" }, 400);
4125
+ return;
4126
+ }
4127
+ this.firebaseSetup.saveState({ dbMode: mode });
4128
+ await this.loadRoutes();
4129
+ this.regeneratePlayground();
4130
+ sendJson({ success: true, mode, message: `Database mode switched to "${mode}".` });
4131
+ } catch (err) {
4132
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 400);
4133
+ }
4134
+ });
4135
+ return;
4136
+ }
4137
+ if (url.pathname === "/__dev/setup/create-firestore" && req.method === "POST") {
4138
+ (async () => {
4139
+ try {
4140
+ const createResult = await this.firebaseSetup.createFirestoreDatabase();
4141
+ if (!createResult.success) {
4142
+ sendJson(createResult);
4143
+ return;
4144
+ }
4145
+ const enableResult = this.firebaseSetup.enableService("firestore");
4146
+ if (!enableResult.success) {
4147
+ sendJson(enableResult);
4148
+ return;
4149
+ }
4150
+ const rulesResult = await this.firebaseSetup.deployFirestoreRules(true);
4151
+ if (!rulesResult.success) {
4152
+ sendJson(rulesResult);
4153
+ return;
4154
+ }
4155
+ this.firebaseSetup.saveState({ dbMode: "firestore" });
4156
+ await this.loadRoutes();
4157
+ this.regeneratePlayground();
4158
+ clearFirebaseStatusCache();
4159
+ sendJson({
4160
+ success: true,
4161
+ message: "Firestore connected! Database created, rules deployed, mode switched to Firestore."
4162
+ });
4163
+ } catch (err) {
4164
+ sendJson({
4165
+ success: false,
4166
+ message: err instanceof Error ? err.message : "Failed to setup Firestore"
4167
+ }, 500);
4168
+ }
4169
+ })();
4170
+ return;
4171
+ }
3914
4172
  if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
3915
4173
  let body = "";
3916
4174
  req.on("data", (chunk) => {
package/dist/dev.cjs CHANGED
@@ -1816,7 +1816,48 @@ function generateDashboardHtml(options) {
1816
1816
  </div>
1817
1817
  </div>
1818
1818
 
1819
- <!-- Section 3: Config Overview -->
1819
+ <!-- Section 3: Database -->
1820
+ <div style="margin-bottom:32px;">
1821
+ <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Database</h2>
1822
+ <div id="db-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
1823
+ <!-- Mode Toggle -->
1824
+ <div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
1825
+ <span style="font-size:13px;color:#a3a3a3;font-weight:600;">Mode:</span>
1826
+ <label id="db-mode-memory" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('memory')">
1827
+ <span id="db-radio-memory" style="width:16px;height:16px;border-radius:50%;border:2px solid #f97316;display:inline-flex;align-items:center;justify-content:center;">
1828
+ <span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>
1829
+ </span>
1830
+ <span style="color:#e5e5e5;font-size:13px;">In-Memory</span>
1831
+ </label>
1832
+ <label id="db-mode-firestore" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('firestore')">
1833
+ <span id="db-radio-firestore" style="width:16px;height:16px;border-radius:50%;border:2px solid #666;display:inline-flex;align-items:center;justify-content:center;">
1834
+ </span>
1835
+ <span style="color:#a3a3a3;font-size:13px;">Firestore</span>
1836
+ </label>
1837
+ </div>
1838
+
1839
+ <!-- Connect to Firestore panel -->
1840
+ <div id="db-connect-panel" style="padding:16px;border-radius:8px;background:#0a0a0a;border:1px solid #2a2a2a;">
1841
+ <div style="font-weight:600;color:#e5e5e5;font-size:14px;margin-bottom:6px;">Connect to Firestore</div>
1842
+ <div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Creates database, deploys open security rules, and enables Firestore mode.</div>
1843
+ <button id="db-connect-btn" onclick="connectFirestore()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1844
+ Connect to Firestore
1845
+ </button>
1846
+ <div style="margin-top:10px;font-size:12px;color:#eab308;">
1847
+ &#9888; Open rules (allow all) for dev only. Use /clawfire-model for production rules.
1848
+ </div>
1849
+ <div id="db-connect-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
1850
+ </div>
1851
+
1852
+ <!-- Firestore Connected banner -->
1853
+ <div id="db-connected-banner" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
1854
+ <div style="font-size:14px;color:#22c55e;font-weight:700;">Firestore Connected</div>
1855
+ <div style="font-size:12px;color:#a3a3a3;margin-top:4px;">Data persists across server restarts.</div>
1856
+ </div>
1857
+ </div>
1858
+ </div>
1859
+
1860
+ <!-- Section 4: Config Overview -->
1820
1861
  <div style="margin-bottom:32px;">
1821
1862
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
1822
1863
  <h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
@@ -1912,11 +1953,13 @@ function generateDashboardHtml(options) {
1912
1953
  fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
1913
1954
  fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
1914
1955
  fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
1956
+ fetch(API + '/__dev/db-mode').then(function(r) { return r.json(); }),
1915
1957
  ]).then(function(results) {
1916
1958
  renderFirebaseStatus(results[0]);
1917
1959
  renderConfig(results[1]);
1918
1960
  renderEnvVars(results[2]);
1919
1961
  renderSetupWizard(results[3]);
1962
+ renderDbMode(results[4].mode || 'memory');
1920
1963
  document.getElementById('dash-loading').style.display = 'none';
1921
1964
  document.getElementById('dash-loaded').style.display = 'block';
1922
1965
  }).catch(function(err) {
@@ -2701,6 +2744,100 @@ function generateDashboardHtml(options) {
2701
2744
  });
2702
2745
  };
2703
2746
 
2747
+ // \u2500\u2500\u2500 Database Mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2748
+ function loadDbMode() {
2749
+ fetch(API + '/__dev/db-mode')
2750
+ .then(function(r) { return r.json(); })
2751
+ .then(function(data) { renderDbMode(data.mode || 'memory'); })
2752
+ .catch(function() { renderDbMode('memory'); });
2753
+ }
2754
+
2755
+ function renderDbMode(mode) {
2756
+ var radioMem = document.getElementById('db-radio-memory');
2757
+ var radioFs = document.getElementById('db-radio-firestore');
2758
+ var connectPanel = document.getElementById('db-connect-panel');
2759
+ var connectedBanner = document.getElementById('db-connected-banner');
2760
+ var memLabel = document.getElementById('db-mode-memory');
2761
+ var fsLabel = document.getElementById('db-mode-firestore');
2762
+
2763
+ if (mode === 'firestore') {
2764
+ radioMem.innerHTML = '';
2765
+ radioMem.style.borderColor = '#666';
2766
+ radioFs.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
2767
+ radioFs.style.borderColor = '#f97316';
2768
+ memLabel.querySelector('span:last-child').style.color = '#a3a3a3';
2769
+ fsLabel.querySelector('span:last-child').style.color = '#e5e5e5';
2770
+ connectPanel.style.display = 'none';
2771
+ connectedBanner.style.display = 'block';
2772
+ } else {
2773
+ radioMem.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
2774
+ radioMem.style.borderColor = '#f97316';
2775
+ radioFs.innerHTML = '';
2776
+ radioFs.style.borderColor = '#666';
2777
+ memLabel.querySelector('span:last-child').style.color = '#e5e5e5';
2778
+ fsLabel.querySelector('span:last-child').style.color = '#a3a3a3';
2779
+ connectPanel.style.display = 'block';
2780
+ connectedBanner.style.display = 'none';
2781
+ }
2782
+ }
2783
+
2784
+ window.switchDbMode = function(mode) {
2785
+ var status = document.getElementById('db-connect-status');
2786
+ status.style.display = 'none';
2787
+
2788
+ fetch(API + '/__dev/db-mode', {
2789
+ method: 'POST',
2790
+ headers: { 'Content-Type': 'application/json' },
2791
+ body: JSON.stringify({ mode: mode })
2792
+ })
2793
+ .then(function(r) { return r.json(); })
2794
+ .then(function(data) {
2795
+ if (data.success) {
2796
+ renderDbMode(data.mode);
2797
+ } else {
2798
+ status.textContent = data.message;
2799
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2800
+ }
2801
+ })
2802
+ .catch(function(err) {
2803
+ status.textContent = 'Error: ' + err.message;
2804
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2805
+ });
2806
+ };
2807
+
2808
+ window.connectFirestore = function() {
2809
+ var btn = document.getElementById('db-connect-btn');
2810
+ var status = document.getElementById('db-connect-status');
2811
+ btn.disabled = true;
2812
+ btn.textContent = 'Connecting...';
2813
+ status.textContent = 'Creating Firestore database... This may take a minute.';
2814
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';
2815
+
2816
+ fetch(API + '/__dev/setup/create-firestore', { method: 'POST' })
2817
+ .then(function(r) { return r.json(); })
2818
+ .then(function(data) {
2819
+ if (data.success) {
2820
+ status.textContent = data.message;
2821
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2822
+ // Switch UI to firestore mode
2823
+ setTimeout(function() { renderDbMode('firestore'); }, 1500);
2824
+ // Refresh firebase status
2825
+ fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
2826
+ } else {
2827
+ status.textContent = data.message;
2828
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2829
+ }
2830
+ btn.disabled = false;
2831
+ btn.textContent = 'Connect to Firestore';
2832
+ })
2833
+ .catch(function(err) {
2834
+ status.textContent = 'Error: ' + err.message;
2835
+ status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2836
+ btn.disabled = false;
2837
+ btn.textContent = 'Connect to Firestore';
2838
+ });
2839
+ };
2840
+
2704
2841
  // \u2500\u2500\u2500 Environment Variables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2705
2842
  function renderEnvVars(data) {
2706
2843
  envData = data.variables || [];
@@ -3277,6 +3414,51 @@ var FirebaseSetup = class {
3277
3414
  (0, import_node_fs4.writeFileSync)(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3278
3415
  return { success: true, message: `${service} enabled in firebase.json.` };
3279
3416
  }
3417
+ // ─── Firestore Database Automation ──────────────────────────────────
3418
+ /**
3419
+ * Create Firestore database via CLI.
3420
+ * Handles "ALREADY_EXISTS" gracefully — returns success.
3421
+ */
3422
+ async createFirestoreDatabase(location = "nam5") {
3423
+ try {
3424
+ await this.execTimeout(
3425
+ "firebase",
3426
+ ["firestore:databases:create", "(default)", "--location", location, "--json"],
3427
+ 6e4
3428
+ );
3429
+ return { success: true, message: `Firestore database created (location: ${location}).` };
3430
+ } catch (err) {
3431
+ const msg = err instanceof Error ? err.message : "Unknown error";
3432
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3433
+ return { success: true, message: "Firestore database already exists." };
3434
+ }
3435
+ return { success: false, message: `Failed to create Firestore database: ${msg}` };
3436
+ }
3437
+ }
3438
+ /**
3439
+ * Deploy open Firestore security rules for dev testing.
3440
+ * Writes `allow read, write: if true` rules and deploys them.
3441
+ */
3442
+ async deployFirestoreRules(openForDev = true) {
3443
+ const rulesPath = (0, import_node_path4.resolve)(this.projectDir, "firestore.rules");
3444
+ try {
3445
+ if (openForDev) {
3446
+ (0, import_node_fs4.writeFileSync)(
3447
+ rulesPath,
3448
+ "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
3449
+ );
3450
+ }
3451
+ await this.execTimeout(
3452
+ "firebase",
3453
+ ["deploy", "--only", "firestore:rules", "--json"],
3454
+ 6e4
3455
+ );
3456
+ return { success: true, message: "Firestore rules deployed." };
3457
+ } catch (err) {
3458
+ const msg = err instanceof Error ? err.message : "Unknown error";
3459
+ return { success: false, message: `Failed to deploy Firestore rules: ${msg}` };
3460
+ }
3461
+ }
3280
3462
  // ─── Helpers ───────────────────────────────────────────────────────
3281
3463
  execTimeout(command, args, timeoutMs) {
3282
3464
  return new Promise((resolve7, reject) => {
@@ -3613,6 +3795,19 @@ var DevServer = class {
3613
3795
  }
3614
3796
  // ─── Route Loading ─────────────────────────────────────────────────
3615
3797
  async loadRoutes() {
3798
+ const setupState = this.firebaseSetup.loadState();
3799
+ if (setupState.dbMode === "firestore") {
3800
+ const config = this.readProjectConfig();
3801
+ const firebaseConfig = {};
3802
+ for (const field of config.fields) {
3803
+ if (!field.isPlaceholder) firebaseConfig[field.key] = field.value;
3804
+ }
3805
+ if (firebaseConfig.apiKey && firebaseConfig.projectId) {
3806
+ process.env.CLAWFIRE_FIREBASE_CONFIG = JSON.stringify(firebaseConfig);
3807
+ }
3808
+ } else {
3809
+ delete process.env.CLAWFIRE_FIREBASE_CONFIG;
3810
+ }
3616
3811
  this.router.destroy();
3617
3812
  this.router = createRouter({
3618
3813
  cors: ["*"],
@@ -4323,6 +4518,69 @@ ${liveReloadScript}
4323
4518
  });
4324
4519
  return;
4325
4520
  }
4521
+ if (url.pathname === "/__dev/db-mode" && req.method === "GET") {
4522
+ const state = this.firebaseSetup.loadState();
4523
+ sendJson({ mode: state.dbMode || "memory" });
4524
+ return;
4525
+ }
4526
+ if (url.pathname === "/__dev/db-mode" && req.method === "POST") {
4527
+ let body = "";
4528
+ req.on("data", (chunk) => {
4529
+ body += chunk;
4530
+ });
4531
+ req.on("end", async () => {
4532
+ try {
4533
+ const data = JSON.parse(body);
4534
+ const mode = data.mode;
4535
+ if (mode !== "memory" && mode !== "firestore") {
4536
+ sendJson({ success: false, message: "Invalid mode. Use: memory, firestore" }, 400);
4537
+ return;
4538
+ }
4539
+ this.firebaseSetup.saveState({ dbMode: mode });
4540
+ await this.loadRoutes();
4541
+ this.regeneratePlayground();
4542
+ sendJson({ success: true, mode, message: `Database mode switched to "${mode}".` });
4543
+ } catch (err) {
4544
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 400);
4545
+ }
4546
+ });
4547
+ return;
4548
+ }
4549
+ if (url.pathname === "/__dev/setup/create-firestore" && req.method === "POST") {
4550
+ (async () => {
4551
+ try {
4552
+ const createResult = await this.firebaseSetup.createFirestoreDatabase();
4553
+ if (!createResult.success) {
4554
+ sendJson(createResult);
4555
+ return;
4556
+ }
4557
+ const enableResult = this.firebaseSetup.enableService("firestore");
4558
+ if (!enableResult.success) {
4559
+ sendJson(enableResult);
4560
+ return;
4561
+ }
4562
+ const rulesResult = await this.firebaseSetup.deployFirestoreRules(true);
4563
+ if (!rulesResult.success) {
4564
+ sendJson(rulesResult);
4565
+ return;
4566
+ }
4567
+ this.firebaseSetup.saveState({ dbMode: "firestore" });
4568
+ await this.loadRoutes();
4569
+ this.regeneratePlayground();
4570
+ clearFirebaseStatusCache();
4571
+ sendJson({
4572
+ success: true,
4573
+ message: "Firestore connected! Database created, rules deployed, mode switched to Firestore."
4574
+ });
4575
+ } catch (err) {
4576
+ sendJson({
4577
+ success: false,
4578
+ message: err instanceof Error ? err.message : "Failed to setup Firestore"
4579
+ }, 500);
4580
+ }
4581
+ })();
4582
+ return;
4583
+ }
4326
4584
  if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
4327
4585
  let body = "";
4328
4586
  req.on("data", (chunk) => {