fluxflow-cli 1.4.3 → 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,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
  });
@@ -753,7 +784,7 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
753
784
  });
754
785
 
755
786
  // src/utils/history.js
756
- import fs3 from "fs-extra";
787
+ import fs4 from "fs-extra";
757
788
  import path4 from "path";
758
789
  import { nanoid } from "nanoid";
759
790
  var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, getTruncatedHistory;
@@ -776,9 +807,9 @@ var init_history = __esm({
776
807
  return nextLock;
777
808
  };
778
809
  loadHistory = async () => {
779
- if (await fs3.pathExists(HISTORY_FILE)) {
810
+ if (await fs4.pathExists(HISTORY_FILE)) {
780
811
  try {
781
- return await fs3.readJson(HISTORY_FILE);
812
+ return await fs4.readJson(HISTORY_FILE);
782
813
  } catch (e) {
783
814
  return {};
784
815
  }
@@ -796,8 +827,8 @@ var init_history = __esm({
796
827
  messages: persistentMessages,
797
828
  updatedAt: Date.now()
798
829
  };
799
- await fs3.ensureDir(path4.dirname(HISTORY_FILE));
800
- 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 });
801
832
  });
802
833
  };
803
834
  saveChatTitle = async (id, title) => {
@@ -809,15 +840,15 @@ var init_history = __esm({
809
840
  } else {
810
841
  history[id] = { name: title, messages: [], updatedAt: Date.now() };
811
842
  }
812
- await fs3.ensureDir(path4.dirname(HISTORY_FILE));
813
- 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 });
814
845
  });
815
846
  };
