fluxflow-cli 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -1,48 +1,48 @@
1
- # 🏛️ Architecture & Design
2
-
3
- Flux Flow is built on a modern, reactive stack that brings web-like development paradigms to the terminal. It utilizes a custom agentic loop for reasoning and a unique dual-model system for background processing.
4
-
5
- ## UI Layer: React & Ink
6
-
7
- The entire terminal interface is built using **React** via the [Ink](https://github.com/vadimdemedes/ink) renderer.
8
- - **Component-Based**: The UI is composed of isolated, reusable React components (`ChatLayout`, `StatusBar`, `CommandMenu`, `TerminalBox`, `ProfileForm`).
9
- - **Reactive State**: The application uses React hooks (`useState`, `useEffect`) to manage user input, application mode, model selection, and the terminal's resizing events.
10
- - **Zero-Render Overheads**: Critical performance trackers, like the session start time, are kept outside the React render cycle to maintain terminal responsiveness during high-speed AI text streaming.
11
-
12
- ## The Agentic Loop
13
-
14
- The core intelligence of Flux Flow resides in `src/utils/ai.js`. It does not rely on opaque third-party agent frameworks; instead, it uses a custom, highly transparent string-based protocol powered by an asynchronous generator (`async function*`). This approach allows for real-time UI updates while managing complex multi-step reasoning.
15
-
16
- The execution flow of a single user prompt follows this loop:
17
-
18
- 1. **Context Assembly**: The user's prompt is combined with the system instructions, temporary session context, persistent user memories, and the current chat history. If the history gets too large (e.g., >128k tokens) and compression is disabled, it is gracefully truncated.
19
- 2. **Stream Processing**: The main loop initiates a streaming request to the Gemini API (`client.models.generateContentStream`). It yields chunks of text and status updates directly back to the React UI as they arrive.
20
- 3. **Detection & Tool Execution**: Once the stream completes for a given turn, the entire response is scanned for tool calls using a custom regex and bracket-balancing parser (looking for `tool:functions.tool_name(args...)`).
21
- - If tools are found, the loop pauses.
22
- - Each tool is dispatched to its respective handler in `src/tools/`.
23
- - Tool outputs are collected and appended to the context as `[TOOL_RESULT]: ...`.
24
- 4. **Security Governance**: During tool execution, the loop enforces security checks (e.g., blocking `exec_command` from accessing system root drives if "External Workspace Access" is off) and pauses for Human-in-the-Loop (HITL) approval if necessary.
25
- 5. **Turn Management & Continuation**: The model is instructed to append `[turn: finish]` if its goal is complete, or `[turn: continue]` if it expects tool results.
26
- - If tools were called or `[turn: continue]` is present, the loop increments and re-prompts the model with the newly gathered `[TOOL_RESULT]` data.
27
- - If `[turn: finish]` is detected and no further tools were called, the main loop terminates, passing the final synthesized context to the background Janitor process.
28
- 6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 50 iterations per user prompt, while **Flow mode** is capped at 5.
29
- - **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
30
- - **Critical Fallback Pivot**: If the primary model fails 5 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite-preview`) for the final 3 attempts to ensure session navigation through API congestion.
31
-
32
- ## The Dual-Model System
33
-
34
- To maintain a fast, snappy UI while still performing complex data management, Flux Flow employs two separate AI models for every interaction:
35
-
36
- ### 1. The Main Agent
37
- - **Responsibility**: Direct user interaction, reasoning, and tool execution.
38
- - **Behavior**: Streams text directly to the UI. It focuses entirely on solving the user's immediate problem or answering their question.
39
-
40
- ### 2. The Janitor (Background Process)
41
- - **Responsibility**: System maintenance, long-term memory extraction, and chat summarization.
42
- - **Behavior**: After the Main Agent finishes its loop, the entire context (User Prompt + Agent Raws) is sent to the Janitor model.
43
- - **Headless Operation**: The Janitor is explicitly instructed to be a "silent background system process" with "no mouth." It *only* outputs valid tool calls (e.g., updating the chat title or saving a new user preference to the persistent memory vault).
44
-
45
- ## Data Persistence & Safety
46
-
47
- - **High-Fidelity Lock**: Because both the UI and the Janitor model may attempt to write to the `history.json` file simultaneously, a Promise-based `WRITE_LOCK` (`src/utils/history.js`) is utilized. This prevents race conditions and ensures data integrity.
1
+ # 🏛️ Architecture & Design
2
+
3
+ Flux Flow is built on a modern, reactive stack that brings web-like development paradigms to the terminal. It utilizes a custom agentic loop for reasoning and a unique dual-model system for background processing.
4
+
5
+ ## UI Layer: React & Ink
6
+
7
+ The entire terminal interface is built using **React** via the [Ink](https://github.com/vadimdemedes/ink) renderer.
8
+ - **Component-Based**: The UI is composed of isolated, reusable React components (`ChatLayout`, `StatusBar`, `CommandMenu`, `TerminalBox`, `ProfileForm`).
9
+ - **Reactive State**: The application uses React hooks (`useState`, `useEffect`) to manage user input, application mode, model selection, and the terminal's resizing events.
10
+ - **Zero-Render Overheads**: Critical performance trackers, like the session start time, are kept outside the React render cycle to maintain terminal responsiveness during high-speed AI text streaming.
11
+
12
+ ## The Agentic Loop
13
+
14
+ The core intelligence of Flux Flow resides in `src/utils/ai.js`. It does not rely on opaque third-party agent frameworks; instead, it uses a custom, highly transparent string-based protocol powered by an asynchronous generator (`async function*`). This approach allows for real-time UI updates while managing complex multi-step reasoning.
15
+
16
+ The execution flow of a single user prompt follows this loop:
17
+
18
+ 1. **Context Assembly**: The user's prompt is combined with the system instructions, temporary session context, persistent user memories, and the current chat history. If the history gets too large (e.g., >128k tokens) and compression is disabled, it is gracefully truncated.
19
+ 2. **Stream Processing**: The main loop initiates a streaming request to the Gemini API (`client.models.generateContentStream`). It yields chunks of text and status updates directly back to the React UI as they arrive.
20
+ 3. **Detection & Tool Execution**: Once the stream completes for a given turn, the entire response is scanned for tool calls using a custom regex and bracket-balancing parser (looking for `tool:functions.tool_name(args...)`).
21
+ - If tools are found, the loop pauses.
22
+ - Each tool is dispatched to its respective handler in `src/tools/`.
23
+ - Tool outputs are collected and appended to the context as `[TOOL_RESULT]: ...`.
24
+ 4. **Security Governance**: During tool execution, the loop enforces security checks (e.g., blocking `exec_command` from accessing system root drives if "External Workspace Access" is off) and pauses for Human-in-the-Loop (HITL) approval if necessary.
25
+ 5. **Turn Management & Continuation**: The model is instructed to append `[turn: finish]` if its goal is complete, or `[turn: continue]` if it expects tool results.
26
+ - If tools were called or `[turn: continue]` is present, the loop increments and re-prompts the model with the newly gathered `[TOOL_RESULT]` data.
27
+ - If `[turn: finish]` is detected and no further tools were called, the main loop terminates, passing the final synthesized context to the background Janitor process.
28
+ 6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 50 iterations per user prompt, while **Flow mode** is capped at 5.
29
+ - **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
30
+ - **Critical Fallback Pivot**: If the primary model fails 5 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite-preview`) for the final 3 attempts to ensure session navigation through API congestion.
31
+
32
+ ## The Dual-Model System
33
+
34
+ To maintain a fast, snappy UI while still performing complex data management, Flux Flow employs two separate AI models for every interaction:
35
+
36
+ ### 1. The Main Agent
37
+ - **Responsibility**: Direct user interaction, reasoning, and tool execution.
38
+ - **Behavior**: Streams text directly to the UI. It focuses entirely on solving the user's immediate problem or answering their question.
39
+
40
+ ### 2. The Janitor (Background Process)
41
+ - **Responsibility**: System maintenance, long-term memory extraction, and chat summarization.
42
+ - **Behavior**: After the Main Agent finishes its loop, the entire context (User Prompt + Agent Raws) is sent to the Janitor model.
43
+ - **Headless Operation**: The Janitor is explicitly instructed to be a "silent background system process" with "no mouth." It *only* outputs valid tool calls (e.g., updating the chat title or saving a new user preference to the persistent memory vault).
44
+
45
+ ## Data Persistence & Safety
46
+
47
+ - **High-Fidelity Lock**: Because both the UI and the Janitor model may attempt to write to the `history.json` file simultaneously, a Promise-based `WRITE_LOCK` (`src/utils/history.js`) is utilized. This prevents race conditions and ensures data integrity.
48
48
  - **Encryption**: User secrets and persistent memories (`secret/memories.json`) are handled by `src/utils/crypto.js` to ensure local privacy.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Kushal Roy Chowdhury
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kushal Roy Chowdhury
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/TOOLS.md CHANGED
@@ -1,48 +1,48 @@
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
- | **View/Read Files** | ✅ | ❌ |
12
- | **Write/Update Files** | ✅ | ❌ |
13
- | **Execute Commands** | ✅ | ❌ |
14
-
15
- ---
16
-
17
- ## Core Tools
18
-
19
- ### 🌐 Web & Research
20
- - **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
21
- - **`web_scrape`**: Extracts the detailed text content from a specific URL, allowing the agent to read documentation or articles.
22
-
23
- ### 📁 File System Operations
24
- - **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
25
- - **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
26
- - **`view_file`**: Reads the content of a file. Supports reading specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
27
-
28
- ### ✍️ Code Editing
29
- - **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
30
- - **`update_file` (Smart Patching)**: Surgically replaces a specific block of text within a file.
31
- - *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.
32
-
33
- ### 💻 Terminal Execution
34
- - **`exec_command`**: Runs a shell command directly in the terminal using Node's `child_process.spawn`.
35
- - *Context Aware*: Runs in the current working directory.
36
- - *Cross-Platform*: Uses `shell: true` to handle Windows `.cmd`/`.bat` files natively.
37
-
38
- ---
39
-
40
- ## Memory Management
41
-
42
- The memory tool (`memory.js`) is primarily used by the background **Janitor** model, but can be accessed by the main agent if necessary.
43
-
44
- - **Temporary Context (`action='temp'`)**: Saves a rolling summary of the current session to maintain conversational context without bloating the main prompt history.
45
- - **Persistent User Memory (`action='user'`)**:
46
- - The Janitor analyzes conversations to detect user preferences, hobbies, or instructions.
47
- - 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
+ | **View/Read Files** | ✅ | ❌ |
12
+ | **Write/Update Files** | ✅ | ❌ |
13
+ | **Execute Commands** | ✅ | ❌ |
14
+
15
+ ---
16
+
17
+ ## Core Tools
18
+
19
+ ### 🌐 Web & Research
20
+ - **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
21
+ - **`web_scrape`**: Extracts the detailed text content from a specific URL, allowing the agent to read documentation or articles.
22
+
23
+ ### 📁 File System Operations
24
+ - **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
25
+ - **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
26
+ - **`view_file`**: Reads the content of a file. Supports reading specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
27
+
28
+ ### ✍️ Code Editing
29
+ - **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
30
+ - **`update_file` (Smart Patching)**: Surgically replaces a specific block of text within a file.
31
+ - *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.
32
+
33
+ ### 💻 Terminal Execution
34
+ - **`exec_command`**: Runs a shell command directly in the terminal using Node's `child_process.spawn`.
35
+ - *Context Aware*: Runs in the current working directory.
36
+ - *Cross-Platform*: Uses `shell: true` to handle Windows `.cmd`/`.bat` files natively.
37
+
38
+ ---
39
+
40
+ ## Memory Management
41
+
42
+ The memory tool (`memory.js`) is primarily used by the background **Janitor** model, but can be accessed by the main agent if necessary.
43
+
44
+ - **Temporary Context (`action='temp'`)**: Saves a rolling summary of the current session to maintain conversational context without bloating the main prompt history.
45
+ - **Persistent User Memory (`action='user'`)**:
46
+ - The Janitor analyzes conversations to detect user preferences, hobbies, or instructions.
47
+ - It uses `add`, `update`, or `delete` methods to manage facts in the encrypted `memories.json` vault.
48
48
  - 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,67 +1,67 @@
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
- - **/memory**: View and manage the persistent memories extracted by the Janitor.
16
- - **/resume <chat-id>**: Switch back to a previous conversation.
17
- - **/changelog**: Open the project's changelog in your default browser.
18
- - **/help**: List all available commands.
19
-
20
- ### Command Shortcuts
21
-
22
- For power users, several commands support direct arguments to skip the menus:
23
- - ` /mode flux ` or ` /mode flow `
24
- - ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
25
- - ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
26
- - ` /model gemini-3.1-pro-preview ` (Switches model directly)
27
-
28
- ## 🧠 Thinking Levels & Visualization
29
-
30
- Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
31
-
32
- - **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.
33
- - **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
34
- - 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.
35
-
36
- ## 🛡️ Human-in-the-Loop (HITL) Verification
37
-
38
- Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
39
-
40
- ### Tool Approval
41
- By default, the agent **cannot** execute dangerous actions without your consent.
42
- - **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.
43
- - **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
44
- - **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
45
-
46
- ### Auto-Execution (Advanced)
47
- For power users, **Auto-Exec** can be enabled in `/settings`.
48
- - **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
49
- - **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
50
-
51
- ## 🔄 Steering & Resolution
52
-
53
- ### Real-time Steering
54
- 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.
55
-
56
- ### Resolution Modal
57
- If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
58
- - **Send Anyway**: Start a new loop with your feedback.
59
- - **Edit Prompt**: Refine your feedback before sending.
60
-
61
- ## 📊 Status Bar & Feedback
62
- The bottom of the screen features a dynamic status bar showing:
63
- - **Active Mode** (Flux/Flow)
64
- - **Thinking Level**
65
- - **Token Usage**: Real-time tracking of tokens used in the current session.
66
- - **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
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
+ - **/memory**: View and manage the persistent memories extracted by the Janitor.
16
+ - **/resume <chat-id>**: Switch back to a previous conversation.
17
+ - **/changelog**: Open the project's changelog in your default browser.
18
+ - **/help**: List all available commands.
19
+
20
+ ### Command Shortcuts
21
+
22
+ For power users, several commands support direct arguments to skip the menus:
23
+ - ` /mode flux ` or ` /mode flow `
24
+ - ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
25
+ - ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
26
+ - ` /model gemini-3.1-pro-preview ` (Switches model directly)
27
+
28
+ ## 🧠 Thinking Levels & Visualization
29
+
30
+ Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
31
+
32
+ - **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.
33
+ - **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
34
+ - 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.
35
+
36
+ ## 🛡️ Human-in-the-Loop (HITL) Verification
37
+
38
+ Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
39
+
40
+ ### Tool Approval
41
+ By default, the agent **cannot** execute dangerous actions without your consent.
42
+ - **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.
43
+ - **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
44
+ - **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
45
+
46
+ ### Auto-Execution (Advanced)
47
+ For power users, **Auto-Exec** can be enabled in `/settings`.
48
+ - **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
49
+ - **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
50
+
51
+ ## 🔄 Steering & Resolution
52
+
53
+ ### Real-time Steering
54
+ 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.
55
+
56
+ ### Resolution Modal
57
+ If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
58
+ - **Send Anyway**: Start a new loop with your feedback.
59
+ - **Edit Prompt**: Refine your feedback before sending.
60
+
61
+ ## 📊 Status Bar & Feedback
62
+ The bottom of the screen features a dynamic status bar showing:
63
+ - **Active Mode** (Flux/Flow)
64
+ - **Thinking Level**
65
+ - **Token Usage**: Real-time tracking of tokens used in the current session.
66
+ - **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
67
67
  - **API Status**: Visual feedback when the model is thinking or executing a tool.
