fluxflow-cli 1.5.3 → 1.6.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/TOOLS.md +51 -51
- package/UI_FEATURES.md +100 -100
- package/dist/fluxflow.js +345 -168
- package/package.json +3 -2
package/TOOLS.md
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
# 🧰 Agent Tools & Capabilities
|
|
2
|
-
|
|
3
|
-
Flux Flow provides a robust set of tools that allow the AI to interact with the file system, execute code, and search the web. The availability of these tools depends on the active operating mode.
|
|
4
|
-
|
|
5
|
-
## Tool Availability by Mode
|
|
6
|
-
|
|
7
|
-
| Tool | Flux Mode (Dev) | Flow Mode (Chat) |
|
|
8
|
-
| :--- | :---: | :---: |
|
|
9
|
-
| **Web Search** | ✅ | ✅ |
|
|
10
|
-
| **Web Scrape** | ✅ | ✅ |
|
|
11
|
-
| **Write PDF** | ✅ | ✅ |
|
|
12
|
-
| **View/Read Files** | ✅ | ❌ |
|
|
13
|
-
| **Write/Update Files** | ✅ | ❌ |
|
|
14
|
-
| **Execute Commands** | ✅ | ❌ |
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Core Tools
|
|
19
|
-
|
|
20
|
-
### 🌐 Web & Research
|
|
21
|
-
- **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
|
|
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.
|
|
24
|
-
|
|
25
|
-
### 📁 File System Operations
|
|
26
|
-
- **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
|
|
27
|
-
- **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
|
|
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.
|
|
31
|
-
|
|
32
|
-
### ✍️ Code Editing
|
|
33
|
-
- **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
|
|
34
|
-
- **`update_file` (Smart Patching)**: Surgically replaces a specific block of text within a file.
|
|
35
|
-
- *Diff Generation*: It returns a high-fidelity visual diff (Red/Green changes with context lines) to the UI, allowing the user to see exactly what the agent modified.
|
|
36
|
-
|
|
37
|
-
### 💻 Terminal Execution
|
|
38
|
-
- **`exec_command`**: Runs a shell command directly in the terminal using Node's `child_process.spawn`.
|
|
39
|
-
- *Context Aware*: Runs in the current working directory.
|
|
40
|
-
- *Cross-Platform*: Uses `shell: true` to handle Windows `.cmd`/`.bat` files natively.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Memory Management
|
|
45
|
-
|
|
46
|
-
The memory tool (`memory.js`) is primarily used by the background **Janitor** model, but can be accessed by the main agent if necessary.
|
|
47
|
-
|
|
48
|
-
- **Temporary Context (`action='temp'`)**: Saves a rolling summary of the current session to maintain conversational context without bloating the main prompt history.
|
|
49
|
-
- **Persistent User Memory (`action='user'`)**:
|
|
50
|
-
- The Janitor analyzes conversations to detect user preferences, hobbies, or instructions.
|
|
51
|
-
- It uses `add`, `update`, or `delete` methods to manage facts in the encrypted `memories.json` vault.
|
|
1
|
+
# 🧰 Agent Tools & Capabilities
|
|
2
|
+
|
|
3
|
+
Flux Flow provides a robust set of tools that allow the AI to interact with the file system, execute code, and search the web. The availability of these tools depends on the active operating mode.
|
|
4
|
+
|
|
5
|
+
## Tool Availability by Mode
|
|
6
|
+
|
|
7
|
+
| Tool | Flux Mode (Dev) | Flow Mode (Chat) |
|
|
8
|
+
| :--- | :---: | :---: |
|
|
9
|
+
| **Web Search** | ✅ | ✅ |
|
|
10
|
+
| **Web Scrape** | ✅ | ✅ |
|
|
11
|
+
| **Write PDF** | ✅ | ✅ |
|
|
12
|
+
| **View/Read Files** | ✅ | ❌ |
|
|
13
|
+
| **Write/Update Files** | ✅ | ❌ |
|
|
14
|
+
| **Execute Commands** | ✅ | ❌ |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Tools
|
|
19
|
+
|
|
20
|
+
### 🌐 Web & Research
|
|
21
|
+
- **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
|
|
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.
|
|
24
|
+
|
|
25
|
+
### 📁 File System Operations
|
|
26
|
+
- **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
|
|
27
|
+
- **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
|
|
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.
|
|
31
|
+
|
|
32
|
+
### ✍️ Code Editing
|
|
33
|
+
- **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
|
|
34
|
+
- **`update_file` (Smart Patching)**: Surgically replaces a specific block of text within a file.
|
|
35
|
+
- *Diff Generation*: It returns a high-fidelity visual diff (Red/Green changes with context lines) to the UI, allowing the user to see exactly what the agent modified.
|
|
36
|
+
|
|
37
|
+
### 💻 Terminal Execution
|
|
38
|
+
- **`exec_command`**: Runs a shell command directly in the terminal using Node's `child_process.spawn`.
|
|
39
|
+
- *Context Aware*: Runs in the current working directory.
|
|
40
|
+
- *Cross-Platform*: Uses `shell: true` to handle Windows `.cmd`/`.bat` files natively.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Memory Management
|
|
45
|
+
|
|
46
|
+
The memory tool (`memory.js`) is primarily used by the background **Janitor** model, but can be accessed by the main agent if necessary.
|
|
47
|
+
|
|
48
|
+
- **Temporary Context (`action='temp'`)**: Saves a rolling summary of the current session to maintain conversational context without bloating the main prompt history.
|
|
49
|
+
- **Persistent User Memory (`action='user'`)**:
|
|
50
|
+
- The Janitor analyzes conversations to detect user preferences, hobbies, or instructions.
|
|
51
|
+
- It uses `add`, `update`, or `delete` methods to manage facts in the encrypted `memories.json` vault.
|
|
52
52
|
- These memories are injected into the system prompt of *future* sessions, allowing Flux Flow to learn and adapt to the user over time.
|
package/UI_FEATURES.md
CHANGED
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
# 🎮 User Interface & Interaction Features
|
|
2
|
-
|
|
3
|
-
Flux Flow is designed to be a high-performance terminal application. Beyond basic chat, it includes a variety of advanced UI features and human-in-the-loop controls to keep you in command of the agent.
|
|
4
|
-
|
|
5
|
-
## ⌨️ Command System
|
|
6
|
-
|
|
7
|
-
You can control the application using `/` commands directly in the chat input:
|
|
8
|
-
|
|
9
|
-
- **/mode [flux|flow]**: Quickly switch between **Flux** (Dev) and **Flow** (Chat) modes. Using it without arguments opens the selection menu.
|
|
10
|
-
- **/thinking [low|medium|high|max|show|hide]**: Adjust reasoning depth or toggle visibility of the thinking process. Using it without arguments opens the selection menu.
|
|
11
|
-
- **/model [name]**: Choose which AI model to use for the main interaction.
|
|
12
|
-
- **/key**: Open the API Key management view to update or remove your credentials.
|
|
13
|
-
- **/settings**: Access the system configuration menu.
|
|
14
|
-
- **/profile**: Update your name, nickname, and custom instructions.
|
|
15
|
-
- **/update**: Update Flux Flow to the latest version.
|
|
16
|
-
- **/memory**: View and manage the persistent memories extracted by the Janitor.
|
|
17
|
-
- **/resume <chat-id>**: Switch back to a previous conversation.
|
|
18
|
-
- **/changelog**: Open the project's changelog in your default browser.
|
|
19
|
-
- **/help**: List all available commands.
|
|
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
|
-
|
|
30
|
-
### Command Shortcuts
|
|
31
|
-
|
|
32
|
-
For power users, several commands support direct arguments to skip the menus:
|
|
33
|
-
- ` /mode flux ` or ` /mode flow `
|
|
34
|
-
- ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
|
|
35
|
-
- ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
|
|
36
|
-
- ` /model gemini-3.1-pro-preview ` (Switches model directly)
|
|
37
|
-
- ` /update check ` (Checks for updates)
|
|
38
|
-
|
|
39
|
-
## 🧠 Thinking Levels & Visualization
|
|
40
|
-
|
|
41
|
-
Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
|
|
42
|
-
|
|
43
|
-
- **Thinking Levels**: Depending on the mode, you can choose from **Low**, **Medium**, **High**, or **Max**. Higher levels allow the agent more "space" to reason through complex architecture or debugging problems.
|
|
44
|
-
- **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
|
|
45
|
-
- When **Hidden**, the agent doesn't just disappear; it provides a "minimalist" view showing only the core **Headings** and **Action Steps** (bolded lines) from its reasoning. This keeps you informed of its current "step" without cluttering the screen with detailed internal monologue.
|
|
46
|
-
|
|
47
|
-
## ⚡ Interactive Sub-Terminal
|
|
48
|
-
|
|
49
|
-
Flux Flow features a high-fidelity, interactive sub-terminal that allows you to engage with commands spawned by the agent in real-time.
|
|
50
|
-
|
|
51
|
-
- **Live Interaction**: When an agent executes a command (like `npm init`, `git commit`, or a custom script), the terminal output appears in a dedicated box.
|
|
52
|
-
- **Focus Toggling (`TAB`)**: While a terminal is live, you can press **`TAB`** to shift your keyboard focus from the chat prompt directly into the terminal.
|
|
53
|
-
- **Visual Feedback**:
|
|
54
|
-
- **Yellow Glow**: When the terminal is focused, its border changes to a "Double Yellow" style to indicate it is capturing input.
|
|
55
|
-
- **Status Indicator**: The footer will change from `● LIVE` to `▶ TERMINAL FOCUSED`.
|
|
56
|
-
- **Cross-Platform Compatibility**: The input bridge automatically detects your OS and sends the correct line endings (`\r\n` for Windows, `\n` for Unix), ensuring that interactive prompts like `y/n` work seamlessly across environments.
|
|
57
|
-
- **Input Mirroring**: Your keystrokes are mirrored in the terminal box in real-time, providing an IDE-grade responsive feel.
|
|
58
|
-
|
|
59
|
-
> [!TIP]
|
|
60
|
-
> Use the Interactive Sub-Terminal to handle authentication prompts, confirmation dialogs, or to manually steering a long-running process without leaving the Flux Flow interface.
|
|
61
|
-
|
|
62
|
-
## 🛡️ Human-in-the-Loop (HITL) Verification
|
|
63
|
-
|
|
64
|
-
Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
|
|
65
|
-
|
|
66
|
-
### Tool Approval
|
|
67
|
-
By default, the agent **cannot** execute dangerous actions without your consent.
|
|
68
|
-
- **Terminal Approval**: If the agent attempts to run a shell command (`exec_command`), a dedicated approval screen appears showing the exact command. You can **Allow** or **Deny** the execution.
|
|
69
|
-
- **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
|
|
70
|
-
- **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
|
|
71
|
-
|
|
72
|
-
### Auto-Execution (Advanced)
|
|
73
|
-
For power users, **Auto-Exec** can be enabled in `/settings`.
|
|
74
|
-
- **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
|
|
75
|
-
- **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
|
|
76
|
-
|
|
77
|
-
## 🔄 Steering & Resolution
|
|
78
|
-
|
|
79
|
-
### Real-time Steering
|
|
80
|
-
If you realize the agent is going down the wrong path *while* it is in an agentic loop, you can provide "Steering Hints." The system will inject your feedback into the next loop to course-correct the agent.
|
|
81
|
-
|
|
82
|
-
### Resolution Modal
|
|
83
|
-
If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
|
|
84
|
-
- **Send Anyway**: Start a new loop with your feedback.
|
|
85
|
-
- **Edit Prompt**: Refine your feedback before sending.
|
|
86
|
-
|
|
87
|
-
## 📊 Status Bar & Feedback
|
|
88
|
-
The bottom of the screen features a dynamic status bar showing:
|
|
89
|
-
- **Active Mode** (Flux/Flow)
|
|
90
|
-
- **Thinking Level**
|
|
91
|
-
- **Token Usage**: Real-time tracking of tokens used in the current session.
|
|
92
|
-
- **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
|
|
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.
|
|
1
|
+
# 🎮 User Interface & Interaction Features
|
|
2
|
+
|
|
3
|
+
Flux Flow is designed to be a high-performance terminal application. Beyond basic chat, it includes a variety of advanced UI features and human-in-the-loop controls to keep you in command of the agent.
|
|
4
|
+
|
|
5
|
+
## ⌨️ Command System
|
|
6
|
+
|
|
7
|
+
You can control the application using `/` commands directly in the chat input:
|
|
8
|
+
|
|
9
|
+
- **/mode [flux|flow]**: Quickly switch between **Flux** (Dev) and **Flow** (Chat) modes. Using it without arguments opens the selection menu.
|
|
10
|
+
- **/thinking [low|medium|high|max|show|hide]**: Adjust reasoning depth or toggle visibility of the thinking process. Using it without arguments opens the selection menu.
|
|
11
|
+
- **/model [name]**: Choose which AI model to use for the main interaction.
|
|
12
|
+
- **/key**: Open the API Key management view to update or remove your credentials.
|
|
13
|
+
- **/settings**: Access the system configuration menu.
|
|
14
|
+
- **/profile**: Update your name, nickname, and custom instructions.
|
|
15
|
+
- **/update**: Update Flux Flow to the latest version.
|
|
16
|
+
- **/memory**: View and manage the persistent memories extracted by the Janitor.
|
|
17
|
+
- **/resume <chat-id>**: Switch back to a previous conversation.
|
|
18
|
+
- **/changelog**: Open the project's changelog in your default browser.
|
|
19
|
+
- **/help**: List all available commands.
|
|
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
|
+
|
|
30
|
+
### Command Shortcuts
|
|
31
|
+
|
|
32
|
+
For power users, several commands support direct arguments to skip the menus:
|
|
33
|
+
- ` /mode flux ` or ` /mode flow `
|
|
34
|
+
- ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
|
|
35
|
+
- ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
|
|
36
|
+
- ` /model gemini-3.1-pro-preview ` (Switches model directly)
|
|
37
|
+
- ` /update check ` (Checks for updates)
|
|
38
|
+
|
|
39
|
+
## 🧠 Thinking Levels & Visualization
|
|
40
|
+
|
|
41
|
+
Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
|
|
42
|
+
|
|
43
|
+
- **Thinking Levels**: Depending on the mode, you can choose from **Low**, **Medium**, **High**, or **Max**. Higher levels allow the agent more "space" to reason through complex architecture or debugging problems.
|
|
44
|
+
- **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
|
|
45
|
+
- When **Hidden**, the agent doesn't just disappear; it provides a "minimalist" view showing only the core **Headings** and **Action Steps** (bolded lines) from its reasoning. This keeps you informed of its current "step" without cluttering the screen with detailed internal monologue.
|
|
46
|
+
|
|
47
|
+
## ⚡ Interactive Sub-Terminal
|
|
48
|
+
|
|
49
|
+
Flux Flow features a high-fidelity, interactive sub-terminal that allows you to engage with commands spawned by the agent in real-time.
|
|
50
|
+
|
|
51
|
+
- **Live Interaction**: When an agent executes a command (like `npm init`, `git commit`, or a custom script), the terminal output appears in a dedicated box.
|
|
52
|
+
- **Focus Toggling (`TAB`)**: While a terminal is live, you can press **`TAB`** to shift your keyboard focus from the chat prompt directly into the terminal.
|
|
53
|
+
- **Visual Feedback**:
|
|
54
|
+
- **Yellow Glow**: When the terminal is focused, its border changes to a "Double Yellow" style to indicate it is capturing input.
|
|
55
|
+
- **Status Indicator**: The footer will change from `● LIVE` to `▶ TERMINAL FOCUSED`.
|
|
56
|
+
- **Cross-Platform Compatibility**: The input bridge automatically detects your OS and sends the correct line endings (`\r\n` for Windows, `\n` for Unix), ensuring that interactive prompts like `y/n` work seamlessly across environments.
|
|
57
|
+
- **Input Mirroring**: Your keystrokes are mirrored in the terminal box in real-time, providing an IDE-grade responsive feel.
|
|
58
|
+
|
|
59
|
+
> [!TIP]
|
|
60
|
+
> Use the Interactive Sub-Terminal to handle authentication prompts, confirmation dialogs, or to manually steering a long-running process without leaving the Flux Flow interface.
|
|
61
|
+
|
|
62
|
+
## 🛡️ Human-in-the-Loop (HITL) Verification
|
|
63
|
+
|
|
64
|
+
Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
|
|
65
|
+
|
|
66
|
+
### Tool Approval
|
|
67
|
+
By default, the agent **cannot** execute dangerous actions without your consent.
|
|
68
|
+
- **Terminal Approval**: If the agent attempts to run a shell command (`exec_command`), a dedicated approval screen appears showing the exact command. You can **Allow** or **Deny** the execution.
|
|
69
|
+
- **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
|
|
70
|
+
- **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
|
|
71
|
+
|
|
72
|
+
### Auto-Execution (Advanced)
|
|
73
|
+
For power users, **Auto-Exec** can be enabled in `/settings`.
|
|
74
|
+
- **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
|
|
75
|
+
- **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
|
|
76
|
+
|
|
77
|
+
## 🔄 Steering & Resolution
|
|
78
|
+
|
|
79
|
+
### Real-time Steering
|
|
80
|
+
If you realize the agent is going down the wrong path *while* it is in an agentic loop, you can provide "Steering Hints." The system will inject your feedback into the next loop to course-correct the agent.
|
|
81
|
+
|
|
82
|
+
### Resolution Modal
|
|
83
|
+
If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
|
|
84
|
+
- **Send Anyway**: Start a new loop with your feedback.
|
|
85
|
+
- **Edit Prompt**: Refine your feedback before sending.
|
|
86
|
+
|
|
87
|
+
## 📊 Status Bar & Feedback
|
|
88
|
+
The bottom of the screen features a dynamic status bar showing:
|
|
89
|
+
- **Active Mode** (Flux/Flow)
|
|
90
|
+
- **Thinking Level**
|
|
91
|
+
- **Token Usage**: Real-time tracking of tokens used in the current session.
|
|
92
|
+
- **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
|
|
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
101
|
- **Silent Maintenance**: Once the engine is ready, you'll receive a `✅ [SYSTEM] All dependencies installed successfully.` confirmation.
|
package/dist/fluxflow.js
CHANGED
|
@@ -23,39 +23,81 @@ var init_TerminalBox = __esm({
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
// src/components/ChatLayout.jsx
|
|
26
|
-
import React2 from "react";
|
|
26
|
+
import React2, { useState, useEffect, useRef } from "react";
|
|
27
27
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
28
|
-
var cleanSignals, formatThinkText, InlineMarkdown, wrapText, TableRenderer, MarkdownText, DiffLine, DiffBlock, CodeRenderer, MessageItem, ChatLayout, ChatLayout_default;
|
|
28
|
+
var TypewriterText, cleanSignals, formatThinkText, InlineMarkdown, wrapText, TableRenderer, MarkdownText, DiffLine, DiffBlock, CodeRenderer, MessageItem, ChatLayout, ChatLayout_default;
|
|
29
29
|
var init_ChatLayout = __esm({
|
|
30
30
|
"src/components/ChatLayout.jsx"() {
|
|
31
31
|
init_TerminalBox();
|
|
32
|
+
TypewriterText = ({ text, isStreaming, onComplete, columns = 80, color = "white", speed = 50, render }) => {
|
|
33
|
+
const [displayedText, setDisplayedText] = useState("");
|
|
34
|
+
const fullTextRef = useRef(text);
|
|
35
|
+
const displayedTextRef = useRef("");
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
fullTextRef.current = text;
|
|
38
|
+
}, [text]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const timer = setInterval(() => {
|
|
41
|
+
const currentFull = fullTextRef.current;
|
|
42
|
+
const currentDisp = displayedTextRef.current;
|
|
43
|
+
if (currentDisp.length < currentFull.length) {
|
|
44
|
+
const remaining = currentFull.slice(currentDisp.length);
|
|
45
|
+
const match = remaining.match(/^(\S+\s*|\s+)/);
|
|
46
|
+
if (match) {
|
|
47
|
+
const chunk = match[0];
|
|
48
|
+
const gap = currentFull.length - currentDisp.length;
|
|
49
|
+
let revealedChunk = chunk;
|
|
50
|
+
if (gap > 100) {
|
|
51
|
+
const extraMatch = remaining.slice(chunk.length).match(/^(\S+\s*|\s+){0,2}/);
|
|
52
|
+
if (extraMatch) revealedChunk += extraMatch[0];
|
|
53
|
+
}
|
|
54
|
+
const nextText = currentDisp + revealedChunk;
|
|
55
|
+
setDisplayedText(nextText);
|
|
56
|
+
displayedTextRef.current = nextText;
|
|
57
|
+
}
|
|
58
|
+
} else if (!isStreaming) {
|
|
59
|
+
if (onComplete) onComplete();
|
|
60
|
+
clearInterval(timer);
|
|
61
|
+
}
|
|
62
|
+
}, speed);
|
|
63
|
+
return () => clearInterval(timer);
|
|
64
|
+
}, [isStreaming, speed]);
|
|
65
|
+
if (render) return render(displayedText);
|
|
66
|
+
return /* @__PURE__ */ React2.createElement(CodeRenderer, { text: displayedText, columns });
|
|
67
|
+
};
|
|
32
68
|
cleanSignals = (text) => {
|
|
33
69
|
if (!text) return text;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
while (
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
let result = text;
|
|
71
|
+
const trigger = "tool:functions.";
|
|
72
|
+
while (true) {
|
|
73
|
+
const lowerResult = result.toLowerCase();
|
|
74
|
+
const startIdx = lowerResult.indexOf(trigger);
|
|
75
|
+
if (startIdx === -1) break;
|
|
76
|
+
let balance = 0;
|
|
77
|
+
let foundStart = false;
|
|
78
|
+
let inString = null;
|
|
79
|
+
let j = startIdx;
|
|
80
|
+
while (j < result.length) {
|
|
81
|
+
const char = result[j];
|
|
82
|
+
if (!inString && (char === "'" || char === '"' || char === "`")) {
|
|
83
|
+
inString = char;
|
|
84
|
+
} else if (inString && char === inString && result[j - 1] !== "\\") {
|
|
85
|
+
inString = null;
|
|
86
|
+
}
|
|
87
|
+
if (!inString) {
|
|
88
|
+
if (char === "(") {
|
|
44
89
|
balance++;
|
|
45
90
|
foundStart = true;
|
|
46
|
-
} else if (
|
|
91
|
+
} else if (char === ")") {
|
|
47
92
|
balance--;
|
|
48
93
|
}
|
|
49
|
-
j++;
|
|
50
|
-
if (foundStart && balance === 0) break;
|
|
51
94
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
parts.push(text[i]);
|
|
55
|
-
i++;
|
|
95
|
+
j++;
|
|
96
|
+
if (foundStart && balance === 0 && !inString) break;
|
|
56
97
|
}
|
|
98
|
+
result = result.substring(0, startIdx) + result.substring(j);
|
|
57
99
|
}
|
|
58
|
-
return
|
|
100
|
+
return result.replace(/^\[TOOL_RESULT\]:\s*/gi, "").split("\n").filter((line) => !line.trim().startsWith("SUCCESS:") && !line.trim().startsWith("ERROR:")).join("\n").replace(/\[\s*(turn\s*:)?\s*(continue|finish)\s*\]/gi, "").replace(/\n\s*turn\s*:\s*(continue|finish)\s*$/gi, "").replace(/\n\nResponded on .*/g, "").replace(/\n\n\[Prompted on: .*\]/g, "").replace(/(\$?\\?\/?\\rightarrow\$?|\$\\rightarrow\$)/gi, "\u2192").replace(/(\$?\\?\/?\\leftarrow\$?|\$\\leftarrow\$)/gi, "\u2190").replace(/(\$?\\?\/?\\uparrow\$?|\$\\uparrow\$)/gi, "\u2191").replace(/(\$?\\?\/?\\downarrow\$?|\$\\downarrow\$)/gi, "\u2193").replace(/(\$?\\?\/?\\leftrightarrow\$?|\$\\leftrightarrow\$)/gi, "\u2194").trim();
|
|
59
101
|
};
|
|
60
102
|
formatThinkText = (cleaned, columns = 80) => {
|
|
61
103
|
if (!cleaned) return null;
|
|
@@ -257,6 +299,9 @@ var init_ChatLayout = __esm({
|
|
|
257
299
|
MessageItem = React2.memo(({ msg, showFullThinking, columns = 80 }) => {
|
|
258
300
|
const isDiffResult = msg.role === "system" && msg.text?.includes("[DIFF_START]");
|
|
259
301
|
const isTerminalRecord = msg.isTerminalRecord;
|
|
302
|
+
if (msg.isVisualFeedback) {
|
|
303
|
+
return /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { color: "white" }, msg.text));
|
|
304
|
+
}
|
|
260
305
|
if (msg.role === "system" && msg.text?.includes("[TOOL_RESULT]") && !isDiffResult && !isTerminalRecord) return null;
|
|
261
306
|
if (msg.isAskRecord) {
|
|
262
307
|
const selectionMatch = msg.text.match(/Selection: (.*)/);
|
|
@@ -290,7 +335,11 @@ var init_ChatLayout = __esm({
|
|
|
290
335
|
const outputList = outputMatch ? outputMatch[1] : "";
|
|
291
336
|
return /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(TerminalBox, { command: cmd, output: outputList, completed: true }));
|
|
292
337
|
}
|
|
338
|
+
const [animationDone, setAnimationDone] = React2.useState(!msg.isStreaming);
|
|
293
339
|
const content = React2.useMemo(() => cleanSignals(msg.text), [msg.text]);
|
|
340
|
+
React2.useEffect(() => {
|
|
341
|
+
if (msg.isStreaming) setAnimationDone(false);
|
|
342
|
+
}, [msg.id]);
|
|
294
343
|
const finalContent = React2.useMemo(() => {
|
|
295
344
|
if (msg.role === "think" && !showFullThinking) {
|
|
296
345
|
const lines = content.split("\n").filter((line) => {
|
|
@@ -317,7 +366,24 @@ var init_ChatLayout = __esm({
|
|
|
317
366
|
finalContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\n/g, "\n").replace(/\\$/, ""),
|
|
318
367
|
columns - 6
|
|
319
368
|
).split("\n").map((line, lineIdx) => /* @__PURE__ */ React2.createElement(Box2, { key: lineIdx, flexDirection: "row", width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { flexShrink: 0, width: 2 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, lineIdx === 0 ? "\u276F" : " ")), /* @__PURE__ */ React2.createElement(Box2, { flexGrow: 1, marginLeft: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: msg.color || "white", wrap: "anywhere" }, line))))
|
|
320
|
-
) : msg.role === "think" ? /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Thinking..."), /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, paddingLeft: 2, flexDirection: "column", width: "100%" },
|
|
369
|
+
) : msg.role === "think" ? /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Thinking..."), /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, paddingLeft: 2, flexDirection: "column", width: "100%" }, !animationDone ? /* @__PURE__ */ React2.createElement(
|
|
370
|
+
TypewriterText,
|
|
371
|
+
{
|
|
372
|
+
text: finalContent,
|
|
373
|
+
isStreaming: msg.isStreaming,
|
|
374
|
+
onComplete: () => setAnimationDone(true),
|
|
375
|
+
speed: 25,
|
|
376
|
+
render: (t) => formatThinkText(t, columns)
|
|
377
|
+
}
|
|
378
|
+
) : formatThinkText(finalContent, columns))) : /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginTop: 1, width: "100%" }, !animationDone ? /* @__PURE__ */ React2.createElement(
|
|
379
|
+
TypewriterText,
|
|
380
|
+
{
|
|
381
|
+
text: finalContent,
|
|
382
|
+
isStreaming: msg.isStreaming,
|
|
383
|
+
onComplete: () => setAnimationDone(true),
|
|
384
|
+
columns
|
|
385
|
+
}
|
|
386
|
+
) : /* @__PURE__ */ React2.createElement(CodeRenderer, { text: finalContent, columns }), msg.memoryUpdated && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow", italic: true }, "\u2728 [Memory Updated]"))));
|
|
321
387
|
});
|
|
322
388
|
ChatLayout = React2.memo(({ messages, showFullThinking, columns = 80 }) => {
|
|
323
389
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", width: "100%" }, messages.map((msg, idx) => /* @__PURE__ */ React2.createElement(
|
|
@@ -389,13 +455,13 @@ var init_CommandMenu = __esm({
|
|
|
389
455
|
});
|
|
390
456
|
|
|
391
457
|
// src/components/ProfileForm.jsx
|
|
392
|
-
import React5, { useState } from "react";
|
|
458
|
+
import React5, { useState as useState2 } from "react";
|
|
393
459
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
394
460
|
import TextInput from "ink-text-input";
|
|
395
461
|
function ProfileForm({ onSave, onCancel }) {
|
|
396
|
-
const [step, setStep] =
|
|
397
|
-
const [currentInput, setCurrentInput] =
|
|
398
|
-
const [profile, setProfile] =
|
|
462
|
+
const [step, setStep] = useState2(0);
|
|
463
|
+
const [currentInput, setCurrentInput] = useState2("");
|
|
464
|
+
const [profile, setProfile] = useState2({ name: "", nickname: "", instructions: "" });
|
|
399
465
|
const steps = [
|
|
400
466
|
{ key: "name", label: "Enter your Name: " },
|
|
401
467
|
{ key: "nickname", label: "Enter a Nickname (Agent will use this): " },
|
|
@@ -427,16 +493,16 @@ var init_ProfileForm = __esm({
|
|
|
427
493
|
});
|
|
428
494
|
|
|
429
495
|
// src/components/AskUserModal.jsx
|
|
430
|
-
import React6, { useState as
|
|
496
|
+
import React6, { useState as useState3 } from "react";
|
|
431
497
|
import { Box as Box6, Text as Text6, useInput } from "ink";
|
|
432
498
|
import TextInput2 from "ink-text-input";
|
|
433
499
|
var AskUserModal, AskUserModal_default;
|
|
434
500
|
var init_AskUserModal = __esm({
|
|
435
501
|
"src/components/AskUserModal.jsx"() {
|
|
436
502
|
AskUserModal = ({ question, options, onResolve }) => {
|
|
437
|
-
const [isSuggestingElse, setIsSuggestingElse] =
|
|
438
|
-
const [customInput, setCustomInput] =
|
|
439
|
-
const [selectedIndex, setSelectedIndex] =
|
|
503
|
+
const [isSuggestingElse, setIsSuggestingElse] = useState3(false);
|
|
504
|
+
const [customInput, setCustomInput] = useState3("");
|
|
505
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
440
506
|
const allOptions = [...options, { id: "CUSTOM", label: "Suggest something else...", description: "Provide a custom response" }];
|
|
441
507
|
useInput((input, key) => {
|
|
442
508
|
if (isSuggestingElse) return;
|
|
@@ -612,10 +678,10 @@ ${mode === "Flux" ? `
|
|
|
612
678
|
3. Read Folder: tool:functions.read_folder(path="relative/path"). Detailed stats of a directory.
|
|
613
679
|
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.
|
|
614
680
|
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.
|
|
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.
|
|
681
|
+
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, so any multi-page PDFs calculate your alightment and page breaks to not mess up A4 page layout . DO NOT ADD FOOTER MANUALLY, the system will handle it automatically. USE CSS TO VISUALLY BEAUTIFY THE DOCUMENT.
|
|
616
682
|
7. Execution: tool:functions.exec_command(command="terminal command"). Runs a shell command.
|
|
617
683
|
|
|
618
|
-
**NOTE:** WHEN WRITING/UPDATING FILES, USE ACTUAL NEW LINE CHARACTER FOR LINE BREAKS RATHER THAN STRING '\\n'`.trim() : `
|
|
684
|
+
**NOTE:** WHEN WRITING/UPDATING FILES, USE ACTUAL NEW LINE CONTROL CHARACTER (LF) FOR LINE BREAKS RATHER THAN STRING '\\n'`.trim() : `
|
|
619
685
|
- 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()}
|
|
620
686
|
-----------------
|
|
621
687
|
Results will be provided in the next loop as: [TOOL_RESULT]: [content]
|
|
@@ -957,21 +1023,24 @@ var init_arg_parser = __esm({
|
|
|
957
1023
|
"src/utils/arg_parser.js"() {
|
|
958
1024
|
parseArgs = (argsString) => {
|
|
959
1025
|
const args = {};
|
|
960
|
-
const regex = /(\w+)\s*=\s*(?:(["'])((?:\\.|(?!\2)[\s\S])*)\2|([^,\s\)]+))/g;
|
|
1026
|
+
const regex = /(\w+)\s*=\s*(?:(["'`])((?:\\.|(?!\2)[\s\S])*)\2|([^,\s\)]+))/g;
|
|
961
1027
|
let match;
|
|
962
1028
|
while ((match = regex.exec(argsString)) !== null) {
|
|
963
1029
|
const key = match[1];
|
|
964
1030
|
let value = match[3] !== void 0 ? match[3] : match[4];
|
|
965
1031
|
if (match[3] !== void 0) {
|
|
966
1032
|
try {
|
|
967
|
-
value = JSON.parse(`"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`);
|
|
1033
|
+
value = JSON.parse(`"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`);
|
|
968
1034
|
} catch (e) {
|
|
969
|
-
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
|
|
1035
|
+
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\`/g, "`").replace(/\\\\/g, "\\");
|
|
970
1036
|
}
|
|
971
1037
|
}
|
|
972
1038
|
if (value === "true") value = true;
|
|
973
1039
|
else if (value === "false") value = false;
|
|
974
1040
|
else if (typeof value === "string" && !isNaN(value) && value.trim() !== "") value = Number(value);
|
|
1041
|
+
if (typeof value === "string" && (key.toLowerCase().includes("path") || ["dest", "source", "to", "from"].includes(key.toLowerCase()))) {
|
|
1042
|
+
value = value.replace(/\x0C/g, "\\f").replace(/\x0D/g, "\\r").replace(/\x0B/g, "\\v").replace(/\x08/g, "\\b");
|
|
1043
|
+
}
|
|
975
1044
|
args[key] = value;
|
|
976
1045
|
}
|
|
977
1046
|
return args;
|
|
@@ -1360,7 +1429,11 @@ var init_view_file = __esm({
|
|
|
1360
1429
|
}
|
|
1361
1430
|
};
|
|
1362
1431
|
}
|
|
1363
|
-
|
|
1432
|
+
let content = fs9.readFileSync(absolutePath, "utf8");
|
|
1433
|
+
if (content.startsWith("\uFEFF")) {
|
|
1434
|
+
content = content.slice(1);
|
|
1435
|
+
}
|
|
1436
|
+
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1364
1437
|
const lines = content.split("\n");
|
|
1365
1438
|
const totalLines = lines.length;
|
|
1366
1439
|
const start = Math.max(0, start_line - 1);
|
|
@@ -1389,7 +1462,7 @@ var init_write_file = __esm({
|
|
|
1389
1462
|
let { path: targetPath, content } = parseArgs(args);
|
|
1390
1463
|
if (!targetPath) return 'ERROR: Missing "path" argument for write_file.';
|
|
1391
1464
|
if (content === void 0) return 'ERROR: Missing "content" argument for write_file.';
|
|
1392
|
-
content = content.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").
|
|
1465
|
+
content = content.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1393
1466
|
const absolutePath = path10.resolve(process.cwd(), targetPath);
|
|
1394
1467
|
const parentDir = path10.dirname(absolutePath);
|
|
1395
1468
|
try {
|
|
@@ -1459,7 +1532,7 @@ var init_update_file = __esm({
|
|
|
1459
1532
|
if (!targetPath) return 'ERROR: Missing "path" argument for update_file.';
|
|
1460
1533
|
if (content_to_replace === void 0) return 'ERROR: Missing "content_to_replace" argument.';
|
|
1461
1534
|
if (content_to_add === void 0) return 'ERROR: Missing "content_to_add" argument.';
|
|
1462
|
-
const strip = (t) => t.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").
|
|
1535
|
+
const strip = (t) => t.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1463
1536
|
content_to_replace = strip(content_to_replace);
|
|
1464
1537
|
content_to_add = strip(content_to_add);
|
|
1465
1538
|
const absolutePath = path11.resolve(process.cwd(), targetPath);
|
|
@@ -1467,9 +1540,23 @@ var init_update_file = __esm({
|
|
|
1467
1540
|
if (!fs11.existsSync(absolutePath)) {
|
|
1468
1541
|
return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
|
|
1469
1542
|
}
|
|
1470
|
-
|
|
1543
|
+
let diskContent = fs11.readFileSync(absolutePath, "utf8");
|
|
1544
|
+
if (diskContent.startsWith("\uFEFF")) {
|
|
1545
|
+
diskContent = diskContent.slice(1);
|
|
1546
|
+
}
|
|
1547
|
+
const normalizedDisk = diskContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1548
|
+
if (diskContent !== normalizedDisk) {
|
|
1549
|
+
fs11.writeFileSync(absolutePath, normalizedDisk, "utf8");
|
|
1550
|
+
diskContent = normalizedDisk;
|
|
1551
|
+
}
|
|
1552
|
+
const currentContent = diskContent;
|
|
1471
1553
|
if (!currentContent.includes(content_to_replace)) {
|
|
1472
|
-
|
|
1554
|
+
const diskLen = currentContent.length;
|
|
1555
|
+
const matchLen = content_to_replace.length;
|
|
1556
|
+
return `ERROR: Could not find exact match for the specified "content_to_replace" in [${targetPath}].
|
|
1557
|
+
- Disk Content Length (Normalized): ${diskLen}
|
|
1558
|
+
- Match String Length (Normalized): ${matchLen}
|
|
1559
|
+
- Check indentation/whitespace/line breaks. Try re-reading the file for latest changes.`;
|
|
1473
1560
|
}
|
|
1474
1561
|
const startPos = currentContent.indexOf(content_to_replace);
|
|
1475
1562
|
const startLine = currentContent.substring(0, startPos).split(/\r?\n/).length;
|
|
@@ -1709,12 +1796,18 @@ var init_ask_user = __esm({
|
|
|
1709
1796
|
import puppeteer3 from "puppeteer";
|
|
1710
1797
|
import path13 from "path";
|
|
1711
1798
|
import fs13 from "fs-extra";
|
|
1799
|
+
import { PDFDocument } from "pdf-lib";
|
|
1712
1800
|
var write_pdf;
|
|
1713
1801
|
var init_write_pdf = __esm({
|
|
1714
1802
|
"src/tools/write_pdf.js"() {
|
|
1715
1803
|
init_arg_parser();
|
|
1716
1804
|
write_pdf = async (args) => {
|
|
1717
|
-
const {
|
|
1805
|
+
const {
|
|
1806
|
+
path: targetPath,
|
|
1807
|
+
content,
|
|
1808
|
+
orientation = "portrait",
|
|
1809
|
+
margin = "10px"
|
|
1810
|
+
} = parseArgs(args);
|
|
1718
1811
|
if (!targetPath) return 'ERROR: Missing "path" argument for write_pdf.';
|
|
1719
1812
|
if (!content) return 'ERROR: Missing "content" (HTML/CSS) for write_pdf.';
|
|
1720
1813
|
const absolutePath = path13.resolve(process.cwd(), targetPath);
|
|
@@ -1733,39 +1826,57 @@ var init_write_pdf = __esm({
|
|
|
1733
1826
|
const page = await browser.newPage();
|
|
1734
1827
|
const styledContent = `
|
|
1735
1828
|
<style>
|
|
1736
|
-
@page {
|
|
1737
|
-
margin: ${margin};
|
|
1829
|
+
@page {
|
|
1830
|
+
margin: ${margin};
|
|
1738
1831
|
}
|
|
1739
|
-
body {
|
|
1740
|
-
margin: 0;
|
|
1832
|
+
body {
|
|
1833
|
+
margin: 0;
|
|
1741
1834
|
padding: 0;
|
|
1742
1835
|
font-family: system-ui, -apple-system, sans-serif;
|
|
1743
1836
|
}
|
|
1744
1837
|
* { box-sizing: border-box; }
|
|
1838
|
+
.watermark {
|
|
1839
|
+
position: fixed;
|
|
1840
|
+
top: 50%;
|
|
1841
|
+
left: 50%;
|
|
1842
|
+
transform: translate(-50%, -50%) rotate(-50deg);
|
|
1843
|
+
font-size: 52px;
|
|
1844
|
+
font-weight: bold;
|
|
1845
|
+
color: rgba(0, 0, 0, 0.005);
|
|
1846
|
+
pointer-events: none;
|
|
1847
|
+
z-index: -1000;
|
|
1848
|
+
text-align: center;
|
|
1849
|
+
width: 150%;
|
|
1850
|
+
white-space: nowrap;
|
|
1851
|
+
text-transform: uppercase;
|
|
1852
|
+
letter-spacing: 5px;
|
|
1853
|
+
}
|
|
1745
1854
|
</style>
|
|
1855
|
+
<div class="watermark">Generated by FluxFlow CLI (AI)</div>
|
|
1746
1856
|
${content}
|
|
1747
1857
|
`;
|
|
1748
1858
|
await page.setContent(styledContent, { waitUntil: "networkidle0" });
|
|
1749
|
-
await page.pdf({
|
|
1750
|
-
path: absolutePath,
|
|
1859
|
+
const pdfBytes = await page.pdf({
|
|
1751
1860
|
format: "A4",
|
|
1752
1861
|
landscape: orientation.toLowerCase() === "landscape",
|
|
1753
1862
|
margin: {
|
|
1754
1863
|
top: margin,
|
|
1755
1864
|
right: margin,
|
|
1756
|
-
bottom:
|
|
1757
|
-
// Space for watermark
|
|
1865
|
+
bottom: margin,
|
|
1758
1866
|
left: margin
|
|
1759
1867
|
},
|
|
1760
|
-
displayHeaderFooter: true,
|
|
1761
|
-
headerTemplate: "<span></span>",
|
|
1762
|
-
footerTemplate: `
|
|
1763
|
-
<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;">
|
|
1764
|
-
FluxFlow CLI
|
|
1765
|
-
</div>
|
|
1766
|
-
`,
|
|
1767
1868
|
printBackground: true
|
|
1768
1869
|
});
|
|
1870
|
+
const pdfDoc = await PDFDocument.load(pdfBytes);
|
|
1871
|
+
const fileName = path13.basename(targetPath);
|
|
1872
|
+
pdfDoc.setTitle(`FluxFlow ${fileName}`);
|
|
1873
|
+
pdfDoc.setAuthor("FluxFlow CLI");
|
|
1874
|
+
pdfDoc.setSubject("Generated with AI");
|
|
1875
|
+
pdfDoc.setKeywords(["FluxFlow", "AI", "Agentic", "Automated"]);
|
|
1876
|
+
pdfDoc.setCreator("FluxFlow PDF Engine");
|
|
1877
|
+
pdfDoc.setProducer("FluxFlow (Generative AI)");
|
|
1878
|
+
const finalPdfBytes = await pdfDoc.save();
|
|
1879
|
+
await fs13.writeFile(absolutePath, finalPdfBytes);
|
|
1769
1880
|
const stats = await fs13.stat(absolutePath);
|
|
1770
1881
|
return `SUCCESS: PDF generated successfully at [${targetPath}] (${(stats.size / 1024).toFixed(2)} KB).`;
|
|
1771
1882
|
} catch (err) {
|
|
@@ -1865,39 +1976,47 @@ var init_ai = __esm({
|
|
|
1865
1976
|
};
|
|
1866
1977
|
detectToolCalls = (text) => {
|
|
1867
1978
|
const results = [];
|
|
1868
|
-
const toolRegex = /(?:\[?\s*tool:functions\.)([a-z0-9_]+)\s*\(
|
|
1979
|
+
const toolRegex = /(?:\[?\s*tool:functions\.)([a-z0-9_]+)\s*\(/gi;
|
|
1869
1980
|
let match;
|
|
1870
1981
|
while ((match = toolRegex.exec(text)) !== null) {
|
|
1871
|
-
const fullMatch = match[0];
|
|
1872
1982
|
const toolName = match[1];
|
|
1873
|
-
const
|
|
1874
|
-
let
|
|
1875
|
-
let
|
|
1876
|
-
let
|
|
1877
|
-
let
|
|
1878
|
-
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1983
|
+
const startIdx = match.index + match[0].length - 1;
|
|
1984
|
+
let balance = 0;
|
|
1985
|
+
let inString = null;
|
|
1986
|
+
let isEscaped = false;
|
|
1987
|
+
let endIdx = -1;
|
|
1988
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
1989
|
+
const char = text[i];
|
|
1990
|
+
if (!inString && (char === '"' || char === "'" || char === "`")) {
|
|
1991
|
+
inString = char;
|
|
1992
|
+
isEscaped = false;
|
|
1993
|
+
} else if (inString && char === inString && !isEscaped) {
|
|
1994
|
+
inString = null;
|
|
1995
|
+
}
|
|
1996
|
+
if (!inString) {
|
|
1997
|
+
if (char === "(") balance++;
|
|
1998
|
+
else if (char === ")") balance--;
|
|
1885
1999
|
if (balance === 0) {
|
|
1886
2000
|
endIdx = i;
|
|
1887
2001
|
break;
|
|
1888
2002
|
}
|
|
1889
2003
|
}
|
|
1890
|
-
if (
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2004
|
+
if (char === "\\") {
|
|
2005
|
+
isEscaped = !isEscaped;
|
|
2006
|
+
} else {
|
|
2007
|
+
isEscaped = false;
|
|
1894
2008
|
}
|
|
1895
2009
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2010
|
+
if (endIdx !== -1) {
|
|
2011
|
+
const finalArgs = text.substring(startIdx + 1, endIdx);
|
|
2012
|
+
const finalFullMatch = text.substring(match.index, endIdx + 1);
|
|
2013
|
+
results.push({
|
|
2014
|
+
fullMatch: finalFullMatch,
|
|
2015
|
+
toolName: toolName.trim(),
|
|
2016
|
+
args: finalArgs.trim()
|
|
2017
|
+
});
|
|
2018
|
+
toolRegex.lastIndex = endIdx + 1;
|
|
2019
|
+
}
|
|
1901
2020
|
}
|
|
1902
2021
|
return results;
|
|
1903
2022
|
};
|
|
@@ -1933,7 +2052,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
1933
2052
|
let lastUsage = null;
|
|
1934
2053
|
const MAX_LOOPS = mode === "Flux" ? 50 : 7;
|
|
1935
2054
|
const MAX_RETRIES = 7;
|
|
1936
|
-
yield { type: "status", content: "
|
|
2055
|
+
yield { type: "status", content: "Connecting..." };
|
|
1937
2056
|
TERMINATION_SIGNAL = false;
|
|
1938
2057
|
let fullAgentResponseChunks = [];
|
|
1939
2058
|
modifiedHistory.forEach((msg) => {
|
|
@@ -2036,6 +2155,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
2036
2155
|
lastUsage = chunk.usageMetadata;
|
|
2037
2156
|
if (lastUsage) {
|
|
2038
2157
|
yield { type: "liveTokens", content: lastUsage.totalTokenCount };
|
|
2158
|
+
yield { type: "status", content: "Working..." };
|
|
2039
2159
|
}
|
|
2040
2160
|
}
|
|
2041
2161
|
await incrementUsage("agent");
|
|
@@ -2043,7 +2163,11 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
2043
2163
|
yield { type: "usage", content: lastUsage };
|
|
2044
2164
|
}
|
|
2045
2165
|
fullAgentResponseChunks.push(turnText);
|
|
2046
|
-
|
|
2166
|
+
let textToProcess = turnText;
|
|
2167
|
+
const thinkMatch = turnText.match(/<think>([\s\S]*?)<\/think>/i);
|
|
2168
|
+
if (thinkMatch) {
|
|
2169
|
+
textToProcess = turnText.replace(/<think>[\s\S]*?<\/think>/i, "");
|
|
2170
|
+
}
|
|
2047
2171
|
const turnTextLower = textToProcess.toLowerCase();
|
|
2048
2172
|
const hasFinish = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(turnTextLower);
|
|
2049
2173
|
const toolCalls = detectToolCalls(textToProcess);
|
|
@@ -2102,7 +2226,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
2102
2226
|
const boxTop = `\u256D${"\u2500".repeat(boxWidth)}\u256E`;
|
|
2103
2227
|
const boxMid = `\u2502 ${label.padEnd(boxWidth - 2).substring(0, boxWidth - 2)} \u2502`;
|
|
2104
2228
|
const boxBottom = `\u2570${"\u2500".repeat(boxWidth)}\u256F`;
|
|
2105
|
-
yield { type: "
|
|
2229
|
+
yield { type: "visual_feedback", content: `
|
|
2106
2230
|
|
|
2107
2231
|
${boxTop}
|
|
2108
2232
|
${boxMid}
|
|
@@ -2404,13 +2528,13 @@ var init_settings = __esm({
|
|
|
2404
2528
|
});
|
|
2405
2529
|
|
|
2406
2530
|
// src/components/ResumeModal.jsx
|
|
2407
|
-
import React7, { useState as
|
|
2531
|
+
import React7, { useState as useState4, useEffect as useEffect2 } from "react";
|
|
2408
2532
|
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
2409
2533
|
function ResumeModal({ onSelect, onDelete, onClose }) {
|
|
2410
|
-
const [history, setHistory] =
|
|
2411
|
-
const [keys, setKeys] =
|
|
2412
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2413
|
-
|
|
2534
|
+
const [history, setHistory] = useState4({});
|
|
2535
|
+
const [keys, setKeys] = useState4([]);
|
|
2536
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
2537
|
+
useEffect2(() => {
|
|
2414
2538
|
const fetchHistory = async () => {
|
|
2415
2539
|
const h = await loadHistory();
|
|
2416
2540
|
setHistory(h);
|
|
@@ -2447,16 +2571,16 @@ var init_ResumeModal = __esm({
|
|
|
2447
2571
|
});
|
|
2448
2572
|
|
|
2449
2573
|
// src/components/MemoryModal.jsx
|
|
2450
|
-
import React8, { useState as
|
|
2574
|
+
import React8, { useState as useState5, useEffect as useEffect3 } from "react";
|
|
2451
2575
|
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
2452
2576
|
function MemoryModal({ onClose }) {
|
|
2453
|
-
const [memories, setMemories] =
|
|
2454
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2577
|
+
const [memories, setMemories] = useState5([]);
|
|
2578
|
+
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
2455
2579
|
const loadMemories = () => {
|
|
2456
2580
|
const data = readEncryptedJson(MEMORIES_FILE, []);
|
|
2457
2581
|
setMemories(data);
|
|
2458
2582
|
};
|
|
2459
|
-
|
|
2583
|
+
useEffect3(() => {
|
|
2460
2584
|
loadMemories();
|
|
2461
2585
|
}, []);
|
|
2462
2586
|
useInput3((input, key) => {
|
|
@@ -2486,7 +2610,7 @@ var init_MemoryModal = __esm({
|
|
|
2486
2610
|
});
|
|
2487
2611
|
|
|
2488
2612
|
// src/components/UpdateProcessor.jsx
|
|
2489
|
-
import React9, { useState as
|
|
2613
|
+
import React9, { useState as useState6, useEffect as useEffect4 } from "react";
|
|
2490
2614
|
import { Box as Box9, Text as Text9 } from "ink";
|
|
2491
2615
|
import Spinner from "ink-spinner";
|
|
2492
2616
|
import { exec } from "child_process";
|
|
@@ -2494,10 +2618,10 @@ var UpdateProcessor, UpdateProcessor_default;
|
|
|
2494
2618
|
var init_UpdateProcessor = __esm({
|
|
2495
2619
|
"src/components/UpdateProcessor.jsx"() {
|
|
2496
2620
|
UpdateProcessor = ({ latest, current, settings, onClose, onUpdateSettings, onSuccess }) => {
|
|
2497
|
-
const [status, setStatus] =
|
|
2498
|
-
const [log, setLog] =
|
|
2499
|
-
const [error, setError] =
|
|
2500
|
-
|
|
2621
|
+
const [status, setStatus] = useState6("initializing");
|
|
2622
|
+
const [log, setLog] = useState6("");
|
|
2623
|
+
const [error, setError] = useState6(null);
|
|
2624
|
+
useEffect4(() => {
|
|
2501
2625
|
const runUpdate = async () => {
|
|
2502
2626
|
const manager = settings.updateManager || "npm";
|
|
2503
2627
|
if (!settings.updateManager) {
|
|
@@ -2584,7 +2708,7 @@ var app_exports = {};
|
|
|
2584
2708
|
__export(app_exports, {
|
|
2585
2709
|
default: () => App
|
|
2586
2710
|
});
|
|
2587
|
-
import React10, { useState as
|
|
2711
|
+
import React10, { useState as useState7, useEffect as useEffect5, useRef as useRef2, useMemo } from "react";
|
|
2588
2712
|
import { Box as Box10, Text as Text10, useInput as useInput4, useStdout } from "ink";
|
|
2589
2713
|
import Spinner2 from "ink-spinner";
|
|
2590
2714
|
import fs17 from "fs-extra";
|
|
@@ -2594,14 +2718,14 @@ import TextInput3 from "ink-text-input";
|
|
|
2594
2718
|
import gradient from "gradient-string";
|
|
2595
2719
|
function App() {
|
|
2596
2720
|
const { stdout } = useStdout();
|
|
2597
|
-
const [input, setInput] =
|
|
2598
|
-
const [isExpanded, setIsExpanded] =
|
|
2599
|
-
const [mode, setMode] =
|
|
2600
|
-
const [terminalSize, setTerminalSize] =
|
|
2721
|
+
const [input, setInput] = useState7("");
|
|
2722
|
+
const [isExpanded, setIsExpanded] = useState7(false);
|
|
2723
|
+
const [mode, setMode] = useState7("Flux");
|
|
2724
|
+
const [terminalSize, setTerminalSize] = useState7({
|
|
2601
2725
|
columns: stdout?.columns || 80,
|
|
2602
2726
|
rows: stdout?.rows || 24
|
|
2603
2727
|
});
|
|
2604
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2728
|
+
const [selectedIndex, setSelectedIndex] = useState7(0);
|
|
2605
2729
|
const performVersionCheck = async (manual = false, settingsOverride = null) => {
|
|
2606
2730
|
const settingsToUse = settingsOverride || systemSettings;
|
|
2607
2731
|
if (manual) {
|
|
@@ -2648,7 +2772,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2648
2772
|
}
|
|
2649
2773
|
}
|
|
2650
2774
|
};
|
|
2651
|
-
|
|
2775
|
+
useEffect5(() => {
|
|
2652
2776
|
const handleResize = () => {
|
|
2653
2777
|
stdout.write("\x1Bc");
|
|
2654
2778
|
setTerminalSize({
|
|
@@ -2661,29 +2785,29 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2661
2785
|
stdout.off("resize", handleResize);
|
|
2662
2786
|
};
|
|
2663
2787
|
}, [stdout]);
|
|
2664
|
-
const [thinkingLevel, setThinkingLevel] =
|
|
2665
|
-
const [latestVer, setLatestVer] =
|
|
2666
|
-
const [showFullThinking, setShowFullThinking] =
|
|
2667
|
-
const [activeModel, setActiveModel] =
|
|
2668
|
-
const [janitorModel, setJanitorModel] =
|
|
2669
|
-
const [isInitializing, setIsInitializing] =
|
|
2670
|
-
const [apiKey, setApiKey] =
|
|
2671
|
-
const [tempKey, setTempKey] =
|
|
2672
|
-
const [activeView, setActiveView] =
|
|
2673
|
-
const [apiTier, setApiTier] =
|
|
2674
|
-
const [quotas, setQuotas] =
|
|
2675
|
-
const [inputConfig, setInputConfig] =
|
|
2676
|
-
const [systemSettings, setSystemSettings] =
|
|
2677
|
-
const [profileData, setProfileData] =
|
|
2678
|
-
const [sessionStats, setSessionStats] =
|
|
2679
|
-
const [sessionAgentCalls, setSessionAgentCalls] =
|
|
2680
|
-
const [sessionBackgroundCalls, setSessionBackgroundCalls] =
|
|
2681
|
-
const [sessionTotalTokens, setSessionTotalTokens] =
|
|
2682
|
-
const [dailyUsage, setDailyUsage] =
|
|
2683
|
-
const [chatId, setChatId] =
|
|
2684
|
-
const [activeCommand, setActiveCommand] =
|
|
2685
|
-
const [execOutput, setExecOutput] =
|
|
2686
|
-
const [isTerminalFocused, setIsTerminalFocused] =
|
|
2788
|
+
const [thinkingLevel, setThinkingLevel] = useState7("Medium");
|
|
2789
|
+
const [latestVer, setLatestVer] = useState7(null);
|
|
2790
|
+
const [showFullThinking, setShowFullThinking] = useState7(false);
|
|
2791
|
+
const [activeModel, setActiveModel] = useState7("gemma-4-31b-it");
|
|
2792
|
+
const [janitorModel, setJanitorModel] = useState7("gemma-4-26b-a4b-it");
|
|
2793
|
+
const [isInitializing, setIsInitializing] = useState7(true);
|
|
2794
|
+
const [apiKey, setApiKey] = useState7(null);
|
|
2795
|
+
const [tempKey, setTempKey] = useState7("");
|
|
2796
|
+
const [activeView, setActiveView] = useState7("chat");
|
|
2797
|
+
const [apiTier, setApiTier] = useState7("Free");
|
|
2798
|
+
const [quotas, setQuotas] = useState7({ agentLimit: 1500, backgroundLimit: 1500, searchLimit: 100, customModelId: "", customLimit: 0 });
|
|
2799
|
+
const [inputConfig, setInputConfig] = useState7(null);
|
|
2800
|
+
const [systemSettings, setSystemSettings] = useState7({ memory: true, compression: 0, autoExec: false, autoDeleteHistory: "7d", autoUpdate: false, updateManager: "npm", customUpdateCommand: "" });
|
|
2801
|
+
const [profileData, setProfileData] = useState7({ name: null, nickname: null, instructions: null });
|
|
2802
|
+
const [sessionStats, setSessionStats] = useState7({ tokens: 0 });
|
|
2803
|
+
const [sessionAgentCalls, setSessionAgentCalls] = useState7(0);
|
|
2804
|
+
const [sessionBackgroundCalls, setSessionBackgroundCalls] = useState7(0);
|
|
2805
|
+
const [sessionTotalTokens, setSessionTotalTokens] = useState7(0);
|
|
2806
|
+
const [dailyUsage, setDailyUsage] = useState7(null);
|
|
2807
|
+
const [chatId, setChatId] = useState7(generateChatId());
|
|
2808
|
+
const [activeCommand, setActiveCommand] = useState7(null);
|
|
2809
|
+
const [execOutput, setExecOutput] = useState7("");
|
|
2810
|
+
const [isTerminalFocused, setIsTerminalFocused] = useState7(false);
|
|
2687
2811
|
const terminalEnv = useMemo(() => {
|
|
2688
2812
|
const isIDE = process.env.TERM_PROGRAM === "vscode" || !!process.env.VSC_TERMINAL_URL || !!process.env.INTELLIJ_TERMINAL_COMMAND_BLOCKS;
|
|
2689
2813
|
return {
|
|
@@ -2691,17 +2815,17 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2691
2815
|
shortcut: isIDE ? "Shift + Enter" : "Ctrl + Enter"
|
|
2692
2816
|
};
|
|
2693
2817
|
}, []);
|
|
2694
|
-
const activeCommandRef =
|
|
2695
|
-
const execOutputRef =
|
|
2696
|
-
|
|
2818
|
+
const activeCommandRef = useRef2(null);
|
|
2819
|
+
const execOutputRef = useRef2("");
|
|
2820
|
+
useEffect5(() => {
|
|
2697
2821
|
activeCommandRef.current = activeCommand;
|
|
2698
2822
|
}, [activeCommand]);
|
|
2699
|
-
|
|
2823
|
+
useEffect5(() => {
|
|
2700
2824
|
execOutputRef.current = execOutput;
|
|
2701
2825
|
}, [execOutput]);
|
|
2702
|
-
const [autoAcceptWrites, setAutoAcceptWrites] =
|
|
2703
|
-
const [pendingApproval, setPendingApproval] =
|
|
2704
|
-
const [pendingAsk, setPendingAsk] =
|
|
2826
|
+
const [autoAcceptWrites, setAutoAcceptWrites] = useState7(false);
|
|
2827
|
+
const [pendingApproval, setPendingApproval] = useState7(null);
|
|
2828
|
+
const [pendingAsk, setPendingAsk] = useState7(null);
|
|
2705
2829
|
const formatDuration = (totalSecs) => {
|
|
2706
2830
|
const h = Math.floor(totalSecs / 3600);
|
|
2707
2831
|
const m = Math.floor(totalSecs % 3600 / 60);
|
|
@@ -2712,18 +2836,18 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2712
2836
|
parts.push(`${s}s`);
|
|
2713
2837
|
return parts.join(" ");
|
|
2714
2838
|
};
|
|
2715
|
-
const [statusText, setStatusText] =
|
|
2716
|
-
const [isProcessing, setIsProcessing] =
|
|
2717
|
-
const [escPressed, setEscPressed] =
|
|
2718
|
-
const [escTimer, setEscTimer] =
|
|
2719
|
-
const [queuedPrompt, setQueuedPrompt] =
|
|
2720
|
-
const [resolutionData, setResolutionData] =
|
|
2721
|
-
const [tempModelOverride, setTempModelOverride] =
|
|
2722
|
-
const [messages, setMessages] =
|
|
2839
|
+
const [statusText, setStatusText] = useState7(null);
|
|
2840
|
+
const [isProcessing, setIsProcessing] = useState7(false);
|
|
2841
|
+
const [escPressed, setEscPressed] = useState7(false);
|
|
2842
|
+
const [escTimer, setEscTimer] = useState7(null);
|
|
2843
|
+
const [queuedPrompt, setQueuedPrompt] = useState7(null);
|
|
2844
|
+
const [resolutionData, setResolutionData] = useState7(null);
|
|
2845
|
+
const [tempModelOverride, setTempModelOverride] = useState7(null);
|
|
2846
|
+
const [messages, setMessages] = useState7([
|
|
2723
2847
|
{ id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n", isMeta: true }
|
|
2724
2848
|
]);
|
|
2725
|
-
const queuedPromptRef =
|
|
2726
|
-
const [completedIndex, setCompletedIndex] =
|
|
2849
|
+
const queuedPromptRef = useRef2(null);
|
|
2850
|
+
const [completedIndex, setCompletedIndex] = useState7(1);
|
|
2727
2851
|
const windowedHistory = useMemo(() => {
|
|
2728
2852
|
const MAX_LINES = 1e3;
|
|
2729
2853
|
const width = stdout?.columns || 80;
|
|
@@ -2825,7 +2949,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2825
2949
|
setInput((prev) => prev.replace(/\\\r?$/, "").replace(/\r?$/, "") + "\n");
|
|
2826
2950
|
}
|
|
2827
2951
|
});
|
|
2828
|
-
|
|
2952
|
+
useEffect5(() => {
|
|
2829
2953
|
async function init() {
|
|
2830
2954
|
if (!checkPuppeteerReady()) {
|
|
2831
2955
|
setMessages((prev) => {
|
|
@@ -2870,7 +2994,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2870
2994
|
}
|
|
2871
2995
|
init();
|
|
2872
2996
|
}, []);
|
|
2873
|
-
|
|
2997
|
+
useEffect5(() => {
|
|
2874
2998
|
if (!isInitializing) {
|
|
2875
2999
|
saveSettings({
|
|
2876
3000
|
mode,
|
|
@@ -3214,7 +3338,7 @@ ${timestamp}` };
|
|
|
3214
3338
|
setIsExpanded(false);
|
|
3215
3339
|
try {
|
|
3216
3340
|
const cleanHistoryForAI = [...messages, userMessage].filter(
|
|
3217
|
-
(m) => m.role !== "think" && !String(m.id).startsWith("welcome")
|
|
3341
|
+
(m) => m.role !== "think" && !m.isVisualFeedback && !String(m.id).startsWith("welcome")
|
|
3218
3342
|
).map((m) => ({
|
|
3219
3343
|
...m,
|
|
3220
3344
|
text: m.fullText || m.text
|
|
@@ -3315,6 +3439,12 @@ Selection: ${val}`,
|
|
|
3315
3439
|
let inThinkMode = false;
|
|
3316
3440
|
let currentThinkId = null;
|
|
3317
3441
|
let currentAgentId = null;
|
|
3442
|
+
let inCodeBlock = false;
|
|
3443
|
+
let inToolCall = false;
|
|
3444
|
+
let thinkConsumedInTurn = false;
|
|
3445
|
+
let toolCallEncounteredInTurn = false;
|
|
3446
|
+
let toolCallBalance = 0;
|
|
3447
|
+
let inToolCallString = null;
|
|
3318
3448
|
const signalRegex = /\[?_DISABLED_SIGNAL_REGEX_\]?/gi;
|
|
3319
3449
|
for await (const packet of stream) {
|
|
3320
3450
|
if (packet.type === "status") {
|
|
@@ -3329,6 +3459,9 @@ Selection: ${val}`,
|
|
|
3329
3459
|
currentThinkId = null;
|
|
3330
3460
|
currentAgentId = null;
|
|
3331
3461
|
inThinkMode = false;
|
|
3462
|
+
inCodeBlock = false;
|
|
3463
|
+
inToolCall = false;
|
|
3464
|
+
toolCallEncounteredInTurn = false;
|
|
3332
3465
|
continue;
|
|
3333
3466
|
}
|
|
3334
3467
|
if (packet.type === "memory_updated") {
|
|
@@ -3341,6 +3474,15 @@ Selection: ${val}`,
|
|
|
3341
3474
|
});
|
|
3342
3475
|
continue;
|
|
3343
3476
|
}
|
|
3477
|
+
if (packet.type === "visual_feedback") {
|
|
3478
|
+
setMessages((prev) => [...prev, {
|
|
3479
|
+
id: "feedback-" + Date.now(),
|
|
3480
|
+
role: "system",
|
|
3481
|
+
text: packet.content,
|
|
3482
|
+
isVisualFeedback: true
|
|
3483
|
+
}]);
|
|
3484
|
+
continue;
|
|
3485
|
+
}
|
|
3344
3486
|
if (packet.type === "exec_start") {
|
|
3345
3487
|
continue;
|
|
3346
3488
|
}
|
|
@@ -3372,23 +3514,50 @@ Selection: ${val}`,
|
|
|
3372
3514
|
continue;
|
|
3373
3515
|
}
|
|
3374
3516
|
let chunkText = packet.content;
|
|
3375
|
-
|
|
3517
|
+
const chunkLower = chunkText.toLowerCase();
|
|
3518
|
+
if (chunkText.includes("```")) inCodeBlock = !inCodeBlock;
|
|
3519
|
+
if (chunkLower.includes("tool:functions.")) {
|
|
3520
|
+
inToolCall = true;
|
|
3521
|
+
toolCallBalance = 0;
|
|
3522
|
+
inToolCallString = null;
|
|
3523
|
+
}
|
|
3524
|
+
if (inToolCall) {
|
|
3525
|
+
for (let j = 0; j < chunkText.length; j++) {
|
|
3526
|
+
const char = chunkText[j];
|
|
3527
|
+
if (!inToolCallString && (char === "'" || char === '"' || char === "`")) {
|
|
3528
|
+
inToolCallString = char;
|
|
3529
|
+
} else if (inToolCallString && char === inToolCallString && chunkText[j - 1] !== "\\") {
|
|
3530
|
+
inToolCallString = null;
|
|
3531
|
+
}
|
|
3532
|
+
if (!inToolCallString) {
|
|
3533
|
+
if (char === "(") toolCallBalance++;
|
|
3534
|
+
else if (char === ")") toolCallBalance--;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
if (toolCallBalance <= 0 && !inToolCallString) {
|
|
3538
|
+
inToolCall = false;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
const hasThinkTag = chunkLower.includes("<think") || chunkLower.includes("<thought");
|
|
3542
|
+
const canThink = !inThinkMode && !inCodeBlock && !inToolCall && !thinkConsumedInTurn;
|
|
3543
|
+
if (hasThinkTag && canThink) {
|
|
3376
3544
|
inThinkMode = true;
|
|
3377
|
-
|
|
3545
|
+
thinkConsumedInTurn = true;
|
|
3546
|
+
chunkText = chunkText.replace(/<(think|thought)>[\s\S]*?<\/(think|thought)>/gi, "").replace(/<(think|thought)>/gi, "");
|
|
3378
3547
|
currentThinkId = "think-" + Date.now();
|
|
3379
|
-
setMessages((prev) => [...prev, { id: currentThinkId, role: "think", text: "" }]);
|
|
3548
|
+
setMessages((prev) => [...prev, { id: currentThinkId, role: "think", text: "", isStreaming: true }]);
|
|
3380
3549
|
}
|
|
3381
|
-
if (
|
|
3550
|
+
if (chunkLower.includes("</think>") || chunkLower.includes("</thought>")) {
|
|
3382
3551
|
const parts = chunkText.split(/<\/(think|thought)>/gi);
|
|
3383
3552
|
const thinkPart = parts[0] || "";
|
|
3384
|
-
const agentPart = parts.slice(2).join("")
|
|
3553
|
+
const agentPart = parts.slice(2).join("").replace(/<\/?(think|thought)>/gi, "");
|
|
3385
3554
|
setMessages((prev) => {
|
|
3386
3555
|
const newMsgs = prev.map(
|
|
3387
|
-
(m) => m.id === currentThinkId ? { ...m, text: m.text + thinkPart } : m
|
|
3556
|
+
(m) => m.id === currentThinkId ? { ...m, text: m.text + thinkPart, isStreaming: true } : m
|
|
3388
3557
|
);
|
|
3389
3558
|
inThinkMode = false;
|
|
3390
3559
|
currentAgentId = "agent-" + Date.now();
|
|
3391
|
-
return [...newMsgs, { id: currentAgentId, role: "agent", text: agentPart
|
|
3560
|
+
return [...newMsgs, { id: currentAgentId, role: "agent", text: agentPart, isStreaming: true }];
|
|
3392
3561
|
});
|
|
3393
3562
|
continue;
|
|
3394
3563
|
}
|
|
@@ -3405,26 +3574,33 @@ Selection: ${val}`,
|
|
|
3405
3574
|
transitionContent = parts.slice(1).join("</think>") || "";
|
|
3406
3575
|
return { ...m, text: parts[0] };
|
|
3407
3576
|
}
|
|
3408
|
-
return { ...m, text: newText };
|
|
3577
|
+
return { ...m, text: newText, isStreaming: true };
|
|
3409
3578
|
}
|
|
3410
3579
|
return m;
|
|
3411
3580
|
});
|
|
3412
3581
|
if (transitioning) {
|
|
3413
3582
|
inThinkMode = false;
|
|
3414
3583
|
currentAgentId = "agent-" + Date.now();
|
|
3415
|
-
return [...newMsgs, { id: currentAgentId, role: "agent", text: transitionContent.replace(/<\/?think>/gi, "") }];
|
|
3584
|
+
return [...newMsgs, { id: currentAgentId, role: "agent", text: transitionContent.replace(/<\/?(think|thought)>/gi, ""), isStreaming: true }];
|
|
3416
3585
|
}
|
|
3417
3586
|
return newMsgs;
|
|
3418
3587
|
});
|
|
3419
|
-
} else if (!inThinkMode) {
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
)
|
|
3588
|
+
} else if (!inThinkMode && !toolCallEncounteredInTurn) {
|
|
3589
|
+
let cleanedText = chunkText.replace(/<(think|thought)>[\s\S]*?<\/(think|thought)>/gi, "").replace(/<\/?(think|thought)>/gi, "").replace(signalRegex, "");
|
|
3590
|
+
const toolIdx = cleanedText.toLowerCase().indexOf("tool:functions.");
|
|
3591
|
+
if (toolIdx !== -1) {
|
|
3592
|
+
cleanedText = cleanedText.substring(0, toolIdx);
|
|
3593
|
+
toolCallEncounteredInTurn = true;
|
|
3594
|
+
}
|
|
3595
|
+
if (cleanedText) {
|
|
3596
|
+
if (!currentAgentId) {
|
|
3597
|
+
currentAgentId = "agent-" + Date.now();
|
|
3598
|
+
setMessages((prev) => [...prev, { id: currentAgentId, role: "agent", text: cleanedText, isStreaming: true }]);
|
|
3599
|
+
} else {
|
|
3600
|
+
setMessages((prev) => prev.map(
|
|
3601
|
+
(m) => m.id === currentAgentId ? { ...m, text: m.text + cleanedText, isStreaming: true } : m
|
|
3602
|
+
));
|
|
3603
|
+
}
|
|
3428
3604
|
}
|
|
3429
3605
|
}
|
|
3430
3606
|
}
|
|
@@ -3452,10 +3628,11 @@ Selection: ${val}`,
|
|
|
3452
3628
|
setActiveView("resolution");
|
|
3453
3629
|
}
|
|
3454
3630
|
setMessages((prev) => {
|
|
3455
|
-
const
|
|
3631
|
+
const newMsgs = prev.map((m) => m.isStreaming ? { ...m, isStreaming: false } : m);
|
|
3632
|
+
const historyToSave = newMsgs.filter((m) => !String(m.id).startsWith("welcome") && !m.isVisualFeedback);
|
|
3456
3633
|
saveChat(chatId, null, historyToSave);
|
|
3457
|
-
setCompletedIndex(
|
|
3458
|
-
return
|
|
3634
|
+
setCompletedIndex(newMsgs.length);
|
|
3635
|
+
return newMsgs;
|
|
3459
3636
|
});
|
|
3460
3637
|
}
|
|
3461
3638
|
};
|
|
@@ -3483,7 +3660,7 @@ Selection: ${val}`,
|
|
|
3483
3660
|
}
|
|
3484
3661
|
return [];
|
|
3485
3662
|
}, [input]);
|
|
3486
|
-
|
|
3663
|
+
useEffect5(() => {
|
|
3487
3664
|
setSelectedIndex(0);
|
|
3488
3665
|
}, [suggestions]);
|
|
3489
3666
|
const renderActiveView = () => {
|
|
@@ -4121,7 +4298,7 @@ var init_app = __esm({
|
|
|
4121
4298
|
init_setup();
|
|
4122
4299
|
SESSION_START_TIME = Date.now();
|
|
4123
4300
|
CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
|
|
4124
|
-
versionFluxflow = "1.
|
|
4301
|
+
versionFluxflow = "1.6.0";
|
|
4125
4302
|
updatedOn = "2026-05-02";
|
|
4126
4303
|
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(
|
|
4127
4304
|
CommandMenu,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluxflow-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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:nanoid --external:puppeteer --external:typescript"
|
|
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:nanoid --external:puppeteer --external:pdf-lib --external:typescript"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@google/genai": "^1.50.1",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"ink-spinner": "^5.0.0",
|
|
48
48
|
"ink-text-input": "^6.0.0",
|
|
49
49
|
"nanoid": "^5.1.9",
|
|
50
|
+
"pdf-lib": "^1.17.1",
|
|
50
51
|
"puppeteer": "^24.42.0",
|
|
51
52
|
"react": "^19.2.5",
|
|
52
53
|
"zod": "^4.3.6"
|