fluxflow-cli 1.0.1 → 1.0.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/LICENSE +21 -0
- package/TOOLS.md +48 -0
- package/UI_FEATURES.md +66 -0
- package/dist/{index.js → fluxflow.js} +93 -113
- package/package.json +7 -4
package/LICENSE
ADDED
|
@@ -0,0 +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.
|
package/TOOLS.md
ADDED
|
@@ -0,0 +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.
|
|
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
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
- **/help**: List all available commands.
|
|
18
|
+
|
|
19
|
+
### Command Shortcuts
|
|
20
|
+
|
|
21
|
+
For power users, several commands support direct arguments to skip the menus:
|
|
22
|
+
- ` /mode flux ` or ` /mode flow `
|
|
23
|
+
- ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
|
|
24
|
+
- ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
|
|
25
|
+
- ` /model gemini-3.1-pro-preview ` (Switches model directly)
|
|
26
|
+
|
|
27
|
+
## 🧠 Thinking Levels & Visualization
|
|
28
|
+
|
|
29
|
+
Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
|
|
30
|
+
|
|
31
|
+
- **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.
|
|
32
|
+
- **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
|
|
33
|
+
- 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.
|
|
34
|
+
|
|
35
|
+
## 🛡️ Human-in-the-Loop (HITL) Verification
|
|
36
|
+
|
|
37
|
+
Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
|
|
38
|
+
|
|
39
|
+
### Tool Approval
|
|
40
|
+
By default, the agent **cannot** execute dangerous actions without your consent.
|
|
41
|
+
- **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.
|
|
42
|
+
- **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
|
|
43
|
+
- **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
|
|
44
|
+
|
|
45
|
+
### Auto-Execution (Advanced)
|
|
46
|
+
For power users, **Auto-Exec** can be enabled in `/settings`.
|
|
47
|
+
- **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
|
|
48
|
+
- **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
|
|
49
|
+
|
|
50
|
+
## 🔄 Steering & Resolution
|
|
51
|
+
|
|
52
|
+
### Real-time Steering
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
### Resolution Modal
|
|
56
|
+
If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
|
|
57
|
+
- **Send Anyway**: Start a new loop with your feedback.
|
|
58
|
+
- **Edit Prompt**: Refine your feedback before sending.
|
|
59
|
+
|
|
60
|
+
## 📊 Status Bar & Feedback
|
|
61
|
+
The bottom of the screen features a dynamic status bar showing:
|
|
62
|
+
- **Active Mode** (Flux/Flow)
|
|
63
|
+
- **Thinking Level**
|
|
64
|
+
- **Token Usage**: Real-time tracking of tokens used in the current session.
|
|
65
|
+
- **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
|
|
66
|
+
- **API Status**: Visual feedback when the model is thinking or executing a tool.
|
|
@@ -8,8 +8,6 @@ import { render } from "ink";
|
|
|
8
8
|
import React8, { useState as useState4, useEffect as useEffect3, useRef, useMemo } from "react";
|
|
9
9
|
import { Box as Box8, Text as Text8, useInput as useInput3, useStdout, Static } from "ink";
|
|
10
10
|
import fs14 from "fs-extra";
|
|
11
|
-
import path16 from "path";
|
|
12
|
-
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
13
11
|
import { MultilineInput } from "ink-multiline-input";
|
|
14
12
|
import TextInput2 from "ink-text-input";
|
|
15
13
|
|
|
@@ -194,7 +192,7 @@ function StatusBar({ mode, thinkingLevel, tokens = "0.0k", chatId = "NEW-SESSION
|
|
|
194
192
|
},
|
|
195
193
|
/* @__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)),
|
|
196
194
|
/* @__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())),
|
|
197
|
-
/* @__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 /
|
|
195
|
+
/* @__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 / 196e3 * 100), "%)"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "dim" }, "ID: ", chatId, " "))
|
|
198
196
|
);
|
|
199
197
|
}
|
|
200
198
|
|
|
@@ -257,8 +255,6 @@ import gradient from "gradient-string";
|
|
|
257
255
|
|
|
258
256
|
// src/utils/secrets.js
|
|
259
257
|
import fs2 from "fs-extra";
|
|
260
|
-
import path2 from "path";
|
|
261
|
-
import { fileURLToPath } from "url";
|
|
262
258
|
|
|
263
259
|
// src/utils/crypto.js
|
|
264
260
|
import fs from "fs";
|
|
@@ -295,12 +291,21 @@ var writeEncryptedJson = (filePath, data) => {
|
|
|
295
291
|
}
|
|
296
292
|
};
|
|
297
293
|
|
|
294
|
+
// src/utils/paths.js
|
|
295
|
+
import os from "os";
|
|
296
|
+
import path2 from "path";
|
|
297
|
+
var FLUXFLOW_DIR = path2.join(os.homedir(), ".fluxflow");
|
|
298
|
+
var LOGS_DIR = path2.join(FLUXFLOW_DIR, "logs");
|
|
299
|
+
var SECRET_DIR = path2.join(FLUXFLOW_DIR, "secret");
|
|
300
|
+
var SETTINGS_FILE = path2.join(FLUXFLOW_DIR, "settings.json");
|
|
301
|
+
var HISTORY_FILE = path2.join(SECRET_DIR, "history.json");
|
|
302
|
+
var USAGE_FILE = path2.join(SECRET_DIR, "usage.json");
|
|
303
|
+
var MEMORIES_FILE = path2.join(SECRET_DIR, "memories.json");
|
|
304
|
+
var TEMP_MEM_FILE = path2.join(SECRET_DIR, "memory-temp.json");
|
|
305
|
+
|
|
298
306
|
// src/utils/secrets.js
|
|
299
|
-
|
|
300
|
-
var
|
|
301
|
-
var AGENT_ROOT = path2.join(__dirname, "../../");
|
|
302
|
-
var SECRET_DIR = path2.join(AGENT_ROOT, "secret");
|
|
303
|
-
var SECRET_FILE = path2.join(SECRET_DIR, "secrets.json");
|
|
307
|
+
import path3 from "path";
|
|
308
|
+
var SECRET_FILE = path3.join(SECRET_DIR, "secrets.json");
|
|
304
309
|
var getAPIKey = async () => {
|
|
305
310
|
try {
|
|
306
311
|
const secrets = readEncryptedJson(SECRET_FILE, {});
|
|
@@ -476,13 +481,8 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
|
476
481
|
|
|
477
482
|
// src/utils/history.js
|
|
478
483
|
import fs3 from "fs-extra";
|
|
479
|
-
import
|
|
480
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
484
|
+
import path4 from "path";
|
|
481
485
|
import { nanoid } from "nanoid";
|
|
482
|
-
var __filename2 = fileURLToPath2(import.meta.url);
|
|
483
|
-
var __dirname2 = path3.dirname(__filename2);
|
|
484
|
-
var AGENT_ROOT2 = path3.join(__dirname2, "../../");
|
|
485
|
-
var HISTORY_FILE = path3.join(AGENT_ROOT2, "secret/history.json");
|
|
486
486
|
var WRITE_LOCK = Promise.resolve();
|
|
487
487
|
var withLock = (op) => {
|
|
488
488
|
const nextLock = WRITE_LOCK.then(async () => {
|
|
@@ -517,7 +517,7 @@ var saveChat = async (id, name, messages) => {
|
|
|
517
517
|
messages,
|
|
518
518
|
updatedAt: Date.now()
|
|
519
519
|
};
|
|
520
|
-
await fs3.ensureDir(
|
|
520
|
+
await fs3.ensureDir(path4.dirname(HISTORY_FILE));
|
|
521
521
|
await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
|
|
522
522
|
});
|
|
523
523
|
};
|
|
@@ -530,7 +530,7 @@ var saveChatTitle = async (id, title) => {
|
|
|
530
530
|
} else {
|
|
531
531
|
history[id] = { name: title, messages: [], updatedAt: Date.now() };
|
|
532
532
|
}
|
|
533
|
-
await fs3.ensureDir(
|
|
533
|
+
await fs3.ensureDir(path4.dirname(HISTORY_FILE));
|
|
534
534
|
await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
|
|
535
535
|
});
|
|
536
536
|
};
|
|
@@ -539,13 +539,12 @@ var deleteChat = async (id) => {
|
|
|
539
539
|
const history = await loadHistory();
|
|
540
540
|
delete history[id];
|
|
541
541
|
await fs3.writeJson(HISTORY_FILE, history, { spaces: 2 });
|
|
542
|
-
|
|
543
|
-
if (await fs3.pathExists(tempFile)) {
|
|
542
|
+
if (await fs3.pathExists(TEMP_MEM_FILE)) {
|
|
544
543
|
try {
|
|
545
|
-
const temp = await fs3.readJson(
|
|
544
|
+
const temp = await fs3.readJson(TEMP_MEM_FILE);
|
|
546
545
|
if (temp[id]) {
|
|
547
546
|
delete temp[id];
|
|
548
|
-
await fs3.writeJson(
|
|
547
|
+
await fs3.writeJson(TEMP_MEM_FILE, temp, { spaces: 2 });
|
|
549
548
|
}
|
|
550
549
|
} catch (e) {
|
|
551
550
|
}
|
|
@@ -582,15 +581,11 @@ var getTruncatedHistory = (history, exchangesToRemove = 4) => {
|
|
|
582
581
|
|
|
583
582
|
// src/utils/usage.js
|
|
584
583
|
import fs4 from "fs-extra";
|
|
585
|
-
import path4 from "path";
|
|
586
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
587
|
-
var __dirname3 = path4.dirname(fileURLToPath3(import.meta.url));
|
|
588
|
-
var USAGE_PATH = path4.join(__dirname3, "../../secret/usage.json");
|
|
589
584
|
var getDailyUsage = async () => {
|
|
590
585
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
591
586
|
try {
|
|
592
|
-
if (await fs4.exists(
|
|
593
|
-
const data = await fs4.readJson(
|
|
587
|
+
if (await fs4.exists(USAGE_FILE)) {
|
|
588
|
+
const data = await fs4.readJson(USAGE_FILE);
|
|
594
589
|
if (data.date === today) {
|
|
595
590
|
return data.stats;
|
|
596
591
|
}
|
|
@@ -599,19 +594,19 @@ var getDailyUsage = async () => {
|
|
|
599
594
|
console.error("Failed to read usage:", err);
|
|
600
595
|
}
|
|
601
596
|
const defaultStats = { agent: 0, background: 0, search: 0 };
|
|
602
|
-
await fs4.writeJson(
|
|
597
|
+
await fs4.writeJson(USAGE_FILE, { date: today, stats: defaultStats }, { spaces: 2 });
|
|
603
598
|
return defaultStats;
|
|
604
599
|
};
|
|
605
600
|
var incrementUsage = async (key) => {
|
|
606
601
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
607
|
-
const data = await fs4.readJson(
|
|
602
|
+
const data = await fs4.readJson(USAGE_FILE).catch(() => ({ date: today, stats: { agent: 0, background: 0, search: 0 } }));
|
|
608
603
|
if (data.date !== today) {
|
|
609
604
|
data.date = today;
|
|
610
605
|
data.stats = { agent: 0, background: 0, search: 0 };
|
|
611
606
|
}
|
|
612
607
|
if (data.stats[key] !== void 0) {
|
|
613
608
|
data.stats[key]++;
|
|
614
|
-
await fs4.writeJson(
|
|
609
|
+
await fs4.writeJson(USAGE_FILE, data, { spaces: 2 });
|
|
615
610
|
}
|
|
616
611
|
};
|
|
617
612
|
var checkQuota = async (key, settings) => {
|
|
@@ -662,10 +657,6 @@ var parseArgs = (argsString) => {
|
|
|
662
657
|
// src/tools/web_search.js
|
|
663
658
|
import fs5 from "fs";
|
|
664
659
|
import path5 from "path";
|
|
665
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
666
|
-
var __filename3 = fileURLToPath4(import.meta.url);
|
|
667
|
-
var __dirname4 = path5.dirname(__filename3);
|
|
668
|
-
var AGENT_ROOT3 = path5.join(__dirname4, "../../");
|
|
669
660
|
var web_search = async (argsString) => {
|
|
670
661
|
const { query, limit = 10 } = parseArgs(argsString);
|
|
671
662
|
if (!query) return 'ERROR: Missing "query" argument for web_search.';
|
|
@@ -693,7 +684,7 @@ Source: ${url}
|
|
|
693
684
|
Snippet: ${snippet}`);
|
|
694
685
|
count++;
|
|
695
686
|
}
|
|
696
|
-
const toolLogDir = path5.join(
|
|
687
|
+
const toolLogDir = path5.join(LOGS_DIR, "tools");
|
|
697
688
|
if (!fs5.existsSync(toolLogDir)) {
|
|
698
689
|
fs5.mkdirSync(toolLogDir, { recursive: true });
|
|
699
690
|
}
|
|
@@ -705,7 +696,7 @@ Results: ${results}
|
|
|
705
696
|
`);
|
|
706
697
|
if (results.length === 0) {
|
|
707
698
|
if (html.includes("anomaly")) {
|
|
708
|
-
const toolErrDir = path5.join(
|
|
699
|
+
const toolErrDir = path5.join(LOGS_DIR, "tools");
|
|
709
700
|
if (!fs5.existsSync(toolErrDir)) {
|
|
710
701
|
fs5.mkdirSync(toolErrDir, { recursive: true });
|
|
711
702
|
}
|
|
@@ -727,11 +718,7 @@ ${finalResults}`;
|
|
|
727
718
|
// src/tools/web_scrape.js
|
|
728
719
|
import fs6 from "fs";
|
|
729
720
|
import path6 from "path";
|
|
730
|
-
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
731
721
|
import * as cuimp2 from "cuimp";
|
|
732
|
-
var __filename4 = fileURLToPath5(import.meta.url);
|
|
733
|
-
var __dirname5 = path6.dirname(__filename4);
|
|
734
|
-
var AGENT_ROOT4 = path6.join(__dirname5, "../../");
|
|
735
722
|
var web_scrape = async (args) => {
|
|
736
723
|
const urlMatch = args.match(/url\s*=\s*["'](.*)["']/);
|
|
737
724
|
const url = urlMatch ? urlMatch[1] : args;
|
|
@@ -752,7 +739,7 @@ var web_scrape = async (args) => {
|
|
|
752
739
|
html = html.replace(/<header\b[^<]*(?:(?!<\/header>)<[^<]*)*<\/header>/gi, "");
|
|
753
740
|
let text = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
754
741
|
const finalContent = text.substring(0, 2e4);
|
|
755
|
-
const toolLogDir = path6.join(
|
|
742
|
+
const toolLogDir = path6.join(LOGS_DIR, "tools");
|
|
756
743
|
if (!fs6.existsSync(toolLogDir)) {
|
|
757
744
|
fs6.mkdirSync(toolLogDir, { recursive: true });
|
|
758
745
|
}
|
|
@@ -771,14 +758,6 @@ ${finalContent}${text.length > 2e4 ? "\n\n[TRUNCATED AT 20K CHARS]" : ""}`;
|
|
|
771
758
|
};
|
|
772
759
|
|
|
773
760
|
// src/tools/memory.js
|
|
774
|
-
import path7 from "path";
|
|
775
|
-
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
776
|
-
var __filename5 = fileURLToPath6(import.meta.url);
|
|
777
|
-
var __dirname6 = path7.dirname(__filename5);
|
|
778
|
-
var AGENT_ROOT5 = path7.join(__dirname6, "../../");
|
|
779
|
-
var SECRET_DIR2 = path7.join(AGENT_ROOT5, "secret");
|
|
780
|
-
var MEMORIES_PATH = path7.join(SECRET_DIR2, "memories.json");
|
|
781
|
-
var TEMP_MEM_PATH = path7.join(SECRET_DIR2, "memory-temp.json");
|
|
782
761
|
var memory = async (rawArgs, context = {}) => {
|
|
783
762
|
const parseArg = (key) => {
|
|
784
763
|
const regex = new RegExp(`${key}\\s*=\\s*(["'])(.*?)\\1(?=\\s*[,)]|\\s+\\w+\\s*=|$)`, "s");
|
|
@@ -793,23 +772,23 @@ var memory = async (rawArgs, context = {}) => {
|
|
|
793
772
|
const chatId = parseArg("chat-id") || context.chatId || context.sessionId || "default-session";
|
|
794
773
|
if (action === "temp") {
|
|
795
774
|
if (!content) return "ERROR: Missing 'content' for temp memory.";
|
|
796
|
-
const tempStorage = readEncryptedJson(
|
|
775
|
+
const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
|
|
797
776
|
if (!tempStorage[chatId]) tempStorage[chatId] = [];
|
|
798
|
-
const MAX_CHARS =
|
|
777
|
+
const MAX_CHARS = 3e3 * 4;
|
|
799
778
|
let currentTotalLength = tempStorage[chatId].reduce((acc, m) => acc + m.length, 0);
|
|
800
779
|
while (tempStorage[chatId].length > 0 && currentTotalLength + content.length > MAX_CHARS) {
|
|
801
780
|
const removed = tempStorage[chatId].shift();
|
|
802
781
|
currentTotalLength -= removed.length;
|
|
803
782
|
}
|
|
804
783
|
tempStorage[chatId].push(content);
|
|
805
|
-
writeEncryptedJson(
|
|
784
|
+
writeEncryptedJson(TEMP_MEM_FILE, tempStorage);
|
|
806
785
|
return `SUCCESS: Temporary context saved for session [${chatId}]. (Size: ${currentTotalLength + content.length} chars)`;
|
|
807
786
|
}
|
|
808
787
|
if (action === "user") {
|
|
809
|
-
const memories = readEncryptedJson(
|
|
788
|
+
const memories = readEncryptedJson(MEMORIES_FILE, []);
|
|
810
789
|
if (method === "add") {
|
|
811
790
|
if (!content) return "ERROR: Missing 'content' for memory addition.";
|
|
812
|
-
const MAX_CHARS =
|
|
791
|
+
const MAX_CHARS = 2e3 * 4;
|
|
813
792
|
let currentTotalLength = memories.reduce((acc, m) => acc + (m.memory?.length || 0), 0);
|
|
814
793
|
while (memories.length > 0 && currentTotalLength + content.length > MAX_CHARS) {
|
|
815
794
|
const removed = memories.shift();
|
|
@@ -817,7 +796,7 @@ var memory = async (rawArgs, context = {}) => {
|
|
|
817
796
|
}
|
|
818
797
|
const newMemory = { id: `mem-${Date.now().toString(36)}`, memory: content };
|
|
819
798
|
memories.push(newMemory);
|
|
820
|
-
writeEncryptedJson(
|
|
799
|
+
writeEncryptedJson(MEMORIES_FILE, memories);
|
|
821
800
|
return `SUCCESS: Memory added with ID [${newMemory.id}]. (Vault Size: ${currentTotalLength + content.length} chars)`;
|
|
822
801
|
}
|
|
823
802
|
if (method === "update") {
|
|
@@ -827,7 +806,7 @@ var memory = async (rawArgs, context = {}) => {
|
|
|
827
806
|
const index = memories.findIndex((m) => m.id === memId);
|
|
828
807
|
if (index === -1) return `ERROR: Memory ID [${memId}] not found.`;
|
|
829
808
|
memories[index].memory = newText;
|
|
830
|
-
writeEncryptedJson(
|
|
809
|
+
writeEncryptedJson(MEMORIES_FILE, memories);
|
|
831
810
|
return `SUCCESS: Memory [${memId}] updated.`;
|
|
832
811
|
}
|
|
833
812
|
if (method === "delete") {
|
|
@@ -836,7 +815,7 @@ var memory = async (rawArgs, context = {}) => {
|
|
|
836
815
|
const initialLen = memories.length;
|
|
837
816
|
const updatedMemories = memories.filter((m) => m.id !== memId);
|
|
838
817
|
if (updatedMemories.length === initialLen) return `ERROR: Memory ID [${memId}] not found.`;
|
|
839
|
-
writeEncryptedJson(
|
|
818
|
+
writeEncryptedJson(MEMORIES_FILE, updatedMemories);
|
|
840
819
|
return `SUCCESS: Memory [${memId}] deleted.`;
|
|
841
820
|
}
|
|
842
821
|
return `ERROR: Invalid method [${method}] for user memory. Use 'add', 'update', or 'delete'.`;
|
|
@@ -900,10 +879,10 @@ var summary = async (rawArgs, context = {}) => {
|
|
|
900
879
|
|
|
901
880
|
// src/tools/list_files.js
|
|
902
881
|
import fs7 from "fs";
|
|
903
|
-
import
|
|
882
|
+
import path7 from "path";
|
|
904
883
|
var list_files = async (args) => {
|
|
905
884
|
const { path: targetPath = "." } = parseArgs(args);
|
|
906
|
-
const absolutePath =
|
|
885
|
+
const absolutePath = path7.resolve(process.cwd(), targetPath);
|
|
907
886
|
try {
|
|
908
887
|
if (!fs7.existsSync(absolutePath)) {
|
|
909
888
|
return `ERROR: Path [${targetPath}] does not exist.`;
|
|
@@ -920,7 +899,7 @@ var list_files = async (args) => {
|
|
|
920
899
|
const maxDisplay = 100;
|
|
921
900
|
const displayFiles = files2.slice(0, maxDisplay);
|
|
922
901
|
const list = displayFiles.map((file) => {
|
|
923
|
-
const fPath =
|
|
902
|
+
const fPath = path7.join(absolutePath, file);
|
|
924
903
|
let indicator = "\u{1F4C4}";
|
|
925
904
|
let metaPart = "";
|
|
926
905
|
try {
|
|
@@ -955,11 +934,11 @@ ${list}${footer}`;
|
|
|
955
934
|
|
|
956
935
|
// src/tools/view_file.js
|
|
957
936
|
import fs8 from "fs";
|
|
958
|
-
import
|
|
937
|
+
import path8 from "path";
|
|
959
938
|
var view_file = async (args) => {
|
|
960
939
|
const { path: targetPath, start_line = 1, end_line = 500 } = parseArgs(args);
|
|
961
940
|
if (!targetPath) return 'ERROR: Missing "path" argument for view_file.';
|
|
962
|
-
const absolutePath =
|
|
941
|
+
const absolutePath = path8.resolve(process.cwd(), targetPath);
|
|
963
942
|
try {
|
|
964
943
|
if (!fs8.existsSync(absolutePath)) {
|
|
965
944
|
return `ERROR: File [${targetPath}] does not exist.`;
|
|
@@ -986,14 +965,14 @@ ${code}`;
|
|
|
986
965
|
|
|
987
966
|
// src/tools/write_file.js
|
|
988
967
|
import fs9 from "fs";
|
|
989
|
-
import
|
|
968
|
+
import path9 from "path";
|
|
990
969
|
var write_file = async (args) => {
|
|
991
970
|
let { path: targetPath, content } = parseArgs(args);
|
|
992
971
|
if (!targetPath) return 'ERROR: Missing "path" argument for write_file.';
|
|
993
972
|
if (content === void 0) return 'ERROR: Missing "content" argument for write_file.';
|
|
994
973
|
content = content.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").trim();
|
|
995
|
-
const absolutePath =
|
|
996
|
-
const parentDir =
|
|
974
|
+
const absolutePath = path9.resolve(process.cwd(), targetPath);
|
|
975
|
+
const parentDir = path9.dirname(absolutePath);
|
|
997
976
|
try {
|
|
998
977
|
if (!fs9.existsSync(parentDir)) {
|
|
999
978
|
fs9.mkdirSync(parentDir, { recursive: true });
|
|
@@ -1034,7 +1013,7 @@ ${snippet}`;
|
|
|
1034
1013
|
|
|
1035
1014
|
// src/tools/update_file.js
|
|
1036
1015
|
import fs10 from "fs";
|
|
1037
|
-
import
|
|
1016
|
+
import path10 from "path";
|
|
1038
1017
|
var update_file = async (args) => {
|
|
1039
1018
|
let { path: targetPath, content_to_replace, content_to_add } = parseArgs(args);
|
|
1040
1019
|
if (!targetPath) return 'ERROR: Missing "path" argument for update_file.';
|
|
@@ -1043,7 +1022,7 @@ var update_file = async (args) => {
|
|
|
1043
1022
|
const strip = (t) => t.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").trim();
|
|
1044
1023
|
content_to_replace = strip(content_to_replace);
|
|
1045
1024
|
content_to_add = strip(content_to_add);
|
|
1046
|
-
const absolutePath =
|
|
1025
|
+
const absolutePath = path10.resolve(process.cwd(), targetPath);
|
|
1047
1026
|
try {
|
|
1048
1027
|
if (!fs10.existsSync(absolutePath)) {
|
|
1049
1028
|
return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
|
|
@@ -1136,10 +1115,10 @@ ${finalOutput}`);
|
|
|
1136
1115
|
|
|
1137
1116
|
// src/tools/read_folder.js
|
|
1138
1117
|
import fs11 from "fs";
|
|
1139
|
-
import
|
|
1118
|
+
import path11 from "path";
|
|
1140
1119
|
var read_folder = async (args) => {
|
|
1141
1120
|
const { path: targetPath = "." } = parseArgs(args);
|
|
1142
|
-
const absolutePath =
|
|
1121
|
+
const absolutePath = path11.resolve(process.cwd(), targetPath);
|
|
1143
1122
|
try {
|
|
1144
1123
|
if (!fs11.existsSync(absolutePath)) {
|
|
1145
1124
|
return `ERROR: Path [${targetPath}] does not exist.`;
|
|
@@ -1153,7 +1132,7 @@ var read_folder = async (args) => {
|
|
|
1153
1132
|
const displayItems = files.slice(0, maxDisplay);
|
|
1154
1133
|
const folderData = [];
|
|
1155
1134
|
for (const file of displayItems) {
|
|
1156
|
-
const fPath =
|
|
1135
|
+
const fPath = path11.join(absolutePath, file);
|
|
1157
1136
|
let indicator = "\u{1F4C4}";
|
|
1158
1137
|
let info = { name: file, type: "unknown", size: "N/A", mtime: "N/A" };
|
|
1159
1138
|
try {
|
|
@@ -1223,15 +1202,9 @@ var dispatchTool = async (toolName, args, context = {}) => {
|
|
|
1223
1202
|
};
|
|
1224
1203
|
|
|
1225
1204
|
// src/utils/ai.js
|
|
1226
|
-
import
|
|
1227
|
-
import path13 from "path";
|
|
1205
|
+
import path12 from "path";
|
|
1228
1206
|
import fs12 from "fs";
|
|
1229
|
-
var __filename6 = fileURLToPath7(import.meta.url);
|
|
1230
|
-
var __dirname7 = path13.dirname(__filename6);
|
|
1231
|
-
var AGENT_ROOT6 = path13.join(__dirname7, "../../");
|
|
1232
1207
|
var client = null;
|
|
1233
|
-
var TEMP_MEM_PATH2 = path13.join(AGENT_ROOT6, "secret", "memory-temp.json");
|
|
1234
|
-
var PERSISTENT_MEM_PATH = path13.join(AGENT_ROOT6, "secret", "memories.json");
|
|
1235
1208
|
var TERMINATION_SIGNAL = false;
|
|
1236
1209
|
var signalTermination = () => {
|
|
1237
1210
|
TERMINATION_SIGNAL = true;
|
|
@@ -1285,12 +1258,12 @@ var getAIStream = async function* (modelName, history, settings, steeringCallbac
|
|
|
1285
1258
|
const needTitle = isFirstPrompt || hasTitleSignal;
|
|
1286
1259
|
const agentText = originalText.replace(/\[TITLE-UPDATE\]/g, "").trim();
|
|
1287
1260
|
let modifiedHistory = [...history.slice(0, -1)];
|
|
1288
|
-
if (systemSettings?.compression === 0 && (sessionStats?.tokens || 0) >
|
|
1261
|
+
if (systemSettings?.compression === 0 && (sessionStats?.tokens || 0) > 196e3) {
|
|
1289
1262
|
modifiedHistory = getTruncatedHistory(modifiedHistory, 4);
|
|
1290
1263
|
}
|
|
1291
|
-
const tempStorage = readEncryptedJson(
|
|
1264
|
+
const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
|
|
1292
1265
|
const otherMemories = Object.entries(tempStorage).filter(([id]) => id !== chatId).flatMap(([_, mems]) => mems).map((mem) => `- ${mem}`).join("\n");
|
|
1293
|
-
const persistentStorage = readEncryptedJson(
|
|
1266
|
+
const persistentStorage = readEncryptedJson(MEMORIES_FILE, []);
|
|
1294
1267
|
const mainUserMemories = persistentStorage.map((m) => `- ${m.memory}`).join("\n");
|
|
1295
1268
|
const janitorUserMemories = persistentStorage.map((m) => `- [${m.id}]: ${m.memory}`).join("\n");
|
|
1296
1269
|
const systemInstruction = getSystemInstruction(profile, thinkingLevel, mode, systemSettings, otherMemories, mainUserMemories, isMemoryEnabled);
|
|
@@ -1344,11 +1317,11 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
1344
1317
|
} catch (err) {
|
|
1345
1318
|
const errMsg = err.message || String(err);
|
|
1346
1319
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
1347
|
-
const agentErrDir =
|
|
1320
|
+
const agentErrDir = path12.join(LOGS_DIR, "agent");
|
|
1348
1321
|
if (!fs12.existsSync(agentErrDir)) {
|
|
1349
1322
|
fs12.mkdirSync(agentErrDir, { recursive: true });
|
|
1350
1323
|
}
|
|
1351
|
-
fs12.appendFileSync(
|
|
1324
|
+
fs12.appendFileSync(path12.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errMsg}
|
|
1352
1325
|
`);
|
|
1353
1326
|
const isRetryable = errMsg.includes("429") || errMsg.includes("503") || errMsg.includes("overloaded") || errMsg.includes("deadline");
|
|
1354
1327
|
if (isRetryable && retryCount < MAX_RETRIES) {
|
|
@@ -1394,7 +1367,17 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
1394
1367
|
const url = parseArgs(toolCall.args).url || "...";
|
|
1395
1368
|
label = `\u{1F4D6} READING SITE: ${url}`.toUpperCase();
|
|
1396
1369
|
} else if (toolCall.toolName === "view_file") {
|
|
1397
|
-
|
|
1370
|
+
const { path: targetPath2, start_line = 1, end_line = 500 } = parseArgs(toolCall.args);
|
|
1371
|
+
let totalLines = "...";
|
|
1372
|
+
try {
|
|
1373
|
+
const absPath = path12.resolve(process.cwd(), targetPath2);
|
|
1374
|
+
if (fs12.existsSync(absPath)) {
|
|
1375
|
+
const content = fs12.readFileSync(absPath, "utf8");
|
|
1376
|
+
totalLines = content.split("\n").length;
|
|
1377
|
+
}
|
|
1378
|
+
} catch (e) {
|
|
1379
|
+
}
|
|
1380
|
+
label = `\u{1F4C4} READING FILE: ${targetPath2}. LINES ${start_line} - ${end_line} FROM ${totalLines}`.toUpperCase();
|
|
1398
1381
|
} else if (toolCall.toolName === "list_files" || toolCall.toolName === "read_folder") {
|
|
1399
1382
|
const action = toolCall.toolName === "list_files" ? "LISTING" : "DISCOVERING";
|
|
1400
1383
|
label = `\u{1F4C2} ${action} DIRECTORY: ${parseArgs(toolCall.args).path || "."}`.toUpperCase();
|
|
@@ -1435,7 +1418,7 @@ ${boxBottom}
|
|
|
1435
1418
|
/\/usr\//
|
|
1436
1419
|
// Sensitive Linux paths
|
|
1437
1420
|
];
|
|
1438
|
-
const currentDrive =
|
|
1421
|
+
const currentDrive = path12.resolve(process.cwd()).substring(0, 3).toLowerCase();
|
|
1439
1422
|
const isViolating = riskyPatterns.some((pattern) => {
|
|
1440
1423
|
if (pattern.source === "[a-zA-Z]:[\\\\\\/]") {
|
|
1441
1424
|
const driveMatch = command.match(/[a-zA-Z]:[\\\/]/i);
|
|
@@ -1457,8 +1440,8 @@ ${boxBottom}
|
|
|
1457
1440
|
const targetPath = parsedArgs.path || parsedArgs.targetPath || null;
|
|
1458
1441
|
if (targetPath) {
|
|
1459
1442
|
const isExternalOff = settings.systemSettings && settings.systemSettings.allowExternalAccess === false;
|
|
1460
|
-
const absoluteTarget =
|
|
1461
|
-
const absoluteCwd =
|
|
1443
|
+
const absoluteTarget = path12.resolve(targetPath);
|
|
1444
|
+
const absoluteCwd = path12.resolve(process.cwd());
|
|
1462
1445
|
if (isExternalOff && !absoluteTarget.startsWith(absoluteCwd)) {
|
|
1463
1446
|
const denyMsg = `Access Denied. You are not allowed to access files outside the current workspace. To enable this, ask the user to turn on "External Workspace Access" in /settings.`;
|
|
1464
1447
|
toolResults.push(`[TOOL_RESULT]: ERROR: ${denyMsg}`);
|
|
@@ -1497,11 +1480,11 @@ ${boxBottom}
|
|
|
1497
1480
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
1498
1481
|
const isErr = result.startsWith("ERROR:");
|
|
1499
1482
|
const logStatus = isErr ? result.trim() : "SUCCESS";
|
|
1500
|
-
const toolHistDir =
|
|
1483
|
+
const toolHistDir = path12.join(LOGS_DIR, "tools");
|
|
1501
1484
|
if (!fs12.existsSync(toolHistDir)) {
|
|
1502
1485
|
fs12.mkdirSync(toolHistDir, { recursive: true });
|
|
1503
1486
|
}
|
|
1504
|
-
fs12.appendFileSync(
|
|
1487
|
+
fs12.appendFileSync(path12.join(toolHistDir, "history.log"), `HISTORY [${timestamp}]: ${toolCall.toolName} [${logStatus}]
|
|
1505
1488
|
`);
|
|
1506
1489
|
} catch (logErr) {
|
|
1507
1490
|
}
|
|
@@ -1555,11 +1538,11 @@ ${boxBottom}
|
|
|
1555
1538
|
if (parts && parts[1]?.text) {
|
|
1556
1539
|
finalSynthesis = parts[1].text;
|
|
1557
1540
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
1558
|
-
const janitorLogDir =
|
|
1541
|
+
const janitorLogDir = path12.join(LOGS_DIR, "janitor");
|
|
1559
1542
|
if (!fs12.existsSync(janitorLogDir)) {
|
|
1560
1543
|
fs12.mkdirSync(janitorLogDir, { recursive: true });
|
|
1561
1544
|
}
|
|
1562
|
-
fs12.appendFileSync(
|
|
1545
|
+
fs12.appendFileSync(path12.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: ${finalSynthesis}
|
|
1563
1546
|
`);
|
|
1564
1547
|
} else if (parts && parts[0]?.text) finalSynthesis = parts[0].text;
|
|
1565
1548
|
else if (janitorResult.response && janitorResult.response.text) finalSynthesis = janitorResult.response.text();
|
|
@@ -1571,8 +1554,8 @@ ${boxBottom}
|
|
|
1571
1554
|
const toolContext = { chatId, sessionId: chatId, history };
|
|
1572
1555
|
const result = await dispatchTool(janitorToolCall.toolName, janitorToolCall.args, toolContext);
|
|
1573
1556
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
1574
|
-
const janitorLogDir =
|
|
1575
|
-
fs12.appendFileSync(
|
|
1557
|
+
const janitorLogDir = path12.join(LOGS_DIR, "janitor");
|
|
1558
|
+
fs12.appendFileSync(path12.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
|
|
1576
1559
|
`);
|
|
1577
1560
|
if (janitorToolCall.toolName === "memory" && !janitorToolCall.args.includes("action='temp'")) {
|
|
1578
1561
|
yield { type: "memory_updated" };
|
|
@@ -1580,11 +1563,11 @@ ${boxBottom}
|
|
|
1580
1563
|
}
|
|
1581
1564
|
} catch (janitorErr) {
|
|
1582
1565
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
1583
|
-
const janitorErrDir =
|
|
1566
|
+
const janitorErrDir = path12.join(LOGS_DIR, "janitor");
|
|
1584
1567
|
if (!fs12.existsSync(janitorErrDir)) {
|
|
1585
1568
|
fs12.mkdirSync(janitorErrDir, { recursive: true });
|
|
1586
1569
|
}
|
|
1587
|
-
fs12.appendFileSync(
|
|
1570
|
+
fs12.appendFileSync(path12.join(janitorErrDir, "error.log"), `ERROR [${date}]: ${janitorErr.message}
|
|
1588
1571
|
`);
|
|
1589
1572
|
console.error("Janitor Background Tasks Failed:", janitorErr.message);
|
|
1590
1573
|
}
|
|
@@ -1612,10 +1595,6 @@ ${timestamp}`;
|
|
|
1612
1595
|
|
|
1613
1596
|
// src/utils/settings.js
|
|
1614
1597
|
import fs13 from "fs-extra";
|
|
1615
|
-
import path14 from "path";
|
|
1616
|
-
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
1617
|
-
var __dirname8 = path14.dirname(fileURLToPath8(import.meta.url));
|
|
1618
|
-
var SETTINGS_PATH = path14.join(__dirname8, "../../settings.json");
|
|
1619
1598
|
var DEFAULT_SETTINGS = {
|
|
1620
1599
|
mode: "Flux",
|
|
1621
1600
|
thinkingLevel: "Medium",
|
|
@@ -1644,8 +1623,8 @@ var DEFAULT_SETTINGS = {
|
|
|
1644
1623
|
};
|
|
1645
1624
|
var loadSettings = async () => {
|
|
1646
1625
|
try {
|
|
1647
|
-
if (await fs13.exists(
|
|
1648
|
-
const saved = await fs13.readJson(
|
|
1626
|
+
if (await fs13.exists(SETTINGS_FILE)) {
|
|
1627
|
+
const saved = await fs13.readJson(SETTINGS_FILE);
|
|
1649
1628
|
return {
|
|
1650
1629
|
...DEFAULT_SETTINGS,
|
|
1651
1630
|
...saved,
|
|
@@ -1663,7 +1642,7 @@ var saveSettings = async (settings) => {
|
|
|
1663
1642
|
try {
|
|
1664
1643
|
const current = await loadSettings();
|
|
1665
1644
|
const updated = { ...current, ...settings };
|
|
1666
|
-
await fs13.writeJson(
|
|
1645
|
+
await fs13.writeJson(SETTINGS_FILE, updated, { spaces: 2 });
|
|
1667
1646
|
return true;
|
|
1668
1647
|
} catch (err) {
|
|
1669
1648
|
console.error("Failed to save settings:", err);
|
|
@@ -1712,13 +1691,13 @@ function ResumeModal({ onSelect, onDelete, onClose }) {
|
|
|
1712
1691
|
// src/components/MemoryModal.jsx
|
|
1713
1692
|
import React7, { useState as useState3, useEffect as useEffect2 } from "react";
|
|
1714
1693
|
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
1715
|
-
import
|
|
1716
|
-
var
|
|
1694
|
+
import path13 from "path";
|
|
1695
|
+
var MEMORIES_PATH = path13.join(process.cwd(), "secret", "memories.json");
|
|
1717
1696
|
function MemoryModal({ onClose }) {
|
|
1718
1697
|
const [memories, setMemories] = useState3([]);
|
|
1719
1698
|
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1720
1699
|
const loadMemories = () => {
|
|
1721
|
-
const data = readEncryptedJson(
|
|
1700
|
+
const data = readEncryptedJson(MEMORIES_PATH, []);
|
|
1722
1701
|
setMemories(data);
|
|
1723
1702
|
};
|
|
1724
1703
|
useEffect2(() => {
|
|
@@ -1731,7 +1710,7 @@ function MemoryModal({ onClose }) {
|
|
|
1731
1710
|
if (input === "x" && memories.length > 0) {
|
|
1732
1711
|
const idToDelete = memories[selectedIndex].id;
|
|
1733
1712
|
const updated = memories.filter((m) => m.id !== idToDelete);
|
|
1734
|
-
writeEncryptedJson(
|
|
1713
|
+
writeEncryptedJson(MEMORIES_PATH, updated);
|
|
1735
1714
|
setMemories(updated);
|
|
1736
1715
|
if (selectedIndex >= updated.length && updated.length > 0) {
|
|
1737
1716
|
setSelectedIndex(updated.length - 1);
|
|
@@ -2086,18 +2065,19 @@ ${list || "No saved chats found."}` }];
|
|
|
2086
2065
|
}
|
|
2087
2066
|
case "/reset": {
|
|
2088
2067
|
const runReset = async () => {
|
|
2089
|
-
const AGENT_ROOT7 = path16.join(path16.dirname(fileURLToPath9(import.meta.url)), "../");
|
|
2090
|
-
const logsDir = path16.join(AGENT_ROOT7, "logs");
|
|
2091
|
-
const secretDir = path16.join(AGENT_ROOT7, "secret");
|
|
2092
|
-
const settingsFile = path16.join(AGENT_ROOT7, "settings.json");
|
|
2093
2068
|
try {
|
|
2094
2069
|
setMessages((prev) => {
|
|
2095
2070
|
setCompletedIndex(prev.length + 1);
|
|
2096
2071
|
return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset..." }];
|
|
2097
2072
|
});
|
|
2098
|
-
if (fs14.existsSync(
|
|
2099
|
-
if (fs14.existsSync(
|
|
2100
|
-
if (fs14.existsSync(
|
|
2073
|
+
if (fs14.existsSync(LOGS_DIR)) fs14.removeSync(LOGS_DIR);
|
|
2074
|
+
if (fs14.existsSync(SECRET_DIR)) fs14.removeSync(SECRET_DIR);
|
|
2075
|
+
if (fs14.existsSync(SETTINGS_FILE)) fs14.removeSync(SETTINGS_FILE);
|
|
2076
|
+
try {
|
|
2077
|
+
const items = fs14.readdirSync(FLUXFLOW_DIR);
|
|
2078
|
+
if (items.length === 0) fs14.removeSync(FLUXFLOW_DIR);
|
|
2079
|
+
} catch (e) {
|
|
2080
|
+
}
|
|
2101
2081
|
setTimeout(() => {
|
|
2102
2082
|
setActiveView("exit");
|
|
2103
2083
|
setTimeout(() => process.exit(0), 500);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluxflow-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A high-fidelity agentic terminal assistant for the Flux Era.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -23,15 +23,18 @@
|
|
|
23
23
|
"dist",
|
|
24
24
|
"README.md",
|
|
25
25
|
"package.json",
|
|
26
|
-
"ARCHITECTURE.md"
|
|
26
|
+
"ARCHITECTURE.md",
|
|
27
|
+
"TOOLS.md",
|
|
28
|
+
"UI_FEATURES.md"
|
|
27
29
|
],
|
|
28
30
|
"type": "module",
|
|
29
31
|
"bin": {
|
|
30
|
-
"fluxflow-cli": "dist/
|
|
32
|
+
"fluxflow-cli": "dist/fluxflow.js",
|
|
33
|
+
"fluxflow": "dist/fluxflow.js"
|
|
31
34
|
},
|
|
32
35
|
"scripts": {
|
|
33
36
|
"start": "tsx ./src/cli.jsx",
|
|
34
|
-
"build": "esbuild ./src/cli.jsx --bundle --platform=node --format=esm --outfile=./dist/
|
|
37
|
+
"build": "esbuild ./src/cli.jsx --bundle --platform=node --format=esm --outfile=./dist/fluxflow.js --external:react --external:ink --external:chalk --external:fs-extra --external:gradient-string --external:ink-text-input --external:ink-select-input --external:ink-spinner --external:ink-multiline-input --external:@google/genai --external:zod --external:duck-duck-scrape --external:nanoid --external:cuimp"
|
|
35
38
|
},
|
|
36
39
|
"dependencies": {
|
|
37
40
|
"@google/genai": "^1.50.1",
|