package/dist/fluxflow.js CHANGED
@@ -199,7 +199,7 @@ var ChatLayout_default = ChatLayout;
199
199
  // src/components/StatusBar.jsx
200
200
  import React3 from "react";
201
201
  import { Box as Box3, Text as Text3 } from "ink";
202
- var StatusBar = React3.memo(({ mode, thinkingLevel, tokens = "0.0k", chatId = "NEW-SESSION", isMemoryEnabled = true }) => {
202
+ var StatusBar = React3.memo(({ mode, thinkingLevel, tokens = "0.0k", tokensTotal = "0.0k", chatId = "NEW-SESSION", isMemoryEnabled = true }) => {
203
203
  const modeColor = mode === "Flux" ? "yellow" : "cyan";
204
204
  const modeIcon = mode === "Flux" ? "\u26A1" : "\u{1F30A}";
205
205
  const memStatus = isMemoryEnabled ? "ON" : "OFF";
@@ -215,7 +215,7 @@ var StatusBar = React3.memo(({ mode, thinkingLevel, tokens = "0.0k", chatId = "N
215
215
  },
216
216
  /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: modeColor, bold: true }, modeIcon, " ", mode.toUpperCase()), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "magenta" }, "\u{1F9E0} ", thinkingLevel)),
217
217
  /* @__PURE__ */ React3.createElement(Box3, { flexGrow: 1, justifyContent: "center", paddingX: 2 }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "\u{1F4C1} "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue", dimColor: true, italic: true }, process.cwd())),
218
- /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "MEM: "), /* @__PURE__ */ React3.createElement(Text3, { color: memStatus === "ON" ? "green" : "red" }, memStatus), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, " Tokens ", tokens > 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : tokens, " (", Math.round(tokens / 254e3 * 100), "%)"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "dim" }, "ID: ", chatId, " "))
218
+ /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "MEM: "), /* @__PURE__ */ React3.createElement(Text3, { color: memStatus === "ON" ? "green" : "red" }, memStatus), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, " Tokens ", tokensTotal > 1e3 ? `${(tokensTotal / 1e3).toFixed(1)}k` : tokensTotal, " (", Math.round(tokens / 254e3 * 100), "%)"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "dim" }, "ID: ", chatId, " "))
219
219
  );
