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/dev.js CHANGED
@@ -1778,7 +1778,48 @@ function generateDashboardHtml(options) {
1778
1778
  </div>
1779
1779
  </div>
1780
1780
 
1781
- <!-- Section 3: Config Overview -->
1781
+ <!-- Section 3: Database -->
1782
+ <div style="margin-bottom:32px;">
1783
+ <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Database</h2>
1784
+ <div id="db-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
1785
+ <!-- Mode Toggle -->
1786
+ <div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
1787
+ <span style="font-size:13px;color:#a3a3a3;font-weight:600;">Mode:</span>
1788
+ <label id="db-mode-memory" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('memory')">
1789
+ <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;">
1790
+ <span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>
1791
+ </span>
1792
+ <span style="color:#e5e5e5;font-size:13px;">In-Memory</span>
1793
+ </label>
1794
+ <label id="db-mode-firestore" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('firestore')">
1795
+ <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;">
1796
+ </span>
1797
+ <span style="color:#a3a3a3;font-size:13px;">Firestore</span>
1798
+ </label>
1799
+ </div>
1800
+
1801
+ <!-- Connect to Firestore panel -->
1802
+ <div id="db-connect-panel" style="padding:16px;border-radius:8px;background:#0a0a0a;border:1px solid #2a2a2a;">
1803
+ <div style="font-weight:600;color:#e5e5e5;font-size:14px;margin-bottom:6px;">Connect to Firestore</div>
1804
+ <div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Creates database, deploys open security rules, and enables Firestore mode.</div>
1805
+ <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;">
1806
+ Connect to Firestore
1807
+ </button>
1808
+ <div style="margin-top:10px;font-size:12px;color:#eab308;">
1809
+ &#9888; Open rules (allow all) for dev only. Use /clawfire-model for production rules.
1810
+ </div>
1811
+ <div id="db-connect-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
1812
+ </div>
1813
+
1814
+ <!-- Firestore Connected banner -->
1815
+ <div id="db-connected-banner" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
1816
+ <div style="font-size:14px;color:#22c55e;font-weight:700;">Firestore Connected</div>
1817
+ <div style="font-size:12px;color:#a3a3a3;margin-top:4px;">Data persists across server restarts.</div>
1818
+ </div>
1819
+ </div>
1820
+ </div>
1821
+
1822
+ <!-- Section 4: Config Overview -->
1782
1823
  <div style="margin-bottom:32px;">
1783
1824
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
1784
1825
  <h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
@@ -1874,11 +1915,13 @@ function generateDashboardHtml(options) {
1874
1915
  fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
1875
1916
  fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
1876
1917
  fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
1918
+ fetch(API + '/__dev/db-mode').then(function(r) { return r.json(); }),
1877
1919
  ]).then(function(results) {
1878
1920
  renderFirebaseStatus(results[0]);
1879
1921
  renderConfig(results[1]);
1880
1922
  renderEnvVars(results[2]);
1881
1923
  renderSetupWizard(results[3]);
1924
+ renderDbMode(results[4].mode || 'memory');
1882
1925
  document.getElementById('dash-loading').style.display = 'none';
1883
1926
  document.getElementById('dash-loaded').style.display = 'block';
1884
1927
  }).catch(function(err) {
@@ -2663,6 +2706,100 @@ function generateDashboardHtml(options) {
2663
2706
  });
2664
2707
  };
2665
2708
 
