clawfire 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dev.cjs CHANGED
@@ -1494,6 +1494,34 @@ async function checkCli(projectDir) {
1494
1494
  }
1495
1495
  return result;
1496
1496
  }
1497
+ async function fetchFirebaseSdkConfig(projectDir) {
1498
+ const output = await execWithTimeout(
1499
+ "firebase",
1500
+ ["apps:sdkconfig", "web", "--json"],
1501
+ projectDir,
1502
+ 15e3
1503
+ );
1504
+ const data = JSON.parse(output);
1505
+ if (data?.result?.sdkConfig) {
1506
+ return data.result.sdkConfig;
1507
+ }
1508
+ if (data?.result?.fileContents) {
1509
+ const contents = data.result.fileContents;
1510
+ const config = {};
1511
+ const extract = (key) => {
1512
+ const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
1513
+ return match ? match[1] : void 0;
1514
+ };
1515
+ config.apiKey = extract("apiKey");
1516
+ config.authDomain = extract("authDomain");
1517
+ config.projectId = extract("projectId");
1518
+ config.storageBucket = extract("storageBucket");
1519
+ config.messagingSenderId = extract("messagingSenderId");
1520
+ config.appId = extract("appId");
1521
+ return config;
1522
+ }
1523
+ throw new Error("Could not parse Firebase SDK config from CLI output");
1524
+ }
1497
1525
  function execWithTimeout(command, args, cwd, timeoutMs) {
1498
1526
  return new Promise((resolve6, reject) => {
1499
1527
  const proc = (0, import_node_child_process.execFile)(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
@@ -1542,9 +1570,25 @@ function generateDashboardHtml(options) {
1542
1570
 
1543
1571
  <!-- Section 2: Config Overview -->
1544
1572
  <div style="margin-bottom:32px;">
1545
- <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Config Overview</h2>
1573
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
1574
+ <h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
1575
+ <button id="autofill-btn" onclick="autoFillConfig()" style="padding:6px 14px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Auto-fill from Firebase</button>
1576
+ </div>
1577
+ <div id="autofill-status" style="display:none;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;"></div>
1546
1578
  <div id="config-section" style="border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;">
1547
- <div id="config-content" style="padding:16px;font-family:monospace;font-size:13px;line-height:1.8;"></div>
1579
+ <table id="config-table" style="width:100%;border-collapse:collapse;font-size:13px;">
1580
+ <thead>
1581
+ <tr style="border-bottom:1px solid #2a2a2a;">
1582
+ <th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;width:180px;">Key</th>
1583
+ <th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;">Value</th>
1584
+ <th style="padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:80px;">Action</th>
1585
+ </tr>
1586
+ </thead>
1587
+ <tbody id="config-tbody"></tbody>
1588
+ </table>
1589
+ <div id="config-empty" style="display:none;padding:32px;text-align:center;color:#666;">
1590
+ No clawfire.config.ts found.
1591
+ </div>
1548
1592
  </div>
1549
1593
  </div>
1550
1594
 
@@ -1574,7 +1618,7 @@ function generateDashboardHtml(options) {
1574
1618
  </div>
1575
1619
 
1576
1620
  <!-- Env Modal -->
1577
- <div id="env-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:none;align-items:center;justify-content:center;">
1621
+ <div id="env-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;align-items:center;justify-content:center;">
1578
1622
  <div style="background:#1e1e1e;border:1px solid #2a2a2a;border-radius:12px;padding:24px;width:440px;max-width:90vw;">
1579
1623
  <h3 id="modal-title" style="font-size:16px;font-weight:700;color:#e5e5e5;margin-bottom:16px;">Add Variable</h3>
1580
1624
  <div style="margin-bottom:12px;">
@@ -1602,6 +1646,7 @@ function generateDashboardHtml(options) {
1602
1646
  (function() {
1603
1647
  var API = 'http://localhost:${apiPort}';
1604
1648
  var envData = [];
1649
+ var configData = [];
1605
1650
  var editingKey = null;
1606
1651
 
1607
1652
  // \u2500\u2500\u2500 Load Dashboard Data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1627,7 +1672,6 @@ function generateDashboardHtml(options) {
1627
1672
 
1628
1673
  // \u2500\u2500\u2500 Firebase 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\u2500\u2500\u2500\u2500
1629
1674
  function renderFirebaseStatus(data) {
1630
- // CLI Banner
1631
1675
  var dot = document.getElementById('cli-dot');
1632
1676
  var text = document.getElementById('cli-text');
1633
1677
  var proj = document.getElementById('cli-project');
@@ -1669,7 +1713,6 @@ function generateDashboardHtml(options) {
1669
1713
  grid.appendChild(card);
1670
1714
  });
1671
1715
 
1672
- // Config Warnings
1673
1716
  if (data.configWarnings && data.configWarnings.length > 0) {
1674
1717
  var warningCard = document.createElement('div');
1675
1718
  warningCard.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #eab308;background:#1a1a0a;grid-column:1/-1;';
@@ -1682,26 +1725,167 @@ function generateDashboardHtml(options) {
1682
1725
  }
1683
1726
  }
1684
1727
 
1685
- // \u2500\u2500\u2500 Config Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1728
+ // \u2500\u2500\u2500 Config Overview (editable) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1686
1729
  function renderConfig(data) {
1687
- var el = document.getElementById('config-content');
1688
- if (!data || !data.fields || data.fields.length === 0) {
1689
- el.innerHTML = '<span style="color:#666;">No clawfire.config.ts found or config is empty.</span>';
1730
+ var tbody = document.getElementById('config-tbody');
1731
+ var empty = document.getElementById('config-empty');
1732
+ var table = document.getElementById('config-table');
1733
+
1734
+ configData = (data && data.fields) ? data.fields : [];
1735
+
1736
+ if (configData.length === 0) {
1737
+ table.style.display = 'none';
1738
+ empty.style.display = 'block';
1690
1739
  return;
1691
1740
  }
1692
- var html = '';
1693
- data.fields.forEach(function(field) {
1741
+
1742
+ table.style.display = 'table';
1743
+ empty.style.display = 'none';
1744
+ tbody.innerHTML = '';
1745
+
1746
+ configData.forEach(function(field) {
1747
+ var tr = document.createElement('tr');
1748
+ tr.style.borderBottom = '1px solid #2a2a2a';
1749
+ tr.id = 'cfg-row-' + field.key;
1694
1750
  var color = field.isPlaceholder ? '#eab308' : '#a3a3a3';
1695
1751
  var badge = field.isPlaceholder ? ' <span style="background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;">PLACEHOLDER</span>' : '';
1696
- html += '<div style="padding:4px 0;display:flex;gap:8px;align-items:center;">';
1697
- html += '<span style="color:#e5e5e5;min-width:180px;">' + escHtml(field.key) + '</span>';
1698
- html += '<span style="color:' + color + ';">' + escHtml(field.value) + '</span>';
1699
- html += badge;
1700
- html += '</div>';
1752
+ tr.innerHTML =
1753
+ '<td style="padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;">' + escHtml(field.key) + '</td>' +
1754
+ '<td style="padding:10px 16px;font-family:monospace;">' +
1755
+ '<span id="cfg-val-' + field.key + '" style="color:' + color + ';">' + escHtml(field.value) + '</span>' +
1756
+ badge +
1757
+ '<input id="cfg-input-' + field.key + '" type="text" value="' + escHtml(field.value) + '" style="display:none;width:100%;padding:4px 8px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:4px;color:#e5e5e5;font-family:monospace;font-size:13px;outline:none;" />' +
1758
+ '</td>' +
1759
+ '<td style="padding:10px 16px;text-align:right;white-space:nowrap;">' +
1760
+ '<button id="cfg-edit-' + field.key + '" onclick="editConfigField(\\'' + escHtml(field.key) + '\\')" style="background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;">Edit</button>' +
1761
+ '<button id="cfg-save-' + field.key + '" onclick="saveConfigField(\\'' + escHtml(field.key) + '\\')" style="display:none;background:none;border:none;color:#22c55e;cursor:pointer;font-size:12px;padding:4px 8px;">Save</button>' +
1762
+ '<button id="cfg-cancel-' + field.key + '" onclick="cancelConfigEdit(\\'' + escHtml(field.key) + '\\')" style="display:none;background:none;border:none;color:#a3a3a3;cursor:pointer;font-size:12px;padding:4px 8px;">Cancel</button>' +
1763
+ '</td>';
1764
+ tbody.appendChild(tr);
1701
1765
  });
1702
- el.innerHTML = html;
1703
1766
  }
1704
1767
 
1768
+ window.editConfigField = function(key) {
1769
+ var valSpan = document.getElementById('cfg-val-' + key);
1770
+ var input = document.getElementById('cfg-input-' + key);
1771
+ var editBtn = document.getElementById('cfg-edit-' + key);
1772
+ var saveBtn = document.getElementById('cfg-save-' + key);
1773
+ var cancelBtn = document.getElementById('cfg-cancel-' + key);
1774
+ if (!valSpan || !input) return;
1775
+ // Hide all badges in this cell
1776
+ var badges = valSpan.parentNode.querySelectorAll('span[style*="PLACEHOLDER"]');
1777
+ for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';
1778
+ valSpan.style.display = 'none';
1779
+ input.style.display = 'block';
1780
+ input.focus();
1781
+ input.select();
1782
+ editBtn.style.display = 'none';
1783
+ saveBtn.style.display = 'inline';
1784
+ cancelBtn.style.display = 'inline';
1785
+ };
1786
+
1787
+ window.cancelConfigEdit = function(key) {
1788
+ var valSpan = document.getElementById('cfg-val-' + key);
1789
+ var input = document.getElementById('cfg-input-' + key);
1790
+ var editBtn = document.getElementById('cfg-edit-' + key);
1791
+ var saveBtn = document.getElementById('cfg-save-' + key);
1792
+ var cancelBtn = document.getElementById('cfg-cancel-' + key);
1793
+ if (!valSpan || !input) return;
1794
+ var badges = valSpan.parentNode.querySelectorAll('span[style*="border-radius"]');
1795
+ for (var i = 0; i < badges.length; i++) badges[i].style.display = '';
1796
+ valSpan.style.display = '';
1797
+ input.style.display = 'none';
1798
+ input.value = valSpan.textContent;
1799
+ editBtn.style.display = 'inline';
1800
+ saveBtn.style.display = 'none';
1801
+ cancelBtn.style.display = 'none';
1802
+ };
1803
+
1804
+ window.saveConfigField = function(key) {
1805
+ var input = document.getElementById('cfg-input-' + key);
1806
+ var saveBtn = document.getElementById('cfg-save-' + key);
1807
+ if (!input) return;
1808
+ var value = input.value;
1809
+ saveBtn.textContent = '...';
1810
+ fetch(API + '/__dev/config', {
1811
+ method: 'POST',
1812
+ headers: { 'Content-Type': 'application/json' },
1813
+ body: JSON.stringify({ action: 'set', key: key, value: value })
1814
+ }).then(function(r) { return r.json(); })
1815
+ .then(function(data) {
1816
+ if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }
1817
+ // Reload config
1818
+ return fetch(API + '/__dev/config').then(function(r) { return r.json(); });
1819
+ })
1820
+ .then(function(data) { if (data) renderConfig(data); })
1821
+ .catch(function(err) { alert('Failed: ' + err.message); saveBtn.textContent = 'Save'; });
1822
+ };
1823
+
1824
+ // \u2500\u2500\u2500 Auto-fill from Firebase \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1825
+ window.autoFillConfig = function() {
1826
+ var btn = document.getElementById('autofill-btn');
1827
+ var status = document.getElementById('autofill-status');
1828
+ btn.disabled = true;
1829
+ btn.textContent = 'Fetching...';
1830
+ status.style.display = 'none';
1831
+
1832
+ fetch(API + '/__dev/firebase-sdk-config')
1833
+ .then(function(r) { return r.json(); })
1834
+ .then(function(data) {
1835
+ if (data.error) {
1836
+ status.textContent = data.error;
1837
+ status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1838
+ btn.disabled = false;
1839
+ btn.textContent = 'Auto-fill from Firebase';
1840
+ return;
1841
+ }
1842
+
1843
+ // Map SDK config keys to clawfire.config.ts keys
1844
+ var fields = {};
1845
+ if (data.apiKey) fields.apiKey = data.apiKey;
1846
+ if (data.authDomain) fields.authDomain = data.authDomain;
1847
+ if (data.projectId) fields.projectId = data.projectId;
1848
+ if (data.storageBucket) fields.storageBucket = data.storageBucket;
1849
+ if (data.appId) fields.appId = data.appId;
1850
+
1851
+ var count = Object.keys(fields).length;
1852
+ if (count === 0) {
1853
+ status.textContent = 'No web app config found. Create a Web app in Firebase Console first.';
1854
+ status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
1855
+ btn.disabled = false;
1856
+ btn.textContent = 'Auto-fill from Firebase';
1857
+ return;
1858
+ }
1859
+
1860
+ // Save all fields at once
1861
+ return fetch(API + '/__dev/config', {
1862
+ method: 'POST',
1863
+ headers: { 'Content-Type': 'application/json' },
1864
+ body: JSON.stringify({ action: 'set-multiple', fields: fields })
1865
+ }).then(function(r) { return r.json(); })
1866
+ .then(function(result) {
1867
+ if (result.error) {
1868
+ status.textContent = 'Error saving: ' + result.error;
1869
+ status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1870
+ } else {
1871
+ status.textContent = count + ' fields updated from Firebase project!';
1872
+ status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
1873
+ // Reload config view
1874
+ return fetch(API + '/__dev/config').then(function(r) { return r.json(); })
1875
+ .then(function(cfg) { renderConfig(cfg); });
1876
+ }
1877
+ });
1878
+ })
1879
+ .catch(function(err) {
1880
+ status.textContent = 'Failed: ' + err.message;
1881
+ status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1882
+ })
1883
+ .finally(function() {
1884
+ btn.disabled = false;
1885
+ btn.textContent = 'Auto-fill from Firebase';
1886
+ });
1887
+ };
1888
+
1705
1889
  // \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
1706
1890
  function renderEnvVars(data) {
1707
1891
  envData = data.variables || [];
@@ -1740,7 +1924,7 @@ function generateDashboardHtml(options) {
1740
1924
  });
1741
1925
  }
1742
1926
 
1743
- // \u2500\u2500\u2500 Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1927
+ // \u2500\u2500\u2500 Env Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1744
1928
  window.showEnvModal = function(key) {
1745
1929
  editingKey = key || null;
1746
1930
  var modal = document.getElementById('env-modal');
@@ -2685,6 +2869,37 @@ ${liveReloadScript}
2685
2869
  sendJson(this.readProjectConfig());
2686
2870
  return;
2687
2871
  }
2872
+ if (url.pathname === "/__dev/config" && req.method === "POST") {
2873
+ let body = "";
2874
+ req.on("data", (chunk) => {
2875
+ body += chunk;
2876
+ });
2877
+ req.on("end", () => {
2878
+ try {
2879
+ const data = JSON.parse(body);
2880
+ if (data.action === "set" && data.key && data.value !== void 0) {
2881
+ this.updateProjectConfig(data.key, String(data.value));
2882
+ clearFirebaseStatusCache();
2883
+ sendJson({ ok: true });
2884
+ } else if (data.action === "set-multiple" && data.fields) {
2885
+ for (const [key, value] of Object.entries(data.fields)) {
2886
+ this.updateProjectConfig(key, String(value));
2887
+ }
2888
+ clearFirebaseStatusCache();
2889
+ sendJson({ ok: true });
2890
+ } else {
2891
+ sendJson({ error: "Invalid action" }, 400);
2892
+ }
2893
+ } catch (err) {
2894
+ sendJson({ error: err instanceof Error ? err.message : "Failed" }, 400);
2895
+ }
2896
+ });
2897
+ return;
2898
+ }
2899
+ if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
2900
+ fetchFirebaseSdkConfig(this.options.projectDir).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
2901
+ return;
2902
+ }
2688
2903
  if (url.pathname === "/__dev/env" && req.method === "GET") {
2689
2904
  try {
2690
2905
  sendJson(this.envManager.read());
@@ -2740,6 +2955,25 @@ ${liveReloadScript}
2740
2955
  }
2741
2956
  return { fields };
2742
2957
  }
2958
+ /** Update a single key's value in clawfire.config.ts */
2959
+ updateProjectConfig(key, value) {
2960
+ const configPath = (0, import_node_path4.resolve)(this.options.projectDir, "clawfire.config.ts");
2961
+ if (!(0, import_node_fs4.existsSync)(configPath)) {
2962
+ throw new Error("clawfire.config.ts not found");
2963
+ }
2964
+ let content = (0, import_node_fs4.readFileSync)(configPath, "utf-8");
2965
+ const pattern = new RegExp(
2966
+ `(${this.escapeRegex(key)}\\s*:\\s*)["'\`][^"'\`]*["'\`]`
2967
+ );
2968
+ if (!pattern.test(content)) {
2969
+ throw new Error(`Key "${key}" not found in config`);
2970
+ }
2971
+ content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
2972
+ (0, import_node_fs4.writeFileSync)(configPath, content, "utf-8");
2973
+ }
2974
+ escapeRegex(s) {
2975
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2976
+ }
2743
2977
  };
2744
2978
  async function startDevServer(options) {
2745
2979
  const server = new DevServer(options);