220
220
  });
221
221
  var StatusBar_default = StatusBar;
@@ -410,7 +410,7 @@ tool:functions.tool_name(arguments)
410
410
  - WEB TOOLS (Available in Flux & Flow) -
411
411
  1. Web Search: tool:functions.web_search(query="<query>", limit=number). Find info. limit is optional (3-10, default 10). If user asks about something that is not in your training data, proactively use this tool to find the information.Winder search recomemded (limit = 10) when exploring a topic.
412
412
  2. Web Scrape: tool:functions.web_scrape(url="<url>"). provides detail from a URL.
413
- 3. Ask User: tool:functions.ask(question="...", optionA="Option::Desc", optionB="Option::Desc"). Use this ONLY when you reach a decision point with multiple valid paths and need user preference to proceed / confirmation. This allows you to pause for guidance without ending your task loop. Format options as 'Short Label::Detailed Description'. You can provide 2 - 4 options (optionA, optionB, optionC, optionD). Tool can also return result as none of the 4 if user write custom one.
413
+ 3. Ask User: tool:functions.ask(question="...", optionA="Option::Desc", optionB="Option::Desc"). Mandatory triggers include: 1) **Path Divergence**: When multiple architectural or technical solutions exist, present options via 'ask' instead of choosing arbitrarily. 2) **Security Boundaries**: Explicitly request permission via 'ask' before accessing sensitive files (e.g., .env, config keys, credentials). 3) **Ambiguity Resolution**: Use 'ask' to clarify vague prompts before executing terminal commands or writing code. 4) **Risk Mitigation**: Require a 'Yes/No' confirmation for any destructive or irreversible operations. Options must always follow the 'Short Label::Detailed Description' format. This tool is a non-terminating suspension so you can get guidance without losing context.
414
414
  ${mode === "Flux" ? `
415
415
  - DEV & FILE TOOLS (Available in FLUX MODE ONLY) -
416
416
  1. View File: tool:functions.view_file(path="relative/path", start_line=number, end_line=number). Reads file content. Auto-truncates at 500 lines unless start_line and end_line are provided.
@@ -422,7 +422,7 @@ ${mode === "Flux" ? `
422
422
  - 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()}