2709
+ // \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
2710
+ function loadDbMode() {
2711
+ fetch(API + '/__dev/db-mode')
2712
+ .then(function(r) { return r.json(); })
2713
+ .then(function(data) { renderDbMode(data.mode || 'memory'); })
2714
+ .catch(function() { renderDbMode('memory'); });
2715
+ }
2716
+
2717
+ function renderDbMode(mode) {
2718
+ var radioMem = document.getElementById('db-radio-memory');
2719
+ var radioFs = document.getElementById('db-radio-firestore');
2720
+ var connectPanel = document.getElementById('db-connect-panel');
2721
+ var connectedBanner = document.getElementById('db-connected-banner');
2722
+ var memLabel = document.getElementById('db-mode-memory');
2723
+ var fsLabel = document.getElementById('db-mode-firestore');
2724
+
2725
+ if (mode === 'firestore') {
2726
+ radioMem.innerHTML = '';
2727
+ radioMem.style.borderColor = '#666';
2728
+ radioFs.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
2729
+ radioFs.style.borderColor = '#f97316';
2730
+ memLabel.querySelector('span:last-child').style.color = '#a3a3a3';
2731
+ fsLabel.querySelector('span:last-child').style.color = '#e5e5e5';
2732
+ connectPanel.style.display = 'none';
2733
+ connectedBanner.style.display = 'block';
2734
+ } else {
2735
+ radioMem.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
2736
+ radioMem.style.borderColor = '#f97316';
2737
+ radioFs.innerHTML = '';
2738
+ radioFs.style.borderColor = '#666';
2739
+ memLabel.querySelector('span:last-child').style.color = '#e5e5e5';
2740
+ fsLabel.querySelector('span:last-child').style.color = '#a3a3a3';
2741
+ connectPanel.style.display = 'block';
2742
+ connectedBanner.style.display = 'none';
2743
+ }
2744
+ }
2745
+
2746
+ window.switchDbMode = function(mode) {
2747
+ var status = document.getElementById('db-connect-status');
2748
+ status.style.display = 'none';
2749
+
2750
+ fetch(API + '/__dev/db-mode', {
2751
+ method: 'POST',
2752
+ headers: { 'Content-Type': 'application/json' },
2753
+ body: JSON.stringify({ mode: mode })
2754
+ })
2755
+ .then(function(r) { return r.json(); })
2756
+ .then(function(data) {
2757
+ if (data.success) {
2758
+ renderDbMode(data.mode);
2759
+ } else {
2760
+ status.textContent = data.message;
2761
+ 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;';
2762
+ }
2763
+ })
2764
+ .catch(function(err) {
2765
+ status.textContent = 'Error: ' + err.message;
2766
+ 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;';
2767
+ });
2768
+ };
2769
+
2770
+ window.connectFirestore = function() {
2771
+ var btn = document.getElementById('db-connect-btn');
2772
+ var status = document.getElementById('db-connect-status');
2773
+ btn.disabled = true;
2774
+ btn.textContent = 'Connecting...';
2775
+ status.textContent = 'Creating Firestore database... This may take a minute.';
2776
+ 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;';
2777
+
2778
+ fetch(API + '/__dev/setup/create-firestore', { method: 'POST' })
2779
+ .then(function(r) { return r.json(); })
2780
+ .then(function(data) {
2781
+ if (data.success) {
2782
+ status.textContent = data.message;
2783
+ 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;';
2784
+ // Switch UI to firestore mode
2785
+ setTimeout(function() { renderDbMode('firestore'); }, 1500);
2786
+ // Refresh firebase status
2787
+ fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
2788
+ } else {
2789
+ status.textContent = data.message;
2790
+ 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;';
2791
+ }
2792
+ btn.disabled = false;
2793
+ btn.textContent = 'Connect to Firestore';
2794
+ })
2795
+ .catch(function(err) {
2796
+ status.textContent = 'Error: ' + err.message;
2797
+ 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;';
2798
+ btn.disabled = false;
2799
+ btn.textContent = 'Connect to Firestore';
2800
+ });
2801
+ };
2802
+
2666
2803
  // \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
2667
2804
  function renderEnvVars(data) {
2668
2805
  envData = data.variables || [];
@@ -3239,6 +3376,51 @@ var FirebaseSetup = class {
3239
3376
  writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3240
3377
  return { success: true, message: `${service} enabled in firebase.json.` };
3241
3378
  }
3379
+ // ─── Firestore Database Automation ──────────────────────────────────
3380
+ /**
3381
+ * Create Firestore database via CLI.
3382
+ * Handles "ALREADY_EXISTS" gracefully — returns success.
3383
+ */
3384
+ async createFirestoreDatabase(location = "nam5") {
3385
+ try {
3386
+ await this.execTimeout(
3387
+ "firebase",
3388
+ ["firestore:databases:create", "(default)", "--location", location, "--json"],
3389
+ 6e4
3390
+ );
3391
+ return { success: true, message: `Firestore database created (location: ${location}).` };
3392
+ } catch (err) {
3393
+ const msg = err instanceof Error ? err.message : "Unknown error";
3394
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3395
+ return { success: true, message: "Firestore database already exists." };
3396
+ }
3397
+ return { success: false, message: `Failed to create Firestore database: ${msg}` };
3398
+ }
3399
+ }
3400
+ /**
3401
+ * Deploy open Firestore security rules for dev testing.
3402
+ * Writes `allow read, write: if true` rules and deploys them.
3403
+ */
3404
+ async deployFirestoreRules(openForDev = true) {
3405
+ const rulesPath = resolve5(this.projectDir, "firestore.rules");
3406
+ try {
3407
+ if (openForDev) {
3408
+ writeFileSync2(
3409
+ rulesPath,
3410
+ "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
3411
+ );
3412
+ }
3413
+ await this.execTimeout(
3414
+ "firebase",
3415
+ ["deploy", "--only", "firestore:rules", "--json"],
3416
+ 6e4
3417
+ );
3418
+ return { success: true, message: "Firestore rules deployed." };
3419
+ } catch (err) {
3420
+ const msg = err instanceof Error ? err.message : "Unknown error";
3421
+ return { success: false, message: `Failed to deploy Firestore rules: ${msg}` };
3422
+ }
3423
+ }
3242
3424
  // ─── Helpers ───────────────────────────────────────────────────────
