fluxflow-cli 1.10.8 → 1.11.0

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/ARCHITECTURE.md CHANGED
@@ -25,9 +25,9 @@ The execution flow of a single user prompt follows this loop:
25
25
  5. **Turn Management & Continuation**: The model is instructed to append `[turn: finish]` if its goal is complete, or `[turn: continue]` if it expects tool results.
26
26
  - If tools were called or `[turn: continue]` is present, the loop increments and re-prompts the model with the newly gathered `[TOOL RESULT]` data.
27
27
  - If `[turn: finish]` is detected and no further tools were called, the main loop terminates, passing the final synthesized context to the background Janitor process.
28
- 6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 50 iterations per user prompt, while **Flow mode** is capped at 5.
28
+ 6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 70 iterations per user prompt, while **Flow mode** is capped at 7.
29
29
  - **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
30
- - **Critical Fallback Pivot**: If the primary model fails 5 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite`) for the final 3 attempts to ensure session navigation through API congestion.
30
+ - **Critical Fallback Pivot**: If the primary model fails 13 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite`) for the final 3 attempts to ensure session navigation through API congestion.
31
31
 
32
32
  ## Multimodal Pipeline
33
33
 
package/dist/fluxflow.js CHANGED
@@ -728,7 +728,8 @@ var init_AskUserModal = __esm({
728
728
  // src/utils/crypto.js
729
729
  import fs from "fs";
730
730
  import path from "path";
731
- var XOR_KEY, xorTransform, readEncryptedJson, writeEncryptedJson;
731
+ import crypto from "crypto";
732
+ var XOR_KEY, xorTransform, AES_ALGORITHM, AES_KEY, encryptAes, decryptAes, readEncryptedJson, writeEncryptedJson, readAesEncryptedJson, writeAesEncryptedJson;
732
733
  var init_crypto = __esm({
733
734
  "src/utils/crypto.js"() {
734
735
  XOR_KEY = 66;
@@ -740,12 +741,45 @@ var init_crypto = __esm({
740
741
  }
741
742
  return result;
742
743
  };
744
+ AES_ALGORITHM = "aes-256-cbc";
745
+ AES_KEY = crypto.createHash("sha256").update("fluxflow-cli-sanctuary-key").digest();
746
+ encryptAes = (text) => {
747
+ const iv = crypto.randomBytes(16);
748
+ const cipher = crypto.createCipheriv(AES_ALGORITHM, AES_KEY, iv);
749
+ let encrypted = cipher.update(text, "utf8", "hex");
750
+ encrypted += cipher.final("hex");
751
+ return iv.toString("hex") + ":" + encrypted;
752
+ };
753
+ decryptAes = (encryptedText) => {
754
+ const parts = encryptedText.split(":");
755
+ if (parts.length !== 2) {
756
+ throw new Error("Invalid AES format");
757
+ }
758
+ const iv = Buffer.from(parts[0], "hex");
759
+ const ciphertext = parts[1];
760
+ const decipher = crypto.createDecipheriv(AES_ALGORITHM, AES_KEY, iv);
761
+ let decrypted = decipher.update(ciphertext, "hex", "utf8");
762
+ decrypted += decipher.final("utf8");
763
+ return decrypted;
764
+ };
743
765
  readEncryptedJson = (filePath, defaultValue = {}) => {
744
766
  try {
745
767
  if (!fs.existsSync(filePath)) return defaultValue;
746
- const encryptedData = fs.readFileSync(filePath);
747
- const decryptedData = xorTransform(encryptedData).toString();
748
- return JSON.parse(decryptedData);
768
+ const rawContent = fs.readFileSync(filePath);
769
+ const fileContent = rawContent.toString("utf8").trim();
770
+ if (fileContent.startsWith("{") || fileContent.startsWith("[")) {
771
+ return JSON.parse(fileContent);
772
+ }
773
+ try {
774
+ const decrypted = decryptAes(fileContent);
775
+ return JSON.parse(decrypted);
776
+ } catch (aesErr) {
777
+ }
778
+ const decryptedDataXor = xorTransform(rawContent).toString("utf8");
779
+ if (decryptedDataXor.startsWith("{") || decryptedDataXor.startsWith("[")) {
780
+ return JSON.parse(decryptedDataXor);
781
+ }
782
+ throw new Error("Unsupported or corrupt encryption format");
749
783
  } catch (err) {
750
784
  console.error(`Vault Read Error [${path.basename(filePath)}]:`, err.message);
751
785
  return defaultValue;
@@ -756,12 +790,14 @@ var init_crypto = __esm({
756
790
  const dir = path.dirname(filePath);
757
791
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
758
792
  const jsonData = JSON.stringify(data, null, 2);
759
- const encryptedData = xorTransform(jsonData);
760
- fs.writeFileSync(filePath, encryptedData);
793
+ const encrypted = encryptAes(jsonData);
794
+ fs.writeFileSync(filePath, encrypted, "utf8");
761
795
  } catch (err) {
762
796
  console.error(`Vault Write Error [${path.basename(filePath)}]:`, err.message);
763
797
  }
764
798
  };
799
+ readAesEncryptedJson = readEncryptedJson;
800
+ writeAesEncryptedJson = writeEncryptedJson;
765
801
  }
766
802
  });
767
803
 
@@ -781,6 +817,7 @@ __export(paths_exports, {
781
817
  import os2 from "os";
782
818
  import path2 from "path";
783
819
  import fs2 from "fs";
820
+ import crypto2 from "crypto";
784
821
  var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE;
785
822
  var init_paths = __esm({
786
823
  "src/utils/paths.js"() {
@@ -789,10 +826,27 @@ var init_paths = __esm({
789
826
  externalDir = null;
790
827
  try {
791
828
  if (fs2.existsSync(SETTINGS_FILE)) {
792
- const settings = JSON.parse(fs2.readFileSync(SETTINGS_FILE, "utf8"));
793
- const sys = settings.systemSettings || {};
794
- if (sys.useExternalData && sys.externalDataPath) {
795
- externalDir = sys.externalDataPath;
829
+ const fileContent = fs2.readFileSync(SETTINGS_FILE, "utf8").trim();
830
+ let settings;
831
+ if (fileContent.startsWith("{")) {
832
+ settings = JSON.parse(fileContent);
833
+ } else {
834
+ const parts = fileContent.split(":");
835
+ if (parts.length === 2) {
836
+ const iv = Buffer.from(parts[0], "hex");
837
+ const ciphertext = parts[1];
838
+ const key = crypto2.createHash("sha256").update("fluxflow-cli-sanctuary-key").digest();
839
+ const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
840
+ let decrypted = decipher.update(ciphertext, "hex", "utf8");
841
+ decrypted += decipher.final("utf8");
842
+ settings = JSON.parse(decrypted);
843
+ }
844
+ }
845
+ if (settings) {
846
+ const sys = settings.systemSettings || {};
847
+ if (sys.useExternalData && sys.externalDataPath) {
848
+ externalDir = sys.externalDataPath;
849
+ }
796
850
  }
797
851
  }
798
852
  } catch (e) {
@@ -801,7 +855,7 @@ var init_paths = __esm({
801
855
  LOGS_DIR = path2.join(DATA_DIR, "logs");
802
856
  SECRET_DIR = path2.join(DATA_DIR, "secret");
803
857
  HISTORY_FILE = path2.join(SECRET_DIR, "history.json");
804
- USAGE_FILE = path2.join(SECRET_DIR, "usage.json");
858
+ USAGE_FILE = path2.join(FLUXFLOW_DIR, "usage.json");
805
859
  MEMORIES_FILE = path2.join(SECRET_DIR, "memories.json");
806
860
  TEMP_MEM_FILE = path2.join(SECRET_DIR, "memory-temp.json");
807
861
  }
@@ -1095,7 +1149,7 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString([], { year:
1095
1149
  import fs5 from "fs-extra";
1096
1150
  import path4 from "path";
1097
1151
  import { nanoid } from "nanoid";
1098
- var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, getTruncatedHistory;
1152
+ var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, parseCustomDate, cleanupLogFile, cleanupOldLogs, getTruncatedHistory;
1099
1153
  var init_history = __esm({
1100
1154
  "src/utils/history.js"() {
1101
1155
  init_crypto();
@@ -1183,6 +1237,125 @@ var init_history = __esm({
1183
1237
  }
1184
1238
  return deletedCount;
1185
1239
  };
1240
+ parseCustomDate = (dateStr) => {
1241
+ const cleanStr = dateStr.replace(/[\[\]]/g, "").trim();
1242
+ const parsed = new Date(cleanStr);
1243
+ if (!isNaN(parsed.getTime())) return parsed.getTime();
1244
+ const parts = cleanStr.split(/,\s*|\s+/);
1245
+ if (parts.length === 0) return null;
1246
+ const datePart = parts[0];
1247
+ const timePart = parts[1] || "";
1248
+ const ampm = parts[2] || "";
1249
+ const dateNums = datePart.split(/[-/.]/).map(Number);
1250
+ if (dateNums.length !== 3) return null;
1251
+ let year, month, day;
1252
+ if (dateNums[0] > 1e3) {
1253
+ year = dateNums[0];
1254
+ month = dateNums[1];
1255
+ day = dateNums[2];
1256
+ } else if (dateNums[2] > 1e3) {
1257
+ year = dateNums[2];
1258
+ if (dateNums[0] > 12) {
1259
+ day = dateNums[0];
1260
+ month = dateNums[1];
1261
+ } else if (dateNums[1] > 12) {
1262
+ day = dateNums[1];
1263
+ month = dateNums[0];
1264
+ } else {
1265
+ month = dateNums[0];
1266
+ day = dateNums[1];
1267
+ }
1268
+ } else {
1269
+ return null;
1270
+ }
1271
+ let hours = 0, minutes = 0, seconds = 0;
1272
+ if (timePart) {
1273
+ const timeNums = timePart.split(":").map(Number);
1274
+ hours = timeNums[0] || 0;
1275
+ minutes = timeNums[1] || 0;
1276
+ seconds = timeNums[2] || 0;
1277
+ if (ampm.toLowerCase() === "pm" && hours < 12) {
1278
+ hours += 12;
1279
+ } else if (ampm.toLowerCase() === "am" && hours === 12) {
1280
+ hours = 0;
1281
+ }
1282
+ }
1283
+ const d = new Date(year, month - 1, day, hours, minutes, seconds);
1284
+ return isNaN(d.getTime()) ? null : d.getTime();
1285
+ };
1286
+ cleanupLogFile = async (filePath) => {
1287
+ try {
1288
+ if (!await fs5.pathExists(filePath)) return;
1289
+ const content = await fs5.readFile(filePath, "utf8");
1290
+ if (!content.trim()) return;
1291
+ const lines = content.split("\n");
1292
+ const entries = [];
1293
+ let currentEntry = null;
1294
+ const entryStartRegex = /^\s*(?:DEBUG|ERROR|SEARCH|PUPPETEER)\b/i;
1295
+ for (const line of lines) {
1296
+ if (entryStartRegex.test(line)) {
1297
+ if (currentEntry) {
1298
+ entries.push(currentEntry);
1299
+ }
1300
+ currentEntry = { header: line, body: [] };
1301
+ } else {
1302
+ if (currentEntry) {
1303
+ currentEntry.body.push(line);
1304
+ } else {
1305
+ entries.push({ header: line, body: [] });
1306
+ }
1307
+ }
1308
+ }
1309
+ if (currentEntry) {
1310
+ entries.push(currentEntry);
1311
+ }
1312
+ const threshold = 7 * 24 * 60 * 60 * 1e3;
1313
+ const now = Date.now();
1314
+ const keptEntries = [];
1315
+ const timestampRegex = /(\d{1,4}[-/.]\d{1,4}[-/.]\d{1,4}(?:,\s*|\s+)?(?:\d{1,2}:\d{2}:\d{2}(?:\s*[aApP][mM])?)?)/;
1316
+ for (const entry of entries) {
1317
+ const entryText = entry.header + (entry.body.length > 0 ? "\n" + entry.body.join("\n") : "");
1318
+ const match = entryText.match(timestampRegex);
1319
+ if (match) {
1320
+ const timeMs = parseCustomDate(match[1]);
1321
+ if (timeMs && now - timeMs > threshold) {
1322
+ continue;
1323
+ }
1324
+ }
1325
+ keptEntries.push(entryText);
1326
+ }
1327
+ const finalContent = keptEntries.join("\n").trim();
1328
+ if (finalContent) {
1329
+ await fs5.writeFile(filePath, finalContent + "\n", "utf8");
1330
+ } else {
1331
+ await fs5.writeFile(filePath, "", "utf8");
1332
+ }
1333
+ } catch (e) {
1334
+ }
1335
+ };
1336
+ cleanupOldLogs = async (logsDir) => {
1337
+ try {
1338
+ if (!await fs5.pathExists(logsDir)) return;
1339
+ const cleanRecursive = async (dir) => {
1340
+ const files = await fs5.readdir(dir);
1341
+ for (const file of files) {
1342
+ const fullPath = path4.join(dir, file);
1343
+ const stat = await fs5.stat(fullPath);
1344
+ if (stat.isDirectory()) {
1345
+ await cleanRecursive(fullPath);
1346
+ const subFiles = await fs5.readdir(fullPath);
1347
+ if (subFiles.length === 0) {
1348
+ await fs5.remove(fullPath);
1349
+ }
1350
+ } else if (file.endsWith(".log")) {
1351
+ await cleanupLogFile(fullPath);
1352
+ }
1353
+ }
1354
+ };
1355
+ await cleanRecursive(logsDir);
1356
+ } catch (e) {
1357
+ }
1358
+ };
1186
1359
  getTruncatedHistory = (history, exchangesToRemove = 4) => {
1187
1360
  if (history.length <= 1) return history;
1188
1361
  const welcome = history[0];
@@ -1197,10 +1370,11 @@ var init_history = __esm({
1197
1370
  // src/utils/usage.js
1198
1371
  import fs6 from "fs-extra";
1199
1372
  import path5 from "path";
1200
- var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, checkImageQuota, getImageQuotaStats, recordImageGeneration;
1373
+ var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
1201
1374
  var init_usage = __esm({
1202
1375
  "src/utils/usage.js"() {
1203
1376
  init_paths();
1377
+ init_crypto();
1204
1378
  cachedUsage = null;
1205
1379
  writeTimeout = null;
1206
1380
  lastWriteTime = 0;
@@ -1220,7 +1394,13 @@ var init_usage = __esm({
1220
1394
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1221
1395
  try {
1222
1396
  if (await fs6.exists(USAGE_FILE)) {
1223
- const data = await fs6.readJson(USAGE_FILE);
1397
+ const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
1398
+ let data;
1399
+ if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
1400
+ data = JSON.parse(rawContent);
1401
+ } else {
1402
+ data = JSON.parse(decryptAes(rawContent));
1403
+ }
1224
1404
  if (data && data.date === today && data.stats) {
1225
1405
  const mergedStats = { ...defaultStats, ...data.stats };
1226
1406
  if (!Array.isArray(mergedStats.imageCalls)) {
@@ -1243,7 +1423,12 @@ var init_usage = __esm({
1243
1423
  let diskData = null;
1244
1424
  try {
1245
1425
  if (await fs6.exists(USAGE_FILE)) {
1246
- diskData = await fs6.readJson(USAGE_FILE);
1426
+ const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
1427
+ if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
1428
+ diskData = JSON.parse(rawContent);
1429
+ } else {
1430
+ diskData = JSON.parse(decryptAes(rawContent));
1431
+ }
1247
1432
  }
1248
1433
  } catch (e) {
1249
1434
  }
@@ -1267,7 +1452,8 @@ var init_usage = __esm({
1267
1452
  }
1268
1453
  }
1269
1454
  const tempFile = USAGE_FILE + ".tmp";
1270
- await fs6.writeJson(tempFile, cachedUsage, { spaces: 2 });
1455
+ const encryptedStr = encryptAes(JSON.stringify(cachedUsage, null, 2));
1456
+ await fs6.writeFile(tempFile, encryptedStr, "utf8");
1271
1457
  const fd = await fs6.open(tempFile, "r+");
1272
1458
  await fs6.fsync(fd);
1273
1459
  await fs6.close(fd);
@@ -1346,6 +1532,57 @@ var init_usage = __esm({
1346
1532
  }
1347
1533
  return true;
1348
1534
  };
1535
+ getImageQuotaLimit = (imageCalls, now) => {
1536
+ const hourMs = 60 * 60 * 1e3;
1537
+ if (!imageCalls || imageCalls.length === 0) {
1538
+ return 0.025;
1539
+ }
1540
+ const oldestTimestamp = imageCalls[0].timestamp;
1541
+ const startTime = Math.min(oldestTimestamp, now - 24 * hourMs);
1542
+ const windows = [];
1543
+ let currentEnd = now;
1544
+ while (currentEnd > startTime) {
1545
+ windows.unshift({ start: currentEnd - hourMs, end: currentEnd });
1546
+ currentEnd -= hourMs;
1547
+ }
1548
+ const history = [];
1549
+ for (const win of windows) {
1550
+ const winCalls = imageCalls.filter((c) => c.timestamp >= win.start && c.timestamp < win.end);
1551
+ const usage = winCalls.reduce((sum, c) => sum + c.cost, 0);
1552
+ let limit = 0.025;
1553
+ if (history.length > 0) {
1554
+ const prev1 = history[history.length - 1];
1555
+ let consecutiveMax = false;
1556
+ if (history.length >= 2) {
1557
+ const prev2 = history[history.length - 2];
1558
+ if (prev1.ratio >= 0.8 && prev2.ratio >= 0.8) {
1559
+ consecutiveMax = true;
1560
+ }
1561
+ }
1562
+ if (consecutiveMax) {
1563
+ limit = 0.015;
1564
+ } else {
1565
+ const prevLimit = prev1.limit;
1566
+ const prevRatio = prev1.ratio;
1567
+ if (prevRatio >= 0.8) {
1568
+ limit = prevLimit === 0.015 ? 0.015 : prevLimit;
1569
+ } else if (prevRatio < 0.4) {
1570
+ limit = Math.min(0.025, prevLimit + 5e-3);
1571
+ } else if (prevRatio >= 0.4 && prevRatio < 0.6) {
1572
+ limit = Math.min(0.025, prevLimit + 4e-3);
1573
+ } else {
1574
+ limit = Math.min(0.025, prevLimit + 2e-3);
1575
+ }
1576
+ }
1577
+ }
1578
+ const ratio = limit > 0 ? usage / limit : 0;
1579
+ history.push({ limit, usage, ratio });
1580
+ }
1581
+ if (history.length > 0) {
1582
+ return history[history.length - 1].limit;
1583
+ }
1584
+ return 0.025;
1585
+ };
1349
1586
  checkImageQuota = async (settings) => {
1350
1587
  const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High" };
1351
1588
  if (imageSettings.keyType !== "Default") return true;
@@ -1367,7 +1604,8 @@ var init_usage = __esm({
1367
1604
  const oneHourAgo = now - 60 * 60 * 1e3;
1368
1605
  const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
1369
1606
  const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
1370
- return totalSpent + currentCost <= 0.02;
1607
+ const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1608
+ return totalSpent + currentCost <= currentLimit;
1371
1609
  };
1372
1610
  getImageQuotaStats = async () => {
1373
1611
  const stats = await getDailyUsage();
@@ -1378,7 +1616,8 @@ var init_usage = __esm({
1378
1616
  const oneHourAgo = now - 60 * 60 * 1e3;
1379
1617
  const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
1380
1618
  const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
1381
- const remaining = Math.max(0, 0.02 - totalSpent);
1619
+ const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1620
+ const remaining = Math.max(0, currentLimit - totalSpent);
1382
1621
  let nextResetMin = 0;
1383
1622
  let reclaimCost = 0;
1384
1623
  if (activeCalls.length > 0) {
@@ -1392,7 +1631,8 @@ var init_usage = __esm({
1392
1631
  remaining,
1393
1632
  activeCallsCount: activeCalls.length,
1394
1633
  nextResetMin,
1395
- reclaimCost
1634
+ reclaimCost,
1635
+ limit: currentLimit
1396
1636
  };
1397
1637
  };
1398
1638
  recordImageGeneration = async (settings) => {
@@ -2667,6 +2907,7 @@ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
2667
2907
  var init_settings = __esm({
2668
2908
  "src/utils/settings.js"() {
2669
2909
  init_paths();
2910
+ init_crypto();
2670
2911
  DEFAULT_SETTINGS = {
2671
2912
  mode: "Flux",
2672
2913
  thinkingLevel: "Medium",
@@ -2704,14 +2945,14 @@ var init_settings = __esm({
2704
2945
  let settingsObj = { ...DEFAULT_SETTINGS };
2705
2946
  try {
2706
2947
  if (await fs14.exists(SETTINGS_FILE)) {
2707
- const saved = await fs14.readJson(SETTINGS_FILE);
2948
+ const saved = readAesEncryptedJson(SETTINGS_FILE);
2708
2949
  if (saved.imageSettings && saved.imageSettings.apiKey) {
2709
2950
  try {
2710
2951
  const legacyKey = saved.imageSettings.apiKey;
2711
2952
  const { saveSecret: saveSecret2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
2712
2953
  await saveSecret2("POLLINATIONS_API_KEY", legacyKey);
2713
2954
  saved.imageSettings.apiKey = "";
2714
- await fs14.writeJson(SETTINGS_FILE, saved, { spaces: 2 });
2955
+ writeAesEncryptedJson(SETTINGS_FILE, saved);
2715
2956
  } catch (e) {
2716
2957
  }
2717
2958
  }
@@ -2738,7 +2979,7 @@ var init_settings = __esm({
2738
2979
  if (settingsObj.showFullThinking === false) {
2739
2980
  settingsObj.showFullThinking = true;
2740
2981
  try {
2741
- await fs14.writeJson(SETTINGS_FILE, settingsObj, { spaces: 2 });
2982
+ writeAesEncryptedJson(SETTINGS_FILE, settingsObj);
2742
2983
  } catch (e) {
2743
2984
  }
2744
2985
  }
@@ -2780,7 +3021,7 @@ var init_settings = __esm({
2780
3021
  updated.imageSettings = { ...updated.imageSettings, apiKey: "" };
2781
3022
  }
2782
3023
  await fs14.ensureDir(path13.dirname(SETTINGS_FILE));
2783
- await fs14.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
3024
+ writeAesEncryptedJson(SETTINGS_FILE, updated);
2784
3025
  return true;
2785
3026
  } catch (err) {
2786
3027
  console.error("Failed to save settings:", err);
@@ -2790,6 +3031,14 @@ var init_settings = __esm({
2790
3031
  }
2791
3032
  });
2792
3033
 
3034
+ // src/utils/fallback_key.js
3035
+ var FALLBACK_IMAGE_KEY;
3036
+ var init_fallback_key = __esm({
3037
+ "src/utils/fallback_key.js"() {
3038
+ FALLBACK_IMAGE_KEY = "pk_oH8wkUdQaQK1cmst";
3039
+ }
3040
+ });
3041
+
2793
3042
  // src/tools/generate_image.js
2794
3043
  import fs15 from "fs-extra";
2795
3044
  import path14 from "path";
@@ -2799,6 +3048,7 @@ var init_generate_image = __esm({
2799
3048
  init_arg_parser();
2800
3049
  init_settings();
2801
3050
  init_usage();
3051
+ init_fallback_key();
2802
3052
  injectPngMetadata = (buffer, metadata = {}) => {
2803
3053
  try {
2804
3054
  if (buffer.length < 8 || buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) {
@@ -2871,7 +3121,7 @@ var init_generate_image = __esm({
2871
3121
  return `ERROR: Insufficient Quota for selected quality. Either tell user reduce quality or wait for next refresh cycle (${stats.nextResetMin || 60}m).`;
2872
3122
  }
2873
3123
  const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High", apiKey: "" };
2874
- const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey : "pk_5i7Doib5fATyAN4i";
3124
+ const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey : FALLBACK_IMAGE_KEY;
2875
3125
  const qualityMap = {
2876
3126
  "Low": "flux",
2877
3127
  "Low-High": "zimage",
@@ -3162,17 +3412,6 @@ ${originalTextProcessed.length > 1500 ? "... (truncated) ...\n\n" : ""}
3162
3412
  if (fullContent) {
3163
3413
  finalSynthesis = fullContent;
3164
3414
  if (lastUsage) await addToUsage("tokens", lastUsage.totalTokenCount || 0);
3165
- const date = (/* @__PURE__ */ new Date()).toLocaleString();
3166
- const janitorLogDir = path15.join(LOGS_DIR, "janitor");
3167
- if (!fs16.existsSync(janitorLogDir)) fs16.mkdirSync(janitorLogDir, { recursive: true });
3168
- fs16.appendFileSync(path15.join(janitorLogDir, "debug.log"), `
3169
-
3170
- ---------------------------------------------------
3171
-
3172
-
3173
- DEBUG [${date}]: ${finalSynthesis}
3174
-
3175
- `);
3176
3415
  } else {
3177
3416
  throw new Error("No synthesis generated by Janitor.");
3178
3417
  }
@@ -3507,6 +3746,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3507
3746
  let isDedupeActive = false;
3508
3747
  while (retryCount <= MAX_RETRIES && inStreamRetryCount <= MAX_RETRIES && !success && !TERMINATION_SIGNAL) {
3509
3748
  try {
3749
+ turnText = "";
3510
3750
  if (isInitialAttempt) {
3511
3751
  if (process.stdout.isTTY) {
3512
3752
  process.stdout.write(`\x1B]0;Working...\x07`);
@@ -3623,7 +3863,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3623
3863
  turnText += chunk.text;
3624
3864
  yield { type: "text", content: chunk.text };
3625
3865
  }
3626
- const signalSafeText2 = getSanitizedText(turnText);
3866
+ const signalSafeText3 = getSanitizedText(turnText);
3627
3867
  const toolContext = getActiveToolContext(turnText);
3628
3868
  if (toolContext.inside) {
3629
3869
  if (!lastToolEventTime) lastToolEventTime = Date.now();
@@ -3649,7 +3889,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3649
3889
  const potentialTool = NORMALIZE_MAP[rawToolName] || rawToolName;
3650
3890
  const partialArgs = toolContext.args || "";
3651
3891
  let detail = null;
3652
- if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword"].includes(potentialTool)) {
3892
+ if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword", "generate_image"].includes(potentialTool)) {
3653
3893
  const pArgs = parseArgs(partialArgs);
3654
3894
  const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
3655
3895
  const keyword = pArgs.keyword;
@@ -3717,7 +3957,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3717
3957
  await new Promise((resolve) => setTimeout(resolve, 3e3));
3718
3958
  break;
3719
3959
  }
3720
- const responseContent = signalSafeText2.trim();
3960
+ const responseContent = signalSafeText3.trim();
3721
3961
  const respSentences = responseContent.split(/[.!?]\s+/);
3722
3962
  const uniqueRespSentences = new Set(respSentences);
3723
3963
  const respRepetitionRatio = respSentences.length > 10 ? (respSentences.length - uniqueRespSentences.size) / respSentences.length : 0;
@@ -3993,6 +4233,13 @@ ${boxBottom}` };
3993
4233
  dedupeBuffer = "";
3994
4234
  }
3995
4235
  if (TERMINATION_SIGNAL) break;
4236
+ const signalSafeText2 = (turnText || "").trim();
4237
+ const hasFinish2 = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(signalSafeText2.toLowerCase());
4238
+ const hasContinue = /\[\s*(turn\s*:)?\s*continue\s*\]/i.test(signalSafeText2.toLowerCase());
4239
+ const didCallTool = toolResults.length > 0 || lastToolSniffed !== null;
4240
+ if (!hasFinish2 && !hasContinue && !didCallTool && signalSafeText2.length > 0) {
4241
+ throw new Error("Silent stream cutoff (500): Model stream closed cleanly but missing turn finish/continue signals.");
4242
+ }
3996
4243
  success = true;
3997
4244
  await incrementUsage("agent");
3998
4245
  } catch (err) {
@@ -4021,6 +4268,7 @@ ${boxBottom}` };
4021
4268
  const agentErrDir = path15.join(LOGS_DIR, "agent");
4022
4269
  if (!fs16.existsSync(agentErrDir)) fs16.mkdirSync(agentErrDir, { recursive: true });
4023
4270
  fs16.appendFileSync(path15.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errLog}
4271
+ DEBUG STATE: turnText='${turnText}', length=${turnText.trim().length}, inStreamRetryCount=${inStreamRetryCount}, retryCount=${retryCount}, isDedupeActive=${isDedupeActive}, dedupeBuffer='${dedupeBuffer}'
4024
4272
 
4025
4273
  ----------------------------------------------------------------------
4026
4274
 
@@ -4750,6 +4998,7 @@ function App() {
4750
4998
  if (saved.systemSettings?.autoDeleteHistory) {
4751
4999
  cleanupOldHistory(saved.systemSettings.autoDeleteHistory);
4752
5000
  }
5001
+ cleanupOldLogs(LOGS_DIR);
4753
5002
  performVersionCheck(false, freshSettings);
4754
5003
  await initUsage();
4755
5004
  setIsInitializing(false);
@@ -4850,7 +5099,7 @@ function App() {
4850
5099
  cmd: "key",
4851
5100
  desc: "Set API key strategy",
4852
5101
  subs: [
4853
- { cmd: "default", desc: "Default (Quota: 20 credits/hr)" },
5102
+ { cmd: "default", desc: "Default (Quota: Dynamic 25 max/hr)" },
4854
5103
  { cmd: "custom", desc: "Custom Key" }
4855
5104
  ]
4856
5105
  },
@@ -5055,7 +5304,7 @@ ${hintText}`, color: "magenta" }];
5055
5304
  id: Date.now(),
5056
5305
  role: "system",
5057
5306
  isImageStats: true,
5058
- text: `\u2022 Hourly Limit: 20 credits
5307
+ text: `\u2022 Hourly Limit: ${Number((stats.limit * 1e3).toFixed(0))} credits
5059
5308
  \u2022 Spent (Last 1hr): ${Number((stats.totalSpent * 1e3).toFixed(0))} credits
5060
5309
  \u2022 Remaining: ${Number((stats.remaining * 1e3).toFixed(0))} credits
5061
5310
  \u2022 Requests (Last 1hr): ${stats.activeCallsCount} requests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.10.8",
3
+ "version": "1.11.0",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",