423
423
  -----------------
424
424
  Results will be provided in the next loop as: [TOOL_RESULT]: [content]
425
- WHEN CALLING TOOLS, YOU **MUST** END YOUR RESPONSE WITH '[turn: continue]' AFTER CALLING FUNCTIONS.
425
+ WHEN CALLING TOOLS, YOU **MUST** end your response with '[turn: continue]'. NEVER use '[turn: finish]' in the same turn as a tool call. After receiving the [TOOL_RESULT], acknowledge the output and verify if the goal is met; only then may you use '[turn: finish]', otherwise use '[turn: continue]'.
426
426
  Do NOT over-use tools. Use them only when strictly necessary for the user's objective. You can stack multiple tool calls 1-by-1.
427
427
  Distinguish clearly between tool discussion and execution. Use the 'tool:' prefix ONLY when calling a function. When discussing tools with the user, refer to them by name as nouns (e.g., 'write_file', 'list_files') to avoid accidental triggers and context bloat. Even in your <think> ... </think> tags, do not use the 'tool:' prefix when planning to select a tool.
428
428
  -- END FUNCTION CALLING PROTOCOL --`.trim();
@@ -465,6 +465,7 @@ var getSystemInstruction = (profile, thinkingLevel, mode, systemSettings, tempMe
465
465
  if (thinkingLevel === "Low") levelKey = "Minimal";
466
466
  if (thinkingLevel === "xHigh" || thinkingLevel === "Max") levelKey = "Max";
467
467
  const thinkingConfig = thinking_prompts_default[levelKey] || thinking_prompts_default["Medium"];
468
+ const osDetected = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
468
469
  const nameStr = profile.name && profile.name?.length > 0 ? `User Name: ${profile.name}` : "";
469
470
  const nicknameStr = profile.nickname && profile.nickname?.length > 0 ? `. User Nickname: ${profile.nickname}.` : "";
470
471
  const userInstrStr = profile.instructions && profile.instructions?.length > 0 ? `. User Instructions: ${profile.instructions}.` : "";
@@ -485,8 +486,9 @@ ${userMemories}
485
486
  ` : ""}${isMemoryEnabled ? `${tempMemoriesStr}
486
487
 
487
488
  ` : ""}--- START SYSTEM INSTRUCTION ---
488
- You are Flux Flow. A CLI Agent. Your tone will be friendly, warm, sassy, approchable, respectable, NO ROMANTIC OR FLIRTY WORDS. Dont mention modes unless explicitly asked. ${mode === "Flux" ? "You are currently operating in FLUX (dev) mode. Keep your agentic approach goal oriented. Use provided tools when needed. And try to minimize number of agentic loops (Agent Loop is limited to 50 per turn, finish your goal by then). Analyze user prompt and project requirements, then plan your approach." : "You are currently operating in Flow (chat) mode. Focus more on conversation quality and user experience. Keep Agentic Loops to minimum (Agent Loop is limited to 7 per turn, finish your goal by then). You will get access to Web Tools only in this mode."}
489
+ You are Flux Flow. A CLI Agent. Your tone will be friendly, warm, sassy, approchable, funny, NO ROMANTIC OR FLIRTY WORDS. Dont mention modes unless explicitly asked. ${mode === "Flux" ? "You are currently operating in FLUX (dev) mode. Keep your agentic approach goal oriented. Use provided tools when needed. And try to minimize number of agentic loops (Agent Loop is limited to 50 per turn, finish your goal by then). Analyze user prompt and project requirements, then plan your approach." : "You are currently operating in Flow (chat) mode. Focus more on conversation quality and user experience. Keep Agentic Loops to minimum (Agent Loop is limited to 7 per turn, finish your goal by then). You will get access to Web Tools only in this mode."}
489
490
  CURRENT_WORKING_DIRECTORY: ${cwdStr}.
491
+ OS: ${osDetected}. ${osDetected && mode == "Flux" ? "Your terminal commands will run on CMD. Prefer PS scripts via CMD instead of raw CMD commands." : ""}
490
492
  ${nameStr}${nicknameStr}${userInstrStr}
491
493
 
492
494
  ${thinkingConfig}
@@ -524,6 +526,7 @@ Every ${isMemoryEnabled ? "Prompt, Responses & Memories" : "Prompt & Responses"}
524
526
 
525
527
  -- START REPONSE FINISH PROTOCOL --
526
528
  WHEN YOU ARE DONE AND NEED NO LONGER AGENT LOOP FOR THE TASK, WRITE [turn: finish] AT VERY END OF YOUR RESPONSE TO AVOID AGENT LOOPS. IF YOU ARE NOT COMPLETED YET AND WANT NEXT LOOP WRITE [turn: continue] AT VERY END OF YOUR RESPONSE TO CONTINUE AGENT LOOPS. YOU CAN STACK MULTIPLE TOOLS AT ONCE BUT **HAVE TO** WRITE [turn: continue] AFTER WRITING ALL TOOL CALLS.
529
+ When you 'finish' an agentic loop, you will lose your previous turn 'thinking' data. So only write [turn: finish] when you are absolutely sure that you are done with the task. Or user has to prompt again and re-thinking again from scratch will use tokens that were already planned.
527
530
  -- END REPONSE FINISH PROTOCOL --
528
531
  Current date and Time is: ${dateTimeStr}
