fluxflow-cli 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -29,6 +29,15 @@ The execution flow of a single user prompt follows this loop:
29
29
  - **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
30
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-preview`) for the final 3 attempts to ensure session navigation through API congestion.
31
31
 
32
+ ## Multimodal Pipeline
33
+
34
+ Flux Flow implements a native multimodal processing engine in `src/tools/view_file.js`. This allows the agent to move beyond text-based reasoning and analyze visual assets directly.
35
+
36
+ - **Binary Detection**: The pipeline uses `is-binary-path` to distinguish between text and binary files.
37
+ - **Visual Encoding**: If an image or PDF is detected, the engine reads the raw bytes and converts them into base64-encoded `InlineData` objects.
38
+ - **PDF Extraction**: For PDF documents, the engine extracts visual representation of pages to provide the model with high-fidelity spatial and textual context simultaneously.
39
+ - **Context Injection**: These multimodal assets are injected directly into the Gemini model's multimodal part array, allowing the model to "see" the file as if it were looking at a screenshot.
40
+
32
41
  ## The Dual-Model System
33
42
 
34
43
  To maintain a fast, snappy UI while still performing complex data management, Flux Flow employs two separate AI models for every interaction:
@@ -45,4 +54,12 @@ To maintain a fast, snappy UI while still performing complex data management, Fl
45
54
  ## Data Persistence & Safety
46
55
 
47
56
  - **High-Fidelity Lock**: Because both the UI and the Janitor model may attempt to write to the `history.json` file simultaneously, a Promise-based `WRITE_LOCK` (`src/utils/history.js`) is utilized. This prevents race conditions and ensures data integrity.
48
- - **Encryption**: User secrets and persistent memories (`secret/memories.json`) are handled by `src/utils/crypto.js` to ensure local privacy.
57
+ - **Encryption**: User secrets and persistent memories (`secret/memories.json`) are handled by `src/utils/crypto.js` to ensure local privacy.
58
+
59
+ ## Redirection & The Anchor Strategy
60
+
61
+ To support data portability (e.g., storing all app data on an external encrypted drive), Flux Flow utilizes a synchronous "Anchor" strategy in `src/utils/paths.js`.
62
+
63
+ - **Synchronous Pivot**: Because many core modules (History, Secrets, Usage) initialize their file paths as constants during module loading, the application must determine the "Actual" data root before anything else.
64
+ - **Boot-Sequence Priority**: On every launch, `paths.js` performs a synchronous file system check for `~/.fluxflow/settings.json`. If a redirection path is found (`useExternalData: true`), it immediately overrides the global `DATA_DIR` constant for the entire process.
65
+ - **Sub-Coordinate Resolution**: All secondary directories (`LOGS_DIR`, `SECRET_DIR`) are derived dynamically from the redirected `DATA_DIR`, ensuring that all session data flows to the external sanctuary without requiring individual configuration updates across the codebase.
package/README.md CHANGED
@@ -28,6 +28,16 @@ fluxflow-cli
28
28
  ### 🎨 **Premium Visual Sovereignty**
29
29
  Experience a terminal UI that feels alive. Built with **Ink** and **React**, Flux Flow features:
30
30
  - **Dynamic Status Bar**: Real-time telemetry showing your "Neural Headroom" (token usage), Thinking Level, and Session ID.
31
+
32
+ ### 👁️ **Native Multimodality**
33
+ Flux Flow can now see! Use the `view_file` tool to analyze images (JPG, PNG) or deep-dive into PDF technical papers. The agent extracts high-fidelity visual context natively, making it a true multimodal companion.
34
+
35
+ ### 📑 **Autonomous PDF Generator**
36
+ Need a report? Just ask. Flux Flow uses its internal "Printing Press" to generate professional, branded PDF documents from HTML/CSS, complete with consistent margins and a signature watermark.
37
+
38
+ ### 🚑 **Self-Healing Infrastructure**
39
+ Zero setup means zero setup. On first run, Flux Flow performs an integrity check and autonomously installs its own Chromium engine if needed, ensuring features like PDF generation work 100% of the time without manual intervention.
40
+
31
41
  - **Archived Terminal Flow**: See execution outputs transform from live elements into permanent conversation records.
32
42
  - **Rich Aesthetics**: High-contrast, sleek design with smooth transitions and micro-animations.
33
43
 
package/TOOLS.md CHANGED
@@ -8,6 +8,7 @@ Flux Flow provides a robust set of tools that allow the AI to interact with the
8
8
  | :--- | :---: | :---: |
9
9
  | **Web Search** | ✅ | ✅ |
10
10
  | **Web Scrape** | ✅ | ✅ |
11
+ | **Write PDF** | ✅ | ✅ |
11
12
  | **View/Read Files** | ✅ | ❌ |
12
13
  | **Write/Update Files** | ✅ | ❌ |
13
14
  | **Execute Commands** | ✅ | ❌ |
@@ -19,11 +20,14 @@ Flux Flow provides a robust set of tools that allow the AI to interact with the
19
20
  ### 🌐 Web & Research
20
21
  - **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
21
22
  - **`web_scrape`**: Extracts the detailed text content from a specific URL, allowing the agent to read documentation or articles.
23
+ - **`write_pdf`**: Generates high-fidelity, branded PDF documents from HTML/CSS. Features automatic watermarking and page-aware layout management.
22
24
 
23
25
  ### 📁 File System Operations
24
26
  - **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
25
27
  - **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
26
- - **`view_file`**: Reads the content of a file. Supports reading specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
28
+ - **`view_file`**: Reads the content of a file.
29
+ - **Native Multimodality**: Supports analyzing images (JPG, PNG, WEBP) and PDF documents. The tool automatically detects binary formats and encodes them for AI analysis.
30
+ - **Text Reading**: Supports specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
27
31
 
28
32
  ### ✍️ Code Editing
29
33
  - **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
package/UI_FEATURES.md CHANGED
@@ -18,6 +18,15 @@ You can control the application using `/` commands directly in the chat input:
18
18
  - **/changelog**: Open the project's changelog in your default browser.
19
19
  - **/help**: List all available commands.
20
20
 
21
+ ## 📁 External Data Sanctuary (Redirection)
22
+
23
+ Flux Flow allows you to "Anchor" your data outside of the default user directory. This is perfect for users who want to keep their logs, chat history, and encrypted memories on an external drive, a VeraCrypt volume, or a specific project folder.
24
+
25
+ - **How to Enable**: Open `/settings` and toggle **Use External Data**.
26
+ - **Portability**: Once set, the application will synchronously "pivot" all data operations to your specified `externalDataPath` upon startup.
27
+ - **Privacy**: Keeps sensitive data off your primary system drive.
28
+
29
+
21
30
  ### Command Shortcuts
22
31
 
23
32
  For power users, several commands support direct arguments to skip the menus:
@@ -81,4 +90,12 @@ The bottom of the screen features a dynamic status bar showing:
81
90
  - **Thinking Level**
82
91
  - **Token Usage**: Real-time tracking of tokens used in the current session.
83
92
  - **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
84
- - **API Status**: Visual feedback when the model is thinking or executing a tool.
93
+ - **API Status**: Visual feedback when the model is thinking or executing a tool.
94
+
95
+ ## 🚑 System Integrity & Self-Healing
96
+
97
+ Flux Flow is a "Self-Healing" agent. It actively monitors its own environment to ensure all complex dependencies (like the Chromium engine for PDF generation) are ready for action.
98
+
99
+ - **Startup Integrity Check**: On launch, Flux performs a "Heartbeat Check" on its internal engines.
100
+ - **Automatic Recovery**: If a dependency is missing, you will see a `🔧 [SYSTEM] Initializing...` message. Flux will autonomously download and configure the required binaries using `pnpm` or `npx` fallbacks, keeping you informed every step of the way.
101
+ - **Silent Maintenance**: Once the engine is ready, you'll receive a `✅ [SYSTEM] All dependencies installed successfully.` confirmation.
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,18 +607,22 @@ 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.`.trim() : `
587
- - 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()}
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.
617
+
618
+ **NOTE:** WHEN WRITING/UPDATING FILES, USE ACTUAL NEW LINE CHARACTER FOR LINE BREAKS RATHER THAN STRING '\\n'`.trim() : `
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()}
588
620
  -----------------
589
621
  Results will be provided in the next loop as: [TOOL_RESULT]: [content]
590
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]'.
591
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.
592
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.
593
626
  -- END FUNCTION CALLING PROTOCOL --`.trim();
