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 +18 -1
- package/README.md +10 -0
- package/TOOLS.md +5 -1
- package/UI_FEATURES.md +18 -1
- package/dist/fluxflow.js +379 -137
- 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,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.
|
|
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
|
|
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
|
|
810
|
+
if (await fs4.pathExists(HISTORY_FILE)) {
|
|
780
811
|
try {
|
|
781
|
-
return await
|
|
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
|
|
800
|
-
await
|
|
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
|
|
813
|
-
await
|
|
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
|
|
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
|
|
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
|
|
869
|
-
const data = await
|
|
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
|
|
879
|
-
await
|
|
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
|
|
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
|
|
892
|
-
await
|
|
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
|
|
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 (!
|
|
982
|
-
|
|
1012
|
+
if (!fs6.existsSync(toolLogDir)) {
|
|
1013
|
+
fs6.mkdirSync(toolLogDir, { recursive: true });
|
|
983
1014
|
}
|
|
984
|
-
|
|
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 (!
|
|
994
|
-
|
|
1024
|
+
if (!fs6.existsSync(toolErrDir)) {
|
|
1025
|
+
fs6.mkdirSync(toolErrDir, { recursive: true });
|
|
995
1026
|
}
|
|
996
|
-
|
|
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
|
|
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 (!
|
|
1043
|
-
|
|
1073
|
+
if (!fs7.existsSync(toolLogDir)) {
|
|
1074
|
+
fs7.mkdirSync(toolLogDir, { recursive: true });
|
|
1044
1075
|
}
|
|
1045
|
-
|
|
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
|
|
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 (!
|
|
1197
|
+
if (!fs8.existsSync(absolutePath)) {
|
|
1167
1198
|
return `ERROR: Path [${targetPath}] does not exist.`;
|
|
1168
1199
|
}
|
|
1169
|
-
const stats =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (!
|
|
1259
|
+
if (!fs9.existsSync(absolutePath)) {
|
|
1229
1260
|
return `ERROR: File [${targetPath}] does not exist.`;
|
|
1230
1261
|
}
|
|
1231
|
-
const stats =
|
|
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
|
|
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
|
|
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 (
|
|
1324
|
+
if (fs10.existsSync(absolutePath)) {
|
|
1270
1325
|
try {
|
|
1271
|
-
const oldData =
|
|
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 (!
|
|
1284
|
-
|
|
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
|
-
|
|
1289
|
-
let verifiedContent =
|
|
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
|
|
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 (!
|
|
1394
|
+
if (!fs11.existsSync(absolutePath)) {
|
|
1340
1395
|
return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
|
|
1341
1396
|
}
|
|
1342
|
-
const currentContent =
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
1537
|
+
if (!fs12.existsSync(absolutePath)) {
|
|
1483
1538
|
return `ERROR: Path [${targetPath}] does not exist.`;
|
|
1484
1539
|
}
|
|
1485
|
-
const stats =
|
|
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 =
|
|
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 =
|
|
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
|
|
1625
|
-
import
|
|
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
|
-
|
|
1740
|
-
|
|
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 =
|
|
1776
|
-
if (!
|
|
1777
|
-
|
|
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 =
|
|
1838
|
-
if (
|
|
1839
|
-
const content =
|
|
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 =
|
|
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 =
|
|
1910
|
-
const absoluteCwd =
|
|
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
|
-
|
|
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 =
|
|
1951
|
-
if (!
|
|
1952
|
-
|
|
2115
|
+
const toolHistDir = path14.join(LOGS_DIR, "tools");
|
|
2116
|
+
if (!fs14.existsSync(toolHistDir)) {
|
|
2117
|
+
fs14.mkdirSync(toolHistDir, { recursive: true });
|
|
1953
2118
|
}
|
|
1954
|
-
|
|
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 =
|
|
2015
|
-
if (!
|
|
2016
|
-
|
|
2181
|
+
const janitorLogDir = path14.join(LOGS_DIR, "janitor");
|
|
2182
|
+
if (!fs14.existsSync(janitorLogDir)) {
|
|
2183
|
+
fs14.mkdirSync(janitorLogDir, { recursive: true });
|
|
2017
2184
|
}
|
|
2018
|
-
|
|
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 =
|
|
2031
|
-
|
|
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 =
|
|
2040
|
-
if (!
|
|
2041
|
-
|
|
2206
|
+
const janitorErrDir = path14.join(LOGS_DIR, "janitor");
|
|
2207
|
+
if (!fs14.existsSync(janitorErrDir)) {
|
|
2208
|
+
fs14.mkdirSync(janitorErrDir, { recursive: true });
|
|
2042
2209
|
}
|
|
2043
|
-
|
|
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
|
-
|
|
2061
|
-
|
|
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
|
|
2070
|
-
import
|
|
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
|
|
2104
|
-
const saved = await
|
|
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
|
|
2123
|
-
await
|
|
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/
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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
|
-
|
|
2287
|
-
|
|
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
|
|
2303
|
-
import { exec as
|
|
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 (
|
|
2837
|
-
if (
|
|
2838
|
-
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);
|
|
2839
3056
|
try {
|
|
2840
|
-
const items =
|
|
2841
|
-
if (items.length === 0)
|
|
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
|
-
|
|
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.
|
|
3801
|
-
updatedOn = "2026-
|
|
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.
|
|
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
|
},
|