529
532
  --- END SYSTEM INSTRUCTION ---`.trim();
@@ -1229,7 +1232,7 @@ ${formatted}${footer}`;
1229
1232
  var ask_user = async (args, context) => {
1230
1233
  const parsed = parseArgs(args);
1231
1234
  const { question } = parsed;
1232
- if (!question) return 'ERROR: Missing "question" argument for ask_user.';
1235
+ if (!question) return 'ERROR: Missing "question" argument for ask.';
1233
1236
  if (!context.onAskUser) return "ERROR: onAskUser callback not provided in tool context.";
1234
1237
  const options = [];
1235
1238
  Object.keys(parsed).forEach((key) => {
@@ -1295,35 +1298,39 @@ var signalTermination = () => {
1295
1298
  };
1296
1299
  var detectToolCalls = (text) => {
1297
1300
  const results = [];
1298
- const trigger = "tool:functions.";
1299
- let searchIdx = 0;
1300
- while (true) {
1301
- const startIdx = text.indexOf(trigger, searchIdx);
1302
- if (startIdx === -1) break;
1303
- const openParenIdx = text.indexOf("(", startIdx + trigger.length);
1304
- if (openParenIdx === -1) {
1305
- searchIdx = startIdx + trigger.length;
1306
- continue;
1307
- }
1308
- const toolName = text.substring(startIdx + trigger.length, openParenIdx).trim();
1309
- let balance = 1;
1310
- let endIdx = -1;
1311
- for (let i = openParenIdx + 1; i < text.length; i++) {
1312
- if (text[i] === "(") balance++;
1313
- if (text[i] === ")") balance--;
1314
- if (balance === 0) {
1315
- endIdx = i;
1316
- break;
1301
+ const toolRegex = /(?:\[?\s*tool:functions\.)([a-z0-9_]+)\s*\(([\s\S]*?)\)(?:\s*\]?)/gi;
1302
+ let match;
1303
+ while ((match = toolRegex.exec(text)) !== null) {
1304
+ const fullMatch = match[0];
1305
+ const toolName = match[1];
1306
+ const args = match[2];
1307
+ let openCount = (args.match(/\(/g) || []).length;
1308
+ let closeCount = (args.match(/\)/g) || []).length;
1309
+ let finalArgs = args;
1310
+ let finalFullMatch = fullMatch;
1311
+ if (openCount > closeCount) {
1312
+ const startIdx = match.index + fullMatch.indexOf("(");
1313
+ let balance = 0;
1314
+ let endIdx = -1;
1315
+ for (let i = startIdx; i < text.length; i++) {
1316
+ if (text[i] === "(") balance++;
1317
+ if (text[i] === ")") balance--;
1318
+ if (balance === 0) {
1319
+ endIdx = i;
1320
+ break;
1321
+ }
1322
+ }
1323
+ if (endIdx !== -1) {
1324
+ finalArgs = text.substring(startIdx + 1, endIdx);
1325
+ finalFullMatch = text.substring(match.index, endIdx + 1);
1326
+ toolRegex.lastIndex = endIdx + 1;
1317
1327
  }
1318
1328
  }
1319
- if (endIdx === -1) {
1320
- searchIdx = openParenIdx + 1;
1321
- continue;
1322
- }
1323
- const fullMatch = text.substring(startIdx, endIdx + 1);
1324
- const args = text.substring(openParenIdx + 1, endIdx);
1325
- results.push({ fullMatch, toolName, args });
1326
- searchIdx = endIdx + 1;
1329
+ results.push({
1330
+ fullMatch: finalFullMatch,
1331
+ toolName: toolName.trim(),
1332
+ args: finalArgs.trim()
1333
+ });
1327
1334
  }
1328
1335
  return results;
1329
1336
  };
@@ -1375,7 +1382,7 @@ USER_PROMPT: ${agentText}`.trim();
1375
1382
  }
1376
1383
  }
1377
1384
  yield { type: "turn_reset", content: true };
1378
- const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome")).map((msg) => ({
1385
+ const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => ({
1379
1386
  role: msg.role === "user" || msg.role === "system" ? "user" : "model",
1380
1387
  parts: [{ text: msg.text }]
1381
1388
  }));
@@ -1609,7 +1616,8 @@ ${boxBottom}
1609
1616
  yield { type: "status", content: "Working..." };
1610
1617
  }
1611
1618
  const cleanedTurnText = turnText.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/\[?\s*(turn\s*:)?\s*(continue|finish)\s*\]?/gi, "").trim();
1612
- if (hasFinish || !shouldContinue && toolResults.length === 0) {
1619
+ const isActuallyFinished = hasFinish;
1620
+ if (isActuallyFinished) {
1613
1621
  const lateHint = await steeringCallback();
1614
1622
  if (lateHint) {
1615
1623
  toolResults.push(`[USER STEERING HINT]: ${lateHint}`);
@@ -1694,13 +1702,11 @@ ${timestamp}`;
1694
1702
  }
1695
1703
  break;
1696
1704
  }
1697
- if (!shouldContinue && toolResults.length === 0) break;
1705
+ if (isActuallyFinished) break;
1698
1706
  const nextAgentMsg = cleanedTurnText.trim() || "*Working...*";
1699
1707
  modifiedHistory.push({ role: "agent", text: nextAgentMsg });
1700
- const nextUserMsg = toolResults.length > 0 ? `${toolResults.join("\n")}` : "";
1701
- if (nextUserMsg) {
1702
- modifiedHistory.push({ role: "user", text: nextUserMsg });
1703
- }
1708
+ const nextUserMsg = toolResults.length > 0 ? toolResults.join("\n") : "[turn: continue]";
1709
+ modifiedHistory.push({ role: "user", text: nextUserMsg });
1704
1710
  }
1705
1711
  yield { type: "status", content: null };
1706
1712
  };
@@ -1835,11 +1841,28 @@ function MemoryModal({ onClose }) {
1835
1841
  return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "double", borderColor: "yellow", padding: 1, width: 80 }, /* @__PURE__ */ React8.createElement(Box8, { justifyContent: "center", marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "yellow" }, "\u{1F9E0} LONG-TERM MEMORY VAULT")), memories.length === 0 ? /* @__PURE__ */ React8.createElement(Box8, { justifyContent: "center", paddingY: 2 }, /* @__PURE__ */ React8.createElement(Text8, { italic: true, color: "gray" }, "The vault is currently empty...")) : memories.map((mem, idx) => /* @__PURE__ */ React8.createElement(Box8, { key: mem.id, paddingX: 1, backgroundColor: idx === selectedIndex ? "#333" : void 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: idx === selectedIndex ? "yellow" : "white" }, idx === selectedIndex ? "\u276F " : " ", idx + 1, ". ", cleanDisplay(mem.memory)))), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u2191/\u2193 Navigate \u2022 ", /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, "x"), " Delete Memory \u2022 ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, "Esc"), " Back to Chat")));