594
627
  }
595
628
  });
@@ -751,7 +784,7 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
751
784
  });
752
785
 
753
786
  // src/utils/history.js
754
- import fs3 from "fs-extra";
787
+ import fs4 from "fs-extra";
755
788
  import path4 from "path";
756
789
  import { nanoid } from "nanoid";
757
790
  var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, getTruncatedHistory;
@@ -774,9 +807,9 @@ var init_history = __esm({
774
807
  return nextLock;
775
808
  };
776
809
  loadHistory = async () => {
777
- if (await fs3.pathExists(HISTORY_FILE)) {
810
+ if (await fs4.pathExists(HISTORY_FILE)) {
778
811
  try {
779
- return await fs3.readJson(HISTORY_FILE);
812
+ return await fs4.readJson(HISTORY_FILE);
780
813
  } catch (e) {
781
814
  return {};
782
815
  }
@@ -794,8 +827,8 @@ var init_history = __esm({
794
827
  messages: persistentMessages,
795
828
  updatedAt: Date.now()
796
829
  };
797
- await fs3.ensureDir(path4.dirname(HISTORY_FILE));
798
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
830
+ await fs4.ensureDir(path4.dirname(HISTORY_FILE));
831
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
799
832
  });
800
833
  };
801
834
  saveChatTitle = async (id, title) => {
@@ -807,15 +840,15 @@ var init_history = __esm({
807
840
  } else {
808
841
  history[id] = { name: title, messages: [], updatedAt: Date.now() };
809
842
  }
810
- await fs3.ensureDir(path4.dirname(HISTORY_FILE));
811
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
843
+ await fs4.ensureDir(path4.dirname(HISTORY_FILE));
844
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
812
845
  });
813
846
  };
814
847
  deleteChat = async (id) => {
815
848
  return withLock(async () => {
816
849
  const history = await loadHistory();
817
850
  delete history[id];
818
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
851
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
819
852
  const temp = readEncryptedJson(TEMP_MEM_FILE, {});
820
853
  if (temp[id]) {
821
854
  delete temp[id];
@@ -854,7 +887,7 @@ var init_history = __esm({
854
887
  });
855
888
 
856
889
  // src/utils/usage.js
857
- import fs4 from "fs-extra";
890
+ import fs5 from "fs-extra";
858
891
  import path5 from "path";
859
892
  var getDailyUsage, incrementUsage, checkQuota;
860
893
  var init_usage = __esm({
@@ -863,8 +896,8 @@ var init_usage = __esm({
863
896
  getDailyUsage = async () => {
864
897
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
865
898
  try {
866
- if (await fs4.exists(USAGE_FILE)) {
867
- const data = await fs4.readJson(USAGE_FILE);
899
+ if (await fs5.exists(USAGE_FILE)) {
900
+ const data = await fs5.readJson(USAGE_FILE);
868
901
  if (data.date === today) {
869
902
  return data.stats;
870
903
  }
@@ -873,21 +906,21 @@ var init_usage = __esm({
873
906
  console.error("Failed to read usage:", err);
874
907
  }
875
908
  const defaultStats = { agent: 0, background: 0, search: 0 };
876
- await fs4.ensureDir(path5.dirname(USAGE_FILE));
877
- await fs4.writeJson(USAGE_FILE, { date: today, stats: defaultStats }, { spaces: 2 });
909
+ await fs5.ensureDir(path5.dirname(USAGE_FILE));
910
+ await fs5.writeJson(USAGE_FILE, { date: today, stats: defaultStats }, { spaces: 2 });
878
911
  return defaultStats;
879
912
  };
880
913
  incrementUsage = async (key) => {
881
914
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
882
- const data = await fs4.readJson(USAGE_FILE).catch(() => ({ date: today, stats: { agent: 0, background: 0, search: 0 } }));
915
+ const data = await fs5.readJson(USAGE_FILE).catch(() => ({ date: today, stats: { agent: 0, background: 0, search: 0 } }));
883
916
  if (data.date !== today) {
884
917
  data.date = today;
885
918
  data.stats = { agent: 0, background: 0, search: 0 };
886
919
  }
887
920
  if (data.stats[key] !== void 0) {
888
921
  data.stats[key]++;
889
- await fs4.ensureDir(path5.dirname(USAGE_FILE));
890
- await fs4.writeJson(USAGE_FILE, data, { spaces: 2 });
922
+ await fs5.ensureDir(path5.dirname(USAGE_FILE));
923
+ await fs5.writeJson(USAGE_FILE, data, { spaces: 2 });
891
924
  }
892
925
  };
893
926
  checkQuota = async (key, settings) => {
@@ -941,7 +974,7 @@ var init_arg_parser = __esm({
941
974
 
942
975
  // src/tools/web_search.js
943
976
  import * as cuimp from "cuimp";
944
- import fs5 from "fs";
977
+ import fs6 from "fs";
945
978
  import path6 from "path";
946
979
  var web_search;
947
980
  var init_web_search = __esm({
@@ -976,10 +1009,10 @@ Snippet: ${snippet}`);
976
1009
  count++;
977
1010
  }
978
1011
  const toolLogDir = path6.join(LOGS_DIR, "tools");
979
- if (!fs5.existsSync(toolLogDir)) {
980
- fs5.mkdirSync(toolLogDir, { recursive: true });
1012
+ if (!fs6.existsSync(toolLogDir)) {
1013
+ fs6.mkdirSync(toolLogDir, { recursive: true });
981
1014
  }
982
- fs5.appendFileSync(path6.join(toolLogDir, "search-results.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
1015
+ fs6.appendFileSync(path6.join(toolLogDir, "search-results.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
983
1016
  Query: [${query}]. Results Count: ${results.length}.
984
1017
  Results: ${results}
985
1018
 
@@ -988,10 +1021,10 @@ Results: ${results}
988
1021
  if (results.length === 0) {
989
1022
  if (html.includes("anomaly")) {
990
1023
  const toolErrDir = path6.join(LOGS_DIR, "tools");
991
- if (!fs5.existsSync(toolErrDir)) {
992
- fs5.mkdirSync(toolErrDir, { recursive: true });
1024
+ if (!fs6.existsSync(toolErrDir)) {
1025
+ fs6.mkdirSync(toolErrDir, { recursive: true });
993
1026
  }
994
- fs5.appendFileSync(path6.join(toolErrDir, "error.log"), `ERROR ${(/* @__PURE__ */ new Date()).toISOString()} - DDG detected unusual activity. Cuimp impersonation might need adjustment.
1027
+ fs6.appendFileSync(path6.join(toolErrDir, "error.log"), `ERROR ${(/* @__PURE__ */ new Date()).toISOString()} - DDG detected unusual activity. Cuimp impersonation might need adjustment.
995
1028
  `);
996
1029
  throw new Error("DDG detected unusual activity. Cuimp impersonation might need adjustment.");
997
1030
  }
@@ -1009,7 +1042,7 @@ ${finalResults}`;
1009
1042
  });
1010
1043
 
1011
1044
  // src/tools/web_scrape.js
1012
- import fs6 from "fs";
1045
+ import fs7 from "fs";
1013
1046
  import path7 from "path";
1014
1047
  import * as cuimp2 from "cuimp";
1015
1048
  var web_scrape;
@@ -1037,10 +1070,10 @@ var init_web_scrape = __esm({
1037
1070
  let text = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1038
1071
  const finalContent = text.substring(0, 2e4);
1039
1072
  const toolLogDir = path7.join(LOGS_DIR, "tools");
1040
- if (!fs6.existsSync(toolLogDir)) {
1041
- fs6.mkdirSync(toolLogDir, { recursive: true });
1073
+ if (!fs7.existsSync(toolLogDir)) {
1074
+ fs7.mkdirSync(toolLogDir, { recursive: true });
1042
1075
  }
1043
- fs6.appendFileSync(path7.join(toolLogDir, "search-scraped.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
1076
+ fs7.appendFileSync(path7.join(toolLogDir, "search-scraped.log"), `RESULTS ${(/* @__PURE__ */ new Date()).toISOString()} -
1044
1077
  Query: [${url}].
1045
1078
  Results: ${finalContent}
1046
1079
 
@@ -1151,7 +1184,7 @@ var init_chat = __esm({
1151
1184
  });
1152
1185
 
1153
1186
  // src/tools/list_files.js
1154
- import fs7 from "fs";
1187
+ import fs8 from "fs";
1155
1188
  import path8 from "path";
1156
1189
  var list_files;
1157
1190
  var init_list_files = __esm({
@@ -1161,14 +1194,14 @@ var init_list_files = __esm({
1161
1194
  const { path: targetPath = "." } = parseArgs(args);
1162
1195
  const absolutePath = path8.resolve(process.cwd(), targetPath);
1163
1196
  try {
1164
- if (!fs7.existsSync(absolutePath)) {
1197
+ if (!fs8.existsSync(absolutePath)) {
1165
1198
  return `ERROR: Path [${targetPath}] does not exist.`;
1166
1199
  }
1167
- const stats = fs7.statSync(absolutePath);
1200
+ const stats = fs8.statSync(absolutePath);
1168
1201
  if (!stats.isDirectory()) {
1169
1202
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use view_file instead.`;
1170
1203
  }
1171
- const files = fs7.readdirSync(absolutePath);
1204
+ const files = fs8.readdirSync(absolutePath);
1172
1205
  if (files.length === 0) {
1173
1206
  return `Directory [${targetPath}] is empty.`;
1174
1207
  }
@@ -1180,7 +1213,7 @@ var init_list_files = __esm({
1180
1213
  let indicator = "\u{1F4C4}";
1181
1214
  let metaPart = "";
1182
1215
  try {
1183
- const fStats = fs7.statSync(fPath);
1216
+ const fStats = fs8.statSync(fPath);
1184
1217
  indicator = fStats.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
1185
1218
  const sizeKB = (fStats.size / 1024).toFixed(1);
1186
1219
  metaPart = fStats.isFile() ? ` - [${sizeKB} KB]` : "";
@@ -1212,7 +1245,7 @@ ${list}${footer}`;
1212
1245
  });
1213
1246
 
1214
1247
  // src/tools/view_file.js
1215
- import fs8 from "fs";
1248
+ import fs9 from "fs";
1216
1249
  import path9 from "path";
1217
1250
  var view_file;
1218
1251
  var init_view_file = __esm({
@@ -1223,14 +1256,38 @@ var init_view_file = __esm({
1223
1256
  if (!targetPath) return 'ERROR: Missing "path" argument for view_file.';
1224
1257
  const absolutePath = path9.resolve(process.cwd(), targetPath);
1225
1258
  try {
1226
- if (!fs8.existsSync(absolutePath)) {
1259
+ if (!fs9.existsSync(absolutePath)) {
1227
1260
  return `ERROR: File [${targetPath}] does not exist.`;
1228
1261
  }
1229
- const stats = fs8.statSync(absolutePath);
1262
+ const stats = fs9.statSync(absolutePath);
1230
1263
  if (stats.isDirectory()) {
1231
1264
  return `ERROR: Path [${targetPath}] is a directory. Use list_files instead.`;
1232
1265
  }
1233
- const content = fs8.readFileSync(absolutePath, "utf8");
1266
+ const ext = path9.extname(targetPath).toLowerCase();
1267
+ const mimeMap = {
1268
+ ".pdf": "application/pdf",
1269
+ ".jpg": "image/jpeg",
1270
+ ".jpeg": "image/jpeg",
1271
+ ".png": "image/png",
1272
+ ".webp": "image/webp",
1273
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1274
+ ".doc": "application/msword"
1275
+ };
1276
+ if (mimeMap[ext]) {
1277
+ const buffer = fs9.readFileSync(absolutePath);
1278
+ const base64 = buffer.toString("base64");
1279
+ const mimeType = mimeMap[ext];
1280
+ return {
1281
+ text: `[BINARY_FILE]: ${targetPath} (${mimeType}) - Loaded as multimodal part.`,
1282
+ binaryPart: {
1283
+ inlineData: {
1284
+ data: base64,
1285
+ mimeType
1286
+ }
1287
+ }
1288
+ };
1289
+ }
1290
+ const content = fs9.readFileSync(absolutePath, "utf8");
1234
1291
  const lines = content.split("\n");
1235
1292
  const totalLines = lines.length;
1236
1293
  const start = Math.max(0, start_line - 1);
@@ -1249,7 +1306,7 @@ ${code}`;
1249
1306
  });
1250
1307
 
1251
1308
  // src/tools/write_file.js
1252
- import fs9 from "fs";
1309
+ import fs10 from "fs";
1253
1310
  import path10 from "path";
1254
1311
  var write_file;
1255
1312
  var init_write_file = __esm({
@@ -1264,9 +1321,9 @@ var init_write_file = __esm({
1264
1321
  const parentDir = path10.dirname(absolutePath);
1265
1322
  try {
1266
1323
  let ancestry = "";
1267
- if (fs9.existsSync(absolutePath)) {
1324
+ if (fs10.existsSync(absolutePath)) {
1268
1325
  try {
1269
- const oldData = fs9.readFileSync(absolutePath, "utf8");
1326
+ const oldData = fs10.readFileSync(absolutePath, "utf8");
1270
1327
  const lines = oldData.split(/\r?\n/);
1271
1328
  ancestry = `Old File contents:
1272
1329
  ${lines.map((l, i) => `${i + 1} | ${l}`).join("\n")}
@@ -1278,13 +1335,13 @@ ${lines.map((l, i) => `${i + 1} | ${l}`).join("\n")}
1278
1335
  `;
1279
1336
  }
1280
1337
  }
1281
- if (!fs9.existsSync(parentDir)) {
1282
- fs9.mkdirSync(parentDir, { recursive: true });
1338
+ if (!fs10.existsSync(parentDir)) {
1339
+ fs10.mkdirSync(parentDir, { recursive: true });
1283
1340
  }
1284
1341
  const lineCount = content.split(/\r?\n/).length;
1285
1342
  const originalSize = Buffer.byteLength(content, "utf8");
1286
- fs9.writeFileSync(absolutePath, content, "utf8");
1287
- let verifiedContent = fs9.readFileSync(absolutePath, "utf8");
1343
+ fs10.writeFileSync(absolutePath, content, "utf8");
1344
+ let verifiedContent = fs10.readFileSync(absolutePath, "utf8");
1288
1345
  const verifiedSize = Buffer.byteLength(verifiedContent, "utf8");
1289
1346
  const verifiedLines = verifiedContent.split(/\r?\n/);
1290
1347
  const verifiedLineCount = verifiedLines.length;
@@ -1318,7 +1375,7 @@ ${snippet}`;
1318
1375
  });
1319
1376
 
1320
1377
  // src/tools/update_file.js
1321
- import fs10 from "fs";
1378
+ import fs11 from "fs";
1322
1379
  import path11 from "path";
1323
1380
  var update_file;
1324
1381
  var init_update_file = __esm({
@@ -1334,10 +1391,10 @@ var init_update_file = __esm({
1334
1391
  content_to_add = strip(content_to_add);
1335
1392
  const absolutePath = path11.resolve(process.cwd(), targetPath);
1336
1393
  try {
1337
- if (!fs10.existsSync(absolutePath)) {
1394
+ if (!fs11.existsSync(absolutePath)) {
1338
1395
  return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
1339
1396
  }
1340
- const currentContent = fs10.readFileSync(absolutePath, "utf8");
1397
+ const currentContent = fs11.readFileSync(absolutePath, "utf8");
1341
1398
  if (!currentContent.includes(content_to_replace)) {
1342
1399
  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.`;
1343
1400
  }
@@ -1345,7 +1402,7 @@ var init_update_file = __esm({
1345
1402
  const startLine = currentContent.substring(0, startPos).split(/\r?\n/).length;
1346
1403
  const instances = currentContent.split(content_to_replace).length - 1;
1347
1404
  const newFileContent = currentContent.split(content_to_replace).join(content_to_add);
1348
- fs10.writeFileSync(absolutePath, newFileContent, "utf8");
1405
+ fs11.writeFileSync(absolutePath, newFileContent, "utf8");
1349
1406
  const allOriginalLines = currentContent.split(/\r?\n/);
1350
1407
  const oldLines = content_to_replace.split(/\r?\n/);
1351
1408
  const newLines = content_to_add.split(/\r?\n/);
@@ -1467,7 +1524,7 @@ ${finalOutput}`);
1467
1524
  });
1468
1525
 
1469
1526
  // src/tools/read_folder.js
1470
- import fs11 from "fs";
1527
+ import fs12 from "fs";
1471
1528
  import path12 from "path";
1472
1529
  var read_folder;
1473
1530
  var init_read_folder = __esm({
@@ -1477,14 +1534,14 @@ var init_read_folder = __esm({
1477
1534
  const { path: targetPath = "." } = parseArgs(args);
1478
1535
  const absolutePath = path12.resolve(process.cwd(), targetPath);
1479
1536
  try {
1480
- if (!fs11.existsSync(absolutePath)) {
1537
+ if (!fs12.existsSync(absolutePath)) {
1481
1538
  return `ERROR: Path [${targetPath}] does not exist.`;
1482
1539
  }
1483
- const stats = fs11.statSync(absolutePath);
1540
+ const stats = fs12.statSync(absolutePath);
1484
1541
  if (!stats.isDirectory()) {
1485
1542
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use view_file instead.`;
1486
1543
  }
1487
- const files = fs11.readdirSync(absolutePath);
1544
+ const files = fs12.readdirSync(absolutePath);
1488
1545
  const totalItems = files.length;
1489
1546
  const maxDisplay = 100;
1490
1547
  const displayItems = files.slice(0, maxDisplay);
@@ -1494,7 +1551,7 @@ var init_read_folder = __esm({
1494
1551
  let indicator = "\u{1F4C4}";
1495
1552
  let info = { name: file, type: "unknown", size: "N/A", mtime: "N/A" };
1496
1553
  try {
1497
- const fStats = fs11.statSync(fPath);
1554
+ const fStats = fs12.statSync(fPath);
1498
1555
  info = {
1499
1556
  name: file,
1500
1557
  type: fStats.isDirectory() ? "directory" : "file",
@@ -1575,6 +1632,78 @@ var init_ask_user = __esm({
1575
1632
  }
1576
1633
  });
1577
1634
 
1635
+ // src/tools/write_pdf.js
1636
+ import puppeteer from "puppeteer";
1637
+ import path13 from "path";
1638
+ import fs13 from "fs-extra";
1639
+ var write_pdf;
1640
+ var init_write_pdf = __esm({
1641
+ "src/tools/write_pdf.js"() {
1642
+ init_arg_parser();
1643
+ write_pdf = async (args) => {
1644
+ const { path: targetPath, content, orientation = "portrait", margin = "10px" } = parseArgs(args);
1645
+ if (!targetPath) return 'ERROR: Missing "path" argument for write_pdf.';
1646
+ if (!content) return 'ERROR: Missing "content" (HTML/CSS) for write_pdf.';
1647
+ const absolutePath = path13.resolve(process.cwd(), targetPath);
1648
+ let browser = null;
1649
+ try {
1650
+ await fs13.ensureDir(path13.dirname(absolutePath));
1651
+ browser = await puppeteer.launch({
1652
+ headless: true,
1653
+ args: [
1654
+ "--no-sandbox",
1655
+ "--disable-setuid-sandbox",
1656
+ "--disable-gpu",
1657
+ "--disable-dev-shm-usage"
1658
+ ]
1659
+ });
1660
+ const page = await browser.newPage();
1661
+ const styledContent = `
1662
+ <style>
1663
+ @page {
1664
+ margin: ${margin};
1665
+ }
1666
+ body {
1667
+ margin: 0;
1668
+ padding: 0;
1669
+ font-family: system-ui, -apple-system, sans-serif;
1670
+ }
1671
+ * { box-sizing: border-box; }
1672
+ </style>
1673
+ ${content}
1674
+ `;
1675
+ await page.setContent(styledContent, { waitUntil: "networkidle0" });
1676
+ await page.pdf({
1677
+ path: absolutePath,
1678
+ format: "A4",
1679
+ landscape: orientation.toLowerCase() === "landscape",
1680
+ margin: {
1681
+ top: margin,
1682
+ right: margin,
1683
+ bottom: "15mm",
1684
+ // Space for watermark
1685
+ left: margin
1686
+ },
1687
+ displayHeaderFooter: true,
1688
+ headerTemplate: "<span></span>",
1689
+ footerTemplate: `
1690
+ <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;">
1691
+ FluxFlow CLI
1692
+ </div>
1693
+ `,
1694
+ printBackground: true
1695
+ });
1696
+ const stats = await fs13.stat(absolutePath);
1697
+ return `SUCCESS: PDF generated successfully at [${targetPath}] (${(stats.size / 1024).toFixed(2)} KB).`;
1698
+ } catch (err) {
1699
+ return `ERROR: Failed to generate PDF [${targetPath}]: ${err.message}`;
1700
+ } finally {
1701
+ if (browser) await browser.close();
1702
+ }
1703
+ };
1704
+ }
1705
+ });
1706
+
1578
1707
  // src/utils/tools.js
1579
1708
  var TOOL_MAP, dispatchTool;
1580
1709
  var init_tools = __esm({
@@ -1590,6 +1719,7 @@ var init_tools = __esm({
1590
1719
  init_exec_command();
1591
1720
  init_read_folder();
1592
1721
  init_ask_user();
1722
+ init_write_pdf();
1593
1723
  TOOL_MAP = {
1594
1724
  web_search,
1595
1725
  web_scrape,
@@ -1601,6 +1731,7 @@ var init_tools = __esm({
1601
1731
  update_file,
1602
1732
  exec_command,
1603
1733
  read_folder,
1734
+ write_pdf,
1604
1735
  ask: ask_user
1605
1736
  };
1606
1737
  dispatchTool = async (toolName, args, context = {}) => {
@@ -1617,10 +1748,32 @@ var init_tools = __esm({
1617
1748
  }
1618
1749
  });
1619
1750
 
1751
+ // src/utils/terminal.js
1752
+ var getTerminalEnv, emojiSpace;
1753
+ var init_terminal = __esm({
1754
+ "src/utils/terminal.js"() {
1755
+ getTerminalEnv = () => {
1756
+ if (process.env.TERM_PROGRAM === "vscode") return "vscode";
1757
+ if (process.env.WT_SESSION) return "wt";
1758
+ return "default";
1759
+ };
1760
+ emojiSpace = (baseSpaces = 2) => {
1761
+ const env = getTerminalEnv();
1762
+ if (env === "wt") {
1763
+ return " ".repeat(Math.max(1, baseSpaces - 1));
1764
+ }
1765
+ if (env === "vscode") {
1766
+ return " ".repeat(baseSpaces);
1767
+ }
1768
+ return " ".repeat(baseSpaces);
1769
+ };
1770
+ }
1771
+ });
1772
+
1620
1773
  // src/utils/ai.js
1621
1774
  import { GoogleGenAI, ThinkingLevel } from "@google/genai";
1622
- import path13 from "path";
1623
- import fs12 from "fs";
1775
+ import path14 from "path";
1776
+ import fs14 from "fs";
1624
1777
  var client, TERMINATION_SIGNAL, signalTermination, detectToolCalls, initAI, getAIStream;
1625
1778
  var init_ai = __esm({
1626
1779
  "src/utils/ai.js"() {
@@ -1630,6 +1783,7 @@ var init_ai = __esm({
1630
1783
  init_tools();
1631
1784
  init_crypto();
1632
1785
  init_arg_parser();
1786
+ init_terminal();
1633
1787
  init_paths();
1634
1788
  client = null;
1635
1789
  TERMINATION_SIGNAL = false;
@@ -1733,10 +1887,16 @@ USER_PROMPT: ${agentText}`.trim();
1733
1887
  }
1734
1888
  }
1735
1889
  yield { type: "turn_reset", content: true };
1736
- const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => ({
1737
- role: msg.role === "user" || msg.role === "system" ? "user" : "model",
1738
- parts: [{ text: msg.text }]
1739
- }));
1890
+ const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => {
1891
+ const parts = [{ text: msg.text }];
1892
+ if (msg.binaryPart) {
1893
+ parts.push(msg.binaryPart);
1894
+ }
1895
+ return {
1896
+ role: msg.role === "user" || msg.role === "system" ? "user" : "model",
1897
+ parts
1898
+ };
1899
+ });
1740
1900
  let stream;
1741
1901
  let success = false;
1742
1902
  let retryCount = 0;
@@ -1770,9 +1930,9 @@ USER_PROMPT: ${agentText}`.trim();
1770
1930
  } catch (err) {
1771
1931
  const errMsg = err.status || err.error && err.error.message || String(err);
1772
1932
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1773
- const agentErrDir = path13.join(LOGS_DIR, "agent");
1774
- if (!fs12.existsSync(agentErrDir)) fs12.mkdirSync(agentErrDir, { recursive: true });
1775
- fs12.appendFileSync(path13.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errMsg}
1933
+ const agentErrDir = path14.join(LOGS_DIR, "agent");
1934
+ if (!fs14.existsSync(agentErrDir)) fs14.mkdirSync(agentErrDir, { recursive: true });
1935
+ fs14.appendFileSync(path14.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errMsg}
1776
1936
  `);
1777
1937
  if (retryCount < MAX_RETRIES) {
1778
1938
  retryCount++;
@@ -1832,9 +1992,9 @@ USER_PROMPT: ${agentText}`.trim();
1832
1992
  let totalLines = "...";
1833
1993
  let actualEndLine = end_line;
1834
1994
  try {
1835
- const absPath = path13.resolve(process.cwd(), targetPath2);
1836
- if (fs12.existsSync(absPath)) {
1837
- const content = fs12.readFileSync(absPath, "utf8");
1995
+ const absPath = path14.resolve(process.cwd(), targetPath2);
1996
+ if (fs14.existsSync(absPath)) {
1997
+ const content = fs14.readFileSync(absPath, "utf8");
1838
1998
  const lines = content.split("\n").length;
1839
1999
  totalLines = lines;
1840
2000
  actualEndLine = Math.min(end_line, lines);
@@ -1848,6 +2008,8 @@ USER_PROMPT: ${agentText}`.trim();
1848
2008
  } else if (toolCall.toolName === "write_file" || toolCall.toolName === "update_file") {
1849
2009
  const action = toolCall.toolName === "write_file" ? "WRITING" : "PATCHING";
1850
2010
  label = `\u{1F4BE} ${action} FILE: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2011
+ } else if (toolCall.toolName === "write_pdf") {
2012
+ label = `\u{1F4D1} GENERATING PDF: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
1851
2013
  } else if (toolCall.toolName === "exec_command" || toolCall.toolName === "ask") {
1852
2014
  label = "";
1853
2015
  } else {
@@ -1882,7 +2044,7 @@ ${boxBottom}
1882
2044
  /\/usr\//
1883
2045
  // Sensitive Linux paths
1884
2046
  ];
1885
- const currentDrive = path13.resolve(process.cwd()).substring(0, 3).toLowerCase();
2047
+ const currentDrive = path14.resolve(process.cwd()).substring(0, 3).toLowerCase();
1886
2048
  const isViolating = riskyPatterns.some((pattern) => {
1887
2049
  if (pattern.source === "[a-zA-Z]:[\\\\\\/]") {
1888
2050
  const driveMatch = command.match(/[a-zA-Z]:[\\\/]/i);
@@ -1904,8 +2066,8 @@ ${boxBottom}
1904
2066
  const targetPath = parsedArgs.path || parsedArgs.targetPath || null;
1905
2067
  if (targetPath) {
1906
2068
  const isExternalOff = settings.systemSettings && settings.systemSettings.allowExternalAccess === false;
1907
- const absoluteTarget = path13.resolve(targetPath);
1908
- const absoluteCwd = path13.resolve(process.cwd());
2069
+ const absoluteTarget = path14.resolve(targetPath);
2070
+ const absoluteCwd = path14.resolve(process.cwd());
1909
2071
  if (isExternalOff && !absoluteTarget.startsWith(absoluteCwd)) {
1910
2072
  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.`;
1911
2073
  toolResults.push(`[TOOL_RESULT]: ERROR: ${denyMsg}`);
@@ -1931,12 +2093,17 @@ ${boxBottom}
1931
2093
  }
1932
2094
  }
1933
2095
  }
1934
- const result = await dispatchTool(toolCall.toolName, toolCall.args, {
2096
+ let result = await dispatchTool(toolCall.toolName, toolCall.args, {
1935
2097
  chatId,
1936
2098
  history,
1937
2099
  onChunk: (chunk) => settings.onExecChunk ? settings.onExecChunk(chunk) : null,
1938
2100
  onAskUser: settings.onAskUser
1939
2101
  });
2102
+ let binaryPart = null;
2103
+ if (typeof result === "object" && result.binaryPart) {
2104
+ binaryPart = result.binaryPart;
2105
+ result = result.text;
2106
+ }
1940
2107
  if (toolCall.toolName === "exec_command" && settings.onExecEnd) {
1941
2108
  await new Promise((resolve) => setTimeout(resolve, 800));
1942
2109
  settings.onExecEnd();
@@ -1945,17 +2112,17 @@ ${boxBottom}
1945
2112
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1946
2113
  const isErr = result.startsWith("ERROR:");
1947
2114
  const logStatus = isErr ? result.trim() : "SUCCESS";
1948
- const toolHistDir = path13.join(LOGS_DIR, "tools");
1949
- if (!fs12.existsSync(toolHistDir)) {
1950
- fs12.mkdirSync(toolHistDir, { recursive: true });
2115
+ const toolHistDir = path14.join(LOGS_DIR, "tools");
2116
+ if (!fs14.existsSync(toolHistDir)) {
2117
+ fs14.mkdirSync(toolHistDir, { recursive: true });
1951
2118
  }
1952
- fs12.appendFileSync(path13.join(toolHistDir, "history.log"), `HISTORY [${timestamp}]: ${toolCall.toolName} [${logStatus}]
2119
+ fs14.appendFileSync(path14.join(toolHistDir, "history.log"), `HISTORY [${timestamp}]: ${toolCall.toolName} [${logStatus}]
1953
2120
  `);
1954
2121
  } catch (logErr) {
1955
2122
  }
1956
2123
  const cleanResultForAI = result.split(/\r?\n/).filter((line) => !line.includes("[UI_CONTEXT]")).join("\n");
1957
2124
  const aiContent = `[TOOL_RESULT]: ${cleanResultForAI}`;
1958
- toolResults.push(aiContent);
2125
+ toolResults.push({ role: "user", text: aiContent, binaryPart });
1959
2126
  let uiContent = `[TOOL_RESULT]: ${result}`;
1960
2127
  if (toolCall.toolName === "view_file" || toolCall.toolName === "web_scrape") {
1961
2128
  uiContent = `[TOOL_RESULT]: ${label} (Context Locked for UI Clarity)`;
@@ -1963,7 +2130,9 @@ ${boxBottom}
1963
2130
  yield {
1964
2131
  type: "tool_result",
1965
2132
  content: uiContent,
1966
- aiContent
2133
+ aiContent,
2134
+ binaryPart
2135
+ // Multi-modal stage (v1.5.0)
1967
2136
  };
1968
2137
  if (toolCall.toolName === "memory" && result.includes("SUCCESS")) {
1969
2138
  yield { type: "memory_updated" };
@@ -2009,11 +2178,11 @@ ${boxBottom}
2009
2178
  if (parts && parts[1]?.text) {
2010
2179
  finalSynthesis = parts[1].text;
2011
2180
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2012
- const janitorLogDir = path13.join(LOGS_DIR, "janitor");
2013
- if (!fs12.existsSync(janitorLogDir)) {
2014
- fs12.mkdirSync(janitorLogDir, { recursive: true });
2181
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
2182
+ if (!fs14.existsSync(janitorLogDir)) {
2183
+ fs14.mkdirSync(janitorLogDir, { recursive: true });
2015
2184
  }
2016
- fs12.appendFileSync(path13.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
2185
+ fs14.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
2017
2186
  `);
2018
2187
  } else if (parts && parts[0]?.text) finalSynthesis = parts[0].text;
2019
2188
  else if (janitorResult.response && janitorResult.response.text) finalSynthesis = janitorResult.response.text();
@@ -2025,8 +2194,8 @@ ${boxBottom}
2025
2194
  const toolContext = { chatId, sessionId: chatId, history };
2026
2195
  const result = await dispatchTool(janitorToolCall.toolName, janitorToolCall.args, toolContext);
2027
2196
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2028
- const janitorLogDir = path13.join(LOGS_DIR, "janitor");
2029
- fs12.appendFileSync(path13.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
2197
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
2198
+ fs14.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
2030
2199
  `);
2031
2200
  if (janitorToolCall.toolName === "memory" && !janitorToolCall.args.includes("action='temp'")) {
2032
2201
  yield { type: "memory_updated" };
@@ -2034,11 +2203,11 @@ ${boxBottom}
2034
2203
  }
2035
2204
  } catch (janitorErr) {
2036
2205
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2037
- const janitorErrDir = path13.join(LOGS_DIR, "janitor");
2038
- if (!fs12.existsSync(janitorErrDir)) {
2039
- fs12.mkdirSync(janitorErrDir, { recursive: true });
2206
+ const janitorErrDir = path14.join(LOGS_DIR, "janitor");
2207
+ if (!fs14.existsSync(janitorErrDir)) {
2208
+ fs14.mkdirSync(janitorErrDir, { recursive: true });
2040
2209
  }
2041
- fs12.appendFileSync(path13.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
2210
+ fs14.appendFileSync(path14.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
2042
2211
  `);
2043
2212
  console.error("Janitor Background Tasks Failed:", janitorErr.message);
2044
2213
  }
@@ -2055,8 +2224,11 @@ ${timestamp}`;
2055
2224
  if (isActuallyFinished) break;
2056
2225
  const nextAgentMsg = cleanedTurnText.trim() || "*Working...*";
2057
2226
  modifiedHistory.push({ role: "agent", text: nextAgentMsg });
2058
- const nextUserMsg = toolResults.length > 0 ? toolResults.join("\n") : "[turn: continue]";
2059
- modifiedHistory.push({ role: "user", text: nextUserMsg });
2227
+ if (toolResults.length > 0) {
2228
+ toolResults.forEach((tr) => modifiedHistory.push(tr));
2229
+ } else {
2230
+ modifiedHistory.push({ role: "user", text: "[turn: continue]" });
2231
+ }
2060
2232
  }
2061
2233
  yield { type: "status", content: null };
2062
2234
  };
@@ -2064,9 +2236,9 @@ ${timestamp}`;
2064
2236
  });
2065
2237
 
2066
2238
  // src/utils/settings.js
2067
- import fs13 from "fs-extra";
2068
- import path14 from "path";
2069
- var DEFAULT_SETTINGS, loadSettings, saveSettings;
2239
+ import fs15 from "fs-extra";
2240
+ import path15 from "path";
2241
+ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
2070
2242
  var init_settings = __esm({
2071
2243
  "src/utils/settings.js"() {
2072
2244
  init_paths();
@@ -2088,7 +2260,9 @@ var init_settings = __esm({
2088
2260
  compression: 0,
2089
2261
  autoExec: false,
2090
2262
  allowExternalAccess: false,
2091
- autoDeleteHistory: "7d"
2263
+ autoDeleteHistory: "7d",
2264
+ useExternalData: false,
2265
+ externalDataPath: ""
2092
2266
  },
2093
2267
  profileData: {
2094
2268
  name: null,
@@ -2098,8 +2272,8 @@ var init_settings = __esm({
2098
2272
  };
2099
2273
  loadSettings = async () => {
2100
2274
  try {
2101
- if (await fs13.exists(SETTINGS_FILE)) {
2102
- const saved = await fs13.readJson(SETTINGS_FILE);
2275
+ if (await fs15.exists(SETTINGS_FILE)) {
2276
+ const saved = await fs15.readJson(SETTINGS_FILE);
2103
2277
  return {
2104
2278
  ...DEFAULT_SETTINGS,
2105
2279
  ...saved,
@@ -2113,12 +2287,31 @@ var init_settings = __esm({
2113
2287
  }
2114
2288
  return DEFAULT_SETTINGS;
2115
2289
  };
2290
+ migrateToExternal = async (newPath) => {
2291
+ const { FLUXFLOW_DIR: FLUXFLOW_DIR2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
2292
+ const folders = ["logs", "secret"];
2293
+ for (const folder of folders) {
2294
+ const src = path15.join(FLUXFLOW_DIR2, folder);
2295
+ const dest = path15.join(newPath, folder);
2296
+ try {
2297
+ if (await fs15.exists(src)) {
2298
+ await fs15.ensureDir(dest);
2299
+ await fs15.copy(src, dest, { overwrite: false });
2300
+ }
2301
+ } catch (err) {
2302
+ console.error(`Migration failed for ${folder}:`, err);
2303
+ }
2304
+ }
2305
+ };
2116
2306
  saveSettings = async (settings) => {
2117
2307
  try {
2118
2308
  const current = await loadSettings();
2309
+ if (!current.systemSettings.useExternalData && settings.systemSettings?.useExternalData && settings.systemSettings?.externalDataPath) {
2310
+ await migrateToExternal(settings.systemSettings.externalDataPath);
2311
+ }
2119
2312
  const updated = { ...current, ...settings };
2120
- await fs13.ensureDir(path14.dirname(SETTINGS_FILE));
2121
- await fs13.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
2313
+ await fs15.ensureDir(path15.dirname(SETTINGS_FILE));
2314
+ await fs15.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
2122
2315
  return true;
2123
2316
  } catch (err) {
2124
2317
  console.error("Failed to save settings:", err);
@@ -2267,24 +2460,39 @@ var init_UpdateProcessor = __esm({
2267
2460
  }
2268
2461
  });
2269
2462
 
2270
- // src/utils/terminal.js
2271
- var getTerminalEnv, emojiSpace;
2272
- var init_terminal = __esm({
2273
- "src/utils/terminal.js"() {
2274
- getTerminalEnv = () => {
2275
- if (process.env.TERM_PROGRAM === "vscode") return "vscode";
2276
- if (process.env.WT_SESSION) return "wt";
2277
- return "default";
2278
- };
2279
- emojiSpace = (baseSpaces = 2) => {
2280
- const env = getTerminalEnv();
2281
- if (env === "wt") {
2282
- return " ".repeat(Math.max(1, baseSpaces - 1));
2463
+ // src/utils/setup.js
2464
+ import puppeteer2 from "puppeteer";
2465
+ import { exec as exec2 } from "child_process";
2466
+ import { promisify } from "util";
2467
+ import fs16 from "fs";
2468
+ var execAsync, checkPuppeteerReady, installPuppeteerBrowser;
2469
+ var init_setup = __esm({
2470
+ "src/utils/setup.js"() {
2471
+ execAsync = promisify(exec2);
2472
+ checkPuppeteerReady = () => {
2473
+ try {
2474
+ const exePath = puppeteer2.executablePath();
2475
+ const exists = exePath && fs16.existsSync(exePath);
2476
+ if (exists) return true;
2477
+ } catch (e) {
2478
+ return false;
2283
2479
  }
2284
- if (env === "vscode") {
2285
- return " ".repeat(baseSpaces);
2480
+ return false;
2481
+ };
2482
+ installPuppeteerBrowser = async (onStatus) => {
2483
+ if (onStatus) onStatus("\u{1F4E5} Downloading Chromium engine (Wait a moment)...");
2484
+ try {
2485
+ try {
2486
+ await execAsync("pnpm exec puppeteer browsers install chrome");
2487
+ } catch (pnpmErr) {
2488
+ await execAsync("npx -y puppeteer browsers install chrome");
2489
+ }
2490
+ await new Promise((r) => setTimeout(r, 1e3));
2491
+ return { success: true };
2492
+ } catch (err) {
2493
+ console.error("[SETUP ERROR]", err);
2494
+ return { success: false, error: err.message };
2286
2495
  }
2287
- return " ".repeat(baseSpaces);
2288
2496
  };
2289
2497
  }
2290
2498
  });
@@ -2297,8 +2505,8 @@ __export(app_exports, {
2297
2505
  import React10, { useState as useState6, useEffect as useEffect4, useRef, useMemo } from "react";
2298
2506
  import { Box as Box10, Text as Text10, useInput as useInput4, useStdout } from "ink";
2299
2507
  import Spinner2 from "ink-spinner";
2300
- import fs14 from "fs-extra";
2301
- import { exec as exec2 } from "child_process";
2508
+ import fs17 from "fs-extra";
2509
+ import { exec as exec3 } from "child_process";
2302
2510
  import { MultilineInput } from "ink-multiline-input";
2303
2511
  import TextInput3 from "ink-text-input";
2304
2512
  import gradient from "gradient-string";
@@ -2353,7 +2561,7 @@ Check what's new using \`/changelog\` command.`,
2353
2561
  if (manual) {
2354
2562
  setMessages((prev) => {
2355
2563
  setCompletedIndex(prev.length + 1);
2356
- return [...prev, { id: "check-err-" + Date.now(), role: "system", text: `\u274C ERROR: Failed to check for updates: ${err.message}` }];
2564
+ return [...prev, { id: "check-err-" + Date.now(), role: "system", text: `\u274C ERROR: Failed to check for updates: ${err.message}`, isMeta: true }];
2357
2565
  });
2358
2566
  }
2359
2567
  }
@@ -2430,7 +2638,7 @@ Check what's new using \`/changelog\` command.`,
2430
2638
  const [resolutionData, setResolutionData] = useState6(null);
2431
2639
  const [tempModelOverride, setTempModelOverride] = useState6(null);
2432
2640
  const [messages, setMessages] = useState6([
2433
- { id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n" }
2641
+ { id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n", isMeta: true }
2434
2642
  ]);
2435
2643
  const queuedPromptRef = useRef(null);
2436
2644
  const [completedIndex, setCompletedIndex] = useState6(1);
@@ -2537,6 +2745,17 @@ Check what's new using \`/changelog\` command.`,
2537
2745
  });
2538
2746
  useEffect4(() => {
2539
2747
  async function init() {
2748
+ if (!checkPuppeteerReady()) {
2749
+ setMessages((prev) => {
2750
+ setCompletedIndex(prev.length + 1);
2751
+ return [...prev, { id: "setup-" + Date.now(), role: "system", text: "\u{1F527} [SYSTEM] Installing Required dependencies... (One-time setup)", isMeta: true }];
2752
+ });
2753
+ await installPuppeteerBrowser();
2754
+ setMessages((prev) => {
2755
+ setCompletedIndex(prev.length + 1);
2756
+ return [...prev, { id: "setup-done-" + Date.now(), role: "system", text: "\u2705 [SYSTEM] All dependencies installed successfully.", isMeta: true }];
2757
+ });
2758
+ }
2540
2759
  const saved = await loadSettings();
2541
2760
  setMode(saved.mode);
2542
2761
  setThinkingLevel(saved.thinkingLevel);
@@ -2587,9 +2806,9 @@ Check what's new using \`/changelog\` command.`,
2587
2806
  await saveAPIKey(key);
2588
2807
  setApiKey(key);
2589
2808
  initAI(key);
2590
- setMessages((prev) => [...prev, { role: "system", text: "\u2705 API Key saved successfully! Initialization complete." }]);
2809
+ setMessages((prev) => [...prev, { role: "system", text: "\u2705 API Key saved successfully! Initialization complete.", isMeta: true }]);
2591
2810
  } else {
2592
- setMessages((prev) => [...prev, { role: "system", text: `\u274C INVALID KEY: Gemini API keys must be at least 30 characters.` }]);
2811
+ setMessages((prev) => [...prev, { role: "system", text: `\u274C INVALID KEY: Gemini API keys must be at least 30 characters.`, isMeta: true }]);
2593
2812
  setTempKey("");
2594
2813
  }
2595
2814
  };
@@ -2704,7 +2923,7 @@ ${hintText}`, color: "magenta" }];
2704
2923
  }
2705
2924
  case "/clear": {
2706
2925
  stdout.write("\x1B[2J\x1B[3J\x1B[H");
2707
- setMessages([{ id: "welcome-" + Date.now(), role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome back to Flux Flow! Context cleared.\n" }]);
2926
+ 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 }]);
2708
2927
  setCompletedIndex(0);
2709
2928
  setChatId(generateChatId());
2710
2929
  setSessionStats({ tokens: 0 });
@@ -2831,12 +3050,12 @@ ${list || "No saved chats found."}`, isMeta: true }];
2831
3050
  setCompletedIndex(prev.length + 1);
2832
3051
  return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset...", isMeta: true }];
2833
3052
  });
2834
- if (fs14.existsSync(LOGS_DIR)) fs14.removeSync(LOGS_DIR);
2835
- if (fs14.existsSync(SECRET_DIR)) fs14.removeSync(SECRET_DIR);
2836
- if (fs14.existsSync(SETTINGS_FILE)) fs14.removeSync(SETTINGS_FILE);
3053
+ if (fs17.existsSync(LOGS_DIR)) fs17.removeSync(LOGS_DIR);
3054
+ if (fs17.existsSync(SECRET_DIR)) fs17.removeSync(SECRET_DIR);
3055
+ if (fs17.existsSync(SETTINGS_FILE)) fs17.removeSync(SETTINGS_FILE);
2837
3056
  try {
2838
- const items = fs14.readdirSync(FLUXFLOW_DIR);
2839
- if (items.length === 0) fs14.removeSync(FLUXFLOW_DIR);
3057
+ const items = fs17.readdirSync(FLUXFLOW_DIR);
3058
+ if (items.length === 0) fs17.removeSync(FLUXFLOW_DIR);
2840
3059
  } catch (e) {
2841
3060
  }
2842
3061
  setTimeout(() => {
@@ -2868,7 +3087,7 @@ ${list || "No saved chats found."}`, isMeta: true }];
2868
3087
  case "/changelog": {
2869
3088
  const platform = process.platform;
2870
3089
  const command = platform === "win32" ? "start" : platform === "darwin" ? "open" : "xdg-open";
2871
- exec2(`${command} ${CHANGELOG_URL}`);
3090
+ exec3(`${command} ${CHANGELOG_URL}`);
2872
3091
  setMessages((prev) => {
2873
3092
  setCompletedIndex(prev.length + 1);
2874
3093
  return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}`, isMeta: true }];
@@ -3063,8 +3282,10 @@ Selection: ${val}`,
3063
3282
  id: "tool-" + Date.now(),
3064
3283
  role: "system",
3065
3284
  text: packet.content,
3066
- fullText: packet.aiContent
3285
+ fullText: packet.aiContent,
3067
3286
  // Preserve raw data for next turn
3287
+ binaryPart: packet.binaryPart
3288
+ // v1.5.0 Multimodal Support
3068
3289
  }]);
3069
3290
  continue;
3070
3291
  }
@@ -3251,6 +3472,7 @@ Selection: ${val}`,
3251
3472
  { label: `API Tier [ ${apiTier} ]`, value: "apiTier" },
3252
3473
  { label: `Auto-Update [ ${systemSettings.autoUpdate ? "ON" : "OFF"} ]`, value: "autoUpdate" },
3253
3474
  { label: `Preferred Updater [ ${(systemSettings.updateManager || "npm") === "custom" ? "Custom" : (systemSettings.updateManager || "npm").toUpperCase()} ]`, value: "updateManager" },
3475
+ { label: `Save AppData Externally [ ${systemSettings.useExternalData ? "ON" : "OFF"} ]`, value: "externalData" },
3254
3476
  { label: "Exit Settings", value: "Cancel" }
3255
3477
  ],
3256
3478
  onSelect: (item) => {
@@ -3283,6 +3505,22 @@ Selection: ${val}`,
3283
3505
  setSystemSettings((s) => ({ ...s, autoDeleteHistory: options[nextIndex] }));
3284
3506
  } else if (item.value === "autoUpdate") {
3285
3507
  setSystemSettings((s) => ({ ...s, autoUpdate: !s.autoUpdate }));
3508
+ } else if (item.value === "externalData") {
3509
+ if (!systemSettings.useExternalData) {
3510
+ setInputConfig({
3511
+ label: "Enter absolute path for External AppData:",
3512
+ note: "All history, logs and secrets will be stored here. ~/.fluxflow/settings.json stays as anchor.",
3513
+ key: "externalDataPath",
3514
+ value: systemSettings.externalDataPath || ""
3515
+ });
3516
+ setActiveView("input");
3517
+ } else {
3518
+ const newSettings = { ...systemSettings, useExternalData: false };
3519
+ setSystemSettings(newSettings);
3520
+ saveSettings({ systemSettings: newSettings, apiTier, quotas });
3521
+ setMessages((prev) => [...prev, { id: Date.now(), role: "system", text: "\u{1F3E0} [STORAGE RESET] Flux Flow will return to default ~/.fluxflow after restart." }]);
3522
+ setActiveView("chat");
3523
+ }
3286
3524
  } else if (item.value === "updateManager") {
3287
3525
  setActiveView("updateManager");
3288
3526
  } else if (item.value === "Cancel") setActiveView("chat");
@@ -3375,6 +3613,11 @@ Selection: ${val}`,
3375
3613
  } else if (key === "janitorModel") {
3376
3614
  setJanitorModel(val);
3377
3615
  newSettings.janitorModel = val;
3616
+ } else if (key === "externalDataPath") {
3617
+ const newSysSettings = { ...systemSettings, useExternalData: true, externalDataPath: val.trim() };
3618
+ setSystemSettings(newSysSettings);
3619
+ newSettings.systemSettings = newSysSettings;
3620
+ setMessages((prev) => [...prev, { id: Date.now(), role: "system", text: "\u{1F4C1} [EXTERNAL STORAGE] Flux Flow will use " + val.trim() + " for data after restart." }]);
3378
3621
  }
3379
3622
  if (next) {
3380
3623
  setInputConfig(next(key === "quotas" ? newQuotas : val));
@@ -3793,10 +4036,11 @@ var init_app = __esm({
3793
4036
  init_paths();
3794
4037
  init_terminal();
3795
4038
  init_exec_command();
4039
+ init_setup();
3796
4040
  SESSION_START_TIME = Date.now();
3797
4041
  CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
3798
- versionFluxflow = "1.4.2";
3799
- updatedOn = "2026-04-30";
4042
+ versionFluxflow = "1.5.0";
4043
+ updatedOn = "2026-05-01";
3800
4044
  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(
3801
4045
  CommandMenu,
3802
4046
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "scripts": {
36
36
  "start": "tsx ./src/cli.jsx",
37
- "build": "esbuild ./src/cli.jsx --bundle --platform=node --format=esm --outfile=./dist/fluxflow.js --external:react --external:ink --external:chalk --external:fs-extra --external:gradient-string --external:ink-text-input --external:ink-select-input --external:ink-spinner --external:ink-multiline-input --external:@google/genai --external:zod --external:duck-duck-scrape --external:nanoid --external:cuimp"
37
+ "build": "esbuild ./src/cli.jsx --bundle --platform=node --format=esm --outfile=./dist/fluxflow.js --external:react --external:ink --external:chalk --external:fs-extra --external:gradient-string --external:ink-text-input --external:ink-select-input --external:ink-spinner --external:ink-multiline-input --external:@google/genai --external:zod --external:duck-duck-scrape --external:nanoid --external:cuimp --external:puppeteer --external:typescript"
38
38
  },
39
39
  "dependencies": {
40
40
  "@google/genai": "^1.50.1",
@@ -48,6 +48,7 @@
48
48
  "ink-spinner": "^5.0.0",
49
49
  "ink-text-input": "^6.0.0",
50
50
  "nanoid": "^5.1.9",
51
+ "puppeteer": "^24.42.0",
51
52
  "react": "^19.2.5",
52
53
  "zod": "^4.3.6"
53
54
  },