clawfire 0.3.1 → 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/cli.js CHANGED
@@ -209,7 +209,7 @@ async function runDevServer() {
209
209
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
210
210
  const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
211
211
  const noHotReload = args.includes("--no-hot-reload");
212
- const { startDevServer } = await import("./dev-server-TGEKP4YE.js");
212
+ const { startDevServer } = await import("./dev-server-MIUKPOMC.js");
213
213
  await startDevServer({
214
214
  projectDir,
215
215
  port,
@@ -9,7 +9,7 @@ import {
9
9
  // src/dev/dev-server.ts
10
10
  import http from "http";
11
11
  import { resolve as resolve4, relative as relative2, extname as extname3 } from "path";
12
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
12
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
13
13
  import { pathToFileURL } from "url";
14
14
 
15
15
  // src/core/schema.ts
@@ -1082,6 +1082,34 @@ async function checkCli(projectDir) {
1082
1082
  }
1083
1083
  return result;
1084
1084
  }
1085
+ async function fetchFirebaseSdkConfig(projectDir) {
1086
+ const output = await execWithTimeout(
1087
+ "firebase",
1088
+ ["apps:sdkconfig", "web", "--json"],
1089
+ projectDir,
1090
+ 15e3
1091
+ );
1092
+ const data = JSON.parse(output);
1093
+ if (data?.result?.sdkConfig) {
1094
+ return data.result.sdkConfig;
1095
+ }
1096
+ if (data?.result?.fileContents) {
1097
+ const contents = data.result.fileContents;
1098
+ const config = {};
1099
+ const extract = (key) => {
1100
+ const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
1101
+ return match ? match[1] : void 0;
1102
+ };
1103
+ config.apiKey = extract("apiKey");
1104
+ config.authDomain = extract("authDomain");
1105
+ config.projectId = extract("projectId");
1106
+ config.storageBucket = extract("storageBucket");
1107
+ config.messagingSenderId = extract("messagingSenderId");
1108
+ config.appId = extract("appId");
1109
+ return config;
1110
+ }
1111
+ throw new Error("Could not parse Firebase SDK config from CLI output");
1112
+ }
1085
1113
  function execWithTimeout(command, args, cwd, timeoutMs) {
1086
1114
  return new Promise((resolve5, reject) => {
1087
1115
  const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
@@ -1130,9 +1158,25 @@ function generateDashboardHtml(options) {
1130
1158
 
1131
1159
  <!-- Section 2: Config Overview -->
1132
1160
  <div style="margin-bottom:32px;">
1133
- <h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Config Overview</h2>
1161
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
1162
+ <h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
1163
+ <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>
1164
+ </div>
1165
+ <div id="autofill-status" style="display:none;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;"></div>
1134
1166
  <div id="config-section" style="border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;">
1135
- <div id="config-content" style="padding:16px;font-family:monospace;font-size:13px;line-height:1.8;"></div>
1167
+ <table id="config-table" style="width:100%;border-collapse:collapse;font-size:13px;">
1168
+ <thead>
1169
+ <tr style="border-bottom:1px solid #2a2a2a;">
1170
+ <th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;width:180px;">Key</th>
1171
+ <th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;">Value</th>
1172
+ <th style="padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:80px;">Action</th>
1173
+ </tr>
1174
+ </thead>
1175
+ <tbody id="config-tbody"></tbody>
1176
+ </table>
1177
+ <div id="config-empty" style="display:none;padding:32px;text-align:center;color:#666;">
1178
+ No clawfire.config.ts found.
1179
+ </div>
1136
1180
  </div>
1137
1181
  </div>
1138
1182
 
@@ -1162,7 +1206,7 @@ function generateDashboardHtml(options) {
1162
1206
  </div>
1163
1207
 
1164
1208
  <!-- Env Modal -->
1165
- <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;">
1209
+ <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;">
1166
1210
  <div style="background:#1e1e1e;border:1px solid #2a2a2a;border-radius:12px;padding:24px;width:440px;max-width:90vw;">
1167
1211
  <h3 id="modal-title" style="font-size:16px;font-weight:700;color:#e5e5e5;margin-bottom:16px;">Add Variable</h3>
1168
1212
  <div style="margin-bottom:12px;">
@@ -1190,6 +1234,7 @@ function generateDashboardHtml(options) {
1190
1234
  (function() {
1191
1235
  var API = 'http://localhost:${apiPort}';
1192
1236
  var envData = [];
1237
+ var configData = [];
1193
1238
  var editingKey = null;
1194
1239
 
1195
1240
  // \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
@@ -1215,7 +1260,6 @@ function generateDashboardHtml(options) {
1215
1260
 
1216
1261
  // \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
1217
1262
  function renderFirebaseStatus(data) {
1218
- // CLI Banner
1219
1263
  var dot = document.getElementById('cli-dot');
1220
1264
  var text = document.getElementById('cli-text');
1221
1265
  var proj = document.getElementById('cli-project');
@@ -1257,7 +1301,6 @@ function generateDashboardHtml(options) {
1257
1301
  grid.appendChild(card);
1258
1302
  });
1259
1303
 
1260
- // Config Warnings
1261
1304
  if (data.configWarnings && data.configWarnings.length > 0) {
1262
1305
  var warningCard = document.createElement('div');
1263
1306
  warningCard.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #eab308;background:#1a1a0a;grid-column:1/-1;';
@@ -1270,26 +1313,167 @@ function generateDashboardHtml(options) {
1270
1313
  }
1271
1314
  }
1272
1315
 
1273
- // \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
1316
+ // \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
1274
1317
  function renderConfig(data) {
1275
- var el = document.getElementById('config-content');
1276
- if (!data || !data.fields || data.fields.length === 0) {
1277
- el.innerHTML = '<span style="color:#666;">No clawfire.config.ts found or config is empty.</span>';
1318
+ var tbody = document.getElementById('config-tbody');
1319
+ var empty = document.getElementById('config-empty');
1320
+ var table = document.getElementById('config-table');
1321
+
1322
+ configData = (data && data.fields) ? data.fields : [];
1323
+
1324
+ if (configData.length === 0) {
1325
+ table.style.display = 'none';
1326
+ empty.style.display = 'block';
1278
1327
  return;
1279
1328
  }
1280
- var html = '';
1281
- data.fields.forEach(function(field) {
1329
+
1330
+ table.style.display = 'table';
1331
+ empty.style.display = 'none';
1332
+ tbody.innerHTML = '';
1333
+
1334
+ configData.forEach(function(field) {
1335
+ var tr = document.createElement('tr');
1336
+ tr.style.borderBottom = '1px solid #2a2a2a';
1337
+ tr.id = 'cfg-row-' + field.key;
1282
1338
  var color = field.isPlaceholder ? '#eab308' : '#a3a3a3';
1283
1339
  var badge = field.isPlaceholder ? ' <span style="background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;">PLACEHOLDER</span>' : '';
1284
- html += '<div style="padding:4px 0;display:flex;gap:8px;align-items:center;">';
1285
- html += '<span style="color:#e5e5e5;min-width:180px;">' + escHtml(field.key) + '</span>';
1286
- html += '<span style="color:' + color + ';">' + escHtml(field.value) + '</span>';
1287
- html += badge;
1288
- html += '</div>';
1340
+ tr.innerHTML =
1341
+ '<td style="padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;">' + escHtml(field.key) + '</td>' +
1342
+ '<td style="padding:10px 16px;font-family:monospace;">' +
1343
+ '<span id="cfg-val-' + field.key + '" style="color:' + color + ';">' + escHtml(field.value) + '</span>' +
1344
+ badge +
1345
+ '<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;" />' +
1346
+ '</td>' +
1347
+ '<td style="padding:10px 16px;text-align:right;white-space:nowrap;">' +
1348
+ '<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>' +
1349
+ '<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>' +
1350
+ '<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>' +
1351
+ '</td>';
1352
+ tbody.appendChild(tr);
1289
1353
  });
1290
- el.innerHTML = html;
1291
1354
  }
1292
1355
 
1356
+ window.editConfigField = function(key) {
1357
+ var valSpan = document.getElementById('cfg-val-' + key);
1358
+ var input = document.getElementById('cfg-input-' + key);
1359
+ var editBtn = document.getElementById('cfg-edit-' + key);
1360
+ var saveBtn = document.getElementById('cfg-save-' + key);
1361
+ var cancelBtn = document.getElementById('cfg-cancel-' + key);
1362
+ if (!valSpan || !input) return;
1363
+ // Hide all badges in this cell
1364
+ var badges = valSpan.parentNode.querySelectorAll('span[style*="PLACEHOLDER"]');
1365
+ for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';
1366
+ valSpan.style.display = 'none';
1367
+ input.style.display = 'block';
1368
+ input.focus();
1369
+ input.select();
1370
+ editBtn.style.display = 'none';
1371
+ saveBtn.style.display = 'inline';
1372
+ cancelBtn.style.display = 'inline';
1373
+ };
1374
+
1375
+ window.cancelConfigEdit = function(key) {
1376
+ var valSpan = document.getElementById('cfg-val-' + key);
1377
+ var input = document.getElementById('cfg-input-' + key);
1378
+ var editBtn = document.getElementById('cfg-edit-' + key);
1379
+ var saveBtn = document.getElementById('cfg-save-' + key);
1380
+ var cancelBtn = document.getElementById('cfg-cancel-' + key);
1381
+ if (!valSpan || !input) return;
1382
+ var badges = valSpan.parentNode.querySelectorAll('span[style*="border-radius"]');
1383
+ for (var i = 0; i < badges.length; i++) badges[i].style.display = '';
1384
+ valSpan.style.display = '';
1385
+ input.style.display = 'none';
1386
+ input.value = valSpan.textContent;
1387
+ editBtn.style.display = 'inline';
1388
+ saveBtn.style.display = 'none';
1389
+ cancelBtn.style.display = 'none';
1390
+ };
1391
+
1392
+ window.saveConfigField = function(key) {
1393
+ var input = document.getElementById('cfg-input-' + key);
1394
+ var saveBtn = document.getElementById('cfg-save-' + key);
1395
+ if (!input) return;
1396
+ var value = input.value;
1397
+ saveBtn.textContent = '...';
1398
+ fetch(API + '/__dev/config', {
1399
+ method: 'POST',
1400
+ headers: { 'Content-Type': 'application/json' },
1401
+ body: JSON.stringify({ action: 'set', key: key, value: value })
1402
+ }).then(function(r) { return r.json(); })
1403
+ .then(function(data) {
1404
+ if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }
1405
+ // Reload config
1406
+ return fetch(API + '/__dev/config').then(function(r) { return r.json(); });
1407
+ })
1408
+ .then(function(data) { if (data) renderConfig(data); })
1409
+ .catch(function(err) { alert('Failed: ' + err.message); saveBtn.textContent = 'Save'; });
1410
+ };
1411
+
1412
+ // \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
1413
+ window.autoFillConfig = function() {
1414
+ var btn = document.getElementById('autofill-btn');
1415
+ var status = document.getElementById('autofill-status');
1416
+ btn.disabled = true;
1417
+ btn.textContent = 'Fetching...';
1418
+ status.style.display = 'none';
1419
+
1420
+ fetch(API + '/__dev/firebase-sdk-config')
1421
+ .then(function(r) { return r.json(); })
1422
+ .then(function(data) {
1423
+ if (data.error) {
1424
+ status.textContent = data.error;
1425
+ 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;';
1426
+ btn.disabled = false;
1427
+ btn.textContent = 'Auto-fill from Firebase';
1428
+ return;
1429
+ }
1430
+
1431
+ // Map SDK config keys to clawfire.config.ts keys
1432
+ var fields = {};
1433
+ if (data.apiKey) fields.apiKey = data.apiKey;
1434
+ if (data.authDomain) fields.authDomain = data.authDomain;
1435
+ if (data.projectId) fields.projectId = data.projectId;
1436
+ if (data.storageBucket) fields.storageBucket = data.storageBucket;
1437
+ if (data.appId) fields.appId = data.appId;
1438
+
1439
+ var count = Object.keys(fields).length;
1440
+ if (count === 0) {
1441
+ status.textContent = 'No web app config found. Create a Web app in Firebase Console first.';
1442
+ 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;';
1443
+ btn.disabled = false;
1444
+ btn.textContent = 'Auto-fill from Firebase';
1445
+ return;
1446
+ }
1447
+
1448
+ // Save all fields at once
1449
+ return fetch(API + '/__dev/config', {
1450
+ method: 'POST',
1451
+ headers: { 'Content-Type': 'application/json' },
1452
+ body: JSON.stringify({ action: 'set-multiple', fields: fields })
1453
+ }).then(function(r) { return r.json(); })
1454
+ .then(function(result) {
1455
+ if (result.error) {
1456
+ status.textContent = 'Error saving: ' + result.error;
1457
+ 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;';
1458
+ } else {
1459
+ status.textContent = count + ' fields updated from Firebase project!';
1460
+ 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;';
1461
+ // Reload config view
1462
+ return fetch(API + '/__dev/config').then(function(r) { return r.json(); })
1463
+ .then(function(cfg) { renderConfig(cfg); });
1464
+ }
1465
+ });
1466
+ })
1467
+ .catch(function(err) {
1468
+ status.textContent = 'Failed: ' + err.message;
1469
+ 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;';
1470
+ })
1471
+ .finally(function() {
1472
+ btn.disabled = false;
1473
+ btn.textContent = 'Auto-fill from Firebase';
1474
+ });
1475
+ };
1476
+
1293
1477
  // \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
1294
1478
  function renderEnvVars(data) {
1295
1479
  envData = data.variables || [];
@@ -1328,7 +1512,7 @@ function generateDashboardHtml(options) {
1328
1512
  });
1329
1513
  }
1330
1514
 
1331
- // \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
1515
+ // \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
1332
1516
  window.showEnvModal = function(key) {
1333
1517
  editingKey = key || null;
1334
1518
  var modal = document.getElementById('env-modal');
@@ -2273,6 +2457,37 @@ ${liveReloadScript}
2273
2457
  sendJson(this.readProjectConfig());
2274
2458
  return;
2275
2459
  }
2460
+ if (url.pathname === "/__dev/config" && req.method === "POST") {
2461
+ let body = "";
2462
+ req.on("data", (chunk) => {
2463
+ body += chunk;
2464
+ });
2465
+ req.on("end", () => {
2466
+ try {
2467
+ const data = JSON.parse(body);
2468
+ if (data.action === "set" && data.key && data.value !== void 0) {
2469
+ this.updateProjectConfig(data.key, String(data.value));
2470
+ clearFirebaseStatusCache();
2471
+ sendJson({ ok: true });
2472
+ } else if (data.action === "set-multiple" && data.fields) {
2473
+ for (const [key, value] of Object.entries(data.fields)) {
2474
+ this.updateProjectConfig(key, String(value));
2475
+ }
2476
+ clearFirebaseStatusCache();
2477
+ sendJson({ ok: true });
2478
+ } else {
2479
+ sendJson({ error: "Invalid action" }, 400);
2480
+ }
2481
+ } catch (err) {
2482
+ sendJson({ error: err instanceof Error ? err.message : "Failed" }, 400);
2483
+ }
2484
+ });
2485
+ return;
2486
+ }
2487
+ if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
2488
+ fetchFirebaseSdkConfig(this.options.projectDir).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
2489
+ return;
2490
+ }
2276
2491
  if (url.pathname === "/__dev/env" && req.method === "GET") {
2277
2492
  try {
2278
2493
  sendJson(this.envManager.read());
@@ -2328,6 +2543,25 @@ ${liveReloadScript}
2328
2543
  }
2329
2544
  return { fields };
2330
2545
  }
2546
+ /** Update a single key's value in clawfire.config.ts */
2547
+ updateProjectConfig(key, value) {
2548
+ const configPath = resolve4(this.options.projectDir, "clawfire.config.ts");
2549
+ if (!existsSync5(configPath)) {
2550
+ throw new Error("clawfire.config.ts not found");
2551
+ }
2552
+ let content = readFileSync4(configPath, "utf-8");
2553
+ const pattern = new RegExp(
2554
+ `(${this.escapeRegex(key)}\\s*:\\s*)["'\`][^"'\`]*["'\`]`
2555
+ );
2556
+ if (!pattern.test(content)) {
2557
+ throw new Error(`Key "${key}" not found in config`);
2558
+ }
2559
+ content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
2560
+ writeFileSync2(configPath, content, "utf-8");
2561
+ }
2562
+ escapeRegex(s) {
2563
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2564
+ }
2331
2565
  };
2332
2566
  async function startDevServer(options) {
2333
2567
  const server = new DevServer(options);