1836
1842
  }
1837
1843
 
1844
+ // src/utils/terminal.js
1845
+ var getTerminalEnv = () => {
1846
+ if (process.env.TERM_PROGRAM === "vscode") return "vscode";
1847
+ if (process.env.WT_SESSION) return "wt";
1848
+ return "default";
1849
+ };
1850
+ var emojiSpace = (baseSpaces = 2) => {
1851
+ const env = getTerminalEnv();
1852
+ if (env === "wt") {
1853
+ return " ".repeat(Math.max(1, baseSpaces - 1));
1854
+ }
1855
+ if (env === "vscode") {
1856
+ return " ".repeat(baseSpaces);
1857
+ }
1858
+ return " ".repeat(baseSpaces);
1859
+ };
1860
+
1838
1861
  // src/app.jsx
1839
1862
  var SESSION_START_TIME = Date.now();
1840
1863
  var CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
1841
- var versionFluxflow = "1.1.1";
1842
- var updatedOn = "2026-04-26";
1864
+ var versionFluxflow = "1.1.3";
1865
+ var updatedOn = "2026-04-27";
1843
1866
  var ResolutionModal = ({ data, onResolve, onEdit }) => /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", bold: true, underline: true }, "\u{1F7E3} STEERING HINT RESOLUTION"), /* @__PURE__ */ React9.createElement(Text9, { marginTop: 1 }, "The agent already finished the task (turn: finish) before your hint was consumed."), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1, backgroundColor: "#222", paddingX: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Text9, { italic: true, color: "gray" }, '"', data, '"')), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "How would you like to proceed?")), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
1844
1867
  CommandMenu,
1845
1868
  {
@@ -1865,6 +1888,7 @@ var FLUX_LOGO = gradient(["#00ffff", "#0077ff", "#ff00ff"]).multiline(
1865
1888
  function App() {
1866
1889
  const { stdout } = useStdout();
1867
1890
  const [input, setInput] = useState5("");
1891
+ const [isExpanded, setIsExpanded] = useState5(false);
1868
1892
  const [mode, setMode] = useState5("Flux");
1869
1893
  const [terminalSize, setTerminalSize] = useState5({
1870
1894
  columns: stdout?.columns || 80,
@@ -1899,7 +1923,8 @@ function App() {
1899
1923
  text: `\u{1F680} **New version 'v${latestVersion}' is available!**
1900
1924
  Type \`npm i -g fluxflow-cli\` to update.
1901
1925
  Check what's new using \`/changelog\` command.`,
1902
- isUpdateNotification: true
1926
+ isUpdateNotification: true,
1927
+ isMeta: true
1903
1928
  });
1904
1929
  return newMsgs;
1905
1930
  });
@@ -1971,8 +1996,8 @@ Check what's new using \`/changelog\` command.`,
1971
1996
  for (let i = completedIndex - 1; i >= 0; i--) {
1972
1997
  const msg = messages[i];
1973
1998
  if (!msg) continue;
1974
- let lines = (msg.text || "").split("\n").length;
1975
- msg.text.split("\n").forEach((l) => {
1999
+ let lines = (msg.text || "").split(/\r?\n/).length;
2000
+ msg.text.split(/\r?\n/).forEach((l) => {
1976
2001
  lines += Math.floor(l.length / width);
1977
2002
  });
1978
2003
  lines += msg.role === "think" ? 3 : 2;
@@ -1987,7 +2012,23 @@ Check what's new using \`/changelog\` command.`,
1987
2012
  isTruncated: startIdx > 0
1988
2013
  };
1989
2014
  }, [messages, completedIndex, stdout?.columns]);
2015
+ const terminalWidth = stdout?.columns || 80;
2016
+ const wrapWidth = Math.max(20, terminalWidth - 10);
2017
+ const wrappedLinesCount = input.split(/\r?\n/).reduce((acc, line) => {
2018
+ return acc + Math.max(1, Math.ceil(line.length / wrapWidth));
2019
+ }, 0);
2020
+ const maxLines = Math.max(1, wrappedLinesCount);
1990
2021
  useInput4((inputText, key) => {
2022
+ if (maxLines > 2 && !isExpanded && activeView === "chat") {
2023
+ if (key.backspace || key.delete) {
2024
+ setInput("");
2025
+ return;
2026
+ }
2027
+ if (key.return) {
2028
+ setIsExpanded(true);
2029
+ return;
2030
+ }
2031
+ }
1991
2032
  if (key.escape) {
1992
2033
  if (isProcessing) {
1993
2034
  if (!escPressed) {
@@ -2068,7 +2109,7 @@ Check what's new using \`/changelog\` command.`,
2068
2109
  setInput(normalizedValue.slice(0, -1) + "\n");
2069
2110
  return;
2070
2111
  }
2071
- const absoluteClean = normalizedValue.replace(/\\\s*\n/g, "\n").split("\n").map((l) => l.replace(/\\$/, "")).join("\n");
2112
+ const absoluteClean = normalizedValue.replace(/\\\s*\n/g, "\n").split(/\r?\n/).map((l) => l.replace(/\\$/, "")).join("\n");
2072
2113
  if (!absoluteClean.trim()) return;
2073
2114
  if (isProcessing) {
2074
2115
  const hintText = absoluteClean.trim();
@@ -2128,6 +2169,7 @@ ${hintText}`, color: "magenta" }];
2128
2169
  setCompletedIndex(0);
2129
2170
  setChatId(generateChatId());
2130
2171
  setSessionStats({ tokens: 0 });
2172
+ setIsExpanded(false);
2131
2173
  break;
2132
2174
  }
2133
2175
  case "/mode": {
@@ -2136,7 +2178,7 @@ ${hintText}`, color: "magenta" }];
2136
2178
  setMode(newMode);
2137
2179
  setMessages((prev) => {
2138
2180
  setCompletedIndex(prev.length + 1);
2139
- return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Mode switched to ${newMode}` }];
2181
+ return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Mode switched to ${newMode}`, isMeta: true }];
2140
2182
  });
2141
2183
  } else {
2142
2184
  setActiveView("mode");
@@ -2149,13 +2191,13 @@ ${hintText}`, color: "magenta" }];
2149
2191
  setShowFullThinking(true);
2150
2192
  setMessages((prev) => {
2151
2193
  setCompletedIndex(prev.length + 1);
2152
- return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: VISIBLE" }];
2194
+ return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: VISIBLE", isMeta: true }];
2153
2195
  });
2154
2196
  } else if (arg === "hide") {
2155
2197
  setShowFullThinking(false);
2156
2198
  setMessages((prev) => {
2157
2199
  setCompletedIndex(prev.length + 1);
2158
- return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: HIDDEN (Headings only)" }];
2200
+ return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: HIDDEN (Headings only)", isMeta: true }];
2159
2201
  });
2160
2202
  } else if (parts[1]) {
2161
2203
  let val = parts[1].toLowerCase();
@@ -2170,7 +2212,7 @@ ${hintText}`, color: "magenta" }];
2170
2212
  setThinkingLevel(formattedLevel);
2171
2213
  setMessages((prev) => {
2172
2214
  setCompletedIndex(prev.length + 1);
2173
- return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Thinking level set to ${formattedLevel}` }];
2215
+ return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Thinking level set to ${formattedLevel}`, isMeta: true }];
2174
2216
  });
