@nomad-e/bluma-cli 0.0.104 → 0.0.105
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/README.md +66 -44
- package/dist/config/native_tools.json +43 -0
- package/dist/main.js +131 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# BluMa
|
|
1
|
+
# BluMa — Base Language Unit · Model Agent
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/bluma)
|
|
4
4
|
[](LICENSE)
|
|
@@ -8,16 +8,18 @@
|
|
|
8
8
|
<img src="https://pharmaseedevsa.blob.core.windows.net/pharmassee-dev-storage/bluma.png" alt="Screenshot BluMa CLI" width="1000"/>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
BluMa
|
|
11
|
+
BluMa is a CLI-based model agent responsible for language-level code generation, refactoring and semantic transformations in the Factor AI stack. The project is a conversational assistant that interacts via terminal (CLI), built with React/Ink, supporting smart agents (LLM via OpenRouter), tool execution, persistent history, session management, and extensibility through external plugins/tools.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
## Table of Contents
|
|
16
16
|
- [Overview](#overview)
|
|
17
|
+
- [Why BluMa?](#why-bluma)
|
|
17
18
|
- [Key Features](#key-features)
|
|
18
19
|
- [Requirements](#requirements)
|
|
19
|
-
- [
|
|
20
|
+
- [Quick Start](#quick-start)
|
|
20
21
|
- [Installation](#installation)
|
|
22
|
+
- [Screenshots](#screenshots)
|
|
21
23
|
- [Usage](#usage)
|
|
22
24
|
- [Examples](#-usage-examples)
|
|
23
25
|
- [Configuration and Environment Variables](#configuration-and-environment-variables)
|
|
@@ -33,14 +35,27 @@ BluMa CLI is an independent agent for automation and advanced software engineeri
|
|
|
33
35
|
---
|
|
34
36
|
|
|
35
37
|
## <a name="overview"></a>Overview
|
|
36
|
-
BluMa CLI is a modular conversational agent and task automation framework focused on advanced software engineering workflows. It runs entirely in the terminal using React (via Ink) for a rich interactive UI, and is architected around a **UI layer** (`main.ts` + `App.tsx`) and an **agent layer** (`Agent` orchestrator + `BluMaAgent` core). It enables LLM-powered automation, documentation, refactoring, running complex development tasks, and integrating with both native and external tools. The system features persistent sessions, contextual reasoning, smart feedback, and an interactive confirmation system for controlled execution.
|
|
38
|
+
BluMa is a CLI-based model agent responsible for language-level code generation, refactoring and semantic transformations in the Factor AI stack. It is a modular conversational agent and task automation framework focused on advanced software engineering workflows. It runs entirely in the terminal using React (via Ink) for a rich interactive UI, and is architected around a **UI layer** (`main.ts` + `App.tsx`) and an **agent layer** (`Agent` orchestrator + `BluMaAgent` core). It enables LLM-powered automation, documentation, refactoring, running complex development tasks, and integrating with both native and external tools. The system features persistent sessions, contextual reasoning, smart feedback, and an interactive confirmation system for controlled execution.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why BluMa?
|
|
43
|
+
BluMa stands out as the premier CLI-based model agent for software engineering:
|
|
44
|
+
|
|
45
|
+
- **Language-Level Expertise:** Specializes in code generation, refactoring, and semantic transformations, making it ideal for Factor AI stack development.
|
|
46
|
+
- **Conversational Automation:** Interact naturally with an AI that understands context, history, and your project's needs.
|
|
47
|
+
- **Secure & Controlled:** Built-in confirmations and whitelists ensure safe execution of powerful tools.
|
|
48
|
+
- **Extensible & Modular:** Easily add tools and plugins to adapt to your workflow.
|
|
49
|
+
- **Real-Time Collaboration:** Live overlays allow pair-programming style guidance during processing.
|
|
50
|
+
|
|
51
|
+
Choose BluMa for intelligent, efficient, and collaborative software engineering automation.
|
|
37
52
|
|
|
38
53
|
---
|
|
39
54
|
|
|
40
55
|
## <a name="key-features"></a>Key Features
|
|
41
56
|
- **Rich CLI interface** using React/Ink 5, with interactive prompts and custom components.
|
|
42
57
|
- **Session management:** automatic persistence of conversation and tool history via files.
|
|
43
|
-
- **Central agent (LLM):** orchestrated by
|
|
58
|
+
- **Central agent (LLM):** orchestrated by OpenRouter, enabling natural language-driven automation.
|
|
44
59
|
- **Tool invocation:** native and via MCP SDK for running commands, code manipulation, file management, and more.
|
|
45
60
|
- **Dynamic prompts:** builds live conversational context, behavioral rules, and technical history.
|
|
46
61
|
- **Smart feedback component** with technical suggestions and checks.
|
|
@@ -52,7 +67,7 @@ BluMa CLI is a modular conversational agent and task automation framework focuse
|
|
|
52
67
|
## <a name="requirements"></a>Requirements
|
|
53
68
|
- Node.js >= 18
|
|
54
69
|
- npm >= 9
|
|
55
|
-
-
|
|
70
|
+
- OpenRouter API key (get one at [openrouter.ai](https://openrouter.ai))
|
|
56
71
|
|
|
57
72
|
---
|
|
58
73
|
|
|
@@ -82,27 +97,17 @@ If you get permission errors, EXAMPLES:
|
|
|
82
97
|
> Only use sudo to install, never to run the CLI.
|
|
83
98
|
|
|
84
99
|
### Setting Up Environment Variables
|
|
85
|
-
For BluMa CLI to operate
|
|
100
|
+
For BluMa CLI to operate, set the following environment variable globally in your system.
|
|
86
101
|
|
|
87
102
|
**Required:**
|
|
88
|
-
- `
|
|
89
|
-
- `AZURE_OPENAI_API_KEY`
|
|
90
|
-
- `AZURE_OPENAI_API_VERSION`
|
|
91
|
-
- `AZURE_OPENAI_DEPLOYMENT`
|
|
92
|
-
- `GITHUB_PERSONAL_ACCESS_TOKEN` (if you'll use GitHub)
|
|
93
|
-
- `NOTION_API_TOKEN` (if you'll use Notion)
|
|
103
|
+
- `OPENROUTER_API_KEY` (get your key at [openrouter.ai](https://openrouter.ai))
|
|
94
104
|
|
|
95
105
|
#### How to set environment variables globally:
|
|
96
106
|
|
|
97
107
|
**Linux/macOS:**
|
|
98
108
|
Add to your `~/.bashrc`, `~/.zshrc`, or equivalent:
|
|
99
109
|
```sh
|
|
100
|
-
export
|
|
101
|
-
export AZURE_OPENAI_API_KEY="your_key"
|
|
102
|
-
export AZURE_OPENAI_API_VERSION="2025-01-01-preview"
|
|
103
|
-
export AZURE_OPENAI_DEPLOYMENT="bluma-gpt"
|
|
104
|
-
export GITHUB_PERSONAL_ACCESS_TOKEN="..."
|
|
105
|
-
export NOTION_API_TOKEN="..."
|
|
110
|
+
export OPENROUTER_API_KEY="your_openrouter_key"
|
|
106
111
|
```
|
|
107
112
|
Then run:
|
|
108
113
|
```sh
|
|
@@ -111,23 +116,13 @@ source ~/.bashrc # or whichever file you edited
|
|
|
111
116
|
|
|
112
117
|
**Windows (CMD):**
|
|
113
118
|
```cmd
|
|
114
|
-
setx
|
|
115
|
-
setx AZURE_OPENAI_API_KEY "your_key"
|
|
116
|
-
setx AZURE_OPENAI_API_VERSION "2025-01-01-preview"
|
|
117
|
-
setx AZURE_OPENAI_DEPLOYMENT "bluma-gpt"
|
|
118
|
-
setx GITHUB_PERSONAL_ACCESS_TOKEN "..."
|
|
119
|
-
setx NOTION_API_TOKEN "..."
|
|
119
|
+
setx OPENROUTER_API_KEY "your_openrouter_key"
|
|
120
120
|
```
|
|
121
121
|
(Only needs to be run once per variable. Restart the terminal after.)
|
|
122
122
|
|
|
123
123
|
**Windows (PowerShell):**
|
|
124
124
|
```powershell
|
|
125
|
-
[Environment]::SetEnvironmentVariable("
|
|
126
|
-
[Environment]::SetEnvironmentVariable("AZURE_OPENAI_API_KEY", "your_key", "Machine")
|
|
127
|
-
[Environment]::SetEnvironmentVariable("AZURE_OPENAI_API_VERSION", "2025-01-01-preview", "Machine")
|
|
128
|
-
[Environment]::SetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT", "bluma-gpt", "Machine")
|
|
129
|
-
[Environment]::SetEnvironmentVariable("GITHUB_PERSONAL_ACCESS_TOKEN", "...", "Machine")
|
|
130
|
-
[Environment]::SetEnvironmentVariable("NOTION_API_TOKEN", "...", "Machine")
|
|
125
|
+
[Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "your_openrouter_key", "Machine")
|
|
131
126
|
```
|
|
132
127
|
|
|
133
128
|
### ℹ️ Global Installation of npm Packages in PowerShell (Windows)
|
|
@@ -160,6 +155,40 @@ npx bluma
|
|
|
160
155
|
|
|
161
156
|
---
|
|
162
157
|
|
|
158
|
+
## Quick Start
|
|
159
|
+
|
|
160
|
+
Get up and running with BluMa in minutes:
|
|
161
|
+
|
|
162
|
+
1. **Install BluMa:**
|
|
163
|
+
```bash
|
|
164
|
+
npm install -g @nomad-e/bluma-cli
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
2. **Configure Environment:**
|
|
168
|
+
Set your OpenRouter API key (see [Configuration](#configuration-and-environment-variables)).
|
|
169
|
+
|
|
170
|
+
3. **Launch BluMa:**
|
|
171
|
+
```bash
|
|
172
|
+
bluma
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
4. **Interact:**
|
|
176
|
+
Start a conversation! Try commands like "Help me refactor this code" or "Run tests for my project."
|
|
177
|
+
|
|
178
|
+
For full installation details, see [Installation](#installation).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Screenshots
|
|
183
|
+
|
|
184
|
+
Here's BluMa in action:
|
|
185
|
+
|
|
186
|
+

|
|
187
|
+
|
|
188
|
+
*BluMa's interactive CLI interface for conversational software engineering.*
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
163
192
|
## <a name="project-structure"></a>Project Structure
|
|
164
193
|
```
|
|
165
194
|
bluma-engineer/
|
|
@@ -240,15 +269,10 @@ Notes
|
|
|
240
269
|
---
|
|
241
270
|
|
|
242
271
|
## <a name="configuration-and-environment-variables"></a>Configuration and Environment Variables
|
|
243
|
-
You must create a `.env` file (copy if needed from `.env.example`) with the following
|
|
244
|
-
- `
|
|
245
|
-
- `AZURE_OPENAI_API_KEY`
|
|
246
|
-
- `AZURE_OPENAI_API_VERSION`
|
|
247
|
-
- `AZURE_OPENAI_DEPLOYMENT`
|
|
248
|
-
- `GITHUB_PERSONAL_ACCESS_TOKEN` (optional; required for GitHub integrations)
|
|
249
|
-
- `NOTION_API_TOKEN` (optional; required for Notion integrations)
|
|
272
|
+
You must create a `.env` file (copy if needed from `.env.example`) with the following variable:
|
|
273
|
+
- `OPENROUTER_API_KEY` (required; get from [openrouter.ai](https://openrouter.ai))
|
|
250
274
|
|
|
251
|
-
And others required by your agent/context
|
|
275
|
+
And others required by your agent/context.
|
|
252
276
|
|
|
253
277
|
Advanced config files are located in `src/app/agent/config/`.
|
|
254
278
|
|
|
@@ -261,7 +285,7 @@ Advanced config files are located in `src/app/agent/config/`.
|
|
|
261
285
|
- Bundler: esbuild, with `esbuild-plugin-node-externals`
|
|
262
286
|
- Test Runner: Jest 30 + babel-jest
|
|
263
287
|
- Transpilers: Babel presets (env, react, typescript)
|
|
264
|
-
- LLM/Agent:
|
|
288
|
+
- LLM/Agent: OpenRouter via API; MCP via `@modelcontextprotocol/sdk`
|
|
265
289
|
- Config loading: dotenv
|
|
266
290
|
- Utilities: uuid, diff, react-devtools-core
|
|
267
291
|
|
|
@@ -385,9 +409,7 @@ stateDiagram-v2
|
|
|
385
409
|
```mermaid
|
|
386
410
|
graph TD
|
|
387
411
|
CLI["CLI (BluMa)"] --> LocalFS[("Local File System")]
|
|
388
|
-
CLI -->
|
|
389
|
-
CLI --> GitHubAPI[("GitHub API")]
|
|
390
|
-
CLI --> NotionAPI[("Notion API")]
|
|
412
|
+
CLI --> OpenRouter[("OpenRouter API")]
|
|
391
413
|
CLI --> OtherAPIs[("Other External APIs")]
|
|
392
414
|
CLI --> MCPServer[("MCP Server / Plugins")]
|
|
393
415
|
```
|
|
@@ -472,7 +494,7 @@ We welcome contributions! For full details, read [CONTRIBUTING.md](CONTRIBUTING.
|
|
|
472
494
|
---
|
|
473
495
|
|
|
474
496
|
## ⚠️ Limitations / Next Steps
|
|
475
|
-
- Current LLM integration
|
|
497
|
+
- Current LLM integration uses OpenRouter; add more providers.
|
|
476
498
|
- Logging verbosity could be made configurable.
|
|
477
499
|
- Potential for richer plugin lifecycle (install/remove at runtime).
|
|
478
500
|
- Improve error reporting in subagents.
|
|
@@ -526,6 +526,49 @@
|
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
528
|
},
|
|
529
|
+
{
|
|
530
|
+
"type": "function",
|
|
531
|
+
"function": {
|
|
532
|
+
"name": "create_artifact",
|
|
533
|
+
"description": "Create or update an artifact file in the artifacts directory (~/.bluma/artifacts/). Use this to save implementation plans, walkthroughs, notes, or any document the user should review. Supports markdown files.",
|
|
534
|
+
"parameters": {
|
|
535
|
+
"type": "object",
|
|
536
|
+
"properties": {
|
|
537
|
+
"filename": {
|
|
538
|
+
"type": "string",
|
|
539
|
+
"description": "Name of the artifact file to create. Examples: 'implementation_plan.md', 'walkthrough.md', 'notes.md'."
|
|
540
|
+
},
|
|
541
|
+
"content": {
|
|
542
|
+
"type": "string",
|
|
543
|
+
"description": "Content to write to the artifact file. Supports markdown formatting."
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
"required": [
|
|
547
|
+
"filename",
|
|
548
|
+
"content"
|
|
549
|
+
]
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"type": "function",
|
|
555
|
+
"function": {
|
|
556
|
+
"name": "read_artifact",
|
|
557
|
+
"description": "Read an existing artifact file from the artifacts directory. Use this to retrieve previously saved plans, walkthroughs, or notes.",
|
|
558
|
+
"parameters": {
|
|
559
|
+
"type": "object",
|
|
560
|
+
"properties": {
|
|
561
|
+
"filename": {
|
|
562
|
+
"type": "string",
|
|
563
|
+
"description": "Name of the artifact file to read. Examples: 'implementation_plan.md', 'task.md'."
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
"required": [
|
|
567
|
+
"filename"
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
},
|
|
529
572
|
{
|
|
530
573
|
"type": "function",
|
|
531
574
|
"function": {
|
package/dist/main.js
CHANGED
|
@@ -180,7 +180,7 @@ var HeaderComponent = ({
|
|
|
180
180
|
const dirName = workdir.split("/").pop() || workdir;
|
|
181
181
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
182
182
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
183
|
-
/* @__PURE__ */ jsx(Text, {
|
|
183
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "bluma \u2014 coding agent" }),
|
|
184
184
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
185
185
|
" ",
|
|
186
186
|
VERSION
|
|
@@ -1336,6 +1336,7 @@ import { spawn } from "child_process";
|
|
|
1336
1336
|
var MAX_OUTPUT_SIZE = 1e5;
|
|
1337
1337
|
var OUTPUT_TRUNCATION_MESSAGE = "\n\n[OUTPUT TRUNCATED - exceeded 100KB limit. Use pagination or redirect to file for full output.]";
|
|
1338
1338
|
var DANGEROUS_PATTERNS = [
|
|
1339
|
+
// === ELEVAÇÃO DE PRIVILÉGIOS ===
|
|
1339
1340
|
/^sudo\s+/i,
|
|
1340
1341
|
// sudo commands
|
|
1341
1342
|
/^doas\s+/i,
|
|
@@ -1348,20 +1349,57 @@ var DANGEROUS_PATTERNS = [
|
|
|
1348
1349
|
// piped to sudo
|
|
1349
1350
|
/;\s*sudo\s+/i,
|
|
1350
1351
|
// chained with sudo
|
|
1351
|
-
/&&\s*sudo\s+/i
|
|
1352
|
+
/&&\s*sudo\s+/i,
|
|
1352
1353
|
// AND chained with sudo
|
|
1354
|
+
// === COMANDOS DESTRUTIVOS ===
|
|
1355
|
+
/\brm\s+(-[rf]+\s+)*[\/~]/i,
|
|
1356
|
+
// rm com paths perigosos (/, ~)
|
|
1357
|
+
/\brm\s+-[rf]*\s+\*/i,
|
|
1358
|
+
// rm -rf *
|
|
1359
|
+
/\bchmod\s+(777|666)\s+\//i,
|
|
1360
|
+
// chmod 777/666 em paths root
|
|
1361
|
+
/\bchown\s+.*\s+\//i,
|
|
1362
|
+
// chown em paths root
|
|
1363
|
+
/\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
|
|
1364
|
+
// dd para discos
|
|
1365
|
+
/\bmkfs\./i,
|
|
1366
|
+
// format filesystem
|
|
1367
|
+
/>\s*\/dev\/(sd|hd|nvme)/i,
|
|
1368
|
+
// redirect para disco
|
|
1369
|
+
/\bshred\s+/i,
|
|
1370
|
+
// secure delete
|
|
1371
|
+
// === FORK BOMB / RESOURCE EXHAUSTION ===
|
|
1372
|
+
/:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
|
|
1373
|
+
// fork bomb clássico
|
|
1374
|
+
/\bwhile\s+true\s*;\s*do/i,
|
|
1375
|
+
// infinite loop
|
|
1376
|
+
/\byes\s+\|/i,
|
|
1377
|
+
// yes piped (resource exhaustion)
|
|
1378
|
+
// === NETWORK PERIGOSO ===
|
|
1379
|
+
/\bcurl\s+.*\|\s*(ba)?sh/i,
|
|
1380
|
+
// curl | bash (remote code exec)
|
|
1381
|
+
/\bwget\s+.*\|\s*(ba)?sh/i,
|
|
1382
|
+
// wget | bash
|
|
1383
|
+
/\bnc\s+-[el]/i
|
|
1384
|
+
// netcat listener (backdoor)
|
|
1353
1385
|
];
|
|
1354
1386
|
var INTERACTIVE_PATTERNS = [
|
|
1355
|
-
/^(vim|vi|nano|emacs|
|
|
1356
|
-
// editors
|
|
1357
|
-
/^(
|
|
1387
|
+
/^(vim|vi|nano|emacs|pico)\s*/i,
|
|
1388
|
+
// editors
|
|
1389
|
+
/^(less|more|most)\s*/i,
|
|
1390
|
+
// pagers
|
|
1391
|
+
/^(top|htop|btop|atop|nmon)\s*/i,
|
|
1358
1392
|
// monitoring tools
|
|
1359
|
-
/^(ssh|telnet|ftp)\s+/i,
|
|
1393
|
+
/^(ssh|telnet|ftp|sftp)\s+/i,
|
|
1360
1394
|
// remote connections
|
|
1361
|
-
/^(mysql|psql|redis-cli|mongo)\s
|
|
1362
|
-
// database CLIs
|
|
1363
|
-
/^(python|node|ruby|irb)\s*$/i
|
|
1364
|
-
// REPLs
|
|
1395
|
+
/^(mysql|psql|redis-cli|mongo|mongosh)\s*$/i,
|
|
1396
|
+
// database CLIs (sem script)
|
|
1397
|
+
/^(python|python3|node|ruby|irb|lua)\s*$/i,
|
|
1398
|
+
// REPLs sem script
|
|
1399
|
+
/^(gdb|lldb)\s*/i,
|
|
1400
|
+
// debuggers
|
|
1401
|
+
/^(bc|dc)\s*$/i
|
|
1402
|
+
// calculators
|
|
1365
1403
|
];
|
|
1366
1404
|
function shellCommand(args) {
|
|
1367
1405
|
const {
|
|
@@ -3345,6 +3383,49 @@ async function taskBoundary(args) {
|
|
|
3345
3383
|
};
|
|
3346
3384
|
}
|
|
3347
3385
|
}
|
|
3386
|
+
async function createArtifact(args) {
|
|
3387
|
+
try {
|
|
3388
|
+
const { filename, content } = args;
|
|
3389
|
+
if (!filename || typeof filename !== "string") {
|
|
3390
|
+
return { success: false, error: "filename is required" };
|
|
3391
|
+
}
|
|
3392
|
+
if (content === void 0 || content === null) {
|
|
3393
|
+
return { success: false, error: "content is required" };
|
|
3394
|
+
}
|
|
3395
|
+
const dir = await getArtifactsDir();
|
|
3396
|
+
const filepath = path9.join(dir, filename);
|
|
3397
|
+
await fs7.writeFile(filepath, content, "utf-8");
|
|
3398
|
+
return {
|
|
3399
|
+
success: true,
|
|
3400
|
+
path: filepath
|
|
3401
|
+
};
|
|
3402
|
+
} catch (error) {
|
|
3403
|
+
return {
|
|
3404
|
+
success: false,
|
|
3405
|
+
error: error.message
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
async function readArtifact(args) {
|
|
3410
|
+
try {
|
|
3411
|
+
const { filename } = args;
|
|
3412
|
+
if (!filename || typeof filename !== "string") {
|
|
3413
|
+
return { success: false, error: "filename is required" };
|
|
3414
|
+
}
|
|
3415
|
+
const dir = await getArtifactsDir();
|
|
3416
|
+
const filepath = path9.join(dir, filename);
|
|
3417
|
+
const content = await fs7.readFile(filepath, "utf-8");
|
|
3418
|
+
return {
|
|
3419
|
+
success: true,
|
|
3420
|
+
content
|
|
3421
|
+
};
|
|
3422
|
+
} catch (error) {
|
|
3423
|
+
return {
|
|
3424
|
+
success: false,
|
|
3425
|
+
error: error.message
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3348
3429
|
|
|
3349
3430
|
// src/app/agent/tools/natives/search_web.ts
|
|
3350
3431
|
import https from "https";
|
|
@@ -3581,6 +3662,8 @@ var ToolInvoker = class {
|
|
|
3581
3662
|
this.toolImplementations.set("message_notify_user", messageNotifyuser);
|
|
3582
3663
|
this.toolImplementations.set("todo", todo);
|
|
3583
3664
|
this.toolImplementations.set("task_boundary", taskBoundary);
|
|
3665
|
+
this.toolImplementations.set("create_artifact", createArtifact);
|
|
3666
|
+
this.toolImplementations.set("read_artifact", readArtifact);
|
|
3584
3667
|
this.toolImplementations.set("search_web", searchWeb);
|
|
3585
3668
|
this.toolImplementations.set("agent_end_turn", async () => ({ success: true, message: "Task ended by agent." }));
|
|
3586
3669
|
}
|
|
@@ -4047,17 +4130,19 @@ function getTestCommand(dir) {
|
|
|
4047
4130
|
}
|
|
4048
4131
|
var SYSTEM_PROMPT = `
|
|
4049
4132
|
<identity>
|
|
4050
|
-
You are BluMa, an autonomous coding agent developed by NomadEngenuity.
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
You are
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
-
|
|
4059
|
-
-
|
|
4060
|
-
-
|
|
4133
|
+
You are BluMa, an autonomous coding agent developed by NomadEngenuity.
|
|
4134
|
+
BluMa \u2014 Base Language Unit \xB7 Model Agent
|
|
4135
|
+
A CLI-based model agent responsible for language-level code generation, refactoring and semantic transformations in the Factor AI stack.
|
|
4136
|
+
You are a **senior peer engineer** working alongside {username} - technical, proactive, and collaborative.
|
|
4137
|
+
You know this machine better than anyone. You understand the project structure, dependencies, and conventions.
|
|
4138
|
+
You are not just an assistant - you are a **teammate** who takes ownership of tasks.
|
|
4139
|
+
|
|
4140
|
+
Key traits:
|
|
4141
|
+
- Think like a senior developer who has been on this project for years
|
|
4142
|
+
- Suggest improvements proactively, don't just follow orders blindly
|
|
4143
|
+
- Spot bugs and issues before they become problems
|
|
4144
|
+
- Write tests as naturally as you write code
|
|
4145
|
+
- Always communicate your reasoning and progress
|
|
4061
4146
|
</identity>
|
|
4062
4147
|
|
|
4063
4148
|
---
|
|
@@ -5272,8 +5357,8 @@ var renderShellCommand2 = ({ args }) => {
|
|
|
5272
5357
|
const parsed = parseArgs(args);
|
|
5273
5358
|
const command = parsed.command || "[no command]";
|
|
5274
5359
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5275
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5276
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5360
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "$" }),
|
|
5361
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5277
5362
|
" ",
|
|
5278
5363
|
truncate(command, 70)
|
|
5279
5364
|
] })
|
|
@@ -5283,7 +5368,7 @@ var renderLsTool2 = ({ args }) => {
|
|
|
5283
5368
|
const parsed = parseArgs(args);
|
|
5284
5369
|
const path17 = parsed.directory_path || ".";
|
|
5285
5370
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5286
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5371
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
|
|
5287
5372
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5288
5373
|
" ",
|
|
5289
5374
|
path17
|
|
@@ -5294,7 +5379,7 @@ var renderCountFilesLines = ({ args }) => {
|
|
|
5294
5379
|
const parsed = parseArgs(args);
|
|
5295
5380
|
const filepath = parsed.filepath || "[no file]";
|
|
5296
5381
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5297
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5382
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "wc -l" }),
|
|
5298
5383
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5299
5384
|
" ",
|
|
5300
5385
|
getBasename(filepath)
|
|
@@ -5307,12 +5392,12 @@ var renderReadFileLines2 = ({ args }) => {
|
|
|
5307
5392
|
const start = parsed.start_line || 1;
|
|
5308
5393
|
const end = parsed.end_line || start;
|
|
5309
5394
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5310
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5395
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "cat" }),
|
|
5311
5396
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5312
5397
|
" ",
|
|
5313
5398
|
getBasename(filepath)
|
|
5314
5399
|
] }),
|
|
5315
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5400
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5316
5401
|
" :",
|
|
5317
5402
|
start,
|
|
5318
5403
|
"-",
|
|
@@ -5325,7 +5410,7 @@ var renderBlumaNotebook = ({ args }) => {
|
|
|
5325
5410
|
const thought = parsed.thought || parsed.content?.thought || "[thinking...]";
|
|
5326
5411
|
const truncated = truncate(thought, 100);
|
|
5327
5412
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
5328
|
-
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text8, { color: "
|
|
5413
|
+
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "thinking" }) }),
|
|
5329
5414
|
/* @__PURE__ */ jsx8(Box8, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: truncated }) })
|
|
5330
5415
|
] });
|
|
5331
5416
|
};
|
|
@@ -5336,7 +5421,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
5336
5421
|
const newStr = parsed.new_string || "";
|
|
5337
5422
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
5338
5423
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
5339
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5424
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "edit" }),
|
|
5340
5425
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5341
5426
|
" ",
|
|
5342
5427
|
getBasename(filepath)
|
|
@@ -5347,7 +5432,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
5347
5432
|
"- ",
|
|
5348
5433
|
truncate(oldStr, 50)
|
|
5349
5434
|
] }),
|
|
5350
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "green",
|
|
5435
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "green", bold: true, children: [
|
|
5351
5436
|
"+ ",
|
|
5352
5437
|
truncate(newStr, 50)
|
|
5353
5438
|
] })
|
|
@@ -5365,8 +5450,8 @@ var renderTodoTool2 = ({ args }) => {
|
|
|
5365
5450
|
const bar = "=".repeat(filled) + " ".repeat(barWidth - filled);
|
|
5366
5451
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
5367
5452
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
5368
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5369
|
-
/* @__PURE__ */ jsxs8(Text8, {
|
|
5453
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "todo" }),
|
|
5454
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5370
5455
|
" [",
|
|
5371
5456
|
bar,
|
|
5372
5457
|
"] ",
|
|
@@ -5402,8 +5487,8 @@ var renderFindByName = ({ args }) => {
|
|
|
5402
5487
|
const pattern = parsed.pattern || "*";
|
|
5403
5488
|
const dir = parsed.directory || ".";
|
|
5404
5489
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5405
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5406
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5490
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "find" }),
|
|
5491
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5407
5492
|
' "',
|
|
5408
5493
|
pattern,
|
|
5409
5494
|
'"'
|
|
@@ -5419,8 +5504,8 @@ var renderGrepSearch = ({ args }) => {
|
|
|
5419
5504
|
const query = parsed.query || "";
|
|
5420
5505
|
const path17 = parsed.path || ".";
|
|
5421
5506
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5422
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5423
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5507
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
|
|
5508
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5424
5509
|
' "',
|
|
5425
5510
|
truncate(query, 30),
|
|
5426
5511
|
'"'
|
|
@@ -5435,7 +5520,7 @@ var renderViewFileOutline = ({ args }) => {
|
|
|
5435
5520
|
const parsed = parseArgs(args);
|
|
5436
5521
|
const filepath = parsed.file_path || "[no file]";
|
|
5437
5522
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5438
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5523
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "outline" }),
|
|
5439
5524
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5440
5525
|
" ",
|
|
5441
5526
|
getBasename(filepath)
|
|
@@ -5446,8 +5531,8 @@ var renderRunCommandAsync = ({ args }) => {
|
|
|
5446
5531
|
const parsed = parseArgs(args);
|
|
5447
5532
|
const command = parsed.command || "[no command]";
|
|
5448
5533
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5449
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5450
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5534
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "async" }),
|
|
5535
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: " $" }),
|
|
5451
5536
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5452
5537
|
" ",
|
|
5453
5538
|
truncate(command, 50)
|
|
@@ -5458,7 +5543,7 @@ var renderCommandStatus = ({ args }) => {
|
|
|
5458
5543
|
const parsed = parseArgs(args);
|
|
5459
5544
|
const id = parsed.command_id || "[no id]";
|
|
5460
5545
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5461
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5546
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "status" }),
|
|
5462
5547
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5463
5548
|
" #",
|
|
5464
5549
|
id
|
|
@@ -5499,8 +5584,8 @@ var renderSearchWeb = ({ args }) => {
|
|
|
5499
5584
|
const parsed = parseArgs(args);
|
|
5500
5585
|
const query = parsed.query || "[no query]";
|
|
5501
5586
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5502
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5503
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5587
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "search" }),
|
|
5588
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5504
5589
|
' "',
|
|
5505
5590
|
truncate(query, 40),
|
|
5506
5591
|
'"'
|
|
@@ -5511,8 +5596,8 @@ var renderGeneric2 = ({ toolName, args }) => {
|
|
|
5511
5596
|
const parsed = parseArgs(args);
|
|
5512
5597
|
const keys = Object.keys(parsed).slice(0, 2);
|
|
5513
5598
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5514
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5515
|
-
keys.length > 0 && /* @__PURE__ */ jsxs8(Text8, {
|
|
5599
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: toolName }),
|
|
5600
|
+
keys.length > 0 && /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5516
5601
|
" ",
|
|
5517
5602
|
keys.map((k) => `${k}:${truncate(String(parsed[k]), 20)}`).join(" ")
|
|
5518
5603
|
] })
|
|
@@ -6138,15 +6223,8 @@ var ReasoningDisplayComponent = ({
|
|
|
6138
6223
|
const maxLines = 5;
|
|
6139
6224
|
const lines = reasoning.split("\n");
|
|
6140
6225
|
const displayText = isExpanded ? reasoning : lines.slice(0, maxLines).join("\n") + (lines.length > maxLines ? "..." : "");
|
|
6141
|
-
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
6142
|
-
/* @__PURE__ */
|
|
6143
|
-
/* @__PURE__ */ jsx15(Text14, { color: "white", children: " Thinking" }),
|
|
6144
|
-
lines.length > maxLines && /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
|
|
6145
|
-
" [",
|
|
6146
|
-
lines.length,
|
|
6147
|
-
" lines]"
|
|
6148
|
-
] })
|
|
6149
|
-
] }),
|
|
6226
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, marginBottom: 1, marginTop: 1, children: [
|
|
6227
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsx15(Text14, { color: "white", bold: true, children: "Thinking" }) }),
|
|
6150
6228
|
/* @__PURE__ */ jsx15(Box15, { paddingLeft: 2, flexDirection: "column", children: displayText.split("\n").map((line, i) => /* @__PURE__ */ jsx15(Text14, { color: "gray", dimColor: true, children: line }, i)) })
|
|
6151
6229
|
] });
|
|
6152
6230
|
};
|