@nomad-e/bluma-cli 0.0.104 → 0.0.106
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/{bluma-mcp.json → example.bluma-mcp.json.txt} +1 -2
- package/dist/config/native_tools.json +43 -0
- package/dist/main.js +132 -61
- 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
|
@@ -172,20 +172,13 @@ import { Box as Box16, Text as Text15, Static } from "ink";
|
|
|
172
172
|
import { Box, Text } from "ink";
|
|
173
173
|
import { memo } from "react";
|
|
174
174
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
175
|
-
var VERSION = "0.0.103";
|
|
176
175
|
var HeaderComponent = ({
|
|
177
176
|
sessionId: sessionId2,
|
|
178
177
|
workdir
|
|
179
178
|
}) => {
|
|
180
179
|
const dirName = workdir.split("/").pop() || workdir;
|
|
181
180
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
182
|
-
/* @__PURE__ */
|
|
183
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "bluma" }),
|
|
184
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
185
|
-
" ",
|
|
186
|
-
VERSION
|
|
187
|
-
] })
|
|
188
|
-
] }),
|
|
181
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "bluma \u2014 coding agent" }) }),
|
|
189
182
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
190
183
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "cwd " }),
|
|
191
184
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: dirName }),
|
|
@@ -741,7 +734,7 @@ var TextLine = memo2(({
|
|
|
741
734
|
const after = line.slice(cursorCol + 1);
|
|
742
735
|
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
743
736
|
before,
|
|
744
|
-
/* @__PURE__ */ jsx2(Text2, { inverse: true, color: "
|
|
737
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, color: "white", children: char }),
|
|
745
738
|
after
|
|
746
739
|
] });
|
|
747
740
|
}, (prev, next) => {
|
|
@@ -1336,6 +1329,7 @@ import { spawn } from "child_process";
|
|
|
1336
1329
|
var MAX_OUTPUT_SIZE = 1e5;
|
|
1337
1330
|
var OUTPUT_TRUNCATION_MESSAGE = "\n\n[OUTPUT TRUNCATED - exceeded 100KB limit. Use pagination or redirect to file for full output.]";
|
|
1338
1331
|
var DANGEROUS_PATTERNS = [
|
|
1332
|
+
// === ELEVAÇÃO DE PRIVILÉGIOS ===
|
|
1339
1333
|
/^sudo\s+/i,
|
|
1340
1334
|
// sudo commands
|
|
1341
1335
|
/^doas\s+/i,
|
|
@@ -1348,20 +1342,57 @@ var DANGEROUS_PATTERNS = [
|
|
|
1348
1342
|
// piped to sudo
|
|
1349
1343
|
/;\s*sudo\s+/i,
|
|
1350
1344
|
// chained with sudo
|
|
1351
|
-
/&&\s*sudo\s+/i
|
|
1345
|
+
/&&\s*sudo\s+/i,
|
|
1352
1346
|
// AND chained with sudo
|
|
1347
|
+
// === COMANDOS DESTRUTIVOS ===
|
|
1348
|
+
/\brm\s+(-[rf]+\s+)*[\/~]/i,
|
|
1349
|
+
// rm com paths perigosos (/, ~)
|
|
1350
|
+
/\brm\s+-[rf]*\s+\*/i,
|
|
1351
|
+
// rm -rf *
|
|
1352
|
+
/\bchmod\s+(777|666)\s+\//i,
|
|
1353
|
+
// chmod 777/666 em paths root
|
|
1354
|
+
/\bchown\s+.*\s+\//i,
|
|
1355
|
+
// chown em paths root
|
|
1356
|
+
/\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
|
|
1357
|
+
// dd para discos
|
|
1358
|
+
/\bmkfs\./i,
|
|
1359
|
+
// format filesystem
|
|
1360
|
+
/>\s*\/dev\/(sd|hd|nvme)/i,
|
|
1361
|
+
// redirect para disco
|
|
1362
|
+
/\bshred\s+/i,
|
|
1363
|
+
// secure delete
|
|
1364
|
+
// === FORK BOMB / RESOURCE EXHAUSTION ===
|
|
1365
|
+
/:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
|
|
1366
|
+
// fork bomb clássico
|
|
1367
|
+
/\bwhile\s+true\s*;\s*do/i,
|
|
1368
|
+
// infinite loop
|
|
1369
|
+
/\byes\s+\|/i,
|
|
1370
|
+
// yes piped (resource exhaustion)
|
|
1371
|
+
// === NETWORK PERIGOSO ===
|
|
1372
|
+
/\bcurl\s+.*\|\s*(ba)?sh/i,
|
|
1373
|
+
// curl | bash (remote code exec)
|
|
1374
|
+
/\bwget\s+.*\|\s*(ba)?sh/i,
|
|
1375
|
+
// wget | bash
|
|
1376
|
+
/\bnc\s+-[el]/i
|
|
1377
|
+
// netcat listener (backdoor)
|
|
1353
1378
|
];
|
|
1354
1379
|
var INTERACTIVE_PATTERNS = [
|
|
1355
|
-
/^(vim|vi|nano|emacs|
|
|
1356
|
-
// editors
|
|
1357
|
-
/^(
|
|
1380
|
+
/^(vim|vi|nano|emacs|pico)\s*/i,
|
|
1381
|
+
// editors
|
|
1382
|
+
/^(less|more|most)\s*/i,
|
|
1383
|
+
// pagers
|
|
1384
|
+
/^(top|htop|btop|atop|nmon)\s*/i,
|
|
1358
1385
|
// monitoring tools
|
|
1359
|
-
/^(ssh|telnet|ftp)\s+/i,
|
|
1386
|
+
/^(ssh|telnet|ftp|sftp)\s+/i,
|
|
1360
1387
|
// remote connections
|
|
1361
|
-
/^(mysql|psql|redis-cli|mongo)\s
|
|
1362
|
-
// database CLIs
|
|
1363
|
-
/^(python|node|ruby|irb)\s*$/i
|
|
1364
|
-
// REPLs
|
|
1388
|
+
/^(mysql|psql|redis-cli|mongo|mongosh)\s*$/i,
|
|
1389
|
+
// database CLIs (sem script)
|
|
1390
|
+
/^(python|python3|node|ruby|irb|lua)\s*$/i,
|
|
1391
|
+
// REPLs sem script
|
|
1392
|
+
/^(gdb|lldb)\s*/i,
|
|
1393
|
+
// debuggers
|
|
1394
|
+
/^(bc|dc)\s*$/i
|
|
1395
|
+
// calculators
|
|
1365
1396
|
];
|
|
1366
1397
|
function shellCommand(args) {
|
|
1367
1398
|
const {
|
|
@@ -3345,6 +3376,49 @@ async function taskBoundary(args) {
|
|
|
3345
3376
|
};
|
|
3346
3377
|
}
|
|
3347
3378
|
}
|
|
3379
|
+
async function createArtifact(args) {
|
|
3380
|
+
try {
|
|
3381
|
+
const { filename, content } = args;
|
|
3382
|
+
if (!filename || typeof filename !== "string") {
|
|
3383
|
+
return { success: false, error: "filename is required" };
|
|
3384
|
+
}
|
|
3385
|
+
if (content === void 0 || content === null) {
|
|
3386
|
+
return { success: false, error: "content is required" };
|
|
3387
|
+
}
|
|
3388
|
+
const dir = await getArtifactsDir();
|
|
3389
|
+
const filepath = path9.join(dir, filename);
|
|
3390
|
+
await fs7.writeFile(filepath, content, "utf-8");
|
|
3391
|
+
return {
|
|
3392
|
+
success: true,
|
|
3393
|
+
path: filepath
|
|
3394
|
+
};
|
|
3395
|
+
} catch (error) {
|
|
3396
|
+
return {
|
|
3397
|
+
success: false,
|
|
3398
|
+
error: error.message
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
async function readArtifact(args) {
|
|
3403
|
+
try {
|
|
3404
|
+
const { filename } = args;
|
|
3405
|
+
if (!filename || typeof filename !== "string") {
|
|
3406
|
+
return { success: false, error: "filename is required" };
|
|
3407
|
+
}
|
|
3408
|
+
const dir = await getArtifactsDir();
|
|
3409
|
+
const filepath = path9.join(dir, filename);
|
|
3410
|
+
const content = await fs7.readFile(filepath, "utf-8");
|
|
3411
|
+
return {
|
|
3412
|
+
success: true,
|
|
3413
|
+
content
|
|
3414
|
+
};
|
|
3415
|
+
} catch (error) {
|
|
3416
|
+
return {
|
|
3417
|
+
success: false,
|
|
3418
|
+
error: error.message
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3348
3422
|
|
|
3349
3423
|
// src/app/agent/tools/natives/search_web.ts
|
|
3350
3424
|
import https from "https";
|
|
@@ -3581,6 +3655,8 @@ var ToolInvoker = class {
|
|
|
3581
3655
|
this.toolImplementations.set("message_notify_user", messageNotifyuser);
|
|
3582
3656
|
this.toolImplementations.set("todo", todo);
|
|
3583
3657
|
this.toolImplementations.set("task_boundary", taskBoundary);
|
|
3658
|
+
this.toolImplementations.set("create_artifact", createArtifact);
|
|
3659
|
+
this.toolImplementations.set("read_artifact", readArtifact);
|
|
3584
3660
|
this.toolImplementations.set("search_web", searchWeb);
|
|
3585
3661
|
this.toolImplementations.set("agent_end_turn", async () => ({ success: true, message: "Task ended by agent." }));
|
|
3586
3662
|
}
|
|
@@ -4047,17 +4123,19 @@ function getTestCommand(dir) {
|
|
|
4047
4123
|
}
|
|
4048
4124
|
var SYSTEM_PROMPT = `
|
|
4049
4125
|
<identity>
|
|
4050
|
-
You are BluMa, an autonomous coding agent developed by NomadEngenuity.
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
You are
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
-
|
|
4059
|
-
-
|
|
4060
|
-
-
|
|
4126
|
+
You are BluMa, an autonomous coding agent developed by NomadEngenuity.
|
|
4127
|
+
BluMa \u2014 Base Language Unit \xB7 Model Agent
|
|
4128
|
+
A CLI-based model agent responsible for language-level code generation, refactoring and semantic transformations in the Factor AI stack.
|
|
4129
|
+
You are a **senior peer engineer** working alongside {username} - technical, proactive, and collaborative.
|
|
4130
|
+
You know this machine better than anyone. You understand the project structure, dependencies, and conventions.
|
|
4131
|
+
You are not just an assistant - you are a **teammate** who takes ownership of tasks.
|
|
4132
|
+
|
|
4133
|
+
Key traits:
|
|
4134
|
+
- Think like a senior developer who has been on this project for years
|
|
4135
|
+
- Suggest improvements proactively, don't just follow orders blindly
|
|
4136
|
+
- Spot bugs and issues before they become problems
|
|
4137
|
+
- Write tests as naturally as you write code
|
|
4138
|
+
- Always communicate your reasoning and progress
|
|
4061
4139
|
</identity>
|
|
4062
4140
|
|
|
4063
4141
|
---
|
|
@@ -5272,8 +5350,8 @@ var renderShellCommand2 = ({ args }) => {
|
|
|
5272
5350
|
const parsed = parseArgs(args);
|
|
5273
5351
|
const command = parsed.command || "[no command]";
|
|
5274
5352
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5275
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5276
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5353
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "$" }),
|
|
5354
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5277
5355
|
" ",
|
|
5278
5356
|
truncate(command, 70)
|
|
5279
5357
|
] })
|
|
@@ -5283,7 +5361,7 @@ var renderLsTool2 = ({ args }) => {
|
|
|
5283
5361
|
const parsed = parseArgs(args);
|
|
5284
5362
|
const path17 = parsed.directory_path || ".";
|
|
5285
5363
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5286
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5364
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
|
|
5287
5365
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5288
5366
|
" ",
|
|
5289
5367
|
path17
|
|
@@ -5294,7 +5372,7 @@ var renderCountFilesLines = ({ args }) => {
|
|
|
5294
5372
|
const parsed = parseArgs(args);
|
|
5295
5373
|
const filepath = parsed.filepath || "[no file]";
|
|
5296
5374
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5297
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5375
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "wc -l" }),
|
|
5298
5376
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5299
5377
|
" ",
|
|
5300
5378
|
getBasename(filepath)
|
|
@@ -5307,12 +5385,12 @@ var renderReadFileLines2 = ({ args }) => {
|
|
|
5307
5385
|
const start = parsed.start_line || 1;
|
|
5308
5386
|
const end = parsed.end_line || start;
|
|
5309
5387
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5310
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5388
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "cat" }),
|
|
5311
5389
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5312
5390
|
" ",
|
|
5313
5391
|
getBasename(filepath)
|
|
5314
5392
|
] }),
|
|
5315
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5393
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5316
5394
|
" :",
|
|
5317
5395
|
start,
|
|
5318
5396
|
"-",
|
|
@@ -5325,7 +5403,7 @@ var renderBlumaNotebook = ({ args }) => {
|
|
|
5325
5403
|
const thought = parsed.thought || parsed.content?.thought || "[thinking...]";
|
|
5326
5404
|
const truncated = truncate(thought, 100);
|
|
5327
5405
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
5328
|
-
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text8, { color: "
|
|
5406
|
+
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "thinking" }) }),
|
|
5329
5407
|
/* @__PURE__ */ jsx8(Box8, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: truncated }) })
|
|
5330
5408
|
] });
|
|
5331
5409
|
};
|
|
@@ -5336,7 +5414,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
5336
5414
|
const newStr = parsed.new_string || "";
|
|
5337
5415
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
5338
5416
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
5339
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5417
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "edit" }),
|
|
5340
5418
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5341
5419
|
" ",
|
|
5342
5420
|
getBasename(filepath)
|
|
@@ -5347,7 +5425,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
5347
5425
|
"- ",
|
|
5348
5426
|
truncate(oldStr, 50)
|
|
5349
5427
|
] }),
|
|
5350
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "green",
|
|
5428
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "green", bold: true, children: [
|
|
5351
5429
|
"+ ",
|
|
5352
5430
|
truncate(newStr, 50)
|
|
5353
5431
|
] })
|
|
@@ -5365,8 +5443,8 @@ var renderTodoTool2 = ({ args }) => {
|
|
|
5365
5443
|
const bar = "=".repeat(filled) + " ".repeat(barWidth - filled);
|
|
5366
5444
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
5367
5445
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
5368
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5369
|
-
/* @__PURE__ */ jsxs8(Text8, {
|
|
5446
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "todo" }),
|
|
5447
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5370
5448
|
" [",
|
|
5371
5449
|
bar,
|
|
5372
5450
|
"] ",
|
|
@@ -5402,8 +5480,8 @@ var renderFindByName = ({ args }) => {
|
|
|
5402
5480
|
const pattern = parsed.pattern || "*";
|
|
5403
5481
|
const dir = parsed.directory || ".";
|
|
5404
5482
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5405
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5406
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5483
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "find" }),
|
|
5484
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5407
5485
|
' "',
|
|
5408
5486
|
pattern,
|
|
5409
5487
|
'"'
|
|
@@ -5419,8 +5497,8 @@ var renderGrepSearch = ({ args }) => {
|
|
|
5419
5497
|
const query = parsed.query || "";
|
|
5420
5498
|
const path17 = parsed.path || ".";
|
|
5421
5499
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5422
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5423
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5500
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
|
|
5501
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5424
5502
|
' "',
|
|
5425
5503
|
truncate(query, 30),
|
|
5426
5504
|
'"'
|
|
@@ -5435,7 +5513,7 @@ var renderViewFileOutline = ({ args }) => {
|
|
|
5435
5513
|
const parsed = parseArgs(args);
|
|
5436
5514
|
const filepath = parsed.file_path || "[no file]";
|
|
5437
5515
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5438
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5516
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "outline" }),
|
|
5439
5517
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5440
5518
|
" ",
|
|
5441
5519
|
getBasename(filepath)
|
|
@@ -5446,8 +5524,8 @@ var renderRunCommandAsync = ({ args }) => {
|
|
|
5446
5524
|
const parsed = parseArgs(args);
|
|
5447
5525
|
const command = parsed.command || "[no command]";
|
|
5448
5526
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5449
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5450
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5527
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "async" }),
|
|
5528
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: " $" }),
|
|
5451
5529
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5452
5530
|
" ",
|
|
5453
5531
|
truncate(command, 50)
|
|
@@ -5458,7 +5536,7 @@ var renderCommandStatus = ({ args }) => {
|
|
|
5458
5536
|
const parsed = parseArgs(args);
|
|
5459
5537
|
const id = parsed.command_id || "[no id]";
|
|
5460
5538
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5461
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5539
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "status" }),
|
|
5462
5540
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5463
5541
|
" #",
|
|
5464
5542
|
id
|
|
@@ -5499,8 +5577,8 @@ var renderSearchWeb = ({ args }) => {
|
|
|
5499
5577
|
const parsed = parseArgs(args);
|
|
5500
5578
|
const query = parsed.query || "[no query]";
|
|
5501
5579
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5502
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5503
|
-
/* @__PURE__ */ jsxs8(Text8, { color: "
|
|
5580
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "search" }),
|
|
5581
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5504
5582
|
' "',
|
|
5505
5583
|
truncate(query, 40),
|
|
5506
5584
|
'"'
|
|
@@ -5511,8 +5589,8 @@ var renderGeneric2 = ({ toolName, args }) => {
|
|
|
5511
5589
|
const parsed = parseArgs(args);
|
|
5512
5590
|
const keys = Object.keys(parsed).slice(0, 2);
|
|
5513
5591
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5514
|
-
/* @__PURE__ */ jsx8(Text8, { color: "
|
|
5515
|
-
keys.length > 0 && /* @__PURE__ */ jsxs8(Text8, {
|
|
5592
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: toolName }),
|
|
5593
|
+
keys.length > 0 && /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5516
5594
|
" ",
|
|
5517
5595
|
keys.map((k) => `${k}:${truncate(String(parsed[k]), 20)}`).join(" ")
|
|
5518
5596
|
] })
|
|
@@ -6138,15 +6216,8 @@ var ReasoningDisplayComponent = ({
|
|
|
6138
6216
|
const maxLines = 5;
|
|
6139
6217
|
const lines = reasoning.split("\n");
|
|
6140
6218
|
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
|
-
] }),
|
|
6219
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, marginBottom: 1, marginTop: 1, children: [
|
|
6220
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsx15(Text14, { color: "white", bold: true, children: "Thinking" }) }),
|
|
6150
6221
|
/* @__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
6222
|
] });
|
|
6152
6223
|
};
|