fluxflow-cli 1.10.10 → 1.11.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/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();
@@ -1117,7 +1171,7 @@ var init_history = __esm({
1117
1171
  loadHistory = async () => {
1118
1172
  if (await fs5.pathExists(HISTORY_FILE)) {
1119
1173
  try {
1120
- return await fs5.readJson(HISTORY_FILE);
1174
+ return readEncryptedJson(HISTORY_FILE, {});
1121
1175
  } catch (e) {
1122
1176
  return {};
1123
1177
  }
@@ -1135,8 +1189,7 @@ var init_history = __esm({
1135
1189
  messages: persistentMessages,
1136
1190
  updatedAt: Date.now()
1137
1191
  };
1138
- await fs5.ensureDir(path4.dirname(HISTORY_FILE));
1139
- await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
1192
+ writeEncryptedJson(HISTORY_FILE, history);
1140
1193
  });
1141
1194
  };
1142
1195
  saveChatTitle = async (id, title) => {
@@ -1148,15 +1201,14 @@ var init_history = __esm({
1148
1201
  } else {
1149
1202
  history[id] = { name: title, messages: [], updatedAt: Date.now() };
1150
1203
  }
1151
- await fs5.ensureDir(path4.dirname(HISTORY_FILE));
1152
- await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
1204
+ writeEncryptedJson(HISTORY_FILE, history);
1153
1205
  });
1154
1206
  };
1155
1207
  deleteChat = async (id) => {
1156
1208
  return withLock(async () => {
1157
1209
  const history = await loadHistory();
1158
1210
  delete history[id];
1159
- await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
1211
+ writeEncryptedJson(HISTORY_FILE, history);
1160
1212
  const temp = readEncryptedJson(TEMP_MEM_FILE, {});
1161
1213
  if (temp[id]) {
1162
1214
  delete temp[id];
@@ -1183,6 +1235,125 @@ var init_history = __esm({
1183
1235
  }
1184
1236
  return deletedCount;
1185
1237
  };
1238
+ parseCustomDate = (dateStr) => {
1239
+ const cleanStr = dateStr.replace(/[\[\]]/g, "").trim();
1240
+ const parsed = new Date(cleanStr);
1241
+ if (!isNaN(parsed.getTime())) return parsed.getTime();
1242
+ const parts = cleanStr.split(/,\s*|\s+/);
1243
+ if (parts.length === 0) return null;
1244
+ const datePart = parts[0];
1245
+ const timePart = parts[1] || "";
1246
+ const ampm = parts[2] || "";
1247
+ const dateNums = datePart.split(/[-/.]/).map(Number);
1248
+ if (dateNums.length !== 3) return null;
1249
+ let year, month, day;
1250
+ if (dateNums[0] > 1e3) {
1251
+ year = dateNums[0];
1252
+ month = dateNums[1];
1253
+ day = dateNums[2];
1254
+ } else if (dateNums[2] > 1e3) {
1255
+ year = dateNums[2];
1256
+ if (dateNums[0] > 12) {
1257
+ day = dateNums[0];
1258
+ month = dateNums[1];
1259
+ } else if (dateNums[1] > 12) {
1260
+ day = dateNums[1];
1261
+ month = dateNums[0];
1262
+ } else {
1263
+ month = dateNums[0];
1264
+ day = dateNums[1];
1265
+ }
1266
+ } else {
1267
+ return null;
1268
+ }
1269
+ let hours = 0, minutes = 0, seconds = 0;
1270
+ if (timePart) {
1271
+ const timeNums = timePart.split(":").map(Number);
1272
+ hours = timeNums[0] || 0;
1273
+ minutes = timeNums[1] || 0;
1274
+ seconds = timeNums[2] || 0;
1275
+ if (ampm.toLowerCase() === "pm" && hours < 12) {
1276
+ hours += 12;
1277
+ } else if (ampm.toLowerCase() === "am" && hours === 12) {
1278
+ hours = 0;
1279
+ }
1280
+ }
1281
+ const d = new Date(year, month - 1, day, hours, minutes, seconds);
1282
+ return isNaN(d.getTime()) ? null : d.getTime();
1283
+ };
1284
+ cleanupLogFile = async (filePath) => {
1285
+ try {
1286
+ if (!await fs5.pathExists(filePath)) return;
1287
+ const content = await fs5.readFile(filePath, "utf8");
1288
+ if (!content.trim()) return;
1289
+ const lines = content.split("\n");
1290
+ const entries = [];
1291
+ let currentEntry = null;
1292
+ const entryStartRegex = /^\s*(?:DEBUG|ERROR|SEARCH|PUPPETEER)\b/i;
1293
+ for (const line of lines) {
1294
+ if (entryStartRegex.test(line)) {
1295
+ if (currentEntry) {
1296
+ entries.push(currentEntry);
1297
+ }
1298
+ currentEntry = { header: line, body: [] };
1299
+ } else {
1300
+ if (currentEntry) {
1301
+ currentEntry.body.push(line);
1302
+ } else {
1303
+ entries.push({ header: line, body: [] });
1304
+ }
1305
+ }
1306
+ }
1307
+ if (currentEntry) {
1308
+ entries.push(currentEntry);
1309
+ }
1310
+ const threshold = 7 * 24 * 60 * 60 * 1e3;
1311
+ const now = Date.now();
1312
+ const keptEntries = [];
1313
+ const timestampRegex = /(\d{1,4}[-/.]\d{1,4}[-/.]\d{1,4}(?:,\s*|\s+)?(?:\d{1,2}:\d{2}:\d{2}(?:\s*[aApP][mM])?)?)/;
1314
+ for (const entry of entries) {
1315
+ const entryText = entry.header + (entry.body.length > 0 ? "\n" + entry.body.join("\n") : "");
1316
+ const match = entryText.match(timestampRegex);
1317
+ if (match) {
1318
+ const timeMs = parseCustomDate(match[1]);
1319
+ if (timeMs && now - timeMs > threshold) {
1320
+ continue;
1321
+ }
1322
+ }
1323
+ keptEntries.push(entryText);
1324
+ }
1325
+ const finalContent = keptEntries.join("\n").trim();
1326
+ if (finalContent) {
1327
+ await fs5.writeFile(filePath, finalContent + "\n", "utf8");
1328
+ } else {
1329
+ await fs5.writeFile(filePath, "", "utf8");
1330
+ }
1331
+ } catch (e) {
1332
+ }
1333
+ };
1334
+ cleanupOldLogs = async (logsDir) => {
1335
+ try {
1336
+ if (!await fs5.pathExists(logsDir)) return;
1337
+ const cleanRecursive = async (dir) => {
1338
+ const files = await fs5.readdir(dir);
1339
+ for (const file of files) {
1340
+ const fullPath = path4.join(dir, file);
1341
+ const stat = await fs5.stat(fullPath);
1342
+ if (stat.isDirectory()) {
1343
+ await cleanRecursive(fullPath);
1344
+ const subFiles = await fs5.readdir(fullPath);
1345
+ if (subFiles.length === 0) {
1346
+ await fs5.remove(fullPath);
1347
+ }
1348
+ } else if (file.endsWith(".log")) {
1349
+ await cleanupLogFile(fullPath);
1350
+ }
1351
+ }
1352
+ };
1353
+ await cleanRecursive(logsDir);
1354
+ } catch (e) {
1355
+ }
1356
+ };
1186
1357
  getTruncatedHistory = (history, exchangesToRemove = 4) => {
1187
1358
  if (history.length <= 1) return history;
1188
1359
  const welcome = history[0];
@@ -1197,10 +1368,11 @@ var init_history = __esm({
1197
1368
  // src/utils/usage.js
1198
1369
  import fs6 from "fs-extra";
1199
1370
  import path5 from "path";
1200
- var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, checkImageQuota, getImageQuotaStats, recordImageGeneration;
1371
+ var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, getImageQuotaBuckets, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
1201
1372
  var init_usage = __esm({
1202
1373
  "src/utils/usage.js"() {
1203
1374
  init_paths();
1375
+ init_crypto();
1204
1376
  cachedUsage = null;
1205
1377
  writeTimeout = null;
1206
1378
  lastWriteTime = 0;
@@ -1220,7 +1392,13 @@ var init_usage = __esm({
1220
1392
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1221
1393
  try {
1222
1394
  if (await fs6.exists(USAGE_FILE)) {
1223
- const data = await fs6.readJson(USAGE_FILE);
1395
+ const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
1396
+ let data;
1397
+ if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
1398
+ data = JSON.parse(rawContent);
1399
+ } else {
1400
+ data = JSON.parse(decryptAes(rawContent));
1401
+ }
1224
1402
  if (data && data.date === today && data.stats) {
1225
1403
  const mergedStats = { ...defaultStats, ...data.stats };
1226
1404
  if (!Array.isArray(mergedStats.imageCalls)) {
@@ -1243,7 +1421,12 @@ var init_usage = __esm({
1243
1421
  let diskData = null;
1244
1422
  try {
1245
1423
  if (await fs6.exists(USAGE_FILE)) {
1246
- diskData = await fs6.readJson(USAGE_FILE);
1424
+ const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
1425
+ if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
1426
+ diskData = JSON.parse(rawContent);
1427
+ } else {
1428
+ diskData = JSON.parse(decryptAes(rawContent));
1429
+ }
1247
1430
  }
1248
1431
  } catch (e) {
1249
1432
  }
@@ -1267,7 +1450,8 @@ var init_usage = __esm({
1267
1450
  }
1268
1451
  }
1269
1452
  const tempFile = USAGE_FILE + ".tmp";
1270
- await fs6.writeJson(tempFile, cachedUsage, { spaces: 2 });
1453
+ const encryptedStr = encryptAes(JSON.stringify(cachedUsage, null, 2));
1454
+ await fs6.writeFile(tempFile, encryptedStr, "utf8");
1271
1455
  const fd = await fs6.open(tempFile, "r+");
1272
1456
  await fs6.fsync(fd);
1273
1457
  await fs6.close(fd);
@@ -1346,6 +1530,111 @@ var init_usage = __esm({
1346
1530
  }
1347
1531
  return true;
1348
1532
  };
1533
+ getImageQuotaBuckets = (imageCalls) => {
1534
+ const hourMs = 60 * 60 * 1e3;
1535
+ if (!imageCalls || imageCalls.length === 0) {
1536
+ return [];
1537
+ }
1538
+ const sortedCalls = [...imageCalls].sort((a, b) => a.timestamp - b.timestamp);
1539
+ const buckets = [];
1540
+ for (const call of sortedCalls) {
1541
+ if (buckets.length > 0) {
1542
+ const lastBucket = buckets[buckets.length - 1];
1543
+ if (call.timestamp >= lastBucket.start && call.timestamp < lastBucket.end) {
1544
+ lastBucket.calls.push(call);
1545
+ lastBucket.spent += call.cost;
1546
+ continue;
1547
+ }
1548
+ }
1549
+ buckets.push({
1550
+ start: call.timestamp,
1551
+ end: call.timestamp + hourMs,
1552
+ calls: [call],
1553
+ spent: call.cost
1554
+ });
1555
+ }
1556
+ return buckets;
1557
+ };
1558
+ getImageQuotaLimit = (imageCalls, now) => {
1559
+ const hourMs = 60 * 60 * 1e3;
1560
+ if (!imageCalls || imageCalls.length === 0) {
1561
+ return 0.025;
1562
+ }
1563
+ const buckets = getImageQuotaBuckets(imageCalls);
1564
+ if (buckets.length === 0) {
1565
+ return 0.025;
1566
+ }
1567
+ const history = [];
1568
+ for (const bucket of buckets) {
1569
+ let limit = 0.025;
1570
+ if (history.length > 0) {
1571
+ const prev1 = history[history.length - 1];
1572
+ let consecutiveMax = false;
1573
+ if (history.length >= 2) {
1574
+ const prev2 = history[history.length - 2];
1575
+ if (prev1.ratio >= 0.8 && prev2.ratio >= 0.8) {
1576
+ consecutiveMax = true;
1577
+ }
1578
+ }
1579
+ if (consecutiveMax) {
1580
+ limit = 0.015;
1581
+ } else {
1582
+ const prevLimit2 = prev1.limit;
1583
+ const prevRatio2 = prev1.ratio;
1584
+ if (prevRatio2 >= 0.8) {
1585
+ limit = prevLimit2 === 0.015 ? 0.015 : prevLimit2;
1586
+ } else if (prevRatio2 < 0.4) {
1587
+ limit = Math.min(0.025, prevLimit2 + 5e-3);
1588
+ } else if (prevRatio2 >= 0.4 && prevRatio2 < 0.6) {
1589
+ limit = Math.min(0.025, prevLimit2 + 4e-3);
1590
+ } else {
1591
+ limit = Math.min(0.025, prevLimit2 + 2e-3);
1592
+ }
1593
+ }
1594
+ }
1595
+ const ratio = limit > 0 ? bucket.spent / limit : 0;
1596
+ history.push({ limit, spent: bucket.spent, ratio });
1597
+ }
1598
+ const lastBucket = buckets[buckets.length - 1];
1599
+ if (now < lastBucket.end) {
1600
+ return history[history.length - 1].limit;
1601
+ }
1602
+ let currentLimit = history[history.length - 1].limit;
1603
+ let prevLimit = currentLimit;
1604
+ let prevRatio = history[history.length - 1].ratio;
1605
+ let simulatedTime = lastBucket.end;
1606
+ let consecutiveMaxCount = 0;
1607
+ for (let k = history.length - 1; k >= 0; k--) {
1608
+ if (history[k].ratio >= 0.8) {
1609
+ consecutiveMaxCount++;
1610
+ } else {
1611
+ break;
1612
+ }
1613
+ }
1614
+ while (simulatedTime <= now) {
1615
+ let limit = 0.025;
1616
+ const consecutiveMax = consecutiveMaxCount >= 2;
1617
+ if (consecutiveMax) {
1618
+ limit = 0.015;
1619
+ } else {
1620
+ if (prevRatio >= 0.8) {
1621
+ limit = prevLimit === 0.015 ? 0.015 : prevLimit;
1622
+ } else if (prevRatio < 0.4) {
1623
+ limit = Math.min(0.025, prevLimit + 5e-3);
1624
+ } else if (prevRatio >= 0.4 && prevRatio < 0.6) {
1625
+ limit = Math.min(0.025, prevLimit + 4e-3);
1626
+ } else {
1627
+ limit = Math.min(0.025, prevLimit + 2e-3);
1628
+ }
1629
+ }
1630
+ prevLimit = limit;
1631
+ prevRatio = 0;
1632
+ consecutiveMaxCount = 0;
1633
+ simulatedTime += hourMs;
1634
+ currentLimit = limit;
1635
+ }
1636
+ return currentLimit;
1637
+ };
1349
1638
  checkImageQuota = async (settings) => {
1350
1639
  const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High" };
1351
1640
  if (imageSettings.keyType !== "Default") return true;
@@ -1364,10 +1653,16 @@ var init_usage = __esm({
1364
1653
  stats.imageCalls = [];
1365
1654
  }
1366
1655
  const now = Date.now();
1367
- const oneHourAgo = now - 60 * 60 * 1e3;
1368
- const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
1369
- const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
1370
- return totalSpent + currentCost <= 0.02;
1656
+ const buckets = getImageQuotaBuckets(stats.imageCalls);
1657
+ let totalSpent = 0;
1658
+ if (buckets.length > 0) {
1659
+ const lastBucket = buckets[buckets.length - 1];
1660
+ if (now >= lastBucket.start && now < lastBucket.end) {
1661
+ totalSpent = lastBucket.spent;
1662
+ }
1663
+ }
1664
+ const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1665
+ return totalSpent + currentCost <= currentLimit;
1371
1666
  };
1372
1667
  getImageQuotaStats = async () => {
1373
1668
  const stats = await getDailyUsage();
@@ -1375,24 +1670,28 @@ var init_usage = __esm({
1375
1670
  stats.imageCalls = [];
1376
1671
  }
1377
1672
  const now = Date.now();
1378
- const oneHourAgo = now - 60 * 60 * 1e3;
1379
- const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
1380
- const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
1381
- const remaining = Math.max(0, 0.02 - totalSpent);
1673
+ const buckets = getImageQuotaBuckets(stats.imageCalls);
1674
+ let activeCalls = [];
1675
+ let totalSpent = 0;
1382
1676
  let nextResetMin = 0;
1383
- let reclaimCost = 0;
1384
- if (activeCalls.length > 0) {
1385
- const earliestCall = activeCalls.reduce((min, c) => c.timestamp < min.timestamp ? c : min, activeCalls[0]);
1386
- const nextResetTimestamp = earliestCall.timestamp + 60 * 60 * 1e3;
1387
- nextResetMin = Math.max(0, Math.ceil((nextResetTimestamp - now) / (60 * 1e3)));
1388
- reclaimCost = earliestCall.cost;
1677
+ if (buckets.length > 0) {
1678
+ const lastBucket = buckets[buckets.length - 1];
1679
+ if (now >= lastBucket.start && now < lastBucket.end) {
1680
+ activeCalls = lastBucket.calls;
1681
+ totalSpent = lastBucket.spent;
1682
+ nextResetMin = Math.max(0, Math.ceil((lastBucket.end - now) / (60 * 1e3)));
1683
+ }
1389
1684
  }
1685
+ const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1686
+ const remaining = Math.max(0, currentLimit - totalSpent);
1687
+ const reclaimCost = totalSpent;
1390
1688
  return {
1391
1689
  totalSpent,
1392
1690
  remaining,
1393
1691
  activeCallsCount: activeCalls.length,
1394
1692
  nextResetMin,
1395
- reclaimCost
1693
+ reclaimCost,
1694
+ limit: currentLimit
1396
1695
  };
1397
1696
  };
1398
1697
  recordImageGeneration = async (settings) => {
@@ -2667,6 +2966,7 @@ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
2667
2966
  var init_settings = __esm({
2668
2967
  "src/utils/settings.js"() {
2669
2968
  init_paths();
2969
+ init_crypto();
2670
2970
  DEFAULT_SETTINGS = {
2671
2971
  mode: "Flux",
2672
2972
  thinkingLevel: "Medium",
@@ -2704,14 +3004,14 @@ var init_settings = __esm({
2704
3004
  let settingsObj = { ...DEFAULT_SETTINGS };
2705
3005
  try {
2706
3006
  if (await fs14.exists(SETTINGS_FILE)) {
2707
- const saved = await fs14.readJson(SETTINGS_FILE);
3007
+ const saved = readAesEncryptedJson(SETTINGS_FILE);
2708
3008
  if (saved.imageSettings && saved.imageSettings.apiKey) {
2709
3009
  try {
2710
3010
  const legacyKey = saved.imageSettings.apiKey;
2711
3011
  const { saveSecret: saveSecret2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
2712
3012
  await saveSecret2("POLLINATIONS_API_KEY", legacyKey);
2713
3013
  saved.imageSettings.apiKey = "";
2714
- await fs14.writeJson(SETTINGS_FILE, saved, { spaces: 2 });
3014
+ writeAesEncryptedJson(SETTINGS_FILE, saved);
2715
3015
  } catch (e) {
2716
3016
  }
2717
3017
  }
@@ -2738,7 +3038,7 @@ var init_settings = __esm({
2738
3038
  if (settingsObj.showFullThinking === false) {
2739
3039
  settingsObj.showFullThinking = true;
2740
3040
  try {
2741
- await fs14.writeJson(SETTINGS_FILE, settingsObj, { spaces: 2 });
3041
+ writeAesEncryptedJson(SETTINGS_FILE, settingsObj);
2742
3042
  } catch (e) {
2743
3043
  }
2744
3044
  }
@@ -2780,7 +3080,7 @@ var init_settings = __esm({
2780
3080
  updated.imageSettings = { ...updated.imageSettings, apiKey: "" };
2781
3081
  }
2782
3082
  await fs14.ensureDir(path13.dirname(SETTINGS_FILE));
2783
- await fs14.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
3083
+ writeAesEncryptedJson(SETTINGS_FILE, updated);
2784
3084
  return true;
2785
3085
  } catch (err) {
2786
3086
  console.error("Failed to save settings:", err);
@@ -2790,6 +3090,14 @@ var init_settings = __esm({
2790
3090
  }
2791
3091
  });
2792
3092
 
3093
+ // src/utils/fallback_key.js
3094
+ var FALLBACK_IMAGE_KEY;
3095
+ var init_fallback_key = __esm({
3096
+ "src/utils/fallback_key.js"() {
3097
+ FALLBACK_IMAGE_KEY = "pk_oH8wkUdQaQK1cmst";
3098
+ }
3099
+ });
3100
+
2793
3101
  // src/tools/generate_image.js
2794
3102
  import fs15 from "fs-extra";
2795
3103
  import path14 from "path";
@@ -2799,6 +3107,7 @@ var init_generate_image = __esm({
2799
3107
  init_arg_parser();
2800
3108
  init_settings();
2801
3109
  init_usage();
3110
+ init_fallback_key();
2802
3111
  injectPngMetadata = (buffer, metadata = {}) => {
2803
3112
  try {
2804
3113
  if (buffer.length < 8 || buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) {
@@ -2871,7 +3180,7 @@ var init_generate_image = __esm({
2871
3180
  return `ERROR: Insufficient Quota for selected quality. Either tell user reduce quality or wait for next refresh cycle (${stats.nextResetMin || 60}m).`;
2872
3181
  }
2873
3182
  const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High", apiKey: "" };
2874
- const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey : "pk_5i7Doib5fATyAN4i";
3183
+ const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey : FALLBACK_IMAGE_KEY;
2875
3184
  const qualityMap = {
2876
3185
  "Low": "flux",
2877
3186
  "Low-High": "zimage",
@@ -3162,17 +3471,6 @@ ${originalTextProcessed.length > 1500 ? "... (truncated) ...\n\n" : ""}
3162
3471
  if (fullContent) {
3163
3472
  finalSynthesis = fullContent;
3164
3473
  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
3474
  } else {
3177
3475
  throw new Error("No synthesis generated by Janitor.");
3178
3476
  }
@@ -3650,7 +3948,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3650
3948
  const potentialTool = NORMALIZE_MAP[rawToolName] || rawToolName;
3651
3949
  const partialArgs = toolContext.args || "";
3652
3950
  let detail = null;
3653
- if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword"].includes(potentialTool)) {
3951
+ if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword", "generate_image"].includes(potentialTool)) {
3654
3952
  const pArgs = parseArgs(partialArgs);
3655
3953
  const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
3656
3954
  const keyword = pArgs.keyword;
@@ -4759,6 +5057,7 @@ function App() {
4759
5057
  if (saved.systemSettings?.autoDeleteHistory) {
4760
5058
  cleanupOldHistory(saved.systemSettings.autoDeleteHistory);
4761
5059
  }
5060
+ cleanupOldLogs(LOGS_DIR);
4762
5061
  performVersionCheck(false, freshSettings);
4763
5062
  await initUsage();
4764
5063
  setIsInitializing(false);
@@ -4859,7 +5158,7 @@ function App() {
4859
5158
  cmd: "key",
4860
5159
  desc: "Set API key strategy",
4861
5160
  subs: [
4862
- { cmd: "default", desc: "Default (Quota: 20 credits/hr)" },
5161
+ { cmd: "default", desc: "Default (Quota: Dynamic 25 max/hr)" },
4863
5162
  { cmd: "custom", desc: "Custom Key" }
4864
5163
  ]
4865
5164
  },
@@ -5064,7 +5363,7 @@ ${hintText}`, color: "magenta" }];
5064
5363
  id: Date.now(),
5065
5364
  role: "system",
5066
5365
  isImageStats: true,
5067
- text: `\u2022 Hourly Limit: 20 credits
5366
+ text: `\u2022 Hourly Limit: ${Number((stats.limit * 1e3).toFixed(0))} credits
5068
5367
  \u2022 Spent (Last 1hr): ${Number((stats.totalSpent * 1e3).toFixed(0))} credits
5069
5368
  \u2022 Remaining: ${Number((stats.remaining * 1e3).toFixed(0))} credits
5070
5369
  \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.10",
3
+ "version": "1.11.1",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",