@nomad-e/bluma-cli 0.1.59 → 0.1.60
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 +3 -1
- package/dist/main.js +63 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
**BluMa** is a CLI-based model agent for advanced software engineering workflows. Built with React/Ink 5, it provides an interactive terminal interface for LLM-powered automation, code generation, refactoring, and task execution. Features persistent sessions, contextual reasoning, smart feedback, coordinator mode for worker orchestration, and extensible tools/skills architecture.
|
|
8
8
|
|
|
9
|
+
> **Credit:** BluMa was conceived and architected by **Alex Fonseca**.
|
|
10
|
+
|
|
9
11
|
**Current Version:** 0.1.55
|
|
10
12
|
|
|
11
13
|
---
|
|
@@ -777,7 +779,7 @@ Apache 2.0 — see [LICENSE](LICENSE) for details.
|
|
|
777
779
|
|
|
778
780
|
- **Issues**: [GitHub Issues](https://github.com/nomad-e/bluma-cli/issues)
|
|
779
781
|
- **Documentation**: This README + `docs/` directory
|
|
780
|
-
- **Author**: Alex Fonseca
|
|
782
|
+
- **Author & Architect**: Alex Fonseca (conceived and architected BluMa)
|
|
781
783
|
- **npm Package**: [@nomad-e/bluma-cli](https://www.npmjs.com/package/@nomad-e/bluma-cli)
|
|
782
784
|
|
|
783
785
|
### Runtime Modules (v0.1.41+)
|
package/dist/main.js
CHANGED
|
@@ -221,6 +221,9 @@ __export(async_command_exports, {
|
|
|
221
221
|
import os6 from "os";
|
|
222
222
|
import { spawn as spawn2 } from "child_process";
|
|
223
223
|
import { v4 as uuidv42 } from "uuid";
|
|
224
|
+
function normalizeCommandId(raw) {
|
|
225
|
+
return String(raw ?? "").trim().replace(/^[#:\s]+/, "");
|
|
226
|
+
}
|
|
224
227
|
function cleanupOldCommands() {
|
|
225
228
|
if (runningCommands.size <= MAX_STORED_COMMANDS) return;
|
|
226
229
|
const commands = Array.from(runningCommands.entries()).filter(([_, cmd]) => cmd.status !== "running").sort((a, b) => (a[1].endTime || 0) - (b[1].endTime || 0));
|
|
@@ -370,13 +373,14 @@ async function commandStatus(args) {
|
|
|
370
373
|
error: "command_id is required"
|
|
371
374
|
};
|
|
372
375
|
}
|
|
373
|
-
const
|
|
376
|
+
const normalizedCommandId = normalizeCommandId(command_id);
|
|
377
|
+
const entry = runningCommands.get(normalizedCommandId);
|
|
374
378
|
if (!entry) {
|
|
375
379
|
return {
|
|
376
380
|
success: false,
|
|
377
|
-
command_id,
|
|
381
|
+
command_id: normalizedCommandId,
|
|
378
382
|
status: "not_found",
|
|
379
|
-
error: `Command with id "${
|
|
383
|
+
error: `Command with id "${normalizedCommandId}" not found. It may have expired or never existed.`
|
|
380
384
|
};
|
|
381
385
|
}
|
|
382
386
|
const maxWait = Math.min(wait_seconds, 15);
|
|
@@ -408,7 +412,7 @@ async function commandStatus(args) {
|
|
|
408
412
|
const duration = entry.endTime ? (entry.endTime - entry.startTime) / 1e3 : (Date.now() - entry.startTime) / 1e3;
|
|
409
413
|
return {
|
|
410
414
|
success: true,
|
|
411
|
-
command_id,
|
|
415
|
+
command_id: normalizedCommandId,
|
|
412
416
|
status: entry.status,
|
|
413
417
|
stdout: stdout || void 0,
|
|
414
418
|
stderr: stderr || void 0,
|
|
@@ -434,11 +438,12 @@ async function sendCommandInput(args) {
|
|
|
434
438
|
error: "command_id and input are required"
|
|
435
439
|
};
|
|
436
440
|
}
|
|
437
|
-
const
|
|
441
|
+
const normalizedCommandId = normalizeCommandId(command_id);
|
|
442
|
+
const entry = runningCommands.get(normalizedCommandId);
|
|
438
443
|
if (!entry) {
|
|
439
444
|
return {
|
|
440
445
|
success: false,
|
|
441
|
-
error: `Command with id "${
|
|
446
|
+
error: `Command with id "${normalizedCommandId}" not found`
|
|
442
447
|
};
|
|
443
448
|
}
|
|
444
449
|
if (entry.status !== "running" || !entry.process) {
|
|
@@ -450,7 +455,7 @@ async function sendCommandInput(args) {
|
|
|
450
455
|
entry.process.stdin?.write(input);
|
|
451
456
|
return {
|
|
452
457
|
success: true,
|
|
453
|
-
message: `Sent ${input.length} characters to command ${
|
|
458
|
+
message: `Sent ${input.length} characters to command ${normalizedCommandId}`
|
|
454
459
|
};
|
|
455
460
|
} catch (error) {
|
|
456
461
|
return {
|
|
@@ -462,11 +467,12 @@ async function sendCommandInput(args) {
|
|
|
462
467
|
async function killCommand(args) {
|
|
463
468
|
try {
|
|
464
469
|
const { command_id } = args;
|
|
465
|
-
const
|
|
470
|
+
const normalizedCommandId = normalizeCommandId(command_id);
|
|
471
|
+
const entry = runningCommands.get(normalizedCommandId);
|
|
466
472
|
if (!entry) {
|
|
467
473
|
return {
|
|
468
474
|
success: false,
|
|
469
|
-
error: `Command with id "${
|
|
475
|
+
error: `Command with id "${normalizedCommandId}" not found`
|
|
470
476
|
};
|
|
471
477
|
}
|
|
472
478
|
if (entry.status !== "running" || !entry.process) {
|
|
@@ -480,7 +486,7 @@ async function killCommand(args) {
|
|
|
480
486
|
entry.endTime = Date.now();
|
|
481
487
|
return {
|
|
482
488
|
success: true,
|
|
483
|
-
message: `Command ${
|
|
489
|
+
message: `Command ${normalizedCommandId} killed`
|
|
484
490
|
};
|
|
485
491
|
} catch (error) {
|
|
486
492
|
return {
|
|
@@ -3927,7 +3933,7 @@ var renderCommandStatus = ({ args }) => {
|
|
|
3927
3933
|
const parsed = parseArgs(args);
|
|
3928
3934
|
const id = parsed.command_id || "[no id]";
|
|
3929
3935
|
return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: BLUMA_TERMINAL.muted, children: [
|
|
3930
|
-
"
|
|
3936
|
+
"id ",
|
|
3931
3937
|
id
|
|
3932
3938
|
] }) });
|
|
3933
3939
|
};
|
|
@@ -10088,6 +10094,14 @@ You (Bluma):
|
|
|
10088
10094
|
- **Clean up after yourself** - Remove temporary files when done
|
|
10089
10095
|
- **Respect session boundaries** - Stay in your session workspace
|
|
10090
10096
|
|
|
10097
|
+
### Job wall-clock timeout (orchestrator) \u2014 read this
|
|
10098
|
+
|
|
10099
|
+
The coordinator sets a **single deadline** for the whole stream (e.g. \`timeout_seconds: 60\`). The timer **starts at job start** and counts **everything**: first LLM call (often **30\u201360s+** of \u201CThinking\u201D), tools, follow-up LLM calls, and your final \`message\`+\`result\`.
|
|
10100
|
+
|
|
10101
|
+
- **60 seconds is usually too short** for \`generate_document\` / PDF / multi-step work \u2014 the job can die **after** \`file_write\` succeeds but **before** \`shell_command\` or \`message(result)\`, with \`Job excedeu 60.0s\` / exit \`-9\`. That is **not** proof the sandbox is broken; it means the **budget was too tight**.
|
|
10102
|
+
- Prefer asking coordinators (or docs) to use **\u2265180s** for document generation, **\u2265300s** for heavy tasks.
|
|
10103
|
+
- When a job times out, **do not** claim \u201Csandbox unavailable\u201D unless you have real infra evidence (connection errors, 5xx). Timeout = **deadline exceeded**, often fixable by **raising \`timeout_seconds\`** on the caller side.
|
|
10104
|
+
|
|
10091
10105
|
### You Represent the Platform
|
|
10092
10106
|
|
|
10093
10107
|
- **Severino trusts you** - Don't let him down
|
|
@@ -14235,6 +14249,40 @@ var ToolResultDisplayComponent = ({
|
|
|
14235
14249
|
}
|
|
14236
14250
|
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsx12(MarkdownRenderer, { markdown: String(body) }) });
|
|
14237
14251
|
}
|
|
14252
|
+
if (toolName.includes("ask_user_question")) {
|
|
14253
|
+
const success = parsed?.success === true;
|
|
14254
|
+
const selectedLabel = typeof parsed?.selected_label === "string" ? parsed.selected_label : "";
|
|
14255
|
+
const selectedIndex = typeof parsed?.selected_index === "number" ? parsed.selected_index : null;
|
|
14256
|
+
const questionIndex = typeof parsed?.question_index === "number" ? parsed.question_index : 0;
|
|
14257
|
+
const qs = Array.isArray(args?.questions) ? args.questions : [];
|
|
14258
|
+
const q = qs[questionIndex];
|
|
14259
|
+
const questionText = typeof q?.question === "string" ? q.question : "";
|
|
14260
|
+
if (success && selectedLabel) {
|
|
14261
|
+
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
14262
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
14263
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "Response" }),
|
|
14264
|
+
" \xB7 ",
|
|
14265
|
+
selectedLabel
|
|
14266
|
+
] }),
|
|
14267
|
+
questionText ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, wrap: "wrap", children: [
|
|
14268
|
+
truncate3(questionText, 140),
|
|
14269
|
+
selectedIndex !== null ? ` \xB7 option ${selectedIndex + 1}` : ""
|
|
14270
|
+
] }) : null
|
|
14271
|
+
] }) });
|
|
14272
|
+
}
|
|
14273
|
+
if (parsed?.cancelled === true) {
|
|
14274
|
+
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
14275
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "Response" }),
|
|
14276
|
+
" \xB7 cancelled by user"
|
|
14277
|
+
] }) });
|
|
14278
|
+
}
|
|
14279
|
+
const err = typeof parsed?.error === "string" ? parsed.error : "";
|
|
14280
|
+
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsxs12(Text12, { color: err ? BLUMA_TERMINAL.err : void 0, dimColor: !err, wrap: "wrap", children: [
|
|
14281
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "Response" }),
|
|
14282
|
+
" \xB7 ",
|
|
14283
|
+
err || "No answer returned"
|
|
14284
|
+
] }) });
|
|
14285
|
+
}
|
|
14238
14286
|
if (toolName.includes("file_write") && parsed) {
|
|
14239
14287
|
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
14240
14288
|
parsed.created ? "Created " : "Wrote to ",
|
|
@@ -16043,6 +16091,9 @@ Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
|
|
|
16043
16091
|
}
|
|
16044
16092
|
}
|
|
16045
16093
|
|
|
16094
|
+
// src/app/ui/App.tsx
|
|
16095
|
+
init_sandbox_policy();
|
|
16096
|
+
|
|
16046
16097
|
// src/app/ui/components/UpdateNotice.tsx
|
|
16047
16098
|
import { Box as Box17, Text as Text16 } from "ink";
|
|
16048
16099
|
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
@@ -16693,7 +16744,7 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
|
|
|
16693
16744
|
const [liveToolArgs, setLiveToolArgs] = useState11(void 0);
|
|
16694
16745
|
const [isReasoning, setIsReasoning] = useState11(false);
|
|
16695
16746
|
const alwaysAcceptList = useRef6([]);
|
|
16696
|
-
const workdir =
|
|
16747
|
+
const workdir = getSandboxPolicy().workspaceRoot;
|
|
16697
16748
|
const turnStartedAtRef = useRef6(null);
|
|
16698
16749
|
const [processingStartMs, setProcessingStartMs] = useState11(null);
|
|
16699
16750
|
const markTurnStarted = useCallback4(() => {
|