fluxflow-cli 1.5.4 → 1.6.1
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 +310 -157
- 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;
|
|
@@ -94,7 +136,7 @@ var init_ChatLayout = __esm({
|
|
|
94
136
|
if (content.startsWith("\\text{") && content.endsWith("}")) {
|
|
95
137
|
content = content.slice(6, -1);
|
|
96
138
|
}
|
|
97
|
-
const mathContent = content.replace(/\\multiply/g, "\xD7").replace(/\\
|
|
139
|
+
const mathContent = content.replace(/\\multiply/g, "\xD7").replace(/\\mul/g, "\xD7").replace(/\\div/g, "\xF7");
|
|
98
140
|
return /* @__PURE__ */ React2.createElement(Text2, { key: j, color: "white", backgroundColor: "#4c0099", bold: true, italic: true }, " ", mathContent, " ");
|
|
99
141
|
}
|
|
100
142
|
if (part.startsWith("[") && (part.includes("](") || part.includes("] ("))) {
|
|
@@ -293,7 +335,11 @@ var init_ChatLayout = __esm({
|
|
|
293
335
|
const outputList = outputMatch ? outputMatch[1] : "";
|
|
294
336
|
return /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(TerminalBox, { command: cmd, output: outputList, completed: true }));
|
|
295
337
|
}
|
|
338
|
+
const [animationDone, setAnimationDone] = React2.useState(!msg.isStreaming);
|
|
296
339
|
const content = React2.useMemo(() => cleanSignals(msg.text), [msg.text]);
|
|
340
|
+
React2.useEffect(() => {
|
|
341
|
+
if (msg.isStreaming) setAnimationDone(false);
|
|
342
|
+
}, [msg.id]);
|
|
297
343
|
const finalContent = React2.useMemo(() => {
|
|
298
344
|
if (msg.role === "think" && !showFullThinking) {
|
|
299
345
|
const lines = content.split("\n").filter((line) => {
|
|
@@ -320,7 +366,24 @@ var init_ChatLayout = __esm({
|
|
|
320
366
|
finalContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\n/g, "\n").replace(/\\$/, ""),
|
|
321
367
|
columns - 6
|
|
322
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))))
|
|
323
|
-
) : 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]"))));
|
|
324
387
|
});
|
|
325
388
|
ChatLayout = React2.memo(({ messages, showFullThinking, columns = 80 }) => {
|
|
326
389
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", width: "100%" }, messages.map((msg, idx) => /* @__PURE__ */ React2.createElement(
|
|
@@ -392,13 +455,13 @@ var init_CommandMenu = __esm({
|
|
|
392
455
|
});
|
|
393
456
|
|
|
394
457
|
// src/components/ProfileForm.jsx
|
|
395
|
-
import React5, { useState } from "react";
|
|
458
|
+
import React5, { useState as useState2 } from "react";
|
|
396
459
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
397
460
|
import TextInput from "ink-text-input";
|
|
398
461
|
function ProfileForm({ onSave, onCancel }) {
|
|
399
|
-
const [step, setStep] =
|
|
400
|
-
const [currentInput, setCurrentInput] =
|
|
401
|
-
const [profile, setProfile] =
|
|
462
|
+
const [step, setStep] = useState2(0);
|
|
463
|
+
const [currentInput, setCurrentInput] = useState2("");
|
|
464
|
+
const [profile, setProfile] = useState2({ name: "", nickname: "", instructions: "" });
|
|
402
465
|
const steps = [
|
|
403
466
|
{ key: "name", label: "Enter your Name: " },
|
|
404
467
|
{ key: "nickname", label: "Enter a Nickname (Agent will use this): " },
|
|
@@ -430,16 +493,16 @@ var init_ProfileForm = __esm({
|
|
|
430
493
|
});
|
|
431
494
|
|
|
432
495
|
// src/components/AskUserModal.jsx
|
|
433
|
-
import React6, { useState as
|
|
496
|
+
import React6, { useState as useState3 } from "react";
|
|
434
497
|
import { Box as Box6, Text as Text6, useInput } from "ink";
|
|
435
498
|
import TextInput2 from "ink-text-input";
|
|
436
499
|
var AskUserModal, AskUserModal_default;
|
|
437
500
|
var init_AskUserModal = __esm({
|
|
438
501
|
"src/components/AskUserModal.jsx"() {
|
|
439
502
|
AskUserModal = ({ question, options, onResolve }) => {
|
|
440
|
-
const [isSuggestingElse, setIsSuggestingElse] =
|
|
441
|
-
const [customInput, setCustomInput] =
|
|
442
|
-
const [selectedIndex, setSelectedIndex] =
|
|
503
|
+
const [isSuggestingElse, setIsSuggestingElse] = useState3(false);
|
|
504
|
+
const [customInput, setCustomInput] = useState3("");
|
|
505
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
443
506
|
const allOptions = [...options, { id: "CUSTOM", label: "Suggest something else...", description: "Provide a custom response" }];
|
|
444
507
|
useInput((input, key) => {
|
|
445
508
|
if (isSuggestingElse) return;
|
|
@@ -618,7 +681,7 @@ ${mode === "Flux" ? `
|
|
|
618
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.
|
|
619
682
|
7. Execution: tool:functions.exec_command(command="terminal command"). Runs a shell command.
|
|
620
683
|
|
|
621
|
-
**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() : `
|
|
622
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()}
|
|
623
686
|
-----------------
|
|
624
687
|
Results will be provided in the next loop as: [TOOL_RESULT]: [content]
|
|
@@ -960,16 +1023,16 @@ var init_arg_parser = __esm({
|
|
|
960
1023
|
"src/utils/arg_parser.js"() {
|
|
961
1024
|
parseArgs = (argsString) => {
|
|
962
1025
|
const args = {};
|
|
963
|
-
const regex = /(\w+)\s*=\s*(?:(["'])((?:\\.|(?!\2)[\s\S])*)\2|([^,\s\)]+))/g;
|
|
1026
|
+
const regex = /(\w+)\s*=\s*(?:(["'`])((?:\\.|(?!\2)[\s\S])*)\2|([^,\s\)]+))/g;
|
|
964
1027
|
let match;
|
|
965
1028
|
while ((match = regex.exec(argsString)) !== null) {
|
|
966
1029
|
const key = match[1];
|
|
967
1030
|
let value = match[3] !== void 0 ? match[3] : match[4];
|
|
968
1031
|
if (match[3] !== void 0) {
|
|
969
1032
|
try {
|
|
970
|
-
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")}"`);
|
|
971
1034
|
} catch (e) {
|
|
972
|
-
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
|
|
1035
|
+
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\`/g, "`").replace(/\\\\/g, "\\");
|
|
973
1036
|
}
|
|
974
1037
|
}
|
|
975
1038
|
if (value === "true") value = true;
|
|
@@ -1366,7 +1429,11 @@ var init_view_file = __esm({
|
|
|
1366
1429
|
}
|
|
1367
1430
|
};
|
|
1368
1431
|
}
|
|
1369
|
-
|
|
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");
|
|
1370
1437
|
const lines = content.split("\n");
|
|
1371
1438
|
const totalLines = lines.length;
|
|
1372
1439
|
const start = Math.max(0, start_line - 1);
|
|
@@ -1395,7 +1462,7 @@ var init_write_file = __esm({
|
|
|
1395
1462
|
let { path: targetPath, content } = parseArgs(args);
|
|
1396
1463
|
if (!targetPath) return 'ERROR: Missing "path" argument for write_file.';
|
|
1397
1464
|
if (content === void 0) return 'ERROR: Missing "content" argument for write_file.';
|
|
1398
|
-
content = content.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").
|
|
1465
|
+
content = content.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1399
1466
|
const absolutePath = path10.resolve(process.cwd(), targetPath);
|
|
1400
1467
|
const parentDir = path10.dirname(absolutePath);
|
|
1401
1468
|
try {
|
|
@@ -1465,7 +1532,7 @@ var init_update_file = __esm({
|
|
|
1465
1532
|
if (!targetPath) return 'ERROR: Missing "path" argument for update_file.';
|
|
1466
1533
|
if (content_to_replace === void 0) return 'ERROR: Missing "content_to_replace" argument.';
|
|
1467
1534
|
if (content_to_add === void 0) return 'ERROR: Missing "content_to_add" argument.';
|
|
1468
|
-
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");
|
|
1469
1536
|
content_to_replace = strip(content_to_replace);
|
|
1470
1537
|
content_to_add = strip(content_to_add);
|
|
1471
1538
|
const absolutePath = path11.resolve(process.cwd(), targetPath);
|
|
@@ -1473,9 +1540,23 @@ var init_update_file = __esm({
|
|
|
1473
1540
|
if (!fs11.existsSync(absolutePath)) {
|
|
1474
1541
|
return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
|
|
1475
1542
|
}
|
|
1476
|
-
|
|
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;
|
|
1477
1553
|
if (!currentContent.includes(content_to_replace)) {
|
|
1478
|
-
|
|
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.`;
|
|
1479
1560
|
}
|
|
1480
1561
|
const startPos = currentContent.indexOf(content_to_replace);
|
|
1481
1562
|
const startLine = currentContent.substring(0, startPos).split(/\r?\n/).length;
|
|
@@ -1715,12 +1796,18 @@ var init_ask_user = __esm({
|
|
|
1715
1796
|
import puppeteer3 from "puppeteer";
|
|
1716
1797
|
import path13 from "path";
|
|
1717
1798
|
import fs13 from "fs-extra";
|
|
1799
|
+
import { PDFDocument } from "pdf-lib";
|
|
1718
1800
|
var write_pdf;
|
|
1719
1801
|
var init_write_pdf = __esm({
|
|
1720
1802
|
"src/tools/write_pdf.js"() {
|
|
1721
1803
|
init_arg_parser();
|
|
1722
1804
|
write_pdf = async (args) => {
|
|
1723
|
-
const {
|
|
1805
|
+
const {
|
|
1806
|
+
path: targetPath,
|
|
1807
|
+
content,
|
|
1808
|
+
orientation = "portrait",
|
|
1809
|
+
margin = "10px"
|
|
1810
|
+
} = parseArgs(args);
|
|
1724
1811
|
if (!targetPath) return 'ERROR: Missing "path" argument for write_pdf.';
|
|
1725
1812
|
if (!content) return 'ERROR: Missing "content" (HTML/CSS) for write_pdf.';
|
|
1726
1813
|
const absolutePath = path13.resolve(process.cwd(), targetPath);
|
|
@@ -1757,7 +1844,7 @@ var init_write_pdf = __esm({
|
|
|
1757
1844
|
font-weight: bold;
|
|
1758
1845
|
color: rgba(0, 0, 0, 0.005);
|
|
1759
1846
|
pointer-events: none;
|
|
1760
|
-
z-index: 1000;
|
|
1847
|
+
z-index: -1000;
|
|
1761
1848
|
text-align: center;
|
|
1762
1849
|
width: 150%;
|
|
1763
1850
|
white-space: nowrap;
|
|
@@ -1769,8 +1856,7 @@ var init_write_pdf = __esm({
|
|
|
1769
1856
|
${content}
|
|
1770
1857
|
`;
|
|
1771
1858
|
await page.setContent(styledContent, { waitUntil: "networkidle0" });
|
|
1772
|
-
await page.pdf({
|
|
1773
|
-
path: absolutePath,
|
|
1859
|
+
const pdfBytes = await page.pdf({
|
|
1774
1860
|
format: "A4",
|
|
1775
1861
|
landscape: orientation.toLowerCase() === "landscape",
|
|
1776
1862
|
margin: {
|
|
@@ -1781,6 +1867,16 @@ var init_write_pdf = __esm({
|
|
|
1781
1867
|
},
|
|
1782
1868
|
printBackground: true
|
|
1783
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);
|
|
1784
1880
|
const stats = await fs13.stat(absolutePath);
|
|
1785
1881
|
return `SUCCESS: PDF generated successfully at [${targetPath}] (${(stats.size / 1024).toFixed(2)} KB).`;
|
|
1786
1882
|
} catch (err) {
|
|
@@ -1880,39 +1976,47 @@ var init_ai = __esm({
|
|
|
1880
1976
|
};
|
|
1881
1977
|
detectToolCalls = (text) => {
|
|
1882
1978
|
const results = [];
|
|
1883
|
-
const toolRegex = /(?:\[?\s*tool:functions\.)([a-z0-9_]+)\s*\(
|
|
1979
|
+
const toolRegex = /(?:\[?\s*tool:functions\.)([a-z0-9_]+)\s*\(/gi;
|
|
1884
1980
|
let match;
|
|
1885
1981
|
while ((match = toolRegex.exec(text)) !== null) {
|
|
1886
|
-
const fullMatch = match[0];
|
|
1887
1982
|
const toolName = match[1];
|
|
1888
|
-
const
|
|
1889
|
-
let
|
|
1890
|
-
let
|
|
1891
|
-
let
|
|
1892
|
-
let
|
|
1893
|
-
|
|
1894
|
-
const
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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--;
|
|
1900
1999
|
if (balance === 0) {
|
|
1901
2000
|
endIdx = i;
|
|
1902
2001
|
break;
|
|
1903
2002
|
}
|
|
1904
2003
|
}
|
|
1905
|
-
if (
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2004
|
+
if (char === "\\") {
|
|
2005
|
+
isEscaped = !isEscaped;
|
|
2006
|
+
} else {
|
|
2007
|
+
isEscaped = false;
|
|
1909
2008
|
}
|
|
1910
2009
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
+
}
|
|
1916
2020
|
}
|
|
1917
2021
|
return results;
|
|
1918
2022
|
};
|
|
@@ -1948,7 +2052,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
1948
2052
|
let lastUsage = null;
|
|
1949
2053
|
const MAX_LOOPS = mode === "Flux" ? 50 : 7;
|
|
1950
2054
|
const MAX_RETRIES = 7;
|
|
1951
|
-
yield { type: "status", content: "
|
|
2055
|
+
yield { type: "status", content: "Connecting..." };
|
|
1952
2056
|
TERMINATION_SIGNAL = false;
|
|
1953
2057
|
let fullAgentResponseChunks = [];
|
|
1954
2058
|
modifiedHistory.forEach((msg) => {
|
|
@@ -2051,6 +2155,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
2051
2155
|
lastUsage = chunk.usageMetadata;
|
|
2052
2156
|
if (lastUsage) {
|
|
2053
2157
|
yield { type: "liveTokens", content: lastUsage.totalTokenCount };
|
|
2158
|
+
yield { type: "status", content: "Working..." };
|
|
2054
2159
|
}
|
|
2055
2160
|
}
|
|
2056
2161
|
await incrementUsage("agent");
|
|
@@ -2058,7 +2163,11 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
2058
2163
|
yield { type: "usage", content: lastUsage };
|
|
2059
2164
|
}
|
|
2060
2165
|
fullAgentResponseChunks.push(turnText);
|
|
2061
|
-
|
|
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
|
+
}
|
|
2062
2171
|
const turnTextLower = textToProcess.toLowerCase();
|
|
2063
2172
|
const hasFinish = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(turnTextLower);
|
|
2064
2173
|
const toolCalls = detectToolCalls(textToProcess);
|
|
@@ -2419,13 +2528,13 @@ var init_settings = __esm({
|
|
|
2419
2528
|
});
|
|
2420
2529
|
|
|
2421
2530
|
// src/components/ResumeModal.jsx
|
|
2422
|
-
import React7, { useState as
|
|
2531
|
+
import React7, { useState as useState4, useEffect as useEffect2 } from "react";
|
|
2423
2532
|
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
2424
2533
|
function ResumeModal({ onSelect, onDelete, onClose }) {
|
|
2425
|
-
const [history, setHistory] =
|
|
2426
|
-
const [keys, setKeys] =
|
|
2427
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2428
|
-
|
|
2534
|
+
const [history, setHistory] = useState4({});
|
|
2535
|
+
const [keys, setKeys] = useState4([]);
|
|
2536
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
2537
|
+
useEffect2(() => {
|
|
2429
2538
|
const fetchHistory = async () => {
|
|
2430
2539
|
const h = await loadHistory();
|
|
2431
2540
|
setHistory(h);
|
|
@@ -2462,16 +2571,16 @@ var init_ResumeModal = __esm({
|
|
|
2462
2571
|
});
|
|
2463
2572
|
|
|
2464
2573
|
// src/components/MemoryModal.jsx
|
|
2465
|
-
import React8, { useState as
|
|
2574
|
+
import React8, { useState as useState5, useEffect as useEffect3 } from "react";
|
|
2466
2575
|
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
2467
2576
|
function MemoryModal({ onClose }) {
|
|
2468
|
-
const [memories, setMemories] =
|
|
2469
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2577
|
+
const [memories, setMemories] = useState5([]);
|
|
2578
|
+
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
2470
2579
|
const loadMemories = () => {
|
|
2471
2580
|
const data = readEncryptedJson(MEMORIES_FILE, []);
|
|
2472
2581
|
setMemories(data);
|
|
2473
2582
|
};
|
|
2474
|
-
|
|
2583
|
+
useEffect3(() => {
|
|
2475
2584
|
loadMemories();
|
|
2476
2585
|
}, []);
|
|
2477
2586
|
useInput3((input, key) => {
|
|
@@ -2501,7 +2610,7 @@ var init_MemoryModal = __esm({
|
|
|
2501
2610
|
});
|
|
2502
2611
|
|
|
2503
2612
|
// src/components/UpdateProcessor.jsx
|
|
2504
|
-
import React9, { useState as
|
|
2613
|
+
import React9, { useState as useState6, useEffect as useEffect4 } from "react";
|
|
2505
2614
|
import { Box as Box9, Text as Text9 } from "ink";
|
|
2506
2615
|
import Spinner from "ink-spinner";
|
|
2507
2616
|
import { exec } from "child_process";
|
|
@@ -2509,10 +2618,10 @@ var UpdateProcessor, UpdateProcessor_default;
|
|
|
2509
2618
|
var init_UpdateProcessor = __esm({
|
|
2510
2619
|
"src/components/UpdateProcessor.jsx"() {
|
|
2511
2620
|
UpdateProcessor = ({ latest, current, settings, onClose, onUpdateSettings, onSuccess }) => {
|
|
2512
|
-
const [status, setStatus] =
|
|
2513
|
-
const [log, setLog] =
|
|
2514
|
-
const [error, setError] =
|
|
2515
|
-
|
|
2621
|
+
const [status, setStatus] = useState6("initializing");
|
|
2622
|
+
const [log, setLog] = useState6("");
|
|
2623
|
+
const [error, setError] = useState6(null);
|
|
2624
|
+
useEffect4(() => {
|
|
2516
2625
|
const runUpdate = async () => {
|
|
2517
2626
|
const manager = settings.updateManager || "npm";
|
|
2518
2627
|
if (!settings.updateManager) {
|
|
@@ -2599,7 +2708,7 @@ var app_exports = {};
|
|
|
2599
2708
|
__export(app_exports, {
|
|
2600
2709
|
default: () => App
|
|
2601
2710
|
});
|
|
2602
|
-
import React10, { useState as
|
|
2711
|
+
import React10, { useState as useState7, useEffect as useEffect5, useRef as useRef2, useMemo } from "react";
|
|
2603
2712
|
import { Box as Box10, Text as Text10, useInput as useInput4, useStdout } from "ink";
|
|
2604
2713
|
import Spinner2 from "ink-spinner";
|
|
2605
2714
|
import fs17 from "fs-extra";
|
|
@@ -2609,14 +2718,14 @@ import TextInput3 from "ink-text-input";
|
|
|
2609
2718
|
import gradient from "gradient-string";
|
|
2610
2719
|
function App() {
|
|
2611
2720
|
const { stdout } = useStdout();
|
|
2612
|
-
const [input, setInput] =
|
|
2613
|
-
const [isExpanded, setIsExpanded] =
|
|
2614
|
-
const [mode, setMode] =
|
|
2615
|
-
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({
|
|
2616
2725
|
columns: stdout?.columns || 80,
|
|
2617
2726
|
rows: stdout?.rows || 24
|
|
2618
2727
|
});
|
|
2619
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2728
|
+
const [selectedIndex, setSelectedIndex] = useState7(0);
|
|
2620
2729
|
const performVersionCheck = async (manual = false, settingsOverride = null) => {
|
|
2621
2730
|
const settingsToUse = settingsOverride || systemSettings;
|
|
2622
2731
|
if (manual) {
|
|
@@ -2640,7 +2749,7 @@ function App() {
|
|
|
2640
2749
|
id: "update-" + Date.now(),
|
|
2641
2750
|
role: "system",
|
|
2642
2751
|
text: `\u{1F680} **New version 'v${latestVersion}' is available!**
|
|
2643
|
-
Type \`/update\` to upgrade immediately.
|
|
2752
|
+
Type \`/update latest\` to upgrade immediately.
|
|
2644
2753
|
Check what's new using \`/changelog\` command.`,
|
|
2645
2754
|
isUpdateNotification: true,
|
|
2646
2755
|
isMeta: true
|
|
@@ -2663,7 +2772,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2663
2772
|
}
|
|
2664
2773
|
}
|
|
2665
2774
|
};
|
|
2666
|
-
|
|
2775
|
+
useEffect5(() => {
|
|
2667
2776
|
const handleResize = () => {
|
|
2668
2777
|
stdout.write("\x1Bc");
|
|
2669
2778
|
setTerminalSize({
|
|
@@ -2676,29 +2785,29 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2676
2785
|
stdout.off("resize", handleResize);
|
|
2677
2786
|
};
|
|
2678
2787
|
}, [stdout]);
|
|
2679
|
-
const [thinkingLevel, setThinkingLevel] =
|
|
2680
|
-
const [latestVer, setLatestVer] =
|
|
2681
|
-
const [showFullThinking, setShowFullThinking] =
|
|
2682
|
-
const [activeModel, setActiveModel] =
|
|
2683
|
-
const [janitorModel, setJanitorModel] =
|
|
2684
|
-
const [isInitializing, setIsInitializing] =
|
|
2685
|
-
const [apiKey, setApiKey] =
|
|
2686
|
-
const [tempKey, setTempKey] =
|
|
2687
|
-
const [activeView, setActiveView] =
|
|
2688
|
-
const [apiTier, setApiTier] =
|
|
2689
|
-
const [quotas, setQuotas] =
|
|
2690
|
-
const [inputConfig, setInputConfig] =
|
|
2691
|
-
const [systemSettings, setSystemSettings] =
|
|
2692
|
-
const [profileData, setProfileData] =
|
|
2693
|
-
const [sessionStats, setSessionStats] =
|
|
2694
|
-
const [sessionAgentCalls, setSessionAgentCalls] =
|
|
2695
|
-
const [sessionBackgroundCalls, setSessionBackgroundCalls] =
|
|
2696
|
-
const [sessionTotalTokens, setSessionTotalTokens] =
|
|
2697
|
-
const [dailyUsage, setDailyUsage] =
|
|
2698
|
-
const [chatId, setChatId] =
|
|
2699
|
-
const [activeCommand, setActiveCommand] =
|
|
2700
|
-
const [execOutput, setExecOutput] =
|
|
2701
|
-
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);
|
|
2702
2811
|
const terminalEnv = useMemo(() => {
|
|
2703
2812
|
const isIDE = process.env.TERM_PROGRAM === "vscode" || !!process.env.VSC_TERMINAL_URL || !!process.env.INTELLIJ_TERMINAL_COMMAND_BLOCKS;
|
|
2704
2813
|
return {
|
|
@@ -2706,17 +2815,17 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2706
2815
|
shortcut: isIDE ? "Shift + Enter" : "Ctrl + Enter"
|
|
2707
2816
|
};
|
|
2708
2817
|
}, []);
|
|
2709
|
-
const activeCommandRef =
|
|
2710
|
-
const execOutputRef =
|
|
2711
|
-
|
|
2818
|
+
const activeCommandRef = useRef2(null);
|
|
2819
|
+
const execOutputRef = useRef2("");
|
|
2820
|
+
useEffect5(() => {
|
|
2712
2821
|
activeCommandRef.current = activeCommand;
|
|
2713
2822
|
}, [activeCommand]);
|
|
2714
|
-
|
|
2823
|
+
useEffect5(() => {
|
|
2715
2824
|
execOutputRef.current = execOutput;
|
|
2716
2825
|
}, [execOutput]);
|
|
2717
|
-
const [autoAcceptWrites, setAutoAcceptWrites] =
|
|
2718
|
-
const [pendingApproval, setPendingApproval] =
|
|
2719
|
-
const [pendingAsk, setPendingAsk] =
|
|
2826
|
+
const [autoAcceptWrites, setAutoAcceptWrites] = useState7(false);
|
|
2827
|
+
const [pendingApproval, setPendingApproval] = useState7(null);
|
|
2828
|
+
const [pendingAsk, setPendingAsk] = useState7(null);
|
|
2720
2829
|
const formatDuration = (totalSecs) => {
|
|
2721
2830
|
const h = Math.floor(totalSecs / 3600);
|
|
2722
2831
|
const m = Math.floor(totalSecs % 3600 / 60);
|
|
@@ -2727,18 +2836,18 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2727
2836
|
parts.push(`${s}s`);
|
|
2728
2837
|
return parts.join(" ");
|
|
2729
2838
|
};
|
|
2730
|
-
const [statusText, setStatusText] =
|
|
2731
|
-
const [isProcessing, setIsProcessing] =
|
|
2732
|
-
const [escPressed, setEscPressed] =
|
|
2733
|
-
const [escTimer, setEscTimer] =
|
|
2734
|
-
const [queuedPrompt, setQueuedPrompt] =
|
|
2735
|
-
const [resolutionData, setResolutionData] =
|
|
2736
|
-
const [tempModelOverride, setTempModelOverride] =
|
|
2737
|
-
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([
|
|
2738
2847
|
{ id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n", isMeta: true }
|
|
2739
2848
|
]);
|
|
2740
|
-
const queuedPromptRef =
|
|
2741
|
-
const [completedIndex, setCompletedIndex] =
|
|
2849
|
+
const queuedPromptRef = useRef2(null);
|
|
2850
|
+
const [completedIndex, setCompletedIndex] = useState7(1);
|
|
2742
2851
|
const windowedHistory = useMemo(() => {
|
|
2743
2852
|
const MAX_LINES = 1e3;
|
|
2744
2853
|
const width = stdout?.columns || 80;
|
|
@@ -2840,7 +2949,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2840
2949
|
setInput((prev) => prev.replace(/\\\r?$/, "").replace(/\r?$/, "") + "\n");
|
|
2841
2950
|
}
|
|
2842
2951
|
});
|
|
2843
|
-
|
|
2952
|
+
useEffect5(() => {
|
|
2844
2953
|
async function init() {
|
|
2845
2954
|
if (!checkPuppeteerReady()) {
|
|
2846
2955
|
setMessages((prev) => {
|
|
@@ -2885,7 +2994,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2885
2994
|
}
|
|
2886
2995
|
init();
|
|
2887
2996
|
}, []);
|
|
2888
|
-
|
|
2997
|
+
useEffect5(() => {
|
|
2889
2998
|
if (!isInitializing) {
|
|
2890
2999
|
saveSettings({
|
|
2891
3000
|
mode,
|
|
@@ -2944,7 +3053,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2944
3053
|
{ cmd: "/changelog", desc: "View latest updates" },
|
|
2945
3054
|
{ cmd: "/update", desc: "Check/Install updates", subs: [
|
|
2946
3055
|
{ cmd: "check", desc: "Check for new version" },
|
|
2947
|
-
{ cmd: "
|
|
3056
|
+
{ cmd: "latest", desc: "Install Latest Version" }
|
|
2948
3057
|
] }
|
|
2949
3058
|
];
|
|
2950
3059
|
const handleSubmit = (value) => {
|
|
@@ -3197,7 +3306,7 @@ ${list || "No saved chats found."}`, isMeta: true }];
|
|
|
3197
3306
|
performVersionCheck(true);
|
|
3198
3307
|
break;
|
|
3199
3308
|
}
|
|
3200
|
-
const isForce = parts.includes("--
|
|
3309
|
+
const isForce = parts.includes("--latest");
|
|
3201
3310
|
setActiveView("update");
|
|
3202
3311
|
break;
|
|
3203
3312
|
}
|
|
@@ -3330,6 +3439,12 @@ Selection: ${val}`,
|
|
|
3330
3439
|
let inThinkMode = false;
|
|
3331
3440
|
let currentThinkId = null;
|
|
3332
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;
|
|
3333
3448
|
const signalRegex = /\[?_DISABLED_SIGNAL_REGEX_\]?/gi;
|
|
3334
3449
|
for await (const packet of stream) {
|
|
3335
3450
|
if (packet.type === "status") {
|
|
@@ -3344,6 +3459,9 @@ Selection: ${val}`,
|
|
|
3344
3459
|
currentThinkId = null;
|
|
3345
3460
|
currentAgentId = null;
|
|
3346
3461
|
inThinkMode = false;
|
|
3462
|
+
inCodeBlock = false;
|
|
3463
|
+
inToolCall = false;
|
|
3464
|
+
toolCallEncounteredInTurn = false;
|
|
3347
3465
|
continue;
|
|
3348
3466
|
}
|
|
3349
3467
|
if (packet.type === "memory_updated") {
|
|
@@ -3396,23 +3514,50 @@ Selection: ${val}`,
|
|
|
3396
3514
|
continue;
|
|
3397
3515
|
}
|
|
3398
3516
|
let chunkText = packet.content;
|
|
3399
|
-
|
|
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) {
|
|
3400
3544
|
inThinkMode = true;
|
|
3401
|
-
|
|
3545
|
+
thinkConsumedInTurn = true;
|
|
3546
|
+
chunkText = chunkText.replace(/<(think|thought)>[\s\S]*?<\/(think|thought)>/gi, "").replace(/<(think|thought)>/gi, "");
|
|
3402
3547
|
currentThinkId = "think-" + Date.now();
|
|
3403
|
-
setMessages((prev) => [...prev, { id: currentThinkId, role: "think", text: "" }]);
|
|
3548
|
+
setMessages((prev) => [...prev, { id: currentThinkId, role: "think", text: "", isStreaming: true }]);
|
|
3404
3549
|
}
|
|
3405
|
-
if (
|
|
3550
|
+
if (chunkLower.includes("</think>") || chunkLower.includes("</thought>")) {
|
|
3406
3551
|
const parts = chunkText.split(/<\/(think|thought)>/gi);
|
|
3407
3552
|
const thinkPart = parts[0] || "";
|
|
3408
|
-
const agentPart = parts.slice(2).join("")
|
|
3553
|
+
const agentPart = parts.slice(2).join("").replace(/<\/?(think|thought)>/gi, "");
|
|
3409
3554
|
setMessages((prev) => {
|
|
3410
3555
|
const newMsgs = prev.map(
|
|
3411
|
-
(m) => m.id === currentThinkId ? { ...m, text: m.text + thinkPart } : m
|
|
3556
|
+
(m) => m.id === currentThinkId ? { ...m, text: m.text + thinkPart, isStreaming: true } : m
|
|
3412
3557
|
);
|
|
3413
3558
|
inThinkMode = false;
|
|
3414
3559
|
currentAgentId = "agent-" + Date.now();
|
|
3415
|
-
return [...newMsgs, { id: currentAgentId, role: "agent", text: agentPart
|
|
3560
|
+
return [...newMsgs, { id: currentAgentId, role: "agent", text: agentPart, isStreaming: true }];
|
|
3416
3561
|
});
|
|
3417
3562
|
continue;
|
|
3418
3563
|
}
|
|
@@ -3429,26 +3574,33 @@ Selection: ${val}`,
|
|
|
3429
3574
|
transitionContent = parts.slice(1).join("</think>") || "";
|
|
3430
3575
|
return { ...m, text: parts[0] };
|
|
3431
3576
|
}
|
|
3432
|
-
return { ...m, text: newText };
|
|
3577
|
+
return { ...m, text: newText, isStreaming: true };
|
|
3433
3578
|
}
|
|
3434
3579
|
return m;
|
|
3435
3580
|
});
|
|
3436
3581
|
if (transitioning) {
|
|
3437
3582
|
inThinkMode = false;
|
|
3438
3583
|
currentAgentId = "agent-" + Date.now();
|
|
3439
|
-
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 }];
|
|
3440
3585
|
}
|
|
3441
3586
|
return newMsgs;
|
|
3442
3587
|
});
|
|
3443
|
-
} else if (!inThinkMode) {
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
)
|
|
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
|
+
}
|
|
3452
3604
|
}
|
|
3453
3605
|
}
|
|
3454
3606
|
}
|
|
@@ -3476,10 +3628,11 @@ Selection: ${val}`,
|
|
|
3476
3628
|
setActiveView("resolution");
|
|
3477
3629
|
}
|
|
3478
3630
|
setMessages((prev) => {
|
|
3479
|
-
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);
|
|
3480
3633
|
saveChat(chatId, null, historyToSave);
|
|
3481
|
-
setCompletedIndex(
|
|
3482
|
-
return
|
|
3634
|
+
setCompletedIndex(newMsgs.length);
|
|
3635
|
+
return newMsgs;
|
|
3483
3636
|
});
|
|
3484
3637
|
}
|
|
3485
3638
|
};
|
|
@@ -3507,7 +3660,7 @@ Selection: ${val}`,
|
|
|
3507
3660
|
}
|
|
3508
3661
|
return [];
|
|
3509
3662
|
}, [input]);
|
|
3510
|
-
|
|
3663
|
+
useEffect5(() => {
|
|
3511
3664
|
setSelectedIndex(0);
|
|
3512
3665
|
}, [suggestions]);
|
|
3513
3666
|
const renderActiveView = () => {
|
|
@@ -4145,7 +4298,7 @@ var init_app = __esm({
|
|
|
4145
4298
|
init_setup();
|
|
4146
4299
|
SESSION_START_TIME = Date.now();
|
|
4147
4300
|
CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
|
|
4148
|
-
versionFluxflow = "1.
|
|
4301
|
+
versionFluxflow = "1.6.1";
|
|
4149
4302
|
updatedOn = "2026-05-02";
|
|
4150
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(
|
|
4151
4304
|
CommandMenu,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluxflow-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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"
|