816
847
  deleteChat = async (id) => {
817
848
  return withLock(async () => {
818
849
  const history = await loadHistory();
819
850
  delete history[id];
820
- await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
851
+ await fs4.writeJson(HISTORY_FILE, history, { spaces: 2 });
821
852
  const temp = readEncryptedJson(TEMP_MEM_FILE, {});
822
853
  if (temp[id]) {
823
854
  delete temp[id];
@@ -856,7 +887,7 @@ var init_history = __esm({
856
887
  });
857
888
 
858
889
  // src/utils/usage.js
859
- import fs4 from "fs-extra";
890
+ import fs5 from "fs-extra";
860
891
  import path5 from "path";
861
892
  var getDailyUsage, incrementUsage, checkQuota;
862
893
  var init_usage = __esm({
@@ -865,8 +896,8 @@ var init_usage = __esm({
865
896
  getDailyUsage = async () => {
866
897
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
867
898
  try {
868
- if (await fs4.exists(USAGE_FILE)) {
869
- const data = await fs4.readJson(USAGE_FILE);
899
+ if (await fs5.exists(USAGE_FILE)) {
900
+ const data = await fs5.readJson(USAGE_FILE);
870
901
  if (data.date === today) {
871
902
  return data.stats;
872
903
  }
@@ -875,21 +906,21 @@ var init_usage = __esm({
875
906
  console.error("Failed to read usage:", err);
876
907
  }
877
908
  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 });
909
+ await fs5.ensureDir(path5.dirname(USAGE_FILE));
910
+ await fs5.writeJson(USAGE_FILE, { date: today, stats: defaultStats }, { spaces: 2 });
880
911
  return defaultStats;
881
912
  };
882
913
  incrementUsage = async (key) => {
883
914
  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 } }));
915
+ const data = await fs5.readJson(USAGE_FILE).catch(() => ({ date: today, stats: { agent: 0, background: 0, search: 0 } }));
885
916
  if (data.date !== today) {
886
917
  data.date = today;
887
918
  data.stats = { agent: 0, background: 0, search: 0 };
888
919
  }
889
920
  if (data.stats[key] !== void 0) {
890
921
  data.stats[key]++;
891
- await fs4.ensureDir(path5.dirname(USAGE_FILE));
892
- 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 });
893
924
  }
894
925
  };
895
926
  checkQuota = async (key, settings) => {
@@ -943,7 +974,7 @@ var init_arg_parser = __esm({
943
974
 
944
975
  // src/tools/web_search.js
945
976
  import * as cuimp from "cuimp";
946
- import fs5 from "fs";
977
+ import fs6 from "fs";
947
978
  import path6 from "path";
948
979
  var web_search;
949
980
  var init_web_search = __esm({
@@ -978,10 +1009,10 @@ Snippet: ${snippet}`);
978
1009
  count++;
979
1010
  }
980
1011
  const toolLogDir = path6.join(LOGS_DIR, "tools");
981
- if (!fs5.existsSync(toolLogDir)) {
982
- fs5.mkdirSync(toolLogDir, { recursive: true });
1012
+ if (!fs6.existsSync(toolLogDir)) {
1013
+ fs6.mkdirSync(toolLogDir, { recursive: true });
983
1014
  }
984
- 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()} -
985
1016
  Query: [${query}]. Results Count: ${results.length}.
986
1017
  Results: ${results}
987
1018
 
@@ -990,10 +1021,10 @@ Results: ${results}
990
1021
  if (results.length === 0) {
991
1022
  if (html.includes("anomaly")) {
992
1023
  const toolErrDir = path6.join(LOGS_DIR, "tools");
993
- if (!fs5.existsSync(toolErrDir)) {
994
- fs5.mkdirSync(toolErrDir, { recursive: true });
1024
+ if (!fs6.existsSync(toolErrDir)) {
1025
+ fs6.mkdirSync(toolErrDir, { recursive: true });
995
1026
  }
996
- 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.
997
1028
  `);
998
1029
  throw new Error("DDG detected unusual activity. Cuimp impersonation might need adjustment.");
999
1030
  }
@@ -1011,7 +1042,7 @@ ${finalResults}`;
1011
1042
  });
1012
1043
 
1013
1044
  // src/tools/web_scrape.js
1014
- import fs6 from "fs";
1045
+ import fs7 from "fs";
1015
1046
  import path7 from "path";
1016
1047
  import * as cuimp2 from "cuimp";
1017
1048
  var web_scrape;
@@ -1039,10 +1070,10 @@ var init_web_scrape = __esm({
1039
1070
  let text = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1040
1071
  const finalContent = text.substring(0, 2e4);
1041
1072
  const toolLogDir = path7.join(LOGS_DIR, "tools");
1042
- if (!fs6.existsSync(toolLogDir)) {
1043
- fs6.mkdirSync(toolLogDir, { recursive: true });
1073
+ if (!fs7.existsSync(toolLogDir)) {
1074
+ fs7.mkdirSync(toolLogDir, { recursive: true });
1044
1075
  }
1045
- 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()} -
1046
1077
  Query: [${url}].
1047
1078
  Results: ${finalContent}
1048
1079
 
@@ -1153,7 +1184,7 @@ var init_chat = __esm({
1153
1184
  });
1154
1185
 
1155
1186
  // src/tools/list_files.js
1156
- import fs7 from "fs";
1187
+ import fs8 from "fs";
1157
1188
  import path8 from "path";
1158
1189
  var list_files;
1159
1190
  var init_list_files = __esm({
@@ -1163,14 +1194,14 @@ var init_list_files = __esm({
1163
1194
  const { path: targetPath = "." } = parseArgs(args);
1164
1195
  const absolutePath = path8.resolve(process.cwd(), targetPath);
1165
1196
  try {
1166
- if (!fs7.existsSync(absolutePath)) {
1197
+ if (!fs8.existsSync(absolutePath)) {
1167
1198
  return `ERROR: Path [${targetPath}] does not exist.`;
1168
1199
  }
1169
- const stats = fs7.statSync(absolutePath);
1200
+ const stats = fs8.statSync(absolutePath);
1170
1201
  if (!stats.isDirectory()) {
1171
1202
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use view_file instead.`;
1172
1203
  }
1173
- const files = fs7.readdirSync(absolutePath);
1204
+ const files = fs8.readdirSync(absolutePath);
1174
1205
  if (files.length === 0) {
1175
1206
  return `Directory [${targetPath}] is empty.`;
1176
1207
  }
@@ -1182,7 +1213,7 @@ var init_list_files = __esm({
1182
1213
  let indicator = "\u{1F4C4}";
1183
1214
  let metaPart = "";
1184
1215
  try {
1185
- const fStats = fs7.statSync(fPath);
1216
+ const fStats = fs8.statSync(fPath);
1186
1217
  indicator = fStats.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
1187
1218
  const sizeKB = (fStats.size / 1024).toFixed(1);
1188
1219
  metaPart = fStats.isFile() ? ` - [${sizeKB} KB]` : "";
@@ -1214,7 +1245,7 @@ ${list}${footer}`;
1214
1245
  });
1215
1246
 
1216
1247
  // src/tools/view_file.js
1217
- import fs8 from "fs";
1248
+ import fs9 from "fs";
1218
1249
  import path9 from "path";
1219
1250
  var view_file;
1220
1251
  var init_view_file = __esm({
@@ -1225,14 +1256,38 @@ var init_view_file = __esm({
1225
1256
  if (!targetPath) return 'ERROR: Missing "path" argument for view_file.';
1226
1257
  const absolutePath = path9.resolve(process.cwd(), targetPath);
1227
1258
  try {
1228
- if (!fs8.existsSync(absolutePath)) {
1259
+ if (!fs9.existsSync(absolutePath)) {
1229
1260
  return `ERROR: File [${targetPath}] does not exist.`;
1230
1261
  }
1231
- const stats = fs8.statSync(absolutePath);
1262
+ const stats = fs9.statSync(absolutePath);
1232
1263
  if (stats.isDirectory()) {
1233
1264
  return `ERROR: Path [${targetPath}] is a directory. Use list_files instead.`;
1234
1265
  }
1235
- 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");
1236
1291
  const lines = content.split("\n");
1237
1292
  const totalLines = lines.length;
1238
1293
  const start = Math.max(0, start_line - 1);
@@ -1251,7 +1306,7 @@ ${code}`;
1251
1306
  });
1252
1307
 
1253
1308
  // src/tools/write_file.js
1254
- import fs9 from "fs";
1309
+ import fs10 from "fs";
1255
1310
  import path10 from "path";
1256
1311
  var write_file;
1257
1312
  var init_write_file = __esm({
@@ -1266,9 +1321,9 @@ var init_write_file = __esm({
1266
1321
  const parentDir = path10.dirname(absolutePath);
1267
1322
  try {
1268
1323
  let ancestry = "";
1269
- if (fs9.existsSync(absolutePath)) {
1324
+ if (fs10.existsSync(absolutePath)) {
1270
1325
  try {
1271
- const oldData = fs9.readFileSync(absolutePath, "utf8");
1326
+ const oldData = fs10.readFileSync(absolutePath, "utf8");
1272
1327
  const lines = oldData.split(/\r?\n/);
1273
1328
  ancestry = `Old File contents:
1274
1329
  ${lines.map((l, i) => `${i + 1} | ${l}`).join("\n")}
@@ -1280,13 +1335,13 @@ ${lines.map((l, i) => `${i + 1} | ${l}`).join("\n")}
1280
1335
  `;
1281
1336
  }
1282
1337
  }
1283
- if (!fs9.existsSync(parentDir)) {
1284
- fs9.mkdirSync(parentDir, { recursive: true });
1338
+ if (!fs10.existsSync(parentDir)) {
1339
+ fs10.mkdirSync(parentDir, { recursive: true });
1285
1340
  }
1286
1341
  const lineCount = content.split(/\r?\n/).length;
1287
1342
  const originalSize = Buffer.byteLength(content, "utf8");
1288
- fs9.writeFileSync(absolutePath, content, "utf8");
1289
- let verifiedContent = fs9.readFileSync(absolutePath, "utf8");
1343
+ fs10.writeFileSync(absolutePath, content, "utf8");
1344
+ let verifiedContent = fs10.readFileSync(absolutePath, "utf8");
1290
1345
  const verifiedSize = Buffer.byteLength(verifiedContent, "utf8");
1291
1346
  const verifiedLines = verifiedContent.split(/\r?\n/);
1292
1347
  const verifiedLineCount = verifiedLines.length;
@@ -1320,7 +1375,7 @@ ${snippet}`;
1320
1375
  });
1321
1376
 
1322
1377
  // src/tools/update_file.js
1323
- import fs10 from "fs";
1378
+ import fs11 from "fs";
1324
1379
  import path11 from "path";
1325
1380
  var update_file;
1326
1381
  var init_update_file = __esm({
@@ -1336,10 +1391,10 @@ var init_update_file = __esm({
1336
1391
  content_to_add = strip(content_to_add);
1337
1392
  const absolutePath = path11.resolve(process.cwd(), targetPath);
1338
1393
  try {
1339
- if (!fs10.existsSync(absolutePath)) {
1394
+ if (!fs11.existsSync(absolutePath)) {
1340
1395
  return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
1341
1396
  }
1342
- const currentContent = fs10.readFileSync(absolutePath, "utf8");
1397
+ const currentContent = fs11.readFileSync(absolutePath, "utf8");
1343
1398
  if (!currentContent.includes(content_to_replace)) {
1344
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.`;
1345
1400
  }
@@ -1347,7 +1402,7 @@ var init_update_file = __esm({
1347
1402
  const startLine = currentContent.substring(0, startPos).split(/\r?\n/).length;
1348
1403
  const instances = currentContent.split(content_to_replace).length - 1;
1349
1404
  const newFileContent = currentContent.split(content_to_replace).join(content_to_add);
1350
- fs10.writeFileSync(absolutePath, newFileContent, "utf8");
1405
+ fs11.writeFileSync(absolutePath, newFileContent, "utf8");
1351
1406
  const allOriginalLines = currentContent.split(/\r?\n/);
1352
1407
  const oldLines = content_to_replace.split(/\r?\n/);
1353
1408
  const newLines = content_to_add.split(/\r?\n/);
@@ -1469,7 +1524,7 @@ ${finalOutput}`);
1469
1524
  });
1470
1525
 
1471
1526
  // src/tools/read_folder.js
1472
- import fs11 from "fs";
1527
+ import fs12 from "fs";
1473
1528
  import path12 from "path";
1474
1529
  var read_folder;
1475
1530
  var init_read_folder = __esm({
@@ -1479,14 +1534,14 @@ var init_read_folder = __esm({
1479
1534
  const { path: targetPath = "." } = parseArgs(args);
1480
1535
  const absolutePath = path12.resolve(process.cwd(), targetPath);
1481
1536
  try {
1482
- if (!fs11.existsSync(absolutePath)) {
1537
+ if (!fs12.existsSync(absolutePath)) {
1483
1538
  return `ERROR: Path [${targetPath}] does not exist.`;
1484
1539
  }
1485
- const stats = fs11.statSync(absolutePath);
1540
+ const stats = fs12.statSync(absolutePath);
1486
1541
  if (!stats.isDirectory()) {
1487
1542
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use view_file instead.`;
1488
1543
  }
1489
- const files = fs11.readdirSync(absolutePath);
1544
+ const files = fs12.readdirSync(absolutePath);
1490
1545
  const totalItems = files.length;
1491
1546
  const maxDisplay = 100;
1492
1547
  const displayItems = files.slice(0, maxDisplay);
@@ -1496,7 +1551,7 @@ var init_read_folder = __esm({
1496
1551
  let indicator = "\u{1F4C4}";
1497
1552
  let info = { name: file, type: "unknown", size: "N/A", mtime: "N/A" };
1498
1553
  try {
1499
- const fStats = fs11.statSync(fPath);
1554
+ const fStats = fs12.statSync(fPath);
1500
1555
  info = {
1501
1556
  name: file,
1502
1557
  type: fStats.isDirectory() ? "directory" : "file",
@@ -1577,6 +1632,78 @@ var init_ask_user = __esm({
1577
1632
  }
1578
1633
  });
1579
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
+
1580
1707
  // src/utils/tools.js
1581
1708
  var TOOL_MAP, dispatchTool;
1582
1709
  var init_tools = __esm({
@@ -1592,6 +1719,7 @@ var init_tools = __esm({
1592
1719
  init_exec_command();
1593
1720
  init_read_folder();
1594
1721
  init_ask_user();
1722
+ init_write_pdf();
1595
1723
  TOOL_MAP = {
1596
1724
  web_search,
1597
1725
  web_scrape,
@@ -1603,6 +1731,7 @@ var init_tools = __esm({
1603
1731
  update_file,
1604
1732
  exec_command,
1605
1733
  read_folder,
1734
+ write_pdf,
1606
1735
  ask: ask_user
1607
1736
  };
1608
1737
  dispatchTool = async (toolName, args, context = {}) => {
@@ -1619,10 +1748,32 @@ var init_tools = __esm({
1619
1748
  }
1620
1749
  });
1621
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
+
1622
1773
  // src/utils/ai.js
1623
1774
  import { GoogleGenAI, ThinkingLevel } from "@google/genai";
1624
- import path13 from "path";
1625
- import fs12 from "fs";
1775
+ import path14 from "path";
1776
+ import fs14 from "fs";
1626
1777
  var client, TERMINATION_SIGNAL, signalTermination, detectToolCalls, initAI, getAIStream;
1627
1778
  var init_ai = __esm({
1628
1779
  "src/utils/ai.js"() {
@@ -1632,6 +1783,7 @@ var init_ai = __esm({
1632
1783
  init_tools();
1633
1784
  init_crypto();
1634
1785
  init_arg_parser();
1786
+ init_terminal();
1635
1787
  init_paths();
1636
1788
  client = null;
1637
1789
  TERMINATION_SIGNAL = false;
@@ -1735,10 +1887,16 @@ USER_PROMPT: ${agentText}`.trim();
1735
1887
  }
1736
1888
  }
1737
1889
  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
- }));
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
+ });
1742
1900
  let stream;
1743
1901
  let success = false;
1744
1902
  let retryCount = 0;
@@ -1772,9 +1930,9 @@ USER_PROMPT: ${agentText}`.trim();
1772
1930
  } catch (err) {
1773
1931
  const errMsg = err.status || err.error && err.error.message || String(err);
1774
1932
  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}
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}
1778
1936
  `);
1779
1937
  if (retryCount < MAX_RETRIES) {
1780
1938
  retryCount++;
@@ -1834,9 +1992,9 @@ USER_PROMPT: ${agentText}`.trim();
1834
1992
  let totalLines = "...";
1835
1993
  let actualEndLine = end_line;
1836
1994
  try {
1837
- const absPath = path13.resolve(process.cwd(), targetPath2);
1838
- if (fs12.existsSync(absPath)) {
1839
- 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");
1840
1998
  const lines = content.split("\n").length;
1841
1999
  totalLines = lines;
1842
2000
  actualEndLine = Math.min(end_line, lines);
@@ -1850,6 +2008,8 @@ USER_PROMPT: ${agentText}`.trim();
1850
2008
  } else if (toolCall.toolName === "write_file" || toolCall.toolName === "update_file") {
1851
2009
  const action = toolCall.toolName === "write_file" ? "WRITING" : "PATCHING";
1852
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();
1853
2013
  } else if (toolCall.toolName === "exec_command" || toolCall.toolName === "ask") {
1854
2014
  label = "";
1855
2015
  } else {
@@ -1884,7 +2044,7 @@ ${boxBottom}
1884
2044
  /\/usr\//
1885
2045
  // Sensitive Linux paths
1886
2046
  ];
1887
- const currentDrive = path13.resolve(process.cwd()).substring(0, 3).toLowerCase();
2047
+ const currentDrive = path14.resolve(process.cwd()).substring(0, 3).toLowerCase();
1888
2048
  const isViolating = riskyPatterns.some((pattern) => {
1889
2049
  if (pattern.source === "[a-zA-Z]:[\\\\\\/]") {
1890
2050
  const driveMatch = command.match(/[a-zA-Z]:[\\\/]/i);
@@ -1906,8 +2066,8 @@ ${boxBottom}
1906
2066
  const targetPath = parsedArgs.path || parsedArgs.targetPath || null;
1907
2067
  if (targetPath) {
1908
2068
  const isExternalOff = settings.systemSettings && settings.systemSettings.allowExternalAccess === false;
1909
- const absoluteTarget = path13.resolve(targetPath);
1910
- const absoluteCwd = path13.resolve(process.cwd());
2069
+ const absoluteTarget = path14.resolve(targetPath);
2070
+ const absoluteCwd = path14.resolve(process.cwd());
1911
2071
  if (isExternalOff && !absoluteTarget.startsWith(absoluteCwd)) {
1912
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.`;
1913
2073
  toolResults.push(`[TOOL_RESULT]: ERROR: ${denyMsg}`);
@@ -1933,12 +2093,17 @@ ${boxBottom}
1933
2093
  }
1934
2094
  }
1935
2095
  }
1936
- const result = await dispatchTool(toolCall.toolName, toolCall.args, {
2096
+ let result = await dispatchTool(toolCall.toolName, toolCall.args, {
1937
2097
  chatId,
1938
2098
  history,
1939
2099
  onChunk: (chunk) => settings.onExecChunk ? settings.onExecChunk(chunk) : null,
1940
2100
  onAskUser: settings.onAskUser
1941
2101
  });
2102
+ let binaryPart = null;
2103
+ if (typeof result === "object" && result.binaryPart) {
2104
+ binaryPart = result.binaryPart;
2105
+ result = result.text;
2106
+ }
1942
2107
  if (toolCall.toolName === "exec_command" && settings.onExecEnd) {
1943
2108
  await new Promise((resolve) => setTimeout(resolve, 800));
1944
2109
  settings.onExecEnd();
@@ -1947,17 +2112,17 @@ ${boxBottom}
1947
2112
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1948
2113
  const isErr = result.startsWith("ERROR:");
1949
2114
  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 });
2115
+ const toolHistDir = path14.join(LOGS_DIR, "tools");
2116
+ if (!fs14.existsSync(toolHistDir)) {
2117
+ fs14.mkdirSync(toolHistDir, { recursive: true });
1953
2118
  }
1954
- 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}]
1955
2120
  `);
1956
2121
  } catch (logErr) {
1957
2122
  }
1958
2123
  const cleanResultForAI = result.split(/\r?\n/).filter((line) => !line.includes("[UI_CONTEXT]")).join("\n");
1959
2124
  const aiContent = `[TOOL_RESULT]: ${cleanResultForAI}`;
1960
- toolResults.push(aiContent);
2125
+ toolResults.push({ role: "user", text: aiContent, binaryPart });
1961
2126
  let uiContent = `[TOOL_RESULT]: ${result}`;
1962
2127
  if (toolCall.toolName === "view_file" || toolCall.toolName === "web_scrape") {
1963
2128
  uiContent = `[TOOL_RESULT]: ${label} (Context Locked for UI Clarity)`;
@@ -1965,7 +2130,9 @@ ${boxBottom}
1965
2130
  yield {
1966
2131
  type: "tool_result",
1967
2132
  content: uiContent,
1968
- aiContent
2133
+ aiContent,
2134
+ binaryPart
2135
+ // Multi-modal stage (v1.5.0)
1969
2136
  };
1970
2137
  if (toolCall.toolName === "memory" && result.includes("SUCCESS")) {
1971
2138
  yield { type: "memory_updated" };
@@ -2011,11 +2178,11 @@ ${boxBottom}
2011
2178
  if (parts && parts[1]?.text) {
2012
2179
  finalSynthesis = parts[1].text;
2013
2180
  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 });
2181
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
2182
+ if (!fs14.existsSync(janitorLogDir)) {
2183
+ fs14.mkdirSync(janitorLogDir, { recursive: true });
2017
2184
  }
2018
- fs12.appendFileSync(path13.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
2185
+ fs14.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
2019
2186
  `);
2020
2187
  } else if (parts && parts[0]?.text) finalSynthesis = parts[0].text;
2021
2188
  else if (janitorResult.response && janitorResult.response.text) finalSynthesis = janitorResult.response.text();
@@ -2027,8 +2194,8 @@ ${boxBottom}
2027
2194
  const toolContext = { chatId, sessionId: chatId, history };
2028
2195
  const result = await dispatchTool(janitorToolCall.toolName, janitorToolCall.args, toolContext);
2029
2196
  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}
2197
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
2198
+ fs14.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
2032
2199
  `);
2033
2200
  if (janitorToolCall.toolName === "memory" && !janitorToolCall.args.includes("action='temp'")) {
2034
2201
  yield { type: "memory_updated" };
@@ -2036,11 +2203,11 @@ ${boxBottom}
2036
2203
  }
2037
2204
  } catch (janitorErr) {
2038
2205
  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 });
2206
+ const janitorErrDir = path14.join(LOGS_DIR, "janitor");
2207
+ if (!fs14.existsSync(janitorErrDir)) {
2208
+ fs14.mkdirSync(janitorErrDir, { recursive: true });
2042
2209
  }
2043
- fs12.appendFileSync(path13.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
2210
+ fs14.appendFileSync(path14.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
2044
2211
  `);
2045
2212
  console.error("Janitor Background Tasks Failed:", janitorErr.message);
2046
2213
  }
@@ -2057,8 +2224,11 @@ ${timestamp}`;
2057
2224
  if (isActuallyFinished) break;
2058
2225
  const nextAgentMsg = cleanedTurnText.trim() || "*Working...*";
2059
2226
  modifiedHistory.push({ role: "agent", text: nextAgentMsg });
2060
- const nextUserMsg = toolResults.length > 0 ? toolResults.join("\n") : "[turn: continue]";
2061
- 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
+ }
2062
2232
  }
2063
2233
  yield { type: "status", content: null };
2064
2234
  };
@@ -2066,9 +2236,9 @@ ${timestamp}`;
2066
2236
  });
2067
2237
 
2068
2238
  // src/utils/settings.js
2069
- import fs13 from "fs-extra";
2070
- import path14 from "path";
2071
- var DEFAULT_SETTINGS, loadSettings, saveSettings;
2239
+ import fs15 from "fs-extra";
2240
+ import path15 from "path";
2241
+ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
2072
2242
  var init_settings = __esm({
2073
2243
  "src/utils/settings.js"() {
2074
2244
  init_paths();
@@ -2090,7 +2260,9 @@ var init_settings = __esm({
2090
2260
  compression: 0,
2091
2261
  autoExec: false,
2092
2262
  allowExternalAccess: false,
2093
- autoDeleteHistory: "7d"
2263
+ autoDeleteHistory: "7d",
2264
+ useExternalData: false,
2265
+ externalDataPath: ""
2094
2266
  },
2095
2267
  profileData: {
2096
2268
  name: null,
@@ -2100,8 +2272,8 @@ var init_settings = __esm({
2100
2272
  };
2101
2273
  loadSettings = async () => {
2102
2274
  try {
2103
- if (await fs13.exists(SETTINGS_FILE)) {
2104
- const saved = await fs13.readJson(SETTINGS_FILE);
2275
+ if (await fs15.exists(SETTINGS_FILE)) {
2276
+ const saved = await fs15.readJson(SETTINGS_FILE);
2105
2277
  return {
2106
2278
  ...DEFAULT_SETTINGS,
2107
2279
  ...saved,
@@ -2115,12 +2287,31 @@ var init_settings = __esm({
2115
2287
  }
2116
2288
  return DEFAULT_SETTINGS;
2117
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
+ };
2118
2306
  saveSettings = async (settings) => {
2119
2307
  try {
2120
2308
  const current = await loadSettings();
2309
+ if (!current.systemSettings.useExternalData && settings.systemSettings?.useExternalData && settings.systemSettings?.externalDataPath) {
2310
+ await migrateToExternal(settings.systemSettings.externalDataPath);
2311
+ }
2121
2312
  const updated = { ...current, ...settings };
2122
- await fs13.ensureDir(path14.dirname(SETTINGS_FILE));
2123
- 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 });
2124
2315
  return true;
2125
2316
  } catch (err) {
2126
2317
  console.error("Failed to save settings:", err);
@@ -2269,24 +2460,39 @@ var init_UpdateProcessor = __esm({
2269
2460
  }
2270
2461
  });
2271
2462
 
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));
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;
2285
2479
  }
2286
- if (env === "vscode") {
2287
- 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 };
2288
2495
  }
2289
- return " ".repeat(baseSpaces);
2290
2496
  };
2291
2497
  }
2292
2498
  });
@@ -2299,8 +2505,8 @@ __export(app_exports, {
2299
2505
  import React10, { useState as useState6, useEffect as useEffect4, useRef, useMemo } from "react";
2300
2506
  import { Box as Box10, Text as Text10, useInput as useInput4, useStdout } from "ink";
2301
2507
  import Spinner2 from "ink-spinner";
2302
- import fs14 from "fs-extra";
2303
- import { exec as exec2 } from "child_process";
2508
+ import fs17 from "fs-extra";
2509
+ import { exec as exec3 } from "child_process";
2304
2510
  import { MultilineInput } from "ink-multiline-input";
2305
2511
  import TextInput3 from "ink-text-input";
2306
2512
  import gradient from "gradient-string";
@@ -2355,7 +2561,7 @@ Check what's new using \`/changelog\` command.`,
2355
2561
  if (manual) {
2356
2562
  setMessages((prev) => {
2357
2563
  setCompletedIndex(prev.length + 1);
2358
- 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 }];
2359
2565
  });
2360
2566
  }
2361
2567
  }
@@ -2432,7 +2638,7 @@ Check what's new using \`/changelog\` command.`,
2432
2638
  const [resolutionData, setResolutionData] = useState6(null);
2433
2639
  const [tempModelOverride, setTempModelOverride] = useState6(null);
2434
2640
  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" }
2641
+ { id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n", isMeta: true }
2436
2642
  ]);
2437
2643
  const queuedPromptRef = useRef(null);
2438
2644
  const [completedIndex, setCompletedIndex] = useState6(1);
@@ -2539,6 +2745,17 @@ Check what's new using \`/changelog\` command.`,
2539
2745
  });
2540
2746
  useEffect4(() => {
2541
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
+ }
2542
2759
  const saved = await loadSettings();
2543
2760
  setMode(saved.mode);
2544
2761
  setThinkingLevel(saved.thinkingLevel);
@@ -2589,9 +2806,9 @@ Check what's new using \`/changelog\` command.`,
2589
2806
  await saveAPIKey(key);
2590
2807
  setApiKey(key);
2591
2808
  initAI(key);
2592
- 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 }]);
2593
2810
  } else {
2594
- 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 }]);
2595
2812
  setTempKey("");
2596
2813
  }
2597
2814
  };
@@ -2706,7 +2923,7 @@ ${hintText}`, color: "magenta" }];
2706
2923
  }
2707
2924
  case "/clear": {
2708
2925
  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" }]);
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 }]);
2710
2927
  setCompletedIndex(0);
2711
2928
  setChatId(generateChatId());
2712
2929
  setSessionStats({ tokens: 0 });
@@ -2833,12 +3050,12 @@ ${list || "No saved chats found."}`, isMeta: true }];
2833
3050
  setCompletedIndex(prev.length + 1);
2834
3051
  return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset...", isMeta: true }];
2835
3052
  });
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);
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);
2839
3056
  try {
2840
- const items = fs14.readdirSync(FLUXFLOW_DIR);
2841
- if (items.length === 0) fs14.removeSync(FLUXFLOW_DIR);
3057
+ const items = fs17.readdirSync(FLUXFLOW_DIR);
3058
+ if (items.length === 0) fs17.removeSync(FLUXFLOW_DIR);
2842
3059
  } catch (e) {
2843
3060
  }
2844
3061
  setTimeout(() => {
@@ -2870,7 +3087,7 @@ ${list || "No saved chats found."}`, isMeta: true }];
2870
3087
  case "/changelog": {
2871
3088
  const platform = process.platform;
2872
3089
  const command = platform === "win32" ? "start" : platform === "darwin" ? "open" : "xdg-open";
2873
- exec2(`${command} ${CHANGELOG_URL}`);
3090
+ exec3(`${command} ${CHANGELOG_URL}`);
2874
3091
  setMessages((prev) => {
2875
3092
  setCompletedIndex(prev.length + 1);
2876
3093
  return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}`, isMeta: true }];
@@ -3065,8 +3282,10 @@ Selection: ${val}`,
3065
3282
  id: "tool-" + Date.now(),
3066
3283
  role: "system",
3067
3284
  text: packet.content,
3068
- fullText: packet.aiContent
3285
+ fullText: packet.aiContent,
3069
3286
  // Preserve raw data for next turn
3287
+ binaryPart: packet.binaryPart
3288
+ // v1.5.0 Multimodal Support
3070
3289
  }]);
3071
3290
  continue;
3072
3291
  }
@@ -3253,6 +3472,7 @@ Selection: ${val}`,
3253
3472
  { label: `API Tier [ ${apiTier} ]`, value: "apiTier" },
3254
3473
  { label: `Auto-Update [ ${systemSettings.autoUpdate ? "ON" : "OFF"} ]`, value: "autoUpdate" },
3255
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" },
3256
3476
  { label: "Exit Settings", value: "Cancel" }
3257
3477
  ],
3258
3478
  onSelect: (item) => {
@@ -3285,6 +3505,22 @@ Selection: ${val}`,
3285
3505
  setSystemSettings((s) => ({ ...s, autoDeleteHistory: options[nextIndex] }));
3286
3506
  } else if (item.value === "autoUpdate") {
3287
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
+ }
3288
3524
  } else if (item.value === "updateManager") {
3289
3525
  setActiveView("updateManager");
3290
3526
  } else if (item.value === "Cancel") setActiveView("chat");
@@ -3377,6 +3613,11 @@ Selection: ${val}`,
3377
3613
  } else if (key === "janitorModel") {
3378
3614
  setJanitorModel(val);
3379
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." }]);
3380
3621
  }
3381
3622
  if (next) {
3382
3623
  setInputConfig(next(key === "quotas" ? newQuotas : val));
@@ -3795,10 +4036,11 @@ var init_app = __esm({
3795
4036
  init_paths();
3796
4037
  init_terminal();
3797
4038
  init_exec_command();
4039
+ init_setup();
3798
4040
  SESSION_START_TIME = Date.now();
3799
4041
  CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
3800
- versionFluxflow = "1.4.3";
3801
- updatedOn = "2026-04-30";
4042
+ versionFluxflow = "1.5.0";
4043
+ updatedOn = "2026-05-01";
3802
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(
3803
4045
  CommandMenu,
3804
4046
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.4.3",
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
  },