2175
2217
  }
2176
2218
  } else {
@@ -2184,7 +2226,7 @@ ${hintText}`, color: "magenta" }];
2184
2226
  setActiveModel(mod);
2185
2227
  setMessages((prev) => {
2186
2228
  setCompletedIndex(prev.length + 1);
2187
- return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Model switched to ${mod}` }];
2229
+ return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Model switched to ${mod}`, isMeta: true }];
2188
2230
  });
2189
2231
  } else {
2190
2232
  setActiveView("model");
@@ -2217,7 +2259,7 @@ ${hintText}`, color: "magenta" }];
2217
2259
  saveChat(chatId, name, messages);
2218
2260
  setMessages((prev) => {
2219
2261
  setCompletedIndex(prev.length + 1);
2220
- return [...prev, { id: Date.now(), role: "system", text: `\u{1F4BE} [MEMORY] Chat saved as "${name}" (ID: ${chatId})` }];
2262
+ return [...prev, { id: Date.now(), role: "system", text: `\u{1F4BE} [MEMORY] Chat saved as "${name}" (ID: ${chatId})`, isMeta: true }];
2221
2263
  });
2222
2264
  break;
2223
2265
  }
@@ -2228,7 +2270,7 @@ ${hintText}`, color: "magenta" }];
2228
2270
  setMessages((prev) => {
2229
2271
  setCompletedIndex(prev.length + 1);
2230
2272
  return [...prev, { id: Date.now(), role: "system", text: `\u{1F5C3}\uFE0F [HISTORY] Saved Chats:
2231
- ${list || "No saved chats found."}` }];
2273
+ ${list || "No saved chats found."}`, isMeta: true }];
2232
2274
  });
2233
2275
  };
2234
2276
  run();
@@ -2243,7 +2285,7 @@ ${list || "No saved chats found."}` }];
2243
2285
  try {
2244
2286
  setMessages((prev) => {
2245
2287
  setCompletedIndex(prev.length + 1);
2246
- return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset..." }];
2288
+ return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset...", isMeta: true }];
2247
2289
  });
2248
2290
  if (fs14.existsSync(LOGS_DIR)) fs14.removeSync(LOGS_DIR);
2249
2291
  if (fs14.existsSync(SECRET_DIR)) fs14.removeSync(SECRET_DIR);
@@ -2269,12 +2311,13 @@ ${list || "No saved chats found."}` }];
2269
2311
  }
2270
2312
  case "/about": {
2271
2313
  const updateStatus = latestVer ? latestVer !== versionFluxflow ? `Update Available [v${latestVer}]` : "No Update Available" : "Checking for updates...";
2272
- const aboutText = `\u2139\uFE0F **FluxFlow Version:** v${versionFluxflow}
2314
+ const s = emojiSpace(2);
2315
+ const aboutText = `\u2139\uFE0F${s}**FluxFlow Version:** v${versionFluxflow}
2273
2316
  \u{1F504} **Status:** ${updateStatus}
2274
2317
  \u{1F4C5} **Updated on:** ${updatedOn}`;
2275
2318
  setMessages((prev) => {
2276
2319
  setCompletedIndex(prev.length + 1);
2277
- return [...prev, { id: Date.now(), role: "system", text: aboutText }];
2320
+ return [...prev, { id: Date.now(), role: "system", text: aboutText, isMeta: true }];
2278
2321
  });
2279
2322
  break;
2280
2323
  }
@@ -2284,21 +2327,21 @@ ${list || "No saved chats found."}` }];
2284
2327
  exec(`${command} ${CHANGELOG_URL}`);
2285
2328
  setMessages((prev) => {
2286
2329
  setCompletedIndex(prev.length + 1);
2287
- return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}` }];
2330
+ return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}`, isMeta: true }];
2288
2331
  });
2289
2332
  break;
2290
2333
  }
2291
2334
  case "/help": {
2292
2335
  setMessages((prev) => {
2293
2336
  setCompletedIndex(prev.length + 1);
2294
- return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Available commands: " + COMMANDS.join(", ") }];
2337
+ return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Available commands: " + COMMANDS.join(", "), isMeta: true }];
2295
2338
  });
2296
2339
  break;
2297
2340
  }
2298
2341
  default:
2299
2342
  setMessages((prev) => {
2300
2343
  setCompletedIndex(prev.length + 1);
2301
- return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Unknown command: ${cmd}` }];
2344
+ return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Unknown command: ${cmd}`, isMeta: true }];
2302
2345
  });
2303
2346
  }
2304
2347
  } else {
@@ -2312,6 +2355,7 @@ ${timestamp}` };
2312
2355
  });
2313
2356
  const streamChat = async () => {
2314
2357
  setIsProcessing(true);
2358
+ setIsExpanded(false);
2315
2359
  try {
2316
2360
  const cleanHistoryForAI = [...messages, userMessage].filter(
2317
2361
  (m) => m.role !== "think" && !String(m.id).startsWith("welcome")
@@ -2534,6 +2578,7 @@ Selection: ${val}`,
2534
2578
  streamChat();
2535
2579
  }
2536
2580
  setInput("");
2581
+ setIsExpanded(false);
2537
2582
  };
