fluxflow-cli 1.4.3 → 1.5.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/fluxflow.js CHANGED
@@ -77,7 +77,7 @@ var init_ChatLayout = __esm({
77
77
  };
78
78
  InlineMarkdown = React2.memo(({ text, color }) => {
79
79
  if (!text) return null;
80
- const parts = text.split(/(\*\*.*?\*\*|\*.*?\*|`.*?`|\$\\viewtext\{.*?\}\$|\[.*?\]\s*\(.*?\)|\[.*?\]\s*\[.*?\]|https?:\/\/[^\s]+)/g);
80
+ const parts = text.split(/(\*\*.*?\*\*|\*.*?\*|`.*?`|\$.*?\$|\[.*?\]\s*\(.*?\)|\[.*?\]\s*\[.*?\]|https?:\/\/[^\s]+)/g);
81
81
  return /* @__PURE__ */ React2.createElement(Text2, { color, wrap: "anywhere" }, parts.map((part, j) => {
82
82
  if (!part) return null;
83
83
  if (part.startsWith("**") && part.endsWith("**")) {
@@ -89,9 +89,13 @@ var init_ChatLayout = __esm({
89
89
  if (part.startsWith("`") && part.endsWith("`")) {
90
90
  return /* @__PURE__ */ React2.createElement(Text2, { key: j, color: "cyan", backgroundColor: "#003333" }, " ", part.slice(1, -1), " ");
91
91
  }
92
- if (part.startsWith("$\\viewtext{") && part.endsWith("}$")) {
93
- const content = part.slice(11, -2);
94
- return /* @__PURE__ */ React2.createElement(Text2, { key: j, color: "white", backgroundColor: "#4c0099", bold: true, italic: true }, " ", content, " ");
92
+ if (part.startsWith("$") && part.endsWith("$")) {
93
+ let content = part.slice(1, -1);
94
+ if (content.startsWith("\\text{") && content.endsWith("}")) {
95
+ content = content.slice(6, -1);
96
+ }
97
+ const mathContent = content.replace(/\\multiply/g, "\xD7").replace(/\\divide/g, "\xF7");
98
+ return /* @__PURE__ */ React2.createElement(Text2, { key: j, color: "white", backgroundColor: "#4c0099", bold: true, italic: true }, " ", mathContent, " ");
95
99
  }
96
100
  if (part.startsWith("[") && (part.includes("](") || part.includes("] ("))) {
97
101
  const match = part.match(/\[(.*?)\]\s*\((.*?)\)/);
@@ -511,15 +515,40 @@ var init_crypto = __esm({
511
515
  });
512
516
 
513
517
  // src/utils/paths.js
518
+ var paths_exports = {};
519
+ __export(paths_exports, {
520
+ DATA_DIR: () => DATA_DIR,
521
+ FLUXFLOW_DIR: () => FLUXFLOW_DIR,
522
+ HISTORY_FILE: () => HISTORY_FILE,
523
+ LOGS_DIR: () => LOGS_DIR,
524
+ MEMORIES_FILE: () => MEMORIES_FILE,
525
+ SECRET_DIR: () => SECRET_DIR,
526
+ SETTINGS_FILE: () => SETTINGS_FILE,
527
+ TEMP_MEM_FILE: () => TEMP_MEM_FILE,
528
+ USAGE_FILE: () => USAGE_FILE
529
+ });
514
530
  import os from "os";
515
531
  import path2 from "path";
516
- var FLUXFLOW_DIR, LOGS_DIR, SECRET_DIR, SETTINGS_FILE, HISTORY_FILE, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE;
532
+ import fs2 from "fs";
533
+ var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE;
517
534
  var init_paths = __esm({
518
535
  "src/utils/paths.js"() {
519
536
  FLUXFLOW_DIR = path2.join(os.homedir(), ".fluxflow");
520
- LOGS_DIR = path2.join(FLUXFLOW_DIR, "logs");
521
- SECRET_DIR = path2.join(FLUXFLOW_DIR, "secret");
522
537
  SETTINGS_FILE = path2.join(FLUXFLOW_DIR, "settings.json");
538
+ externalDir = null;
539
+ try {
540
+ if (fs2.existsSync(SETTINGS_FILE)) {
541
+ const settings = JSON.parse(fs2.readFileSync(SETTINGS_FILE, "utf8"));
542
+ const sys = settings.systemSettings || {};
543
+ if (sys.useExternalData && sys.externalDataPath) {
544
+ externalDir = sys.externalDataPath;
545
+ }
546
+ }
547
+ } catch (e) {
548
+ }
549
+ DATA_DIR = externalDir || FLUXFLOW_DIR;
550
+ LOGS_DIR = path2.join(DATA_DIR, "logs");
551
+ SECRET_DIR = path2.join(DATA_DIR, "secret");
523
552
  HISTORY_FILE = path2.join(SECRET_DIR, "history.json");
524
553
  USAGE_FILE = path2.join(SECRET_DIR, "usage.json");
525
554
  MEMORIES_FILE = path2.join(SECRET_DIR, "memories.json");
@@ -528,7 +557,7 @@ var init_paths = __esm({
528
557
  });
529
558
 
530
559
  // src/utils/secrets.js
531
- import fs2 from "fs-extra";
560
+ import fs3 from "fs-extra";
532
561
  import path3 from "path";
533
562
  var SECRET_FILE, getAPIKey, saveSecret, saveAPIKey, removeSecret, removeAPIKey;
534
563
  var init_secrets = __esm({
@@ -545,7 +574,7 @@ var init_secrets = __esm({
545
574
  return null;
546
575
  };
547
576
  saveSecret = async (key, value) => {
548
- await fs2.ensureDir(SECRET_DIR);
577
+ await fs3.ensureDir(SECRET_DIR);
549
578
  let current = readEncryptedJson(SECRET_FILE, {});
550
579
  current[key] = value;
551
580
  writeEncryptedJson(SECRET_FILE, current);
@@ -578,12 +607,13 @@ tool:functions.tool_name(arguments)
578
607
  3. Ask User: tool:functions.ask(question="...", optionA="Option::Desc", optionB="Option::Desc"). Generally use this tool for ANY ambiguity. Mandatory triggers include: 1) **Path Divergence**: When multiple architectural or technical solutions exist, present options via 'ask' instead of choosing arbitrarily. 2) **Security Boundaries**: Explicitly request permission via 'ask' before accessing sensitive files (e.g., .env, config keys, credentials). 3) **Ambiguity Resolution**: Use 'ask' to clarify vague prompts before executing terminal commands or writing code. 4) **Risk Mitigation**: Require a 'Yes/No' confirmation for any destructive or irreversible operations. Options must always follow the 'Short Label::Detailed Description' format. This tool is a non-terminating suspension so you can get guidance without losing context.
579
608
  ${mode === "Flux" ? `
580
609
  - DEV & FILE TOOLS (Available in FLUX MODE ONLY) -
581
- 1. View File: tool:functions.view_file(path="relative/path", start_line=number, end_line=number). Reads file content. Auto-truncates at 500 lines unless start_line and end_line are provided.
610
+ 1. View File: tool:functions.view_file(path="relative/path", start_line=number, end_line=number). Reads file content. Auto-truncates at 500 lines unless start_line and end_line are provided. You can also use this tool to read images & documents.
582
611
  2. List Files: tool:functions.list_files(path="relative/path"). Lists content of a directory.
583
612
  3. Read Folder: tool:functions.read_folder(path="relative/path"). Detailed stats of a directory.
584
613
  4. Write File: tool:functions.write_file(path="path", content="content"). Creates/Overwrites. NO CODE BLOCKS. RETURNS: Disk verification + original content (if overwritten) for 100% reversibility.
585
614
  5. Update File: tool:functions.update_file(path="relative/path", content_to_replace="old", content_to_add="new"). Surgical patching. RETURNS: High-fidelity visual diff and old code block. You MUST verify that the change specifically matches your intent using the returned diff. PREFFER UPDATE FILE OVER WRITE FILE if file already exists for better reversal tracking (if a file has 500+ lines, try to stick with update_file over full-rewrite). DONT WRAP UPDATE FILE CALL CONTENT IN MARKDOWN CODE BLOCKS.
586
- 6. Execution: tool:functions.exec_command(command="terminal command"). Runs a shell command.
615
+ 6. Write PDF: tool:functions.write_pdf(path="path", content="<html/css content>", orientation="portrait/landscape"). Generates a professional PDF document. Orientation are optional. A4 size page will be used. DO NOT ADD FOOTER MANUALLY, the system will handle it automatically. USE CSS TO VISUALLY BEAUTIFY THE DOCUMENT.
616
+ 7. Execution: tool:functions.exec_command(command="terminal command"). Runs a shell command.
587
617
 
588
618
  **NOTE:** WHEN WRITING/UPDATING FILES, USE ACTUAL NEW LINE CHARACTER FOR LINE BREAKS RATHER THAN STRING '\\n'`.trim() : `
589
619
  - DEV & FILE TOOLS ARE NOT AVAILABLE IN FLOW MODE. If you need to access files, tell the user to switch to FLUX MODE (manually by user).`.trim()}
@@ -592,6 +622,7 @@ Results will be provided in the next loop as: [TOOL_RESULT]: [content]
592
622
  WHEN CALLING TOOLS, YOU **MUST** end your response with '[turn: continue]'. NEVER use '[turn: finish]' in the same turn as a tool call. After receiving the [TOOL_RESULT], acknowledge the output and verify if the goal is met; only then may you use '[turn: finish]', otherwise use '[turn: continue]'.
593
623
  Do NOT over-use tools. Use them only when strictly necessary for the user's objective. You can stack multiple tool calls 1-by-1.
594
624
  Distinguish clearly between tool discussion and execution. Use the 'tool:' prefix ONLY when calling a function. When discussing tools with the user, refer to them by name as nouns (e.g., 'write_file', 'list_files') to avoid accidental triggers and context bloat. Even in your <think> ... </think> tags, do not use the 'tool:' prefix when planning to select a tool.
625
+ Use tools contextually when needed, don't flood with unnecessary tool calls.
595
626
  -- END FUNCTION CALLING PROTOCOL --`.trim();
596
627
  }
597
628
  });
@@ -723,28 +754,35 @@ Current date and Time is: ${dateTimeStr}
723
754
  --- END SYSTEM INSTRUCTION ---`.trim();
724
755
  };
725
756
  getJanitorInstruction = (originalText, agentRaws, userMemories = "", isMemoryEnabled = true, needTitle = false) => {
726
- return `ORIGINAL USER PROMPT: ${originalText.substring(0, 500)}
757
+ let agentRes = `${agentRaws.replace(/tool:functions\..*\n/g, "").replace(/<think>.*<\/think>/g, "").replace(/\[Prompted on:.*?\]/g, "").replace(/\[turn: continue\]/g, "").replace(/\[turn: finish\]/g, "").replace(/\[TOOL_RESULTS\]/g, "").replace(/\[tool_results\]/g, "").substring(0, 3500)}`;
758
+ if (agentRes.length > 3500) {
759
+ agentRes += "\n... (truncated) ...";
760
+ }
761
+ let originalTextProcessed = originalText.replace(/\[Prompted on:.*?\]/g, "");
762
+ return `USER PROMPT: ${originalTextProcessed.substring(0, 600)}${originalTextProcessed.length > 600 ? "\n... (truncated) ..." : ""}
727
763
  AGENT RAWS (responses from this turn):
728
- ${agentRaws.substring(0, 2e3).replace(/tool:functions\..*\n/g, "").replace(/<think>.*<\/think>/g, "").replace(/\[Prompted on:.*?\]/g, "").replace(/\[turn: continue\]/g, "").replace(/\[turn: finish\]/g, "")}${agentRaws.length > 1500 ? "\n... (truncated) ..." : ""}
764
+ ${agentRes}
765
+ ${userMemories ? `
766
+
767
+ -- CURRENT PERSISTENT USER MEMORIES --
768
+ ${userMemories}
769
+ -------------------------------------------------
770
+ ` : ""}
729
771
 
730
772
  --- START SYSTEM INSTRUCTION (STRICT HEADLESS LOGIC WORKER: ZERO USER-FACING TEXT POLICY) ---
731
- YOU ARE A SILENT BACKGROUND SYSTEM PROCESS. YOU HAVE NO MOUTH. YOUR ONLY OUTPUT MEDIUM IS VALID TOOL CALL SYNTAX.
773
+ YOU ARE A SILENT BACKGROUND SYSTEM PROCESS. YOU HAVE NO MOUTH. YOUR ONLY OUTPUT MEDIUM IS VALID TOOL CALLS.
732
774
  [CRITICAL RULES]
733
775
  1. OUTPUT ONLY 'tool:functions.xxx' CALLS.
734
- 2. DO NOT EXPLAIN. DO NOT SUMMARIZE AGENT RAWS. DO NOT TALK TO THE USER.
776
+ 2. DO NOT EXPLAIN. DO NOT TALK TO THE USER.
735
777
  3. NON-TOOL TEXT WILL BREAK THE SYSTEM.
736
- 4. DO NOT REPEAT AGENT RAWS IN YOUR RESPONSE.
737
- 5. IF YOU GET ONLY USER RESPONSE AND NO AGENT RAWS, THEN JUST USE TEMP MEMORY TO LOG THE SUMMARY OF USER QUERY.
778
+ 4. DO NOT REPEAT AGENT RAWS AND TOOL RESULTS IN YOUR RESPONSE.
779
+ 5. IF YOU GET ONLY USER QUERY AND NO AGENT RAWS, THEN JUST USE TEMP MEMORY TO LOG THE SUMMARY OF USER QUERY.
738
780
 
739
781
  YOUR JOB: Analyze the 'User prompt' and 'Agent Raws' to extract facts for long-term memory or handle system tasks.
740
782
  ${isMemoryEnabled ? `If user tell something that is important (like, hobbies, preferences, facts about user, hates, likes, etc) to know user better over time, use long term memory tools.` : ""}
741
783
 
742
784
  ${JANITOR_TOOLS_PROTOCOL(isMemoryEnabled, needTitle)}
743
785
 
744
- ${userMemories ? `-- CURRENT PERSISTENT USER MEMORIES --
745
- ${userMemories}
746
- -------------------------------------------------
747
- ` : ""}
748
786
  Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
749
787
 
750
788
  --- END SYSTEM INSTRUCTION ---`.trim();
@@ -753,7 +791,7 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
753
791
  });
754
792
 
755
793
  // src/utils/history.js
756
- import fs3 from "fs-extra";
794
+ import fs4 from "fs-extra";
757
795
  import path4 from "path";
758
796
  import { nanoid } from "nanoid";
759
797
  var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, getTruncatedHistory;
@@ -776,9 +814,9 @@ var init_history = __esm({
776
814
  return nextLock;
777
815
  };
778
816
  loadHistory = async () => {
779
- if (await fs3.pathExists(HISTORY_FILE)) {
817
+ if (await fs4.pathExists(HISTORY_FILE)) {
780
818
  try {
781
- return await fs3.readJson(HISTORY_FILE);
819
+ return await fs4.readJson(HISTORY_FILE);
782
820
  } catch (e) {
783
821
  return {};
784
822
  }
@@ -796,8 +834,8 @@ var init_history = __esm({
796
834
  messages: persistentMessages,
797
835
  updatedAt: Date.now()
798
836
  };
799
- await fs3.ensureDir(path4.dirname(HISTORY_FILE));
800
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
837
+ await fs4.ensureDir(path4.dirname(HISTORY_FILE));
838
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
801
839
  });
802
840
  };
803
841
  saveChatTitle = async (id, title) => {
@@ -809,15 +847,15 @@ var init_history = __esm({
809
847
  } else {
810
848
  history[id] = { name: title, messages: [], updatedAt: Date.now() };
811
849
  }
812
- await fs3.ensureDir(path4.dirname(HISTORY_FILE));
813
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
850
+ await fs4.ensureDir(path4.dirname(HISTORY_FILE));
851
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
814
852
  });
815
853
  };
816
854
  deleteChat = async (id) => {
817
855
  return withLock(async () => {
818
856
  const history = await loadHistory();
819
857
  delete history[id];
820
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
858
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
821
859
  const temp = readEncryptedJson(TEMP_MEM_FILE, {});
822
860
  if (temp[id]) {
823
861
  delete temp[id];
@@ -856,7 +894,7 @@ var init_history = __esm({
856
894
  });
857
895
 
858
896
  // src/utils/usage.js
859
- import fs4 from "fs-extra";
897
+ import fs5 from "fs-extra";
860
898
  import path5 from "path";
861
899
  var getDailyUsage, incrementUsage, checkQuota;
862
900
  var init_usage = __esm({
@@ -865,8 +903,8 @@ var init_usage = __esm({
865
903
  getDailyUsage = async () => {
866
904
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
867
905
  try {
868
- if (await fs4.exists(USAGE_FILE)) {
869
- const data = await fs4.readJson(USAGE_FILE);
906
+ if (await fs5.exists(USAGE_FILE)) {
907
+ const data = await fs5.readJson(USAGE_FILE);
870
908
  if (data.date === today) {
871
909
  return data.stats;
872
910
  }
@@ -875,21 +913,21 @@ var init_usage = __esm({
875
913
  console.error("Failed to read usage:", err);
876
914
  }
877
915
  const defaultStats = { agent: 0, background: 0, search: 0 };
878
- await fs4.ensureDir(path5.dirname(USAGE_FILE));
879
- await fs4.writeJson(USAGE_FILE, { date: today, stats: defaultStats }, { spaces: 2 });
916
+ await fs5.ensureDir(path5.dirname(USAGE_FILE));
917
+ await fs5.writeJson(USAGE_FILE, { date: today, stats: defaultStats }, { spaces: 2 });
880
918
  return defaultStats;
881
919
  };
882
920
  incrementUsage = async (key) => {
883
921
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
884
- const data = await fs4.readJson(USAGE_FILE).catch(() => ({ date: today, stats: { agent: 0, background: 0, search: 0 } }));
922
+ const data = await fs5.readJson(USAGE_FILE).catch(() => ({ date: today, stats: { agent: 0, background: 0, search: 0 } }));
885
923
  if (data.date !== today) {
886
924
  data.date = today;
887
925
  data.stats = { agent: 0, background: 0, search: 0 };
888
926
  }
889
927
  if (data.stats[key] !== void 0) {
890
928
  data.stats[key]++;
891
- await fs4.ensureDir(path5.dirname(USAGE_FILE));
892
- await fs4.writeJson(USAGE_FILE, data, { spaces: 2 });
929
+ await fs5.ensureDir(path5.dirname(USAGE_FILE));
930
+ await fs5.writeJson(USAGE_FILE, data, { spaces: 2 });
893
931
  }
894
932
  };
895
933
  checkQuota = async (key, settings) => {
@@ -943,7 +981,7 @@ var init_arg_parser = __esm({
943
981
 
944
982
  // src/tools/web_search.js
945
983
  import * as cuimp from "cuimp";
946
- import fs5 from "fs";
984
+ import fs6 from "fs";
947
985
  import path6 from "path";
948
986
  var web_search;
949
987
  var init_web_search = __esm({
@@ -953,65 +991,75 @@ var init_web_search = __esm({
953
991
  web_search = async (argsString) => {
954
992
  const { query, limit = 10 } = parseArgs(argsString);
955
993
  if (!query) return 'ERROR: Missing "query" argument for web_search.';
956
- try {
957
- await new Promise((r) => setTimeout(r, Math.random() * 1e3 + 500));
958
- const response = await cuimp.get(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`, {
959
- headers: {
960
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
961
- }
962
- });
963
- const html = response.data;
964
- const results = [];
965
- const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
966
- let match;
967
- let count = 0;
968
- while ((match = resultRegex.exec(html)) !== null && count < limit) {
969
- let url = match[1];
970
- if (url.includes("uddg=")) {
971
- url = decodeURIComponent(url.split("uddg=")[1].split("&")[0]);
972
- }
973
- const title = match[2].replace(/<[^>]*>/g, "").trim();
974
- const snippet = match[3].replace(/<[^>]*>/g, "").trim();
975
- results.push(`${count + 1}. ${title}
994
+ const maxRetries = 3;
995
+ let lastError = null;
996
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
997
+ try {
998
+ const userAgents = [
999
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
1000
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
1001
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
1002
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"
1003
+ ];
1004
+ const randomUA = userAgents[Math.floor(Math.random() * userAgents.length)];
1005
+ const jitter = attempt === 1 ? Math.random() * 1e3 + 500 : Math.random() * 2e3 + 1e3;
1006
+ await new Promise((r) => setTimeout(r, jitter));
1007
+ const response = await cuimp.get(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`, {
1008
+ headers: {
1009
+ "User-Agent": randomUA,
1010
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
1011
+ "Accept-Language": "en-US,en;q=0.9",
1012
+ "Referer": "https://duckduckgo.com/",
1013
+ "Upgrade-Insecure-Requests": "1",
1014
+ "Cache-Control": "max-age=0"
1015
+ }
1016
+ });
1017
+ const html = response.data;
1018
+ const results = [];
1019
+ const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
1020
+ let match;
1021
+ let count = 0;
1022
+ while ((match = resultRegex.exec(html)) !== null && count < limit) {
1023
+ let url = match[1];
1024
+ if (url.includes("uddg=")) {
1025
+ url = decodeURIComponent(url.split("uddg=")[1].split("&")[0]);
1026
+ }
1027
+ const title = match[2].replace(/<[^>]*>/g, "").trim();
1028
+ const snippet = match[3].replace(/<[^>]*>/g, "").trim();
1029
+ results.push(`${count + 1}. ${title}
976
1030
  Source: ${url}
977
1031
  Snippet: ${snippet}`);
978
- count++;
979
- }
980
- const toolLogDir = path6.join(LOGS_DIR, "tools");
981
- if (!fs5.existsSync(toolLogDir)) {
982
- fs5.mkdirSync(toolLogDir, { recursive: true });
983
- }
984
- fs5.appendFileSync(path6.join(toolLogDir, "search-results.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
985
- Query: [${query}]. Results Count: ${results.length}.
986
- Results: ${results}
987
-
988
-
989
- `);
990
- if (results.length === 0) {
991
- if (html.includes("anomaly")) {
992
- const toolErrDir = path6.join(LOGS_DIR, "tools");
993
- if (!fs5.existsSync(toolErrDir)) {
994
- fs5.mkdirSync(toolErrDir, { recursive: true });
1032
+ count++;
1033
+ }
1034
+ if (results.length === 0) {
1035
+ if (html.includes("anomaly")) {
1036
+ throw new Error("DDG_ANOMALY_DETECTED");
995
1037
  }
996
- fs5.appendFileSync(path6.join(toolErrDir, "error.log"), `ERROR ${(/* @__PURE__ */ new Date()).toISOString()} - DDG detected unusual activity. Cuimp impersonation might need adjustment.
997
- `);
998
- throw new Error("DDG detected unusual activity. Cuimp impersonation might need adjustment.");
1038
+ return `No results found for query: [${query}].`;
999
1039
  }
1000
- return `No results found for query: [${query}].`;
1001
- }
1002
- const finalResults = results.join("\n\n");
1003
- return `Search results for [${query}]:
1040
+ const finalResults = results.join("\n\n");
1041
+ return `Search results for [${query}]:
1004
1042
 
1005
1043
  ${finalResults}`;
1006
- } catch (err) {
1007
- return `ERROR: Search failed. Unable to read.`;
1044
+ } catch (err) {
1045
+ lastError = err;
1046
+ const toolErrDir = path6.join(LOGS_DIR, "tools");
1047
+ if (!fs6.existsSync(toolErrDir)) fs6.mkdirSync(toolErrDir, { recursive: true });
1048
+ fs6.appendFileSync(path6.join(toolErrDir, "error.log"), `ERROR ${(/* @__PURE__ */ new Date()).toISOString()} - Attempt ${attempt}/${maxRetries} failed: ${err.message}
1049
+ `);
1050
+ if (attempt < maxRetries) {
1051
+ const backoff = Math.pow(2, attempt) * 1e3;
1052
+ await new Promise((r) => setTimeout(r, backoff));
1053
+ }
1054
+ }
1008
1055
  }
1056
+ return `ERROR: Search failed after ${maxRetries} attempts. Last error: ${lastError.message}`;
1009
1057
  };
1010
1058
  }
1011
1059
  });
1012
1060
 
1013
1061
  // src/tools/web_scrape.js
1014
- import fs6 from "fs";
1062
+ import fs7 from "fs";
1015
1063
  import path7 from "path";
1016
1064
  import * as cuimp2 from "cuimp";
1017
1065
  var web_scrape;
@@ -1021,39 +1069,56 @@ var init_web_scrape = __esm({
1021
1069
  web_scrape = async (args) => {
1022
1070
  const urlMatch = args.match(/url\s*=\s*["'](.*)["']/);
1023
1071
  const url = urlMatch ? urlMatch[1] : args;
1024
- try {
1025
- const response = await cuimp2.get(url, {
1026
- headers: {
1027
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
1028
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
1029
- "Accept-Language": "en-US,en;q=0.5"
1030
- }
1031
- });
1032
- let html = response.data;
1033
- if (!html) throw new Error("No content received from URL.");
1034
- html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
1035
- html = html.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
1036
- html = html.replace(/<nav\b[^<]*(?:(?!<\/nav>)<[^<]*)*<\/nav>/gi, "");
1037
- html = html.replace(/<footer\b[^<]*(?:(?!<\/footer>)<[^<]*)*<\/footer>/gi, "");
1038
- html = html.replace(/<header\b[^<]*(?:(?!<\/header>)<[^<]*)*<\/header>/gi, "");
1039
- let text = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1040
- const finalContent = text.substring(0, 2e4);
1041
- const toolLogDir = path7.join(LOGS_DIR, "tools");
1042
- if (!fs6.existsSync(toolLogDir)) {
1043
- fs6.mkdirSync(toolLogDir, { recursive: true });
1044
- }
1045
- fs6.appendFileSync(path7.join(toolLogDir, "search-scraped.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
1046
- Query: [${url}].
1047
- Results: ${finalContent}
1048
-
1072
+ const maxRetries = 3;
1073
+ let lastError = null;
1074
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1075
+ try {
1076
+ const userAgents = [
1077
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
1078
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
1079
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
1080
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"
1081
+ ];
1082
+ const randomUA = userAgents[Math.floor(Math.random() * userAgents.length)];
1083
+ const jitter = attempt === 1 ? Math.random() * 1e3 + 500 : Math.random() * 2e3 + 1e3;
1084
+ await new Promise((r) => setTimeout(r, jitter));
1085
+ const response = await cuimp2.get(url, {
1086
+ headers: {
1087
+ "User-Agent": randomUA,
1088
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
1089
+ "Accept-Language": "en-US,en;q=0.9",
1090
+ "Referer": "https://www.google.com/",
1091
+ "Upgrade-Insecure-Requests": "1",
1092
+ "Cache-Control": "max-age=0"
1093
+ }
1094
+ });
1095
+ let html = response.data;
1096
+ if (!html) throw new Error("EMPTY_RESPONSE");
1097
+ html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
1098
+ html = html.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
1099
+ html = html.replace(/<nav\b[^<]*(?:(?!<\/nav>)<[^<]*)*<\/nav>/gi, "");
1100
+ html = html.replace(/<footer\b[^<]*(?:(?!<\/footer>)<[^<]*)*<\/footer>/gi, "");
1101
+ html = html.replace(/<header\b[^<]*(?:(?!<\/header>)<[^<]*)*<\/header>/gi, "");
1102
+ let text = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1103
+ const finalContent = text.substring(0, 2e4);
1104
+ const toolLogDir = path7.join(LOGS_DIR, "tools");
1105
+ if (!fs7.existsSync(toolLogDir)) fs7.mkdirSync(toolLogDir, { recursive: true });
1106
+ fs7.appendFileSync(path7.join(toolLogDir, "search-scraped.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
1107
+ URL: [${url}]. Content Length: ${finalContent.length}
1049
1108
 
1050
1109
  `);
1051
- return `CONTENT FROM [${url}]:
1110
+ return `CONTENT FROM [${url}]:
1052
1111
 
1053
1112
  ${finalContent}${text.length > 2e4 ? "\n\n[TRUNCATED AT 20K CHARS]" : ""}`;
1054
- } catch (err) {
1055
- return `ERROR: Failed to read page at ${url}. Unable to read.`;
1113
+ } catch (err) {
1114
+ lastError = err;
1115
+ if (attempt < maxRetries) {
1116
+ const backoff = Math.pow(2, attempt) * 1e3;
1117
+ await new Promise((r) => setTimeout(r, backoff));
1118
+ }
1119
+ }
1056
1120
  }
1121
+ return `ERROR: Scrape failed after ${maxRetries} attempts. Last error: ${lastError.message}`;
1057
1122
  };
1058
1123
  }
1059
1124
  });
@@ -1153,7 +1218,7 @@ var init_chat = __esm({
1153
1218
  });
1154
1219
 
1155
1220
  // src/tools/list_files.js
1156
- import fs7 from "fs";
1221
+ import fs8 from "fs";
1157
1222
  import path8 from "path";
1158
1223
  var list_files;
1159
1224
  var init_list_files = __esm({
@@ -1163,14 +1228,14 @@ var init_list_files = __esm({
1163
1228
  const { path: targetPath = "." } = parseArgs(args);
1164
1229
  const absolutePath = path8.resolve(process.cwd(), targetPath);
1165
1230
  try {
1166
- if (!fs7.existsSync(absolutePath)) {
1231
+ if (!fs8.existsSync(absolutePath)) {
1167
1232
  return `ERROR: Path [${targetPath}] does not exist.`;
1168
1233
  }
1169
- const stats = fs7.statSync(absolutePath);
1234
+ const stats = fs8.statSync(absolutePath);
1170
1235
  if (!stats.isDirectory()) {
1171
1236
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use view_file instead.`;
1172
1237
  }
1173
- const files = fs7.readdirSync(absolutePath);
1238
+ const files = fs8.readdirSync(absolutePath);
1174
1239
  if (files.length === 0) {
1175
1240
  return `Directory [${targetPath}] is empty.`;
1176
1241
  }
@@ -1182,7 +1247,7 @@ var init_list_files = __esm({
1182
1247
  let indicator = "\u{1F4C4}";
1183
1248
  let metaPart = "";
1184
1249
  try {
1185
- const fStats = fs7.statSync(fPath);
1250
+ const fStats = fs8.statSync(fPath);
1186
1251
  indicator = fStats.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
1187
1252
  const sizeKB = (fStats.size / 1024).toFixed(1);
1188
1253
  metaPart = fStats.isFile() ? ` - [${sizeKB} KB]` : "";
@@ -1214,7 +1279,7 @@ ${list}${footer}`;
1214
1279
  });
1215
1280
 
1216
1281
  // src/tools/view_file.js
1217
- import fs8 from "fs";
1282
+ import fs9 from "fs";
1218
1283
  import path9 from "path";
1219
1284
  var view_file;
1220
1285
  var init_view_file = __esm({
@@ -1225,14 +1290,38 @@ var init_view_file = __esm({
1225
1290
  if (!targetPath) return 'ERROR: Missing "path" argument for view_file.';
1226
1291
  const absolutePath = path9.resolve(process.cwd(), targetPath);
1227
1292
  try {
1228
- if (!fs8.existsSync(absolutePath)) {
1293
+ if (!fs9.existsSync(absolutePath)) {
1229
1294
  return `ERROR: File [${targetPath}] does not exist.`;
1230
1295
  }
1231
- const stats = fs8.statSync(absolutePath);
1296
+ const stats = fs9.statSync(absolutePath);
1232
1297
  if (stats.isDirectory()) {
1233
1298
  return `ERROR: Path [${targetPath}] is a directory. Use list_files instead.`;
1234
1299
  }
1235
- const content = fs8.readFileSync(absolutePath, "utf8");
1300
+ const ext = path9.extname(targetPath).toLowerCase();
1301
+ const mimeMap = {
1302
+ ".pdf": "application/pdf",
1303
+ ".jpg": "image/jpeg",
1304
+ ".jpeg": "image/jpeg",
1305
+ ".png": "image/png",
1306
+ ".webp": "image/webp",
1307
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1308
+ ".doc": "application/msword"
1309
+ };
1310
+ if (mimeMap[ext]) {
1311
+ const buffer = fs9.readFileSync(absolutePath);
1312
+ const base64 = buffer.toString("base64");
1313
+ const mimeType = mimeMap[ext];
1314
+ return {
1315
+ text: `[BINARY_FILE]: ${targetPath} (${mimeType}) - Loaded as multimodal part.`,
1316
+ binaryPart: {
1317
+ inlineData: {
1318
+ data: base64,
1319
+ mimeType
1320
+ }
1321
+ }
1322
+ };
1323
+ }
1324
+ const content = fs9.readFileSync(absolutePath, "utf8");
1236
1325
  const lines = content.split("\n");
1237
1326
  const totalLines = lines.length;
1238
1327
  const start = Math.max(0, start_line - 1);
@@ -1251,7 +1340,7 @@ ${code}`;
1251
1340
  });
1252
1341
 
1253
1342
  // src/tools/write_file.js
1254
- import fs9 from "fs";
1343
+ import fs10 from "fs";
1255
1344
  import path10 from "path";
1256
1345
  var write_file;
1257
1346
  var init_write_file = __esm({
@@ -1266,9 +1355,9 @@ var init_write_file = __esm({
1266
1355
  const parentDir = path10.dirname(absolutePath);
1267
1356
  try {
1268
1357
  let ancestry = "";
1269
- if (fs9.existsSync(absolutePath)) {
1358
+ if (fs10.existsSync(absolutePath)) {
1270
1359
  try {
1271
- const oldData = fs9.readFileSync(absolutePath, "utf8");
1360
+ const oldData = fs10.readFileSync(absolutePath, "utf8");
1272
1361
  const lines = oldData.split(/\r?\n/);
1273
1362
  ancestry = `Old File contents:
1274
1363
  ${lines.map((l, i) => `${i + 1} | ${l}`).join("\n")}
@@ -1280,13 +1369,13 @@ ${lines.map((l, i) => `${i + 1} | ${l}`).join("\n")}
1280
1369
  `;
1281
1370
  }
1282
1371
  }
1283
- if (!fs9.existsSync(parentDir)) {
1284
- fs9.mkdirSync(parentDir, { recursive: true });
1372
+ if (!fs10.existsSync(parentDir)) {
1373
+ fs10.mkdirSync(parentDir, { recursive: true });
1285
1374
  }
1286
1375
  const lineCount = content.split(/\r?\n/).length;
1287
1376
  const originalSize = Buffer.byteLength(content, "utf8");
1288
- fs9.writeFileSync(absolutePath, content, "utf8");
1289
- let verifiedContent = fs9.readFileSync(absolutePath, "utf8");
1377
+ fs10.writeFileSync(absolutePath, content, "utf8");
1378
+ let verifiedContent = fs10.readFileSync(absolutePath, "utf8");
1290
1379
  const verifiedSize = Buffer.byteLength(verifiedContent, "utf8");
1291
1380
  const verifiedLines = verifiedContent.split(/\r?\n/);
1292
1381
  const verifiedLineCount = verifiedLines.length;
@@ -1320,7 +1409,7 @@ ${snippet}`;
1320
1409
  });
1321
1410
 
1322
1411
  // src/tools/update_file.js
1323
- import fs10 from "fs";
1412
+ import fs11 from "fs";
1324
1413
  import path11 from "path";
1325
1414
  var update_file;
1326
1415
  var init_update_file = __esm({
@@ -1336,10 +1425,10 @@ var init_update_file = __esm({
1336
1425
  content_to_add = strip(content_to_add);
1337
1426
  const absolutePath = path11.resolve(process.cwd(), targetPath);
1338
1427
  try {
1339
- if (!fs10.existsSync(absolutePath)) {
1428
+ if (!fs11.existsSync(absolutePath)) {
1340
1429
  return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
1341
1430
  }
1342
- const currentContent = fs10.readFileSync(absolutePath, "utf8");
1431
+ const currentContent = fs11.readFileSync(absolutePath, "utf8");
1343
1432
  if (!currentContent.includes(content_to_replace)) {
1344
1433
  return `ERROR: Could not find exact match for the specified "content_to_replace" in [${targetPath}]. Check indentation/whitespace/line breaks(LF or CRLF)/string. Try re-reading the file for latest changes.`;
1345
1434
  }
@@ -1347,7 +1436,7 @@ var init_update_file = __esm({
1347
1436
  const startLine = currentContent.substring(0, startPos).split(/\r?\n/).length;
1348
1437
  const instances = currentContent.split(content_to_replace).length - 1;
1349
1438
  const newFileContent = currentContent.split(content_to_replace).join(content_to_add);
1350
- fs10.writeFileSync(absolutePath, newFileContent, "utf8");
1439
+ fs11.writeFileSync(absolutePath, newFileContent, "utf8");
1351
1440
  const allOriginalLines = currentContent.split(/\r?\n/);
1352
1441
  const oldLines = content_to_replace.split(/\r?\n/);
1353
1442
  const newLines = content_to_add.split(/\r?\n/);
@@ -1469,7 +1558,7 @@ ${finalOutput}`);
1469
1558
  });
1470
1559
 
1471
1560
  // src/tools/read_folder.js
1472
- import fs11 from "fs";
1561
+ import fs12 from "fs";
1473
1562
  import path12 from "path";
1474
1563
  var read_folder;
1475
1564
  var init_read_folder = __esm({
@@ -1479,14 +1568,14 @@ var init_read_folder = __esm({
1479
1568
  const { path: targetPath = "." } = parseArgs(args);
1480
1569
  const absolutePath = path12.resolve(process.cwd(), targetPath);
1481
1570
  try {
1482
- if (!fs11.existsSync(absolutePath)) {
1571
+ if (!fs12.existsSync(absolutePath)) {
1483
1572
  return `ERROR: Path [${targetPath}] does not exist.`;
1484
1573
  }
1485
- const stats = fs11.statSync(absolutePath);
1574
+ const stats = fs12.statSync(absolutePath);
1486
1575
  if (!stats.isDirectory()) {
1487
1576
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use view_file instead.`;
1488
1577
  }
1489
- const files = fs11.readdirSync(absolutePath);
1578
+ const files = fs12.readdirSync(absolutePath);
1490
1579
  const totalItems = files.length;
1491
1580
  const maxDisplay = 100;
1492
1581
  const displayItems = files.slice(0, maxDisplay);
@@ -1496,7 +1585,7 @@ var init_read_folder = __esm({
1496
1585
  let indicator = "\u{1F4C4}";
1497
1586
  let info = { name: file, type: "unknown", size: "N/A", mtime: "N/A" };
1498
1587
  try {
1499
- const fStats = fs11.statSync(fPath);
1588
+ const fStats = fs12.statSync(fPath);
1500
1589
  info = {
1501
1590
  name: file,
1502
1591
  type: fStats.isDirectory() ? "directory" : "file",
@@ -1577,6 +1666,78 @@ var init_ask_user = __esm({
1577
1666
  }
1578
1667
  });
1579
1668
 
1669
+ // src/tools/write_pdf.js
1670
+ import puppeteer from "puppeteer";
1671
+ import path13 from "path";
1672
+ import fs13 from "fs-extra";
1673
+ var write_pdf;
1674
+ var init_write_pdf = __esm({
1675
+ "src/tools/write_pdf.js"() {
1676
+ init_arg_parser();
1677
+ write_pdf = async (args) => {
1678
+ const { path: targetPath, content, orientation = "portrait", margin = "10px" } = parseArgs(args);
1679
+ if (!targetPath) return 'ERROR: Missing "path" argument for write_pdf.';
1680
+ if (!content) return 'ERROR: Missing "content" (HTML/CSS) for write_pdf.';
1681
+ const absolutePath = path13.resolve(process.cwd(), targetPath);
1682
+ let browser = null;
1683
+ try {
1684
+ await fs13.ensureDir(path13.dirname(absolutePath));
1685
+ browser = await puppeteer.launch({
1686
+ headless: true,
1687
+ args: [
1688
+ "--no-sandbox",
1689
+ "--disable-setuid-sandbox",
1690
+ "--disable-gpu",
1691
+ "--disable-dev-shm-usage"
1692
+ ]
1693
+ });
1694
+ const page = await browser.newPage();
1695
+ const styledContent = `
1696
+ <style>
1697
+ @page {
1698
+ margin: ${margin};
1699
+ }
1700
+ body {
1701
+ margin: 0;
1702
+ padding: 0;
1703
+ font-family: system-ui, -apple-system, sans-serif;
1704
+ }
1705
+ * { box-sizing: border-box; }
1706
+ </style>
1707
+ ${content}
1708
+ `;
1709
+ await page.setContent(styledContent, { waitUntil: "networkidle0" });
1710
+ await page.pdf({
1711
+ path: absolutePath,
1712
+ format: "A4",
1713
+ landscape: orientation.toLowerCase() === "landscape",
1714
+ margin: {
1715
+ top: margin,
1716
+ right: margin,
1717
+ bottom: "15mm",
1718
+ // Space for watermark
1719
+ left: margin
1720
+ },
1721
+ displayHeaderFooter: true,
1722
+ headerTemplate: "<span></span>",
1723
+ footerTemplate: `
1724
+ <div style="font-size: 9px; color: rgba(0,0,0,0.2); width: 100%; text-align: right; padding-right: 15mm; font-family: system-ui, sans-serif; -webkit-print-color-adjust: exact;">
1725
+ FluxFlow CLI
1726
+ </div>
1727
+ `,
1728
+ printBackground: true
1729
+ });
1730
+ const stats = await fs13.stat(absolutePath);
1731
+ return `SUCCESS: PDF generated successfully at [${targetPath}] (${(stats.size / 1024).toFixed(2)} KB).`;
1732
+ } catch (err) {
1733
+ return `ERROR: Failed to generate PDF [${targetPath}]: ${err.message}`;
1734
+ } finally {
1735
+ if (browser) await browser.close();
1736
+ }
1737
+ };
1738
+ }
1739
+ });
1740
+
1580
1741
  // src/utils/tools.js
1581
1742
  var TOOL_MAP, dispatchTool;
1582
1743
  var init_tools = __esm({
@@ -1592,6 +1753,7 @@ var init_tools = __esm({
1592
1753
  init_exec_command();
1593
1754
  init_read_folder();
1594
1755
  init_ask_user();
1756
+ init_write_pdf();
1595
1757
  TOOL_MAP = {
1596
1758
  web_search,
1597
1759
  web_scrape,
@@ -1603,6 +1765,7 @@ var init_tools = __esm({
1603
1765
  update_file,
1604
1766
  exec_command,
1605
1767
  read_folder,
1768
+ write_pdf,
1606
1769
  ask: ask_user
1607
1770
  };
1608
1771
  dispatchTool = async (toolName, args, context = {}) => {
@@ -1619,10 +1782,32 @@ var init_tools = __esm({
1619
1782
  }
1620
1783
  });
1621
1784
 
1785
+ // src/utils/terminal.js
1786
+ var getTerminalEnv, emojiSpace;
1787
+ var init_terminal = __esm({
1788
+ "src/utils/terminal.js"() {
1789
+ getTerminalEnv = () => {
1790
+ if (process.env.TERM_PROGRAM === "vscode") return "vscode";
1791
+ if (process.env.WT_SESSION) return "wt";
1792
+ return "default";
1793
+ };
1794
+ emojiSpace = (baseSpaces = 2) => {
1795
+ const env = getTerminalEnv();
1796
+ if (env === "wt") {
1797
+ return " ".repeat(Math.max(1, baseSpaces - 1));
1798
+ }
1799
+ if (env === "vscode") {
1800
+ return " ".repeat(baseSpaces);
1801
+ }
1802
+ return " ".repeat(baseSpaces);
1803
+ };
1804
+ }
1805
+ });
1806
+
1622
1807
  // src/utils/ai.js
1623
1808
  import { GoogleGenAI, ThinkingLevel } from "@google/genai";
1624
- import path13 from "path";
1625
- import fs12 from "fs";
1809
+ import path14 from "path";
1810
+ import fs14 from "fs";
1626
1811
  var client, TERMINATION_SIGNAL, signalTermination, detectToolCalls, initAI, getAIStream;
1627
1812
  var init_ai = __esm({
1628
1813
  "src/utils/ai.js"() {
@@ -1632,6 +1817,7 @@ var init_ai = __esm({
1632
1817
  init_tools();
1633
1818
  init_crypto();
1634
1819
  init_arg_parser();
1820
+ init_terminal();
1635
1821
  init_paths();
1636
1822
  client = null;
1637
1823
  TERMINATION_SIGNAL = false;
@@ -1735,10 +1921,16 @@ USER_PROMPT: ${agentText}`.trim();
1735
1921
  }
1736
1922
  }
1737
1923
  yield { type: "turn_reset", content: true };
1738
- const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => ({
1739
- role: msg.role === "user" || msg.role === "system" ? "user" : "model",
1740
- parts: [{ text: msg.text }]
1741
- }));
1924
+ const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => {
1925
+ const parts = [{ text: msg.text }];
1926
+ if (msg.binaryPart) {
1927
+ parts.push(msg.binaryPart);
1928
+ }
1929
+ return {
1930
+ role: msg.role === "user" || msg.role === "system" ? "user" : "model",
1931
+ parts
1932
+ };
1933
+ });
1742
1934
  let stream;
1743
1935
  let success = false;
1744
1936
  let retryCount = 0;
@@ -1772,9 +1964,9 @@ USER_PROMPT: ${agentText}`.trim();
1772
1964
  } catch (err) {
1773
1965
  const errMsg = err.status || err.error && err.error.message || String(err);
1774
1966
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1775
- const agentErrDir = path13.join(LOGS_DIR, "agent");
1776
- if (!fs12.existsSync(agentErrDir)) fs12.mkdirSync(agentErrDir, { recursive: true });
1777
- fs12.appendFileSync(path13.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errMsg}
1967
+ const agentErrDir = path14.join(LOGS_DIR, "agent");
1968
+ if (!fs14.existsSync(agentErrDir)) fs14.mkdirSync(agentErrDir, { recursive: true });
1969
+ fs14.appendFileSync(path14.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errMsg}
1778
1970
  `);
1779
1971
  if (retryCount < MAX_RETRIES) {
1780
1972
  retryCount++;
@@ -1834,9 +2026,9 @@ USER_PROMPT: ${agentText}`.trim();
1834
2026
  let totalLines = "...";
1835
2027
  let actualEndLine = end_line;
1836
2028
  try {
1837
- const absPath = path13.resolve(process.cwd(), targetPath2);
1838
- if (fs12.existsSync(absPath)) {
1839
- const content = fs12.readFileSync(absPath, "utf8");
2029
+ const absPath = path14.resolve(process.cwd(), targetPath2);
2030
+ if (fs14.existsSync(absPath)) {
2031
+ const content = fs14.readFileSync(absPath, "utf8");
1840
2032
  const lines = content.split("\n").length;
1841
2033
  totalLines = lines;
1842
2034
  actualEndLine = Math.min(end_line, lines);
@@ -1850,6 +2042,8 @@ USER_PROMPT: ${agentText}`.trim();
1850
2042
  } else if (toolCall.toolName === "write_file" || toolCall.toolName === "update_file") {
1851
2043
  const action = toolCall.toolName === "write_file" ? "WRITING" : "PATCHING";
1852
2044
  label = `\u{1F4BE} ${action} FILE: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2045
+ } else if (toolCall.toolName === "write_pdf") {
2046
+ label = `\u{1F4D1} GENERATING PDF: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
1853
2047
  } else if (toolCall.toolName === "exec_command" || toolCall.toolName === "ask") {
1854
2048
  label = "";
1855
2049
  } else {
@@ -1884,7 +2078,7 @@ ${boxBottom}
1884
2078
  /\/usr\//
1885
2079
  // Sensitive Linux paths
1886
2080
  ];
1887
- const currentDrive = path13.resolve(process.cwd()).substring(0, 3).toLowerCase();
2081
+ const currentDrive = path14.resolve(process.cwd()).substring(0, 3).toLowerCase();
1888
2082
  const isViolating = riskyPatterns.some((pattern) => {
1889
2083
  if (pattern.source === "[a-zA-Z]:[\\\\\\/]") {
1890
2084
  const driveMatch = command.match(/[a-zA-Z]:[\\\/]/i);
@@ -1906,8 +2100,8 @@ ${boxBottom}
1906
2100
  const targetPath = parsedArgs.path || parsedArgs.targetPath || null;
1907
2101
  if (targetPath) {
1908
2102
  const isExternalOff = settings.systemSettings && settings.systemSettings.allowExternalAccess === false;
1909
- const absoluteTarget = path13.resolve(targetPath);
1910
- const absoluteCwd = path13.resolve(process.cwd());
2103
+ const absoluteTarget = path14.resolve(targetPath);
2104
+ const absoluteCwd = path14.resolve(process.cwd());
1911
2105
  if (isExternalOff && !absoluteTarget.startsWith(absoluteCwd)) {
1912
2106
  const denyMsg = `Access Denied. You are not allowed to access files outside the current workspace. To enable this, ask the user to turn on "External Workspace Access" in /settings.`;
1913
2107
  toolResults.push(`[TOOL_RESULT]: ERROR: ${denyMsg}`);
@@ -1933,12 +2127,17 @@ ${boxBottom}
1933
2127
  }
1934
2128
  }
1935
2129
  }
1936
- const result = await dispatchTool(toolCall.toolName, toolCall.args, {
2130
+ let result = await dispatchTool(toolCall.toolName, toolCall.args, {
1937
2131
  chatId,
1938
2132
  history,
1939
2133
  onChunk: (chunk) => settings.onExecChunk ? settings.onExecChunk(chunk) : null,
1940
2134
  onAskUser: settings.onAskUser
1941
2135
  });
2136
+ let binaryPart = null;
2137
+ if (typeof result === "object" && result.binaryPart) {
2138
+ binaryPart = result.binaryPart;
2139
+ result = result.text;
2140
+ }
1942
2141
  if (toolCall.toolName === "exec_command" && settings.onExecEnd) {
1943
2142
  await new Promise((resolve) => setTimeout(resolve, 800));
1944
2143
  settings.onExecEnd();
@@ -1947,17 +2146,17 @@ ${boxBottom}
1947
2146
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1948
2147
  const isErr = result.startsWith("ERROR:");
1949
2148
  const logStatus = isErr ? result.trim() : "SUCCESS";
1950
- const toolHistDir = path13.join(LOGS_DIR, "tools");
1951
- if (!fs12.existsSync(toolHistDir)) {
1952
- fs12.mkdirSync(toolHistDir, { recursive: true });
2149
+ const toolHistDir = path14.join(LOGS_DIR, "tools");
2150
+ if (!fs14.existsSync(toolHistDir)) {
2151
+ fs14.mkdirSync(toolHistDir, { recursive: true });
1953
2152
  }
1954
- fs12.appendFileSync(path13.join(toolHistDir, "history.log"), `HISTORY [${timestamp}]: ${toolCall.toolName} [${logStatus}]
2153
+ fs14.appendFileSync(path14.join(toolHistDir, "history.log"), `HISTORY [${timestamp}]: ${toolCall.toolName} [${logStatus}]
1955
2154
  `);
1956
2155
  } catch (logErr) {
1957
2156
  }
1958
2157
  const cleanResultForAI = result.split(/\r?\n/).filter((line) => !line.includes("[UI_CONTEXT]")).join("\n");
1959
2158
  const aiContent = `[TOOL_RESULT]: ${cleanResultForAI}`;
1960
- toolResults.push(aiContent);
2159
+ toolResults.push({ role: "user", text: aiContent, binaryPart });
1961
2160
  let uiContent = `[TOOL_RESULT]: ${result}`;
1962
2161
  if (toolCall.toolName === "view_file" || toolCall.toolName === "web_scrape") {
1963
2162
  uiContent = `[TOOL_RESULT]: ${label} (Context Locked for UI Clarity)`;
@@ -1965,7 +2164,9 @@ ${boxBottom}
1965
2164
  yield {
1966
2165
  type: "tool_result",
1967
2166
  content: uiContent,
1968
- aiContent
2167
+ aiContent,
2168
+ binaryPart
2169
+ // Multi-modal stage (v1.5.0)
1969
2170
  };
1970
2171
  if (toolCall.toolName === "memory" && result.includes("SUCCESS")) {
1971
2172
  yield { type: "memory_updated" };
@@ -2011,11 +2212,11 @@ ${boxBottom}
2011
2212
  if (parts && parts[1]?.text) {
2012
2213
  finalSynthesis = parts[1].text;
2013
2214
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2014
- const janitorLogDir = path13.join(LOGS_DIR, "janitor");
2015
- if (!fs12.existsSync(janitorLogDir)) {
2016
- fs12.mkdirSync(janitorLogDir, { recursive: true });
2215
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
2216
+ if (!fs14.existsSync(janitorLogDir)) {
2217
+ fs14.mkdirSync(janitorLogDir, { recursive: true });
2017
2218
  }
2018
- fs12.appendFileSync(path13.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
2219
+ fs14.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
2019
2220
  `);
2020
2221
  } else if (parts && parts[0]?.text) finalSynthesis = parts[0].text;
2021
2222
  else if (janitorResult.response && janitorResult.response.text) finalSynthesis = janitorResult.response.text();
@@ -2027,8 +2228,8 @@ ${boxBottom}
2027
2228
  const toolContext = { chatId, sessionId: chatId, history };
2028
2229
  const result = await dispatchTool(janitorToolCall.toolName, janitorToolCall.args, toolContext);
2029
2230
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2030
- const janitorLogDir = path13.join(LOGS_DIR, "janitor");
2031
- fs12.appendFileSync(path13.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
2231
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
2232
+ fs14.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
2032
2233
  `);
2033
2234
  if (janitorToolCall.toolName === "memory" && !janitorToolCall.args.includes("action='temp'")) {
2034
2235
  yield { type: "memory_updated" };
@@ -2036,11 +2237,11 @@ ${boxBottom}
2036
2237
  }
2037
2238
  } catch (janitorErr) {
2038
2239
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2039
- const janitorErrDir = path13.join(LOGS_DIR, "janitor");
2040
- if (!fs12.existsSync(janitorErrDir)) {
2041
- fs12.mkdirSync(janitorErrDir, { recursive: true });
2240
+ const janitorErrDir = path14.join(LOGS_DIR, "janitor");
2241
+ if (!fs14.existsSync(janitorErrDir)) {
2242
+ fs14.mkdirSync(janitorErrDir, { recursive: true });
2042
2243
  }
2043
- fs12.appendFileSync(path13.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
2244
+ fs14.appendFileSync(path14.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
2044
2245
  `);
2045
2246
  console.error("Janitor Background Tasks Failed:", janitorErr.message);
2046
2247
  }
@@ -2057,8 +2258,11 @@ ${timestamp}`;
2057
2258
  if (isActuallyFinished) break;
2058
2259
  const nextAgentMsg = cleanedTurnText.trim() || "*Working...*";
2059
2260
  modifiedHistory.push({ role: "agent", text: nextAgentMsg });
2060
- const nextUserMsg = toolResults.length > 0 ? toolResults.join("\n") : "[turn: continue]";
2061
- modifiedHistory.push({ role: "user", text: nextUserMsg });
2261
+ if (toolResults.length > 0) {
2262
+ toolResults.forEach((tr) => modifiedHistory.push(tr));
2263
+ } else {
2264
+ modifiedHistory.push({ role: "user", text: "[turn: continue]" });
2265
+ }
2062
2266
  }
2063
2267
  yield { type: "status", content: null };
2064
2268
  };
@@ -2066,9 +2270,9 @@ ${timestamp}`;
2066
2270
  });
2067
2271
 
2068
2272
  // src/utils/settings.js
2069
- import fs13 from "fs-extra";
2070
- import path14 from "path";
2071
- var DEFAULT_SETTINGS, loadSettings, saveSettings;
2273
+ import fs15 from "fs-extra";
2274
+ import path15 from "path";
2275
+ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
2072
2276
  var init_settings = __esm({
2073
2277
  "src/utils/settings.js"() {
2074
2278
  init_paths();
@@ -2090,7 +2294,9 @@ var init_settings = __esm({
2090
2294
  compression: 0,
2091
2295
  autoExec: false,
2092
2296
  allowExternalAccess: false,
2093
- autoDeleteHistory: "7d"
2297
+ autoDeleteHistory: "7d",
2298
+ useExternalData: false,
2299
+ externalDataPath: ""
2094
2300
  },
2095
2301
  profileData: {
2096
2302
  name: null,
@@ -2100,8 +2306,8 @@ var init_settings = __esm({
2100
2306
  };
2101
2307
  loadSettings = async () => {
2102
2308
  try {
2103
- if (await fs13.exists(SETTINGS_FILE)) {
2104
- const saved = await fs13.readJson(SETTINGS_FILE);
2309
+ if (await fs15.exists(SETTINGS_FILE)) {
2310
+ const saved = await fs15.readJson(SETTINGS_FILE);
2105
2311
  return {
2106
2312
  ...DEFAULT_SETTINGS,
2107
2313
  ...saved,
@@ -2115,12 +2321,31 @@ var init_settings = __esm({
2115
2321
  }
2116
2322
  return DEFAULT_SETTINGS;
2117
2323
  };
2324
+ migrateToExternal = async (newPath) => {
2325
+ const { FLUXFLOW_DIR: FLUXFLOW_DIR2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
2326
+ const folders = ["logs", "secret"];
2327
+ for (const folder of folders) {
2328
+ const src = path15.join(FLUXFLOW_DIR2, folder);
2329
+ const dest = path15.join(newPath, folder);
2330
+ try {
2331
+ if (await fs15.exists(src)) {
2332
+ await fs15.ensureDir(dest);
2333
+ await fs15.copy(src, dest, { overwrite: false });
2334
+ }
2335
+ } catch (err) {
2336
+ console.error(`Migration failed for ${folder}:`, err);
2337
+ }
2338
+ }
2339
+ };
2118
2340
  saveSettings = async (settings) => {
2119
2341
  try {
2120
2342
  const current = await loadSettings();
2343
+ if (!current.systemSettings.useExternalData && settings.systemSettings?.useExternalData && settings.systemSettings?.externalDataPath) {
2344
+ await migrateToExternal(settings.systemSettings.externalDataPath);
2345
+ }
2121
2346
  const updated = { ...current, ...settings };
2122
- await fs13.ensureDir(path14.dirname(SETTINGS_FILE));
2123
- await fs13.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
2347
+ await fs15.ensureDir(path15.dirname(SETTINGS_FILE));
2348
+ await fs15.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
2124
2349
  return true;
2125
2350
  } catch (err) {
2126
2351
  console.error("Failed to save settings:", err);
@@ -2269,24 +2494,39 @@ var init_UpdateProcessor = __esm({
2269
2494
  }
2270
2495
  });
2271
2496
 
2272
- // src/utils/terminal.js
2273
- var getTerminalEnv, emojiSpace;
2274
- var init_terminal = __esm({
2275
- "src/utils/terminal.js"() {
2276
- getTerminalEnv = () => {
2277
- if (process.env.TERM_PROGRAM === "vscode") return "vscode";
2278
- if (process.env.WT_SESSION) return "wt";
2279
- return "default";
2280
- };
2281
- emojiSpace = (baseSpaces = 2) => {
2282
- const env = getTerminalEnv();
2283
- if (env === "wt") {
2284
- return " ".repeat(Math.max(1, baseSpaces - 1));
2497
+ // src/utils/setup.js
2498
+ import puppeteer2 from "puppeteer";
2499
+ import { exec as exec2 } from "child_process";
2500
+ import { promisify } from "util";
2501
+ import fs16 from "fs";
2502
+ var execAsync, checkPuppeteerReady, installPuppeteerBrowser;
2503
+ var init_setup = __esm({
2504
+ "src/utils/setup.js"() {
2505
+ execAsync = promisify(exec2);
2506
+ checkPuppeteerReady = () => {
2507
+ try {
2508
+ const exePath = puppeteer2.executablePath();
2509
+ const exists = exePath && fs16.existsSync(exePath);
2510
+ if (exists) return true;
2511
+ } catch (e) {
2512
+ return false;
2285
2513
  }
2286
- if (env === "vscode") {
2287
- return " ".repeat(baseSpaces);
2514
+ return false;
2515
+ };
2516
+ installPuppeteerBrowser = async (onStatus) => {
2517
+ if (onStatus) onStatus("\u{1F4E5} Downloading Chromium engine (Wait a moment)...");
2518
+ try {
2519
+ try {
2520
+ await execAsync("pnpm exec puppeteer browsers install chrome");
2521
+ } catch (pnpmErr) {
2522
+ await execAsync("npx -y puppeteer browsers install chrome");
2523
+ }
2524
+ await new Promise((r) => setTimeout(r, 1e3));
2525
+ return { success: true };
2526
+ } catch (err) {
2527
+ console.error("[SETUP ERROR]", err);
2528
+ return { success: false, error: err.message };
2288
2529
  }
2289
- return " ".repeat(baseSpaces);
2290
2530
  };
2291
2531
  }
2292
2532
  });
@@ -2299,8 +2539,8 @@ __export(app_exports, {
2299
2539
  import React10, { useState as useState6, useEffect as useEffect4, useRef, useMemo } from "react";
2300
2540
  import { Box as Box10, Text as Text10, useInput as useInput4, useStdout } from "ink";
2301
2541
  import Spinner2 from "ink-spinner";
2302
- import fs14 from "fs-extra";
2303
- import { exec as exec2 } from "child_process";
2542
+ import fs17 from "fs-extra";
2543
+ import { exec as exec3 } from "child_process";
2304
2544
  import { MultilineInput } from "ink-multiline-input";
2305
2545
  import TextInput3 from "ink-text-input";
2306
2546
  import gradient from "gradient-string";
@@ -2355,7 +2595,7 @@ Check what's new using \`/changelog\` command.`,
2355
2595
  if (manual) {
2356
2596
  setMessages((prev) => {
2357
2597
  setCompletedIndex(prev.length + 1);
2358
- return [...prev, { id: "check-err-" + Date.now(), role: "system", text: `\u274C ERROR: Failed to check for updates: ${err.message}` }];
2598
+ return [...prev, { id: "check-err-" + Date.now(), role: "system", text: `\u274C ERROR: Failed to check for updates: ${err.message}`, isMeta: true }];
2359
2599
  });
2360
2600
  }
2361
2601
  }
@@ -2432,7 +2672,7 @@ Check what's new using \`/changelog\` command.`,
2432
2672
  const [resolutionData, setResolutionData] = useState6(null);
2433
2673
  const [tempModelOverride, setTempModelOverride] = useState6(null);
2434
2674
  const [messages, setMessages] = useState6([
2435
- { id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n" }
2675
+ { id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n", isMeta: true }
2436
2676
  ]);
2437
2677
  const queuedPromptRef = useRef(null);
2438
2678
  const [completedIndex, setCompletedIndex] = useState6(1);
@@ -2539,6 +2779,17 @@ Check what's new using \`/changelog\` command.`,
2539
2779
  });
2540
2780
  useEffect4(() => {
2541
2781
  async function init() {
2782
+ if (!checkPuppeteerReady()) {
2783
+ setMessages((prev) => {
2784
+ setCompletedIndex(prev.length + 1);
2785
+ return [...prev, { id: "setup-" + Date.now(), role: "system", text: "\u{1F527} [SYSTEM] Installing Required dependencies... (One-time setup)", isMeta: true }];
2786
+ });
2787
+ await installPuppeteerBrowser();
2788
+ setMessages((prev) => {
2789
+ setCompletedIndex(prev.length + 1);
2790
+ return [...prev, { id: "setup-done-" + Date.now(), role: "system", text: "\u2705 [SYSTEM] All dependencies installed successfully.", isMeta: true }];
2791
+ });
2792
+ }
2542
2793
  const saved = await loadSettings();
2543
2794
  setMode(saved.mode);
2544
2795
  setThinkingLevel(saved.thinkingLevel);
@@ -2589,9 +2840,9 @@ Check what's new using \`/changelog\` command.`,
2589
2840
  await saveAPIKey(key);
2590
2841
  setApiKey(key);
2591
2842
  initAI(key);
2592
- setMessages((prev) => [...prev, { role: "system", text: "\u2705 API Key saved successfully! Initialization complete." }]);
2843
+ setMessages((prev) => [...prev, { role: "system", text: "\u2705 API Key saved successfully! Initialization complete.", isMeta: true }]);
2593
2844
  } else {
2594
- setMessages((prev) => [...prev, { role: "system", text: `\u274C INVALID KEY: Gemini API keys must be at least 30 characters.` }]);
2845
+ setMessages((prev) => [...prev, { role: "system", text: `\u274C INVALID KEY: Gemini API keys must be at least 30 characters.`, isMeta: true }]);
2595
2846
  setTempKey("");
2596
2847
  }
2597
2848
  };
@@ -2706,7 +2957,7 @@ ${hintText}`, color: "magenta" }];
2706
2957
  }
2707
2958
  case "/clear": {
2708
2959
  stdout.write("\x1B[2J\x1B[3J\x1B[H");
2709
- setMessages([{ id: "welcome-" + Date.now(), role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome back to Flux Flow! Context cleared.\n" }]);
2960
+ setMessages([{ id: "welcome-" + Date.now(), role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome back to Flux Flow! Context cleared.\n", isMeta: true }]);
2710
2961
  setCompletedIndex(0);
2711
2962
  setChatId(generateChatId());
2712
2963
  setSessionStats({ tokens: 0 });
@@ -2833,12 +3084,12 @@ ${list || "No saved chats found."}`, isMeta: true }];
2833
3084
  setCompletedIndex(prev.length + 1);
2834
3085
  return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset...", isMeta: true }];
2835
3086
  });
2836
- if (fs14.existsSync(LOGS_DIR)) fs14.removeSync(LOGS_DIR);
2837
- if (fs14.existsSync(SECRET_DIR)) fs14.removeSync(SECRET_DIR);
2838
- if (fs14.existsSync(SETTINGS_FILE)) fs14.removeSync(SETTINGS_FILE);
3087
+ if (fs17.existsSync(LOGS_DIR)) fs17.removeSync(LOGS_DIR);
3088
+ if (fs17.existsSync(SECRET_DIR)) fs17.removeSync(SECRET_DIR);
3089
+ if (fs17.existsSync(SETTINGS_FILE)) fs17.removeSync(SETTINGS_FILE);
2839
3090
  try {
2840
- const items = fs14.readdirSync(FLUXFLOW_DIR);
2841
- if (items.length === 0) fs14.removeSync(FLUXFLOW_DIR);
3091
+ const items = fs17.readdirSync(FLUXFLOW_DIR);
3092
+ if (items.length === 0) fs17.removeSync(FLUXFLOW_DIR);
2842
3093
  } catch (e) {
2843
3094
  }
2844
3095
  setTimeout(() => {
@@ -2870,7 +3121,7 @@ ${list || "No saved chats found."}`, isMeta: true }];
2870
3121
  case "/changelog": {
2871
3122
  const platform = process.platform;
2872
3123
  const command = platform === "win32" ? "start" : platform === "darwin" ? "open" : "xdg-open";
2873
- exec2(`${command} ${CHANGELOG_URL}`);
3124
+ exec3(`${command} ${CHANGELOG_URL}`);
2874
3125
  setMessages((prev) => {
2875
3126
  setCompletedIndex(prev.length + 1);
2876
3127
  return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}`, isMeta: true }];
@@ -3065,8 +3316,10 @@ Selection: ${val}`,
3065
3316
  id: "tool-" + Date.now(),
3066
3317
  role: "system",
3067
3318
  text: packet.content,
3068
- fullText: packet.aiContent
3319
+ fullText: packet.aiContent,
3069
3320
  // Preserve raw data for next turn
3321
+ binaryPart: packet.binaryPart
3322
+ // v1.5.0 Multimodal Support
3070
3323
  }]);
3071
3324
  continue;
3072
3325
  }
@@ -3253,6 +3506,7 @@ Selection: ${val}`,
3253
3506
  { label: `API Tier [ ${apiTier} ]`, value: "apiTier" },
3254
3507
  { label: `Auto-Update [ ${systemSettings.autoUpdate ? "ON" : "OFF"} ]`, value: "autoUpdate" },
3255
3508
  { label: `Preferred Updater [ ${(systemSettings.updateManager || "npm") === "custom" ? "Custom" : (systemSettings.updateManager || "npm").toUpperCase()} ]`, value: "updateManager" },
3509
+ { label: `Save AppData Externally [ ${systemSettings.useExternalData ? "ON" : "OFF"} ]`, value: "externalData" },
3256
3510
  { label: "Exit Settings", value: "Cancel" }
3257
3511
  ],
3258
3512
  onSelect: (item) => {
@@ -3285,6 +3539,22 @@ Selection: ${val}`,
3285
3539
  setSystemSettings((s) => ({ ...s, autoDeleteHistory: options[nextIndex] }));
3286
3540
  } else if (item.value === "autoUpdate") {
3287
3541
  setSystemSettings((s) => ({ ...s, autoUpdate: !s.autoUpdate }));
3542
+ } else if (item.value === "externalData") {
3543
+ if (!systemSettings.useExternalData) {
3544
+ setInputConfig({
3545
+ label: "Enter absolute path for External AppData:",
3546
+ note: "All history, logs and secrets will be stored here. ~/.fluxflow/settings.json stays as anchor.",
3547
+ key: "externalDataPath",
3548
+ value: systemSettings.externalDataPath || ""
3549
+ });
3550
+ setActiveView("input");
3551
+ } else {
3552
+ const newSettings = { ...systemSettings, useExternalData: false };
3553
+ setSystemSettings(newSettings);
3554
+ saveSettings({ systemSettings: newSettings, apiTier, quotas });
3555
+ setMessages((prev) => [...prev, { id: Date.now(), role: "system", text: "\u{1F3E0} [STORAGE RESET] Flux Flow will return to default ~/.fluxflow after restart." }]);
3556
+ setActiveView("chat");
3557
+ }
3288
3558
  } else if (item.value === "updateManager") {
3289
3559
  setActiveView("updateManager");
3290
3560
  } else if (item.value === "Cancel") setActiveView("chat");
@@ -3377,6 +3647,11 @@ Selection: ${val}`,
3377
3647
  } else if (key === "janitorModel") {
3378
3648
  setJanitorModel(val);
3379
3649
  newSettings.janitorModel = val;
3650
+ } else if (key === "externalDataPath") {
3651
+ const newSysSettings = { ...systemSettings, useExternalData: true, externalDataPath: val.trim() };
3652
+ setSystemSettings(newSysSettings);
3653
+ newSettings.systemSettings = newSysSettings;
3654
+ setMessages((prev) => [...prev, { id: Date.now(), role: "system", text: "\u{1F4C1} [EXTERNAL STORAGE] Flux Flow will use " + val.trim() + " for data after restart." }]);
3380
3655
  }
3381
3656
  if (next) {
3382
3657
  setInputConfig(next(key === "quotas" ? newQuotas : val));
@@ -3795,10 +4070,11 @@ var init_app = __esm({
3795
4070
  init_paths();
3796
4071
  init_terminal();
3797
4072
  init_exec_command();
4073
+ init_setup();
3798
4074
  SESSION_START_TIME = Date.now();
3799
4075
  CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
3800
- versionFluxflow = "1.4.3";
3801
- updatedOn = "2026-04-30";
4076
+ versionFluxflow = "1.5.1";
4077
+ updatedOn = "2026-05-01";
3802
4078
  ResolutionModal = ({ data, onResolve, onEdit }) => /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 1, width: "100%" }, /* @__PURE__ */ React10.createElement(Text10, { color: "magenta", bold: true, underline: true }, "\u{1F7E3} STEERING HINT RESOLUTION"), /* @__PURE__ */ React10.createElement(Text10, { marginTop: 1 }, "The agent already finished the task (turn: finish) before your hint was consumed."), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1, backgroundColor: "#222", paddingX: 1, width: "100%" }, /* @__PURE__ */ React10.createElement(Text10, { italic: true, color: "gray" }, '"', data, '"')), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text10, { color: "cyan" }, "How would you like to proceed?")), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(
3803
4079
  CommandMenu,
3804
4080
  {