clawfire 0.3.2 → 0.4.1

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
@@ -38,8 +38,8 @@ module.exports = __toCommonJS(dev_exports);
38
38
 
39
39
  // src/dev/dev-server.ts
40
40
  var import_node_http = __toESM(require("http"), 1);
41
- var import_node_path4 = require("path");
42
- var import_node_fs4 = require("fs");
41
+ var import_node_path5 = require("path");
42
+ var import_node_fs5 = require("fs");
43
43
  var import_node_url = require("url");
44
44
 
45
45
  // src/core/schema.ts
@@ -1523,12 +1523,12 @@ async function fetchFirebaseSdkConfig(projectDir) {
1523
1523
  throw new Error("Could not parse Firebase SDK config from CLI output");
1524
1524
  }
1525
1525
  function execWithTimeout(command, args, cwd, timeoutMs) {
1526
- return new Promise((resolve6, reject) => {
1526
+ return new Promise((resolve7, reject) => {
1527
1527
  const proc = (0, import_node_child_process.execFile)(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
1528
1528
  if (err) {
1529
1529
  reject(err);
1530
1530
  } else {
1531
- resolve6(stdout);
1531
+ resolve7(stdout);
1532
1532
  }
1533
1533
  });
1534
1534
  const timer = setTimeout(() => {
@@ -1553,6 +1553,143 @@ function generateDashboardHtml(options) {
1553
1553
  <!-- Dashboard Sections (hidden until loaded) -->
1554
1554
  <div id="dash-loaded" style="display:none;">
1555
1555
 
1556
+ <!-- Section 0: Firebase Setup Wizard -->
1557
+ <div id="setup-wizard" style="margin-bottom:32px;">
1558
+ <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Firebase Setup</h2>
1559
+ <div id="setup-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
1560
+
1561
+ <!-- Step indicators -->
1562
+ <div id="setup-steps" style="display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;">
1563
+ <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;">
1564
+ 1. CLI Install
1565
+ </div>
1566
+ <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;">
1567
+ 2. Login
1568
+ </div>
1569
+ <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;">
1570
+ 3. Select Project
1571
+ </div>
1572
+ <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;">
1573
+ 4. Auto-fill
1574
+ </div>
1575
+ </div>
1576
+
1577
+ <!-- Step 1: CLI Install -->
1578
+ <div id="step-1" style="display:none;">
1579
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1580
+ <span id="step1-icon" style="font-size:20px;">&#9898;</span>
1581
+ <div>
1582
+ <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Firebase CLI</div>
1583
+ <div id="step1-detail" style="font-size:13px;color:#a3a3a3;">Checking...</div>
1584
+ </div>
1585
+ </div>
1586
+ <div id="step1-action" style="display:none;">
1587
+ <button id="install-cli-btn" onclick="installFirebaseCli()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1588
+ Install Firebase CLI
1589
+ </button>
1590
+ <div id="install-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1591
+ </div>
1592
+ </div>
1593
+
1594
+ <!-- Step 2: Login -->
1595
+ <div id="step-2" style="display:none;">
1596
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1597
+ <span id="step2-icon" style="font-size:20px;">&#9898;</span>
1598
+ <div>
1599
+ <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Firebase Authentication</div>
1600
+ <div id="step2-detail" style="font-size:13px;color:#a3a3a3;">Checking...</div>
1601
+ </div>
1602
+ </div>
1603
+ <div id="step2-action" style="display:none;">
1604
+ <button id="login-btn" onclick="startFirebaseLogin(false)" style="padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1605
+ Login to Firebase
1606
+ </button>
1607
+ <button id="reauth-btn" onclick="startFirebaseLogin(true)" style="display:none;padding:8px 20px;background:#eab308;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1608
+ Re-authenticate
1609
+ </button>
1610
+ <button id="cancel-login-btn" onclick="cancelFirebaseLogin()" style="display:none;padding:8px 20px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;margin-left:8px;">
1611
+ Cancel
1612
+ </button>
1613
+ </div>
1614
+ <!-- Login progress / auth URL -->
1615
+ <div id="login-progress" style="display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;">
1616
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
1617
+ <span id="login-spinner" style="display:inline-block;width:14px;height:14px;border:2px solid #3b82f6;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></span>
1618
+ <span id="login-spinner-text" style="color:#e5e5e5;font-size:13px;font-weight:600;">Starting login...</span>
1619
+ </div>
1620
+ <div id="login-url-box" style="display:none;margin-top:12px;">
1621
+ <div style="font-size:13px;color:#e5e5e5;margin-bottom:6px;font-weight:600;">Step 1: Open this link to authenticate</div>
1622
+ <a id="login-url-link" href="#" target="_blank" rel="noopener" style="display:inline-block;padding:8px 16px;background:#3b82f6;color:#fff;border-radius:6px;font-size:13px;text-decoration:none;margin-bottom:8px;">Open Google Login</a>
1623
+ <div style="font-size:11px;color:#666;margin-top:4px;word-break:break-all;font-family:monospace;" id="login-url-raw"></div>
1624
+ </div>
1625
+ <div id="auth-code-box" style="display:none;margin-top:12px;">
1626
+ <div style="font-size:13px;color:#e5e5e5;margin-bottom:6px;font-weight:600;">Step 2: Paste the authorization code</div>
1627
+ <div style="font-size:12px;color:#a3a3a3;margin-bottom:8px;">After signing in, Google will show an authorization code. Copy it and paste below.</div>
1628
+ <div style="display:flex;gap:8px;">
1629
+ <input id="auth-code-input" type="text" placeholder="Paste authorization code here"
1630
+ style="flex:1;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;" />
1631
+ <button id="auth-code-submit" onclick="submitAuthCode()" style="padding:8px 16px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;">
1632
+ Submit Code
1633
+ </button>
1634
+ </div>
1635
+ <div id="auth-code-error" style="display:none;margin-top:6px;font-size:12px;color:#ef4444;"></div>
1636
+ </div>
1637
+ <div id="login-output" style="display:none;margin-top:8px;max-height:80px;overflow-y:auto;padding:8px;background:#000;border-radius:4px;font-family:monospace;font-size:11px;color:#666;white-space:pre-wrap;"></div>
1638
+ </div>
1639
+ <div id="login-result" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1640
+ </div>
1641
+
1642
+ <!-- Step 3: Select Project -->
1643
+ <div id="step-3" style="display:none;">
1644
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1645
+ <span id="step3-icon" style="font-size:20px;">&#9898;</span>
1646
+ <div>
1647
+ <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Firebase Project</div>
1648
+ <div id="step3-detail" style="font-size:13px;color:#a3a3a3;">Checking...</div>
1649
+ </div>
1650
+ </div>
1651
+ <div id="step3-action" style="display:none;">
1652
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
1653
+ <select id="project-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;">
1654
+ <option value="">Loading projects...</option>
1655
+ </select>
1656
+ <button id="select-project-btn" onclick="selectFirebaseProject()" style="padding:8px 20px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
1657
+ Use This Project
1658
+ </button>
1659
+ <button onclick="refreshProjectList()" style="padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;" title="Refresh">
1660
+ Refresh
1661
+ </button>
1662
+ </div>
1663
+ <div id="project-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1664
+ </div>
1665
+ </div>
1666
+
1667
+ <!-- Step 4: Done / Auto-fill -->
1668
+ <div id="step-4" style="display:none;">
1669
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
1670
+ <span id="step4-icon" style="font-size:20px;">&#9898;</span>
1671
+ <div>
1672
+ <div style="font-weight:600;color:#e5e5e5;font-size:15px;">Auto-fill Config</div>
1673
+ <div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
1674
+ </div>
1675
+ </div>
1676
+ <div id="step4-action">
1677
+ <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;">
1678
+ Auto-fill Config Now
1679
+ </button>
1680
+ <div id="autofill-wizard-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
1681
+ </div>
1682
+ </div>
1683
+
1684
+ <!-- All done banner -->
1685
+ <div id="setup-done" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
1686
+ <div style="font-size:16px;color:#22c55e;font-weight:700;margin-bottom:4px;">Setup Complete</div>
1687
+ <div id="setup-done-detail" style="font-size:13px;color:#a3a3a3;"></div>
1688
+ <button onclick="startFirebaseLogin(true)" style="margin-top:8px;padding:6px 14px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:12px;cursor:pointer;">Re-authenticate</button>
1689
+ </div>
1690
+ </div>
1691
+ </div>
1692
+
1556
1693
  <!-- Section 1: Firebase Status -->
1557
1694
  <div style="margin-bottom:32px;">
1558
1695
  <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Firebase Status</h2>
@@ -1642,12 +1779,18 @@ function generateDashboardHtml(options) {
1642
1779
  </div>
1643
1780
  </div>
1644
1781
 
1782
+ <style>
1783
+ @keyframes spin { to { transform: rotate(360deg); } }
1784
+ #login-spinner { animation: spin 1s linear infinite; }
1785
+ </style>
1786
+
1645
1787
  <script>
1646
1788
  (function() {
1647
1789
  var API = 'http://localhost:${apiPort}';
1648
1790
  var envData = [];
1649
1791
  var configData = [];
1650
1792
  var editingKey = null;
1793
+ var loginPollTimer = null;
1651
1794
 
1652
1795
  // \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
1653
1796
  window._loadDashboard = function() {
@@ -1657,10 +1800,12 @@ function generateDashboardHtml(options) {
1657
1800
  fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }),
1658
1801
  fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
1659
1802
  fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
1803
+ fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
1660
1804
  ]).then(function(results) {
1661
1805
  renderFirebaseStatus(results[0]);
1662
1806
  renderConfig(results[1]);
1663
1807
  renderEnvVars(results[2]);
1808
+ renderSetupWizard(results[3]);
1664
1809
  document.getElementById('dash-loading').style.display = 'none';
1665
1810
  document.getElementById('dash-loaded').style.display = 'block';
1666
1811
  }).catch(function(err) {
@@ -1670,6 +1815,388 @@ function generateDashboardHtml(options) {
1670
1815
  });
1671
1816
  };
1672
1817
 
1818
+ // \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
1819
+
1820
+ function setStepIndicator(activeStep) {
1821
+ for (var i = 1; i <= 4; i++) {
1822
+ var el = document.getElementById('step-ind-' + i);
1823
+ if (i < activeStep) {
1824
+ el.style.background = '#0a1a0a';
1825
+ el.style.color = '#22c55e';
1826
+ } else if (i === activeStep) {
1827
+ el.style.background = '#1a1a2e';
1828
+ el.style.color = '#f97316';
1829
+ } else {
1830
+ el.style.background = '#0a0a0a';
1831
+ el.style.color = '#666';
1832
+ }
1833
+ }
1834
+ }
1835
+
1836
+ function renderSetupWizard(status) {
1837
+ var GREEN = '\\u2705';
1838
+ var YELLOW = '\\u26A0\\uFE0F';
1839
+ var RED = '\\u274C';
1840
+ var CIRCLE = '\\u26AA';
1841
+
1842
+ // Hide all steps first
1843
+ for (var i = 1; i <= 4; i++) {
1844
+ document.getElementById('step-' + i).style.display = 'none';
1845
+ }
1846
+ document.getElementById('setup-done').style.display = 'none';
1847
+
1848
+ if (status.nextStep === 'done') {
1849
+ // All done!
1850
+ setStepIndicator(5);
1851
+ document.getElementById('setup-done').style.display = 'block';
1852
+ document.getElementById('setup-done-detail').textContent =
1853
+ 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
1854
+ return;
1855
+ }
1856
+
1857
+ // Step 1: CLI
1858
+ var step1 = document.getElementById('step-1');
1859
+ step1.style.display = 'block';
1860
+ if (status.cli.installed) {
1861
+ document.getElementById('step1-icon').textContent = GREEN;
1862
+ document.getElementById('step1-detail').textContent = 'Firebase CLI v' + status.cli.version + ' installed';
1863
+ document.getElementById('step1-action').style.display = 'none';
1864
+ } else {
1865
+ document.getElementById('step1-icon').textContent = RED;
1866
+ document.getElementById('step1-detail').textContent = 'Firebase CLI is not installed';
1867
+ document.getElementById('step1-action').style.display = 'block';
1868
+ }
1869
+
1870
+ if (status.nextStep === 'install-cli') {
1871
+ setStepIndicator(1);
1872
+ return;
1873
+ }
1874
+
1875
+ // Step 2: Auth
1876
+ var step2 = document.getElementById('step-2');
1877
+ step2.style.display = 'block';
1878
+ if (status.auth.authenticated) {
1879
+ document.getElementById('step2-icon').textContent = GREEN;
1880
+ document.getElementById('step2-detail').textContent = 'Logged in as ' + status.auth.user;
1881
+ document.getElementById('step2-action').style.display = 'block';
1882
+ document.getElementById('login-btn').style.display = 'none';
1883
+ document.getElementById('reauth-btn').style.display = 'inline-block';
1884
+ document.getElementById('cancel-login-btn').style.display = 'none';
1885
+ } else {
1886
+ document.getElementById('step2-icon').textContent = RED;
1887
+ document.getElementById('step2-detail').textContent = 'Not logged in to Firebase';
1888
+ document.getElementById('step2-action').style.display = 'block';
1889
+ document.getElementById('login-btn').style.display = 'inline-block';
1890
+ document.getElementById('reauth-btn').style.display = 'none';
1891
+ document.getElementById('cancel-login-btn').style.display = 'none';
1892
+ }
1893
+
1894
+ if (status.nextStep === 'login') {
1895
+ setStepIndicator(2);
1896
+ return;
1897
+ }
1898
+
1899
+ // Step 3: Project Selection
1900
+ var step3 = document.getElementById('step-3');
1901
+ step3.style.display = 'block';
1902
+ if (status.project.id) {
1903
+ document.getElementById('step3-icon').textContent = GREEN;
1904
+ document.getElementById('step3-detail').textContent = 'Active project: ' + status.project.id;
1905
+ document.getElementById('step3-action').style.display = 'block';
1906
+ } else {
1907
+ document.getElementById('step3-icon').textContent = YELLOW;
1908
+ document.getElementById('step3-detail').textContent = 'No project selected';
1909
+ document.getElementById('step3-action').style.display = 'block';
1910
+ }
1911
+
1912
+ // Load project list
1913
+ loadProjectList(status.project.id);
1914
+
1915
+ if (status.nextStep === 'select-project') {
1916
+ setStepIndicator(3);
1917
+ return;
1918
+ }
1919
+
1920
+ // Step 4: Auto-fill
1921
+ var step4 = document.getElementById('step-4');
1922
+ step4.style.display = 'block';
1923
+ document.getElementById('step4-icon').textContent = CIRCLE;
1924
+ setStepIndicator(4);
1925
+ }
1926
+
1927
+ // \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
1928
+ window.installFirebaseCli = function() {
1929
+ var btn = document.getElementById('install-cli-btn');
1930
+ var status = document.getElementById('install-status');
1931
+ btn.disabled = true;
1932
+ btn.textContent = 'Installing...';
1933
+ status.style.display = 'block';
1934
+ status.textContent = 'Running npm install -g firebase-tools... This may take a minute.';
1935
+ 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;';
1936
+
1937
+ fetch(API + '/__dev/setup/install-cli', { method: 'POST' })
1938
+ .then(function(r) { return r.json(); })
1939
+ .then(function(data) {
1940
+ if (data.success) {
1941
+ status.textContent = data.message;
1942
+ 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;';
1943
+ // Refresh wizard
1944
+ setTimeout(refreshSetupStatus, 1000);
1945
+ } else {
1946
+ status.textContent = data.message;
1947
+ 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;';
1948
+ btn.disabled = false;
1949
+ btn.textContent = 'Install Firebase CLI';
1950
+ }
1951
+ })
1952
+ .catch(function(err) {
1953
+ status.textContent = 'Error: ' + err.message;
1954
+ 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;';
1955
+ btn.disabled = false;
1956
+ btn.textContent = 'Install Firebase CLI';
1957
+ });
1958
+ };
1959
+
1960
+ // \u2500\u2500\u2500 Step 2: Login \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1961
+ window.startFirebaseLogin = function(reauth) {
1962
+ var loginBtn = document.getElementById('login-btn');
1963
+ var reauthBtn = document.getElementById('reauth-btn');
1964
+ var cancelBtn = document.getElementById('cancel-login-btn');
1965
+ var progress = document.getElementById('login-progress');
1966
+ var result = document.getElementById('login-result');
1967
+
1968
+ loginBtn.style.display = 'none';
1969
+ reauthBtn.style.display = 'none';
1970
+ cancelBtn.style.display = 'inline-block';
1971
+ progress.style.display = 'block';
1972
+ result.style.display = 'none';
1973
+ document.getElementById('login-url-box').style.display = 'none';
1974
+ document.getElementById('auth-code-box').style.display = 'none';
1975
+ document.getElementById('login-spinner-text').textContent = 'Starting login...';
1976
+
1977
+ fetch(API + '/__dev/setup/login', {
1978
+ method: 'POST',
1979
+ headers: { 'Content-Type': 'application/json' },
1980
+ body: JSON.stringify({ reauth: !!reauth })
1981
+ })
1982
+ .then(function(r) { return r.json(); })
1983
+ .then(function() {
1984
+ // Start polling login state
1985
+ startLoginPoll();
1986
+ })
1987
+ .catch(function(err) {
1988
+ progress.style.display = 'none';
1989
+ result.textContent = 'Failed to start login: ' + err.message;
1990
+ result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
1991
+ loginBtn.style.display = 'inline-block';
1992
+ cancelBtn.style.display = 'none';
1993
+ });
1994
+ };
1995
+
1996
+ function startLoginPoll() {
1997
+ if (loginPollTimer) clearInterval(loginPollTimer);
1998
+ loginPollTimer = setInterval(function() {
1999
+ fetch(API + '/__dev/setup/login-state')
2000
+ .then(function(r) { return r.json(); })
2001
+ .then(function(state) {
2002
+ // Show auth URL if available
2003
+ if (state.authUrl) {
2004
+ var urlBox = document.getElementById('login-url-box');
2005
+ var urlLink = document.getElementById('login-url-link');
2006
+ var urlRaw = document.getElementById('login-url-raw');
2007
+ urlBox.style.display = 'block';
2008
+ urlLink.href = state.authUrl;
2009
+ urlLink.textContent = 'Open Google Login';
2010
+ if (urlRaw) urlRaw.textContent = state.authUrl;
2011
+ document.getElementById('login-spinner-text').textContent = 'Waiting for authentication...';
2012
+ }
2013
+
2014
+ // Show auth code input when waiting for code
2015
+ if (state.waitingForCode) {
2016
+ document.getElementById('auth-code-box').style.display = 'block';
2017
+ }
2018
+
2019
+ // Show output log
2020
+ if (state.output) {
2021
+ var outputEl = document.getElementById('login-output');
2022
+ outputEl.style.display = 'block';
2023
+ outputEl.textContent = state.output.slice(-500);
2024
+ }
2025
+
2026
+ // Check if done
2027
+ if (state.completed) {
2028
+ clearInterval(loginPollTimer);
2029
+ loginPollTimer = null;
2030
+ document.getElementById('login-progress').style.display = 'none';
2031
+ document.getElementById('cancel-login-btn').style.display = 'none';
2032
+ var result = document.getElementById('login-result');
2033
+ result.textContent = 'Login successful!';
2034
+ result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2035
+ // Refresh wizard after short delay
2036
+ setTimeout(refreshSetupStatus, 1500);
2037
+ }
2038
+
2039
+ if (state.error) {
2040
+ clearInterval(loginPollTimer);
2041
+ loginPollTimer = null;
2042
+ document.getElementById('login-progress').style.display = 'none';
2043
+ document.getElementById('cancel-login-btn').style.display = 'none';
2044
+ var result2 = document.getElementById('login-result');
2045
+ result2.textContent = state.error;
2046
+ result2.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2047
+ document.getElementById('login-btn').style.display = 'inline-block';
2048
+ }
2049
+
2050
+ if (!state.active && !state.completed && !state.error) {
2051
+ clearInterval(loginPollTimer);
2052
+ loginPollTimer = null;
2053
+ }
2054
+ })
2055
+ .catch(function() {});
2056
+ }, 1500);
2057
+ }
2058
+
2059
+ window.submitAuthCode = function() {
2060
+ var input = document.getElementById('auth-code-input');
2061
+ var errEl = document.getElementById('auth-code-error');
2062
+ var btn = document.getElementById('auth-code-submit');
2063
+ var code = input ? input.value.trim() : '';
2064
+ if (!code) {
2065
+ errEl.textContent = 'Please paste the authorization code.';
2066
+ errEl.style.display = 'block';
2067
+ return;
2068
+ }
2069
+ errEl.style.display = 'none';
2070
+ btn.disabled = true;
2071
+ btn.textContent = 'Submitting...';
2072
+
2073
+ fetch(API + '/__dev/setup/submit-auth-code', {
2074
+ method: 'POST',
2075
+ headers: { 'Content-Type': 'application/json' },
2076
+ body: JSON.stringify({ code: code })
2077
+ })
2078
+ .then(function(r) { return r.json(); })
2079
+ .then(function(data) {
2080
+ if (data.success) {
2081
+ document.getElementById('login-spinner-text').textContent = 'Verifying authorization...';
2082
+ document.getElementById('auth-code-box').style.display = 'none';
2083
+ } else {
2084
+ errEl.textContent = data.message || 'Failed to submit code.';
2085
+ errEl.style.display = 'block';
2086
+ }
2087
+ btn.disabled = false;
2088
+ btn.textContent = 'Submit Code';
2089
+ })
2090
+ .catch(function(err) {
2091
+ errEl.textContent = 'Error: ' + err.message;
2092
+ errEl.style.display = 'block';
2093
+ btn.disabled = false;
2094
+ btn.textContent = 'Submit Code';
2095
+ });
2096
+ };
2097
+
2098
+ window.cancelFirebaseLogin = function() {
2099
+ if (loginPollTimer) { clearInterval(loginPollTimer); loginPollTimer = null; }
2100
+ fetch(API + '/__dev/setup/cancel-login', { method: 'POST' }).catch(function() {});
2101
+ document.getElementById('login-progress').style.display = 'none';
2102
+ document.getElementById('cancel-login-btn').style.display = 'none';
2103
+ document.getElementById('login-btn').style.display = 'inline-block';
2104
+ };
2105
+
2106
+ // \u2500\u2500\u2500 Step 3: Project Selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2107
+ function loadProjectList(currentProjectId) {
2108
+ var select = document.getElementById('project-select');
2109
+ select.innerHTML = '<option value="">Loading projects...</option>';
2110
+ select.disabled = true;
2111
+
2112
+ fetch(API + '/__dev/setup/projects')
2113
+ .then(function(r) { return r.json(); })
2114
+ .then(function(data) {
2115
+ select.innerHTML = '';
2116
+ if (data.error) {
2117
+ select.innerHTML = '<option value="">Error: ' + escHtml(data.error) + '</option>';
2118
+ return;
2119
+ }
2120
+ if (!data.projects || data.projects.length === 0) {
2121
+ select.innerHTML = '<option value="">No projects found</option>';
2122
+ return;
2123
+ }
2124
+
2125
+ select.innerHTML = '<option value="">-- Select a project --</option>';
2126
+ data.projects.forEach(function(p) {
2127
+ var opt = document.createElement('option');
2128
+ opt.value = p.projectId;
2129
+ opt.textContent = p.displayName + ' (' + p.projectId + ')';
2130
+ if (p.projectId === currentProjectId) opt.selected = true;
2131
+ select.appendChild(opt);
2132
+ });
2133
+ select.disabled = false;
2134
+ })
2135
+ .catch(function(err) {
2136
+ select.innerHTML = '<option value="">Failed to load</option>';
2137
+ });
2138
+ }
2139
+
2140
+ window.refreshProjectList = function() {
2141
+ loadProjectList('');
2142
+ };
2143
+
2144
+ window.selectFirebaseProject = function() {
2145
+ var select = document.getElementById('project-select');
2146
+ var btn = document.getElementById('select-project-btn');
2147
+ var status = document.getElementById('project-status');
2148
+ var projectId = select.value;
2149
+
2150
+ if (!projectId) {
2151
+ status.textContent = 'Please select a project first.';
2152
+ 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;';
2153
+ return;
2154
+ }
2155
+
2156
+ btn.disabled = true;
2157
+ btn.textContent = 'Setting...';
2158
+ status.style.display = 'none';
2159
+
2160
+ fetch(API + '/__dev/setup/select-project', {
2161
+ method: 'POST',
2162
+ headers: { 'Content-Type': 'application/json' },
2163
+ body: JSON.stringify({ projectId: projectId })
2164
+ })
2165
+ .then(function(r) { return r.json(); })
2166
+ .then(function(data) {
2167
+ if (data.success) {
2168
+ status.textContent = data.message;
2169
+ 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;';
2170
+ setTimeout(refreshSetupStatus, 1000);
2171
+ } else {
2172
+ status.textContent = data.message;
2173
+ 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;';
2174
+ btn.disabled = false;
2175
+ btn.textContent = 'Use This Project';
2176
+ }
2177
+ })
2178
+ .catch(function(err) {
2179
+ status.textContent = 'Error: ' + err.message;
2180
+ 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;';
2181
+ btn.disabled = false;
2182
+ btn.textContent = 'Use This Project';
2183
+ });
2184
+ };
2185
+
2186
+ // \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
2187
+ function refreshSetupStatus() {
2188
+ fetch(API + '/__dev/setup/status')
2189
+ .then(function(r) { return r.json(); })
2190
+ .then(function(status) { renderSetupWizard(status); })
2191
+ .catch(function() {});
2192
+
2193
+ // Also refresh firebase status section
2194
+ fetch(API + '/__dev/firebase-status')
2195
+ .then(function(r) { return r.json(); })
2196
+ .then(function(data) { renderFirebaseStatus(data); })
2197
+ .catch(function() {});
2198
+ }
2199
+
1673
2200
  // \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
1674
2201
  function renderFirebaseStatus(data) {
1675
2202
  var dot = document.getElementById('cli-dot');
@@ -1681,10 +2208,10 @@ function generateDashboardHtml(options) {
1681
2208
  text.textContent = 'Firebase CLI not installed';
1682
2209
  } else if (!data.cli.authenticated) {
1683
2210
  dot.style.background = '#eab308';
1684
- text.textContent = 'Firebase CLI v' + data.cli.version + ' \u2014 Not logged in';
2211
+ text.textContent = 'Firebase CLI v' + data.cli.version + ' \\u2014 Not logged in';
1685
2212
  } else {
1686
2213
  dot.style.background = '#22c55e';
1687
- text.textContent = 'Firebase CLI v' + data.cli.version + ' \u2014 ' + data.cli.user;
2214
+ text.textContent = 'Firebase CLI v' + data.cli.version + ' \\u2014 ' + data.cli.user;
1688
2215
  }
1689
2216
 
1690
2217
  if (data.project.id) {
@@ -1772,7 +2299,6 @@ function generateDashboardHtml(options) {
1772
2299
  var saveBtn = document.getElementById('cfg-save-' + key);
1773
2300
  var cancelBtn = document.getElementById('cfg-cancel-' + key);
1774
2301
  if (!valSpan || !input) return;
1775
- // Hide all badges in this cell
1776
2302
  var badges = valSpan.parentNode.querySelectorAll('span[style*="PLACEHOLDER"]');
1777
2303
  for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';
1778
2304
  valSpan.style.display = 'none';
@@ -1814,7 +2340,6 @@ function generateDashboardHtml(options) {
1814
2340
  }).then(function(r) { return r.json(); })
1815
2341
  .then(function(data) {
1816
2342
  if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }
1817
- // Reload config
1818
2343
  return fetch(API + '/__dev/config').then(function(r) { return r.json(); });
1819
2344
  })
1820
2345
  .then(function(data) { if (data) renderConfig(data); })
@@ -1824,23 +2349,22 @@ function generateDashboardHtml(options) {
1824
2349
  // \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
2350
  window.autoFillConfig = function() {
1826
2351
  var btn = document.getElementById('autofill-btn');
2352
+ var wizBtn = document.getElementById('autofill-wizard-btn');
1827
2353
  var status = document.getElementById('autofill-status');
1828
- btn.disabled = true;
1829
- btn.textContent = 'Fetching...';
1830
- status.style.display = 'none';
2354
+ var wizStatus = document.getElementById('autofill-wizard-status');
2355
+ if (btn) { btn.disabled = true; btn.textContent = 'Fetching...'; }
2356
+ if (wizBtn) { wizBtn.disabled = true; wizBtn.textContent = 'Fetching...'; }
2357
+ if (status) status.style.display = 'none';
2358
+ if (wizStatus) wizStatus.style.display = 'none';
1831
2359
 
1832
2360
  fetch(API + '/__dev/firebase-sdk-config')
1833
2361
  .then(function(r) { return r.json(); })
1834
2362
  .then(function(data) {
1835
2363
  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';
2364
+ showAutoFillResult(false, data.error);
1840
2365
  return;
1841
2366
  }
1842
2367
 
1843
- // Map SDK config keys to clawfire.config.ts keys
1844
2368
  var fields = {};
1845
2369
  if (data.apiKey) fields.apiKey = data.apiKey;
1846
2370
  if (data.authDomain) fields.authDomain = data.authDomain;
@@ -1850,14 +2374,10 @@ function generateDashboardHtml(options) {
1850
2374
 
1851
2375
  var count = Object.keys(fields).length;
1852
2376
  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';
2377
+ showAutoFillResult(false, 'No web app config found. Create a Web app in Firebase Console first.');
1857
2378
  return;
1858
2379
  }
1859
2380
 
1860
- // Save all fields at once
1861
2381
  return fetch(API + '/__dev/config', {
1862
2382
  method: 'POST',
1863
2383
  headers: { 'Content-Type': 'application/json' },
@@ -1865,27 +2385,37 @@ function generateDashboardHtml(options) {
1865
2385
  }).then(function(r) { return r.json(); })
1866
2386
  .then(function(result) {
1867
2387
  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;';
2388
+ showAutoFillResult(false, 'Error saving: ' + result.error);
1870
2389
  } 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
2390
+ showAutoFillResult(true, count + ' fields updated from Firebase project!');
1874
2391
  return fetch(API + '/__dev/config').then(function(r) { return r.json(); })
1875
2392
  .then(function(cfg) { renderConfig(cfg); });
1876
2393
  }
1877
2394
  });
1878
2395
  })
1879
2396
  .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;';
2397
+ showAutoFillResult(false, 'Failed: ' + err.message);
1882
2398
  })
1883
2399
  .finally(function() {
1884
- btn.disabled = false;
1885
- btn.textContent = 'Auto-fill from Firebase';
2400
+ if (btn) { btn.disabled = false; btn.textContent = 'Auto-fill from Firebase'; }
2401
+ if (wizBtn) { wizBtn.disabled = false; wizBtn.textContent = 'Auto-fill Config Now'; }
1886
2402
  });
1887
2403
  };
1888
2404
 
2405
+ function showAutoFillResult(success, message) {
2406
+ var targets = ['autofill-status', 'autofill-wizard-status'];
2407
+ targets.forEach(function(id) {
2408
+ var el = document.getElementById(id);
2409
+ if (!el) return;
2410
+ el.textContent = message;
2411
+ if (success) {
2412
+ el.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2413
+ } else {
2414
+ el.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
2415
+ }
2416
+ });
2417
+ }
2418
+
1889
2419
  // \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
1890
2420
  function renderEnvVars(data) {
1891
2421
  envData = data.variables || [];
@@ -2035,6 +2565,342 @@ function generateDashboardHtml(options) {
2035
2565
  </script>`;
2036
2566
  }
2037
2567
 
2568
+ // src/dev/firebase-setup.ts
2569
+ var import_node_child_process2 = require("child_process");
2570
+ var import_node_fs4 = require("fs");
2571
+ var import_node_path4 = require("path");
2572
+ var import_node_os = require("os");
2573
+ var FirebaseSetup = class {
2574
+ projectDir;
2575
+ loginProcess = null;
2576
+ loginSession = {
2577
+ active: false,
2578
+ authUrl: null,
2579
+ waitingForCode: false,
2580
+ completed: false,
2581
+ error: null,
2582
+ output: ""
2583
+ };
2584
+ loginTimeout = null;
2585
+ constructor(projectDir) {
2586
+ this.projectDir = projectDir;
2587
+ }
2588
+ // ─── Status Check ──────────────────────────────────────────────────
2589
+ async getStatus() {
2590
+ const status = {
2591
+ cli: { installed: false, version: "" },
2592
+ auth: { authenticated: false, user: "" },
2593
+ project: { id: "", hasFirebaserc: false },
2594
+ ready: false,
2595
+ nextStep: "install-cli"
2596
+ };
2597
+ try {
2598
+ const version = await this.execTimeout("firebase", ["--version"], 5e3);
2599
+ status.cli.installed = true;
2600
+ status.cli.version = version.trim();
2601
+ } catch {
2602
+ status.nextStep = "install-cli";
2603
+ return status;
2604
+ }
2605
+ try {
2606
+ const loginOutput = await this.execTimeout(
2607
+ "firebase",
2608
+ ["login:list", "--json"],
2609
+ 5e3
2610
+ );
2611
+ const loginData = JSON.parse(loginOutput);
2612
+ if (loginData?.result && Array.isArray(loginData.result) && loginData.result.length > 0) {
2613
+ status.auth.authenticated = true;
2614
+ status.auth.user = loginData.result[0]?.user?.email || loginData.result[0]?.email || "";
2615
+ }
2616
+ } catch {
2617
+ }
2618
+ if (!status.auth.authenticated) {
2619
+ status.nextStep = "login";
2620
+ return status;
2621
+ }
2622
+ const firebasercPath = (0, import_node_path4.resolve)(this.projectDir, ".firebaserc");
2623
+ if ((0, import_node_fs4.existsSync)(firebasercPath)) {
2624
+ status.project.hasFirebaserc = true;
2625
+ try {
2626
+ const rc = JSON.parse((0, import_node_fs4.readFileSync)(firebasercPath, "utf-8"));
2627
+ status.project.id = rc?.projects?.default || "";
2628
+ } catch {
2629
+ }
2630
+ }
2631
+ if (!status.project.id) {
2632
+ status.nextStep = "select-project";
2633
+ return status;
2634
+ }
2635
+ status.ready = true;
2636
+ status.nextStep = "done";
2637
+ return status;
2638
+ }
2639
+ // ─── CLI Install ───────────────────────────────────────────────────
2640
+ async installCli() {
2641
+ try {
2642
+ try {
2643
+ await this.execTimeout("firebase", ["--version"], 5e3);
2644
+ return { success: true, message: "Firebase CLI is already installed." };
2645
+ } catch {
2646
+ }
2647
+ await this.execTimeout(
2648
+ "npm",
2649
+ ["install", "-g", "firebase-tools"],
2650
+ 12e4
2651
+ // 2 min timeout for install
2652
+ );
2653
+ try {
2654
+ const version = await this.execTimeout("firebase", ["--version"], 5e3);
2655
+ return { success: true, message: `Firebase CLI v${version.trim()} installed successfully.` };
2656
+ } catch {
2657
+ return { success: false, message: "Installation completed but CLI not found in PATH. Try restarting your terminal." };
2658
+ }
2659
+ } catch (err) {
2660
+ const msg = err instanceof Error ? err.message : "Unknown error";
2661
+ return { success: false, message: `Failed to install Firebase CLI: ${msg}` };
2662
+ }
2663
+ }
2664
+ // ─── Analytics Pre-config ──────────────────────────────────────────
2665
+ /**
2666
+ * Pre-configure Firebase analytics preference to avoid the interactive
2667
+ * "Allow Firebase to collect..." prompt that breaks non-TTY spawns.
2668
+ */
2669
+ ensureAnalyticsConfigured() {
2670
+ try {
2671
+ const home = (0, import_node_os.homedir)();
2672
+ const configPath = (0, import_node_path4.join)(home, ".config", "configstore", "firebase-tools.json");
2673
+ if ((0, import_node_fs4.existsSync)(configPath)) {
2674
+ const config = JSON.parse((0, import_node_fs4.readFileSync)(configPath, "utf-8"));
2675
+ if (config.usage === void 0) {
2676
+ config.usage = false;
2677
+ (0, import_node_fs4.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
2678
+ }
2679
+ } else {
2680
+ const dir = (0, import_node_path4.dirname)(configPath);
2681
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
2682
+ (0, import_node_fs4.writeFileSync)(configPath, JSON.stringify({ usage: false }, null, 2), "utf-8");
2683
+ }
2684
+ } catch {
2685
+ }
2686
+ }
2687
+ // ─── Login Flow ────────────────────────────────────────────────────
2688
+ /**
2689
+ * Start login using `firebase login --no-localhost`.
2690
+ *
2691
+ * This mode is designed for non-TTY environments:
2692
+ * 1. Prints a Google OAuth URL
2693
+ * 2. User opens URL in browser and authenticates
2694
+ * 3. Google shows an authorization code on screen
2695
+ * 4. User pastes code back → call submitAuthCode()
2696
+ */
2697
+ startLogin(reauth = false) {
2698
+ this.cleanupLogin();
2699
+ this.ensureAnalyticsConfigured();
2700
+ this.loginSession = {
2701
+ active: true,
2702
+ authUrl: null,
2703
+ waitingForCode: false,
2704
+ completed: false,
2705
+ error: null,
2706
+ output: ""
2707
+ };
2708
+ try {
2709
+ const args = reauth ? ["login", "--reauth", "--no-localhost"] : ["login", "--no-localhost"];
2710
+ const proc = (0, import_node_child_process2.spawn)("firebase", args, {
2711
+ cwd: this.projectDir,
2712
+ stdio: ["pipe", "pipe", "pipe"],
2713
+ env: { ...process.env, TERM: "dumb" }
2714
+ });
2715
+ this.loginProcess = proc;
2716
+ const appendOutput = (data) => {
2717
+ const text = data.toString();
2718
+ this.loginSession.output += text;
2719
+ const urlMatch = text.match(/(https:\/\/accounts\.google\.com\S+)/);
2720
+ if (urlMatch) {
2721
+ this.loginSession.authUrl = urlMatch[1];
2722
+ this.loginSession.waitingForCode = true;
2723
+ }
2724
+ if (text.includes("Success") || text.includes("Logged in as")) {
2725
+ this.loginSession.completed = true;
2726
+ this.loginSession.active = false;
2727
+ this.loginSession.waitingForCode = false;
2728
+ }
2729
+ if (text.includes("Allow Firebase to collect") || text.includes("(Y/n)")) {
2730
+ try {
2731
+ proc.stdin?.write("n\n");
2732
+ } catch {
2733
+ }
2734
+ }
2735
+ };
2736
+ proc.stdout?.on("data", appendOutput);
2737
+ proc.stderr?.on("data", appendOutput);
2738
+ proc.on("close", (code) => {
2739
+ this.loginSession.active = false;
2740
+ this.loginSession.waitingForCode = false;
2741
+ if (code === 0) {
2742
+ this.loginSession.completed = true;
2743
+ } else if (!this.loginSession.completed) {
2744
+ const lines = this.loginSession.output.trim().split("\n").filter((l) => l.trim());
2745
+ const lastLines = lines.slice(-3).join(" | ");
2746
+ this.loginSession.error = `Login failed (exit code ${code}).` + (lastLines ? ` Details: ${lastLines}` : "") + ` Try running "firebase login" in your terminal.`;
2747
+ }
2748
+ this.loginProcess = null;
2749
+ });
2750
+ proc.on("error", (err) => {
2751
+ this.loginSession.active = false;
2752
+ this.loginSession.error = err.message;
2753
+ this.loginProcess = null;
2754
+ });
2755
+ this.loginTimeout = setTimeout(() => {
2756
+ if (this.loginSession.active) {
2757
+ this.loginSession.error = "Login timed out (5 minutes). Please try again.";
2758
+ this.cleanupLogin();
2759
+ }
2760
+ }, 5 * 60 * 1e3);
2761
+ } catch (err) {
2762
+ this.loginSession.active = false;
2763
+ this.loginSession.error = err instanceof Error ? err.message : "Failed to start login";
2764
+ }
2765
+ return this.loginSession;
2766
+ }
2767
+ getLoginState() {
2768
+ return { ...this.loginSession };
2769
+ }
2770
+ /**
2771
+ * Submit the authorization code that the user copied from the browser.
2772
+ * This writes the code to the Firebase CLI process stdin.
2773
+ */
2774
+ submitAuthCode(code) {
2775
+ if (!this.loginProcess || !this.loginProcess.stdin) {
2776
+ return { success: false, message: "No active login process. Please start login again." };
2777
+ }
2778
+ if (!code || !code.trim()) {
2779
+ return { success: false, message: "Authorization code is required." };
2780
+ }
2781
+ try {
2782
+ this.loginProcess.stdin.write(code.trim() + "\n");
2783
+ this.loginSession.waitingForCode = false;
2784
+ return { success: true, message: "Authorization code submitted. Waiting for verification..." };
2785
+ } catch (err) {
2786
+ return { success: false, message: "Failed to submit code: " + (err instanceof Error ? err.message : "unknown error") };
2787
+ }
2788
+ }
2789
+ cancelLogin() {
2790
+ this.cleanupLogin();
2791
+ this.loginSession.error = "Login cancelled by user.";
2792
+ }
2793
+ cleanupLogin() {
2794
+ if (this.loginProcess) {
2795
+ try {
2796
+ this.loginProcess.kill("SIGTERM");
2797
+ } catch {
2798
+ }
2799
+ this.loginProcess = null;
2800
+ }
2801
+ if (this.loginTimeout) {
2802
+ clearTimeout(this.loginTimeout);
2803
+ this.loginTimeout = null;
2804
+ }
2805
+ this.loginSession.active = false;
2806
+ this.loginSession.waitingForCode = false;
2807
+ }
2808
+ // ─── Project Listing ───────────────────────────────────────────────
2809
+ async listProjects() {
2810
+ try {
2811
+ const output = await this.execTimeout(
2812
+ "firebase",
2813
+ ["projects:list", "--json"],
2814
+ 3e4
2815
+ // 30s — can be slow on first call
2816
+ );
2817
+ const data = JSON.parse(output);
2818
+ if (data?.result && Array.isArray(data.result)) {
2819
+ const projects = data.result.map((p) => ({
2820
+ projectId: p.projectId || "",
2821
+ displayName: p.displayName || p.projectId || "",
2822
+ projectNumber: p.projectNumber || "",
2823
+ state: p.lifecycleState || p.state || "ACTIVE"
2824
+ }));
2825
+ return { projects };
2826
+ }
2827
+ return { projects: [], error: "Unexpected response format" };
2828
+ } catch (err) {
2829
+ const msg = err instanceof Error ? err.message : "Unknown error";
2830
+ if (msg.includes("authenticate") || msg.includes("login") || msg.includes("credential")) {
2831
+ return { projects: [], error: "Session expired. Please re-authenticate first." };
2832
+ }
2833
+ if (msg.includes("Timeout")) {
2834
+ return { projects: [], error: "Request timed out. Please try again." };
2835
+ }
2836
+ return { projects: [], error: msg };
2837
+ }
2838
+ }
2839
+ // ─── Project Selection ─────────────────────────────────────────────
2840
+ async selectProject(projectId) {
2841
+ if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {
2842
+ return { success: false, message: "Invalid project ID format." };
2843
+ }
2844
+ try {
2845
+ await this.execTimeout(
2846
+ "firebase",
2847
+ ["use", projectId],
2848
+ 1e4
2849
+ );
2850
+ const firebasercPath = (0, import_node_path4.resolve)(this.projectDir, ".firebaserc");
2851
+ let rc = {};
2852
+ if ((0, import_node_fs4.existsSync)(firebasercPath)) {
2853
+ try {
2854
+ rc = JSON.parse((0, import_node_fs4.readFileSync)(firebasercPath, "utf-8"));
2855
+ } catch {
2856
+ rc = {};
2857
+ }
2858
+ }
2859
+ if (!rc.projects || typeof rc.projects !== "object") {
2860
+ rc.projects = {};
2861
+ }
2862
+ rc.projects.default = projectId;
2863
+ (0, import_node_fs4.writeFileSync)(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2864
+ return { success: true, message: `Active project set to "${projectId}".` };
2865
+ } catch {
2866
+ try {
2867
+ const firebasercPath = (0, import_node_path4.resolve)(this.projectDir, ".firebaserc");
2868
+ const rc = {
2869
+ projects: { default: projectId }
2870
+ };
2871
+ (0, import_node_fs4.writeFileSync)(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2872
+ return { success: true, message: `Active project set to "${projectId}" (via .firebaserc).` };
2873
+ } catch (writeErr) {
2874
+ const msg = writeErr instanceof Error ? writeErr.message : "Unknown error";
2875
+ return { success: false, message: `Failed to set project: ${msg}` };
2876
+ }
2877
+ }
2878
+ }
2879
+ // ─── Helpers ───────────────────────────────────────────────────────
2880
+ execTimeout(command, args, timeoutMs) {
2881
+ return new Promise((resolve7, reject) => {
2882
+ const proc = (0, import_node_child_process2.execFile)(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
2883
+ if (err) {
2884
+ const detail = stderr?.trim() || stdout?.trim() || "";
2885
+ const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
2886
+ reject(enriched);
2887
+ } else {
2888
+ resolve7(stdout);
2889
+ }
2890
+ });
2891
+ const timer = setTimeout(() => {
2892
+ proc.kill("SIGTERM");
2893
+ reject(new Error("Timeout"));
2894
+ }, timeoutMs + 500);
2895
+ proc.on("exit", () => clearTimeout(timer));
2896
+ });
2897
+ }
2898
+ /** Cleanup resources */
2899
+ destroy() {
2900
+ this.cleanupLogin();
2901
+ }
2902
+ };
2903
+
2038
2904
  // src/dev/dev-server.ts
2039
2905
  var MIME_TYPES = {
2040
2906
  html: "text/html; charset=utf-8",
@@ -2280,6 +3146,7 @@ var DevServer = class {
2280
3146
  isReloading = false;
2281
3147
  pageCompiler;
2282
3148
  envManager;
3149
+ firebaseSetup;
2283
3150
  constructor(options = {}) {
2284
3151
  this.options = {
2285
3152
  projectDir: options.projectDir || process.cwd(),
@@ -2291,13 +3158,14 @@ var DevServer = class {
2291
3158
  onSetupRoutes: options.onSetupRoutes || (() => {
2292
3159
  })
2293
3160
  };
2294
- this.routesDir = (0, import_node_path4.resolve)(this.options.projectDir, "app/routes");
2295
- this.schemasDir = (0, import_node_path4.resolve)(this.options.projectDir, "app/schemas");
2296
- this.publicDir = (0, import_node_path4.resolve)(this.options.projectDir, "public");
2297
- this.pagesDir = (0, import_node_path4.resolve)(this.options.projectDir, "app/pages");
2298
- this.componentsDir = (0, import_node_path4.resolve)(this.options.projectDir, "app/components");
3161
+ this.routesDir = (0, import_node_path5.resolve)(this.options.projectDir, "app/routes");
3162
+ this.schemasDir = (0, import_node_path5.resolve)(this.options.projectDir, "app/schemas");
3163
+ this.publicDir = (0, import_node_path5.resolve)(this.options.projectDir, "public");
3164
+ this.pagesDir = (0, import_node_path5.resolve)(this.options.projectDir, "app/pages");
3165
+ this.componentsDir = (0, import_node_path5.resolve)(this.options.projectDir, "app/components");
2299
3166
  this.pageCompiler = new PageCompiler(this.options.projectDir);
2300
3167
  this.envManager = new EnvManager(this.options.projectDir);
3168
+ this.firebaseSetup = new FirebaseSetup(this.options.projectDir);
2301
3169
  this.router = createRouter({
2302
3170
  cors: ["*"],
2303
3171
  rateLimit: 0,
@@ -2313,18 +3181,19 @@ var DevServer = class {
2313
3181
  if (this.options.hotReload) {
2314
3182
  this.startWatcher();
2315
3183
  }
2316
- await new Promise((resolve6, reject) => {
2317
- this.apiServer.listen(this.options.apiPort, () => resolve6());
3184
+ await new Promise((resolve7, reject) => {
3185
+ this.apiServer.listen(this.options.apiPort, () => resolve7());
2318
3186
  this.apiServer.on("error", reject);
2319
3187
  });
2320
- await new Promise((resolve6, reject) => {
2321
- this.frontendServer.listen(this.options.port, () => resolve6());
3188
+ await new Promise((resolve7, reject) => {
3189
+ this.frontendServer.listen(this.options.port, () => resolve7());
2322
3190
  this.frontendServer.on("error", reject);
2323
3191
  });
2324
3192
  this.printStartupBanner();
2325
3193
  }
2326
3194
  async stop() {
2327
3195
  this.watcher?.close();
3196
+ this.firebaseSetup.destroy();
2328
3197
  for (const client of [...this.frontendSseClients, ...this.apiSseClients]) {
2329
3198
  client.res.end();
2330
3199
  }
@@ -2332,13 +3201,13 @@ var DevServer = class {
2332
3201
  this.apiSseClients = [];
2333
3202
  this.router.destroy();
2334
3203
  if (this.apiServer) {
2335
- await new Promise((resolve6) => {
2336
- this.apiServer.close(() => resolve6());
3204
+ await new Promise((resolve7) => {
3205
+ this.apiServer.close(() => resolve7());
2337
3206
  });
2338
3207
  }
2339
3208
  if (this.frontendServer) {
2340
- await new Promise((resolve6) => {
2341
- this.frontendServer.close(() => resolve6());
3209
+ await new Promise((resolve7) => {
3210
+ this.frontendServer.close(() => resolve7());
2342
3211
  });
2343
3212
  }
2344
3213
  }
@@ -2354,11 +3223,11 @@ var DevServer = class {
2354
3223
  await this.options.onSetupRoutes(this.router);
2355
3224
  if (this.router.getRoutes().length > 0) return;
2356
3225
  }
2357
- if (!(0, import_node_fs4.existsSync)(this.routesDir)) return;
3226
+ if (!(0, import_node_fs5.existsSync)(this.routesDir)) return;
2358
3227
  const discovered = discoverRoutes(this.routesDir);
2359
3228
  for (const route of discovered) {
2360
3229
  try {
2361
- const fullPath = (0, import_node_path4.resolve)(this.routesDir, route.filePath);
3230
+ const fullPath = (0, import_node_path5.resolve)(this.routesDir, route.filePath);
2362
3231
  const fileUrl = (0, import_node_url.pathToFileURL)(fullPath).href;
2363
3232
  const mod = await import(`${fileUrl}?v=${++this.importCounter}`);
2364
3233
  const contract = mod.default;
@@ -2373,7 +3242,7 @@ var DevServer = class {
2373
3242
  async reloadRoutes(event) {
2374
3243
  if (this.isReloading) return;
2375
3244
  this.isReloading = true;
2376
- const relPath = (0, import_node_path4.relative)(this.options.projectDir, event.filePath);
3245
+ const relPath = (0, import_node_path5.relative)(this.options.projectDir, event.filePath);
2377
3246
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
2378
3247
  console.log(`
2379
3248
  \x1B[33m[${timestamp}]\x1B[0m \x1B[36m${relPath}\x1B[0m changed`);
@@ -2411,7 +3280,7 @@ var DevServer = class {
2411
3280
  }
2412
3281
  // ─── Frontend Change Handler ───────────────────────────────────────
2413
3282
  handleFrontendChange(event) {
2414
- const relPath = (0, import_node_path4.relative)(this.options.projectDir, event.filePath);
3283
+ const relPath = (0, import_node_path5.relative)(this.options.projectDir, event.filePath);
2415
3284
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
2416
3285
  if (event.type === "css-change") {
2417
3286
  console.log(`
@@ -2434,23 +3303,23 @@ var DevServer = class {
2434
3303
  // ─── File Watcher ──────────────────────────────────────────────────
2435
3304
  startWatcher() {
2436
3305
  this.watcher = new FileWatcher(this.options.debounceMs);
2437
- if ((0, import_node_fs4.existsSync)(this.routesDir)) {
3306
+ if ((0, import_node_fs5.existsSync)(this.routesDir)) {
2438
3307
  this.watcher.watchDir(this.routesDir, "route-change");
2439
3308
  }
2440
- if ((0, import_node_fs4.existsSync)(this.schemasDir)) {
3309
+ if ((0, import_node_fs5.existsSync)(this.schemasDir)) {
2441
3310
  this.watcher.watchDir(this.schemasDir, "schema-change");
2442
3311
  }
2443
- const configFile = (0, import_node_path4.resolve)(this.options.projectDir, "clawfire.config.ts");
2444
- if ((0, import_node_fs4.existsSync)(configFile)) {
3312
+ const configFile = (0, import_node_path5.resolve)(this.options.projectDir, "clawfire.config.ts");
3313
+ if ((0, import_node_fs5.existsSync)(configFile)) {
2445
3314
  this.watcher.watchFile(configFile, "config-change");
2446
3315
  }
2447
- if ((0, import_node_fs4.existsSync)(this.publicDir)) {
3316
+ if ((0, import_node_fs5.existsSync)(this.publicDir)) {
2448
3317
  this.watcher.watchDirFrontend(this.publicDir);
2449
3318
  }
2450
- if ((0, import_node_fs4.existsSync)(this.pagesDir)) {
3319
+ if ((0, import_node_fs5.existsSync)(this.pagesDir)) {
2451
3320
  this.watcher.watchDir(this.pagesDir, "page-change");
2452
3321
  }
2453
- if ((0, import_node_fs4.existsSync)(this.componentsDir)) {
3322
+ if ((0, import_node_fs5.existsSync)(this.componentsDir)) {
2454
3323
  this.watcher.watchDir(this.componentsDir, "component-change");
2455
3324
  }
2456
3325
  this.watcher.on("route-change", (event) => this.reloadRoutes(event));
@@ -2611,10 +3480,10 @@ ${liveReloadScript}
2611
3480
  }
2612
3481
  // ─── Static File Serving ──────────────────────────────────────────
2613
3482
  serveStaticFile(filePath, res) {
2614
- if (!(0, import_node_fs4.existsSync)(filePath)) return false;
3483
+ if (!(0, import_node_fs5.existsSync)(filePath)) return false;
2615
3484
  try {
2616
- const content = (0, import_node_fs4.readFileSync)(filePath);
2617
- const ext = (0, import_node_path4.extname)(filePath).slice(1).toLowerCase();
3485
+ const content = (0, import_node_fs5.readFileSync)(filePath);
3486
+ const ext = (0, import_node_path5.extname)(filePath).slice(1).toLowerCase();
2618
3487
  const mime = MIME_TYPES[ext] || "application/octet-stream";
2619
3488
  if (ext === "html") {
2620
3489
  const html = content.toString("utf-8");
@@ -2669,9 +3538,9 @@ ${liveReloadScript}
2669
3538
  this.proxyToApiServer(req, res);
2670
3539
  return;
2671
3540
  }
2672
- const ext = (0, import_node_path4.extname)(url.pathname);
3541
+ const ext = (0, import_node_path5.extname)(url.pathname);
2673
3542
  if (ext && STATIC_EXTENSIONS.has(ext)) {
2674
- const filePath2 = (0, import_node_path4.resolve)(this.publicDir, url.pathname.slice(1));
3543
+ const filePath2 = (0, import_node_path5.resolve)(this.publicDir, url.pathname.slice(1));
2675
3544
  if (filePath2.startsWith(this.publicDir) && this.serveStaticFile(filePath2, res)) {
2676
3545
  return;
2677
3546
  }
@@ -2722,7 +3591,7 @@ ${liveReloadScript}
2722
3591
  }
2723
3592
  }
2724
3593
  const requestedPath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
2725
- const filePath = (0, import_node_path4.resolve)(this.publicDir, requestedPath);
3594
+ const filePath = (0, import_node_path5.resolve)(this.publicDir, requestedPath);
2726
3595
  if (!filePath.startsWith(this.publicDir)) {
2727
3596
  res.writeHead(403);
2728
3597
  res.end("Forbidden");
@@ -2731,8 +3600,8 @@ ${liveReloadScript}
2731
3600
  if (this.serveStaticFile(filePath, res)) {
2732
3601
  return;
2733
3602
  }
2734
- const indexPath = (0, import_node_path4.resolve)(this.publicDir, "index.html");
2735
- if ((0, import_node_fs4.existsSync)(indexPath)) {
3603
+ const indexPath = (0, import_node_path5.resolve)(this.publicDir, "index.html");
3604
+ if ((0, import_node_fs5.existsSync)(indexPath)) {
2736
3605
  this.serveStaticFile(indexPath, res);
2737
3606
  return;
2738
3607
  }
@@ -2931,18 +3800,97 @@ ${liveReloadScript}
2931
3800
  });
2932
3801
  return;
2933
3802
  }
3803
+ if (url.pathname === "/__dev/setup/status" && req.method === "GET") {
3804
+ this.firebaseSetup.getStatus().then((status) => sendJson(status)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed" }, 500));
3805
+ return;
3806
+ }
3807
+ if (url.pathname === "/__dev/setup/install-cli" && req.method === "POST") {
3808
+ this.firebaseSetup.installCli().then((result) => sendJson(result)).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
3809
+ return;
3810
+ }
3811
+ if (url.pathname === "/__dev/setup/login" && req.method === "POST") {
3812
+ let body = "";
3813
+ req.on("data", (chunk) => {
3814
+ body += chunk;
3815
+ });
3816
+ req.on("end", () => {
3817
+ let reauth = false;
3818
+ try {
3819
+ const data = JSON.parse(body);
3820
+ reauth = !!data.reauth;
3821
+ } catch {
3822
+ }
3823
+ const session = this.firebaseSetup.startLogin(reauth);
3824
+ sendJson(session);
3825
+ });
3826
+ return;
3827
+ }
3828
+ if (url.pathname === "/__dev/setup/login-state" && req.method === "GET") {
3829
+ sendJson(this.firebaseSetup.getLoginState());
3830
+ return;
3831
+ }
3832
+ if (url.pathname === "/__dev/setup/cancel-login" && req.method === "POST") {
3833
+ this.firebaseSetup.cancelLogin();
3834
+ sendJson({ ok: true });
3835
+ return;
3836
+ }
3837
+ if (url.pathname === "/__dev/setup/submit-auth-code" && req.method === "POST") {
3838
+ let body = "";
3839
+ req.on("data", (chunk) => {
3840
+ body += chunk;
3841
+ });
3842
+ req.on("end", () => {
3843
+ try {
3844
+ const data = JSON.parse(body);
3845
+ if (!data.code) {
3846
+ sendJson({ success: false, message: "code is required" }, 400);
3847
+ return;
3848
+ }
3849
+ sendJson(this.firebaseSetup.submitAuthCode(data.code));
3850
+ } catch {
3851
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
3852
+ }
3853
+ });
3854
+ return;
3855
+ }
3856
+ if (url.pathname === "/__dev/setup/projects" && req.method === "GET") {
3857
+ this.firebaseSetup.listProjects().then((result) => sendJson(result)).catch((err) => sendJson({ projects: [], error: err instanceof Error ? err.message : "Failed" }, 500));
3858
+ return;
3859
+ }
3860
+ if (url.pathname === "/__dev/setup/select-project" && req.method === "POST") {
3861
+ let body = "";
3862
+ req.on("data", (chunk) => {
3863
+ body += chunk;
3864
+ });
3865
+ req.on("end", () => {
3866
+ try {
3867
+ const data = JSON.parse(body);
3868
+ if (!data.projectId) {
3869
+ sendJson({ success: false, message: "projectId is required" }, 400);
3870
+ return;
3871
+ }
3872
+ this.firebaseSetup.selectProject(data.projectId).then((result) => {
3873
+ clearFirebaseStatusCache();
3874
+ sendJson(result);
3875
+ }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
3876
+ } catch {
3877
+ sendJson({ success: false, message: "Invalid JSON body" }, 400);
3878
+ }
3879
+ });
3880
+ return;
3881
+ }
2934
3882
  res.writeHead(404);
2935
3883
  res.end("Not found");
2936
3884
  }
2937
3885
  // ─── Config Reader ────────────────────────────────────────────────
2938
3886
  readProjectConfig() {
2939
- const configPath = (0, import_node_path4.resolve)(this.options.projectDir, "clawfire.config.ts");
3887
+ const configPath = (0, import_node_path5.resolve)(this.options.projectDir, "clawfire.config.ts");
2940
3888
  const fields = [];
2941
- if (!(0, import_node_fs4.existsSync)(configPath)) {
3889
+ if (!(0, import_node_fs5.existsSync)(configPath)) {
2942
3890
  return { fields };
2943
3891
  }
2944
3892
  try {
2945
- const content = (0, import_node_fs4.readFileSync)(configPath, "utf-8");
3893
+ const content = (0, import_node_fs5.readFileSync)(configPath, "utf-8");
2946
3894
  const kvPattern = /(\w+)\s*:\s*["'`]([^"'`]*)["'`]/g;
2947
3895
  let match;
2948
3896
  while ((match = kvPattern.exec(content)) !== null) {
@@ -2957,11 +3905,11 @@ ${liveReloadScript}
2957
3905
  }
2958
3906
  /** Update a single key's value in clawfire.config.ts */
2959
3907
  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)) {
3908
+ const configPath = (0, import_node_path5.resolve)(this.options.projectDir, "clawfire.config.ts");
3909
+ if (!(0, import_node_fs5.existsSync)(configPath)) {
2962
3910
  throw new Error("clawfire.config.ts not found");
2963
3911
  }
2964
- let content = (0, import_node_fs4.readFileSync)(configPath, "utf-8");
3912
+ let content = (0, import_node_fs5.readFileSync)(configPath, "utf-8");
2965
3913
  const pattern = new RegExp(
2966
3914
  `(${this.escapeRegex(key)}\\s*:\\s*)["'\`][^"'\`]*["'\`]`
2967
3915
  );
@@ -2969,7 +3917,7 @@ ${liveReloadScript}
2969
3917
  throw new Error(`Key "${key}" not found in config`);
2970
3918
  }
2971
3919
  content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
2972
- (0, import_node_fs4.writeFileSync)(configPath, content, "utf-8");
3920
+ (0, import_node_fs5.writeFileSync)(configPath, content, "utf-8");
2973
3921
  }
2974
3922
  escapeRegex(s) {
2975
3923
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");