2538
2583
  const getSuggestions = () => {
2539
2584
  if (!input.startsWith("/") || input.includes(" ")) return [];
@@ -2602,11 +2647,12 @@ Selection: ${val}`,
2602
2647
  {
2603
2648
  title: "System Settings",
2604
2649
  items: [
2605
- { label: `Toggle Memory [ ${systemSettings.memory ? "ON" : "OFF"} ]`, value: "memory" },
2606
- { label: `Toggle Auto-Exec [ ${systemSettings.autoExec ? "ON" : "OFF"} ]`, value: "autoExec" },
2607
- { label: `External Workspace Access [ ${systemSettings.allowExternalAccess ? "ON" : "OFF"} ]`, value: "externalAccess" },
2608
- { label: `API Tier [ ${apiTier} ]`, value: "apiTier" },
2609
- { label: `Auto-Delete History [ ${systemSettings.autoDeleteHistory} ]`, value: "autoDelete" },
2650
+ { label: `Toggle Memory [ ${systemSettings.memory ? "ON" : "OFF"} ]`, value: "memory" },
2651
+ { label: `Toggle Auto-Exec [ ${systemSettings.autoExec ? "ON" : "OFF"} ]`, value: "autoExec" },
2652
+ { label: `Alternate Screen Buffer (Experimental) [ ${systemSettings.useAlternateBuffer ? "ON" : "OFF"} ]`, value: "altBuffer" },
2653
+ { label: `External Workspace Access [ ${systemSettings.allowExternalAccess ? "ON" : "OFF"} ]`, value: "externalAccess" },
2654
+ { label: `API Tier [ ${apiTier} ]`, value: "apiTier" },
2655
+ { label: `Auto-Delete History [ ${systemSettings.autoDeleteHistory} ]`, value: "autoDelete" },
2610
2656
  { label: "Exit Settings", value: "Cancel" }
2611
2657
  ],
2612
2658
  onSelect: (item) => {
@@ -2970,45 +3016,29 @@ Selection: ${val}`,
2970
3016
  }
2971
3017
  )));
2972
3018
  default:
2973
- const terminalWidth = stdout.columns || 80;
2974
- const wrapWidth = Math.max(20, terminalWidth - 10);
2975
- const wrappedLines = input.split("\n").reduce((acc, line) => {
2976
- return acc + Math.max(1, Math.ceil(line.length / wrapWidth));
2977
- }, 0);
2978
- const maxLines = Math.max(1, wrappedLines);
2979
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, null, statusText && /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", italic: true }, "\u23F3 ", statusText)), /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "(", tempModelOverride || activeModel, ")")), suggestions.length > 0 && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "\u{1F4A1} Suggestions: "), suggestions.map((s, i) => /* @__PURE__ */ React9.createElement(Text9, { key: s, color: "yellow", bold: i === 0 }, " ", s, " "))), /* @__PURE__ */ React9.createElement(Box9, { backgroundColor: "#333333", paddingX: 1, paddingY: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", width: "100%" }, maxLines > 3 ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", width: "100%", paddingY: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "[\u{1F4E6} ", maxLines, " lines of text in buffer - Full content will be sent]"), /* @__PURE__ */ React9.createElement(
2980
- Box9,
3019
+ return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, null, statusText && /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", italic: true }, "\u23F3 ", statusText)), /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "(", tempModelOverride || activeModel, ")")), suggestions.length > 0 && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "\u{1F4A1} Suggestions: "), suggestions.map((s, i) => /* @__PURE__ */ React9.createElement(Text9, { key: s, color: "yellow", bold: i === 0 }, " ", s, " "))), /* @__PURE__ */ React9.createElement(Box9, { backgroundColor: "#333333", paddingX: 1, paddingY: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", width: "100%" }, maxLines > 2 && !isExpanded ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "row", width: "100%", paddingY: 0, height: 1, overflow: "hidden" }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, flexDirection: "row" }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", bold: true }, "[Pasted ", maxLines, " Lines]")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, marginLeft: 1 }, /* @__PURE__ */ React9.createElement(
3020
+ MultilineInput,
2981
3021
  {
2982
- flexDirection: "row",
2983
- width: "100%",
2984
- height: 1,
2985
- overflow: "hidden",
2986
- alignItems: "flex-end"
2987
- },
2988
- /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")),
2989
- /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1 }, /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, position: "relative" }, input.split("\n").pop() === "" && !isProcessing && /* @__PURE__ */ React9.createElement(Box9, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "Type your message...")), /* @__PURE__ */ React9.createElement(
2990
- MultilineInput,
2991
- {
2992
- value: input.split("\n").pop() || "",
2993
- onChange: (val) => {
2994
- const cleanVal = val.replace(/\\$/, "");
2995
- const lines = input.split("\n");
2996
- lines[lines.length - 1] = cleanVal;
2997
- setInput(lines.join("\n"));
2998
- },
2999
- onSubmit: handleSubmit,
3000
- keyBindings: {
3001
- submit: (key) => key.return && !key.shift && !key.ctrl,
3002
- newline: (key) => key.return && key.shift || key.return && key.ctrl
3022
+ value: "",
3023
+ placeholder: " (Backspace to delete / Enter to expand)",
3024
+ onChange: (val) => {
3025
+ if (val.length > 0) {
3026
+ setIsExpanded(true);
3027
+ setInput(input + val);
3003
3028
  }
3029
+ },
3030
+ onSubmit: () => setIsExpanded(true),
3031
+ keyBindings: {
3032
+ submit: (key) => key.return && !key.shift && !key.ctrl,
3033
+ newline: (key) => key.return && key.shift || key.return && key.ctrl
3004
3034
  }
3005
- )))
3006
- )) : /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1 }, /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, position: "relative" }, input === "" && !isProcessing && /* @__PURE__ */ React9.createElement(Box9, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, escPressed ? " Press ESC again to cancel the request." : " Type your message or /command...")), /* @__PURE__ */ React9.createElement(
3035
+ }
3036
+ )))) : /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1 }, /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, position: "relative" }, input === "" && !isProcessing && /* @__PURE__ */ React9.createElement(Box9, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, escPressed ? " Press ESC again to cancel the request." : " Type your message or /command...")), /* @__PURE__ */ React9.createElement(
3007
3037
  MultilineInput,
3008
3038
  {
3009
3039
  value: input,
3010
3040
  onChange: (val) => {
3011
- const cleanVal = val.replace(/\\\s*\n/g, "\n");
3041
+ const cleanVal = val.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\s*\n/g, "\n");
3012
3042
  setInput(cleanVal);
3013
3043
  },
3014
3044
  onSubmit: handleSubmit,
@@ -3035,6 +3065,7 @@ Selection: ${val}`,
3035
3065
  mode,
3036
3066
  thinkingLevel,
3037
3067
  tokens: sessionStats.tokens,
3068
+ tokensTotal: sessionTotalTokens,
3038
3069
  chatId,
3039
3070
  isMemoryEnabled: systemSettings.memory
3040
3071
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",