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 +18 -1
- package/README.md +10 -0
- package/TOOLS.md +5 -1
- package/UI_FEATURES.md +18 -1
- package/dist/fluxflow.js +382 -138
- package/package.json +3 -2
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.
|
|
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(/(
|
|
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("
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
587
|
-
|
|
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
|
|
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
|
|
810
|
+
if (await fs4.pathExists(HISTORY_FILE)) {
|
|
778
811
|
try {
|
|
779
|
-
return await
|
|
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
|
|
798
|
-
await
|
|
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
|
|
811
|
-
await
|
|
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
|
|
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
|
|
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
|
|
867
|
-
const data = await
|
|
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
|
|
877
|
-
await
|
|
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
|
|
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
|
|
890
|
-
await
|
|
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
|
|
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 (!
|
|
980
|
-
|
|
1012
|
+
if (!fs6.existsSync(toolLogDir)) {
|
|
1013
|
+
fs6.mkdirSync(toolLogDir, { recursive: true });
|
|
981
1014
|
}
|
|
982
|
-
|
|
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 (!
|
|
992
|
-
|
|
1024
|
+
if (!fs6.existsSync(toolErrDir)) {
|
|
1025
|
+
fs6.mkdirSync(toolErrDir, { recursive: true });
|
|
993
1026
|
}
|
|
994
|
-
|
|
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
|
|
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 (!
|
|
1041
|
-
|
|
1073
|
+
if (!fs7.existsSync(toolLogDir)) {
|
|
1074
|
+
fs7.mkdirSync(toolLogDir, { recursive: true });
|
|
1042
1075
|
}
|
|
1043
|
-
|
|
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
|
|
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 (!
|
|
1197
|
+
if (!fs8.existsSync(absolutePath)) {
|
|
1165
1198
|
return `ERROR: Path [${targetPath}] does not exist.`;
|
|
1166
1199
|
}
|
|
1167
|
-
const stats =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (!
|
|
1259
|
+
if (!fs9.existsSync(absolutePath)) {
|
|
1227
1260
|
return `ERROR: File [${targetPath}] does not exist.`;
|
|
1228
1261
|
}
|
|
1229
|
-
const stats =
|
|
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
|
|
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
|
|
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 (
|
|
1324
|
+
if (fs10.existsSync(absolutePath)) {
|
|
1268
1325
|
try {
|
|
1269
|
-
const oldData =
|
|
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 (!
|
|
1282
|
-
|
|
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
|
-
|
|
1287
|
-
let verifiedContent =
|
|
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
|
|
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 (!
|
|
1394
|
+
if (!fs11.existsSync(absolutePath)) {
|
|
1338
1395
|
return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
|
|
1339
1396
|
}
|
|
1340
|
-
const currentContent =
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
1537
|
+
if (!fs12.existsSync(absolutePath)) {
|
|
1481
1538
|
return `ERROR: Path [${targetPath}] does not exist.`;
|
|
1482
1539
|
}
|
|
1483
|
-
const stats =
|
|
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 =
|
|
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 =
|
|
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
|
|
1623
|
-
import
|
|
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
|
-
|
|
1738
|
-
|
|
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 =
|
|
1774
|
-
if (!
|
|
1775
|
-
|
|
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 =
|
|
1836
|
-
if (
|
|
1837
|
-
const content =
|
|
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 =
|
|
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 =
|
|
1908
|
-
const absoluteCwd =
|
|
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
|
-
|
|
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 =
|
|
1949
|
-
if (!
|
|
1950
|
-
|
|
2115
|
+
const toolHistDir = path14.join(LOGS_DIR, "tools");
|
|
2116
|
+
if (!fs14.existsSync(toolHistDir)) {
|
|
2117
|
+
fs14.mkdirSync(toolHistDir, { recursive: true });
|
|
1951
2118
|
}
|
|
1952
|
-
|
|
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 =
|
|
2013
|
-
if (!
|
|
2014
|
-
|
|
2181
|
+
const janitorLogDir = path14.join(LOGS_DIR, "janitor");
|
|
2182
|
+
if (!fs14.existsSync(janitorLogDir)) {
|
|
2183
|
+
fs14.mkdirSync(janitorLogDir, { recursive: true });
|
|
2015
2184
|
}
|
|
2016
|
-
|
|
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 =
|
|
2029
|
-
|
|
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 =
|
|
2038
|
-
if (!
|
|
2039
|
-
|
|
2206
|
+
const janitorErrDir = path14.join(LOGS_DIR, "janitor");
|
|
2207
|
+
if (!fs14.existsSync(janitorErrDir)) {
|
|
2208
|
+
fs14.mkdirSync(janitorErrDir, { recursive: true });
|
|
2040
2209
|
}
|
|
2041
|
-
|
|
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
|
-
|
|
2059
|
-
|
|
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
|
|
2068
|
-
import
|
|
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
|
|
2102
|
-
const saved = await
|
|
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
|
|
2121
|
-
await
|
|
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/
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
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
|
-
|
|
2285
|
-
|
|
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
|
|
2301
|
-
import { exec as
|
|
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 (
|
|
2835
|
-
if (
|
|
2836
|
-
if (
|
|
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 =
|
|
2839
|
-
if (items.length === 0)
|
|
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
|
-
|
|
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.
|
|
3799
|
-
updatedOn = "2026-
|
|
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.
|
|
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
|
},
|