3243
3425
  execTimeout(command, args, timeoutMs) {
3244
3426
  return new Promise((resolve7, reject) => {
@@ -3575,6 +3757,19 @@ var DevServer = class {
3575
3757
  }
3576
3758
  // ─── Route Loading ─────────────────────────────────────────────────
3577
3759
  async loadRoutes() {
3760
+ const setupState = this.firebaseSetup.loadState();
3761
+ if (setupState.dbMode === "firestore") {
3762
+ const config = this.readProjectConfig();
3763
+ const firebaseConfig = {};
3764
+ for (const field of config.fields) {
3765
+ if (!field.isPlaceholder) firebaseConfig[field.key] = field.value;
3766
+ }
3767
+ if (firebaseConfig.apiKey && firebaseConfig.projectId) {
3768
+ process.env.CLAWFIRE_FIREBASE_CONFIG = JSON.stringify(firebaseConfig);
3769
+ }
3770
+ } else {
3771
+ delete process.env.CLAWFIRE_FIREBASE_CONFIG;
3772
+ }
3578
3773
  this.router.destroy();
3579
3774
  this.router = createRouter({
3580
3775
  cors: ["*"],
@@ -4285,6 +4480,69 @@ ${liveReloadScript}
4285
4480
  });
4286
4481
  return;
4287
4482
  }
4483
+ if (url.pathname === "/__dev/db-mode" && req.method === "GET") {
4484
+ const state = this.firebaseSetup.loadState();
4485
+ sendJson({ mode: state.dbMode || "memory" });
4486
+ return;
4487
+ }
4488
+ if (url.pathname === "/__dev/db-mode" && req.method === "POST") {
4489
+ let body = "";
4490
+ req.on("data", (chunk) => {
4491
+ body += chunk;
4492
+ });
4493
+ req.on("end", async () => {
4494
+ try {
4495
+ const data = JSON.parse(body);
4496
+ const mode = data.mode;
4497
+ if (mode !== "memory" && mode !== "firestore") {
4498
+ sendJson({ success: false, message: "Invalid mode. Use: memory, firestore" }, 400);
4499
+ return;
4500
+ }
4501
+ this.firebaseSetup.saveState({ dbMode: mode });
4502
+ await this.loadRoutes();
4503
+ this.regeneratePlayground();
4504
+ sendJson({ success: true, mode, message: `Database mode switched to "${mode}".` });
4505
+ } catch (err) {
4506
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 400);
4507
+ }
4508
+ });
4509
+ return;
4510
+ }
4511
+ if (url.pathname === "/__dev/setup/create-firestore" && req.method === "POST") {
4512
+ (async () => {
4513
+ try {
4514
+ const createResult = await this.firebaseSetup.createFirestoreDatabase();
4515
+ if (!createResult.success) {
4516
+ sendJson(createResult);
4517
+ return;
4518
+ }
4519
+ const enableResult = this.firebaseSetup.enableService("firestore");
4520
+ if (!enableResult.success) {
4521
+ sendJson(enableResult);
4522
+ return;
4523
+ }
4524
+ const rulesResult = await this.firebaseSetup.deployFirestoreRules(true);
4525
+ if (!rulesResult.success) {
4526
+ sendJson(rulesResult);
4527
+ return;
4528
+ }
4529
+ this.firebaseSetup.saveState({ dbMode: "firestore" });
4530
+ await this.loadRoutes();
4531
+ this.regeneratePlayground();
4532
+ clearFirebaseStatusCache();
4533
+ sendJson({
4534
+ success: true,
4535
+ message: "Firestore connected! Database created, rules deployed, mode switched to Firestore."
4536
+ });
4537
+ } catch (err) {
4538
+ sendJson({
4539
+ success: false,
4540
+ message: err instanceof Error ? err.message : "Failed to setup Firestore"
4541
+ }, 500);
4542
+ }
4543
+ })();
4544
+ return;
4545
+ }
4288
4546
  if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
4289
4547
  let body = "";
4290
4548
  req.on("data", (chunk) => {