@nomad-e/bluma-cli 0.1.54 → 0.1.56

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +185 -62
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,7 @@
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
- **Current Version:** 0.1.47
9
+ **Current Version:** 0.1.55
10
10
 
11
11
  ---
12
12
 
package/dist/main.js CHANGED
@@ -9930,6 +9930,51 @@ Since you are in an **isolated sandbox**, ALL tools are auto-approved:
9930
9930
  - **Artifact delivery** - Save outputs with \\\`create_artifact\\\`, declare in attachments
9931
9931
  - **Error transparency** - If something fails, explain why and propose alternatives
9932
9932
 
9933
+ ### \u26A0\uFE0F CRITICAL: message Tool Usage Rules
9934
+
9935
+ The \\\`message\\\` tool has TWO types \u2014 use them CORRECTLY:
9936
+
9937
+ #### \\\`message_type: "info"\\\` \u2014 INFORMATION ONLY
9938
+ - **Purpose**: Report progress, status updates, discoveries, milestones
9939
+ - **Use when**: "Step 1/3 complete", "Found the data", "Processing..."
9940
+ - **NEVER use for**: Asking questions, requesting decisions, seeking clarification
9941
+ - **Does NOT end the turn** \u2014 you continue working
9942
+
9943
+ #### \\\`message_type: "result"\\\` \u2014 FINAL DELIVERY
9944
+ - **Purpose**: Deliver final output, declare attachments, end your turn
9945
+ - **Use when**: Task is complete, artifacts ready for delivery
9946
+ - **Use ONCE per turn** \u2014 only at the very end
9947
+ - **Ends the turn** \u2014 agent waits for next input
9948
+
9949
+ #### \u274C WRONG: Using "info" to ask questions
9950
+ \\\`\\\`\\\`typescript
9951
+ // DON'T DO THIS:
9952
+ message({
9953
+ message_type: "info",
9954
+ content: "Should I generate PDF or Excel?" // \u2190 WRONG! info is NOT for questions
9955
+ })
9956
+ \\\`\\\`\\\`
9957
+
9958
+ #### \u2705 CORRECT: Use mailbox for questions to Severino
9959
+ \\\`\\\`\\\`typescript
9960
+ // DO THIS:
9961
+ sendMailboxMessage({
9962
+ session_id: "chat_abc123",
9963
+ to_agent: "severino",
9964
+ message_type: "question",
9965
+ content: "Should I generate PDF or Excel?"
9966
+ })
9967
+ \\\`\\\`\\\`
9968
+
9969
+ #### \u2705 CORRECT: Use "info" for actual information
9970
+ \\\`\\\`\\\`typescript
9971
+ // DO THIS:
9972
+ message({
9973
+ message_type: "info",
9974
+ content: "Step 1/3: Data extraction complete. Processing..."
9975
+ })
9976
+ \\\`\\\`\\\`
9977
+
9933
9978
  ### Work Ethic
9934
9979
  - **No lazy delegation** - Synthesize information before delegating
9935
9980
  - **Verify assumptions** - Check file paths, validate inputs, confirm context
@@ -10271,6 +10316,13 @@ The user **only** sees chat content you send through the \`message\` tool (\`con
10271
10316
  - \`message_type: "result"\` \u2014 **ends the turn**: final answer, deliverable, or a **question** that needs a user reply; then the agent waits for the user.
10272
10317
  - \`message_type: "info"\` \u2014 **non-terminal**: shown in chat, does **not** end the turn. **Expected behavior:** call \`info\` **multiple times** in a single turn whenever there is something worth saying (even briefly). Under-using \`info\` is a **mistake** in this product.
10273
10318
 
10319
+ **\u26A0\uFE0F CRITICAL: "info" is for INFORMATION ONLY \u2014 NEVER for asking questions**
10320
+ - \`message_type: "info"\` is **ONLY** for reporting progress, discoveries, failures, milestones
10321
+ - **NEVER** use \`info\` to ask the user a question or request a decision
10322
+ - If you need to ask the user something, use \`ask_user_question\` (local mode) or the mailbox (sandbox mode)
10323
+ - \u274C WRONG: \`message({ message_type: "info", content: "Which format do you prefer?" })\`
10324
+ - \u2705 CORRECT: \`ask_user_question({ questions: [...] })\` or \`sendMailboxMessage({ message_type: "question", ... })\`
10325
+
10274
10326
  **When to send \`info\`**
10275
10327
  - Before long sequences (many reads, greps, refactors): one short line \u2014 intent and why.
10276
10328
  - Right after **discoveries** (culprit file, likely cause, relevant API, pattern in codebase).
@@ -10280,7 +10332,7 @@ The user **only** sees chat content you send through the \`message\` tool (\`con
10280
10332
 
10281
10333
  Reasoning streams (if any) do **not** replace \`info\` for user-visible narrative \u2014 see \`<reasoning_and_message_info>\`.
10282
10334
 
10283
- If you need an answer from the user, use \`message\` with \`result\`.
10335
+ If you need an answer from the user, use \`ask_user_question\` (local) or \`message\` with \`result\` (sandbox).
10284
10336
  When addressing {username}: normalize handles (hyphens/underscores/dots \u2192 spaces, title case, strip trailing digits if any).
10285
10337
  </messages>
10286
10338
 
@@ -11008,9 +11060,18 @@ function decideToolExecution(toolName) {
11008
11060
  reason: "Unknown tool metadata; require confirmation by default."
11009
11061
  };
11010
11062
  }
11011
- let autoApprove = policy.isSandbox ? metadata.autoApproveInSandbox : metadata.autoApproveInLocal;
11063
+ if (policy.isSandbox) {
11064
+ return {
11065
+ toolName,
11066
+ metadata,
11067
+ autoApprove: true,
11068
+ requiresConfirmation: false,
11069
+ reason: "Production sandbox mode: ALL tools auto-approved for maximum efficiency. Isolated Docker container ensures safety."
11070
+ };
11071
+ }
11072
+ let autoApprove = metadata.autoApproveInLocal;
11012
11073
  const { permissionMode } = getRuntimeConfig();
11013
- if (permissionMode === "accept_edits" && !policy.isSandbox && (toolName === "edit_tool" || toolName === "file_write")) {
11074
+ if (permissionMode === "accept_edits" && (toolName === "edit_tool" || toolName === "file_write")) {
11014
11075
  autoApprove = true;
11015
11076
  }
11016
11077
  if (planModeForcesConfirmation(toolName)) {
@@ -11021,7 +11082,7 @@ function decideToolExecution(toolName) {
11021
11082
  metadata,
11022
11083
  autoApprove,
11023
11084
  requiresConfirmation: !autoApprove,
11024
- reason: autoApprove ? policy.isSandbox ? "Tool auto-approved inside workspace sandbox." : "Tool marked safe for local autonomous execution." : "Tool requires confirmation outside sandbox mode."
11085
+ reason: autoApprove ? "Tool marked safe for local autonomous execution." : "Tool requires confirmation outside sandbox mode."
11025
11086
  };
11026
11087
  }
11027
11088
 
@@ -15929,11 +15990,12 @@ Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
15929
15990
  }
15930
15991
  }
15931
15992
 
15932
- // src/app/ui/components/UpdateNotice.tsx
15993
+ // src/app/ui/components/StartupUpdateGate.tsx
15933
15994
  import { Box as Box17, Text as Text16 } from "ink";
15934
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
15995
+
15996
+ // src/app/ui/utils/update_message.ts
15935
15997
  function parseUpdateMessage(msg) {
15936
- const lines = msg.split(/\r?\n/).map((l) => l.trim());
15998
+ const lines = String(msg ?? "").split(/\r?\n/).map((l) => l.trim());
15937
15999
  const first = lines[0] || "";
15938
16000
  const hintLine = lines.slice(1).join(" ") || "";
15939
16001
  const nameMatch = first.match(/Update available for\s+([^!]+)!/i);
@@ -15945,19 +16007,38 @@ function parseUpdateMessage(msg) {
15945
16007
  hint: hintLine || void 0
15946
16008
  };
15947
16009
  }
15948
- var UpdateNotice = ({ message: message2 }) => {
16010
+ function extractInstallCommand(hint) {
16011
+ if (!hint) return null;
16012
+ const match = hint.match(/Run:\s*(.+?)(?:\s+to update\.?)?$/i);
16013
+ return match?.[1]?.trim() || null;
16014
+ }
16015
+
16016
+ // src/app/ui/components/StartupUpdateGate.tsx
16017
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
16018
+ var StartupUpdateGate = ({
16019
+ message: message2
16020
+ }) => {
15949
16021
  const { name, current, latest: latest2, hint } = parseUpdateMessage(message2);
15950
- return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingLeft: 2, children: [
15951
- name && current && latest2 ? /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.claude, bold: true, children: name }) : null,
15952
- name && current && latest2 ? /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
16022
+ const command = extractInstallCommand(hint);
16023
+ return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
16024
+ /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
16025
+ /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.orange, children: "\u273B " }),
16026
+ /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.blue, bold: true, children: "Blu" }),
16027
+ /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.magenta, bold: true, children: "Ma" }),
16028
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " update gate" })
16029
+ ] }),
16030
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.claude, children: name || "New BluMa version available" }),
16031
+ current && latest2 ? /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
15953
16032
  current,
15954
16033
  " \u2192 ",
15955
16034
  latest2
15956
- ] }) : /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: message2 }),
15957
- hint ? /* @__PURE__ */ jsx18(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: hint }) }) : null
16035
+ ] }) : null,
16036
+ /* @__PURE__ */ jsx18(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, wrap: "wrap", children: command ? `Run this command to update:
16037
+ ${command}` : hint || "An update is available for BluMa CLI." }) }),
16038
+ /* @__PURE__ */ jsx18(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.warn, bold: true, children: "Press Enter to continue" }) })
15958
16039
  ] }) });
15959
16040
  };
15960
- var UpdateNotice_default = UpdateNotice;
16041
+ var StartupUpdateGate_default = StartupUpdateGate;
15961
16042
 
15962
16043
  // src/app/ui/components/ErrorMessage.tsx
15963
16044
  import { Box as Box18, Text as Text17 } from "ink";
@@ -16549,6 +16630,10 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
16549
16630
  "Initializing agent..."
16550
16631
  );
16551
16632
  const [toolsCount, setToolsCount] = useState11(null);
16633
+ const [startupPhase, setStartupPhase] = useState11("checking");
16634
+ const [startupUpdateMessage, setStartupUpdateMessage] = useState11(
16635
+ null
16636
+ );
16552
16637
  const [mcpStatus, setMcpStatus] = useState11(
16553
16638
  "connecting"
16554
16639
  );
@@ -16562,23 +16647,12 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
16562
16647
  const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState11(null);
16563
16648
  const [showWorkers, setShowWorkers] = useState11(false);
16564
16649
  const [zoomedWorkerSession, setZoomedWorkerSession] = useState11(null);
16565
- useInput6((input, key) => {
16566
- if (key.ctrl && key.shift && input.toLowerCase() === "w") {
16567
- setShowWorkers((prev) => !prev);
16568
- }
16569
- if (key.escape && showWorkers) {
16570
- if (zoomedWorkerSession) {
16571
- setZoomedWorkerSession(null);
16572
- } else {
16573
- setShowWorkers(false);
16574
- }
16575
- }
16576
- });
16577
16650
  const [isInitAgentActive, setIsInitAgentActive] = useState11(false);
16578
16651
  const [liveToolName, setLiveToolName] = useState11(null);
16579
16652
  const [liveToolArgs, setLiveToolArgs] = useState11(void 0);
16580
16653
  const [isReasoning, setIsReasoning] = useState11(false);
16581
16654
  const alwaysAcceptList = useRef6([]);
16655
+ const agentInitializationStartedRef = useRef6(false);
16582
16656
  const workdir = process.cwd();
16583
16657
  const turnStartedAtRef = useRef6(null);
16584
16658
  const [processingStartMs, setProcessingStartMs] = useState11(null);
@@ -16612,6 +16686,33 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
16612
16686
  ];
16613
16687
  });
16614
16688
  }, []);
16689
+ const startAgentInitialization = useCallback4(async () => {
16690
+ if (agentInitializationStartedRef.current) return;
16691
+ agentInitializationStartedRef.current = true;
16692
+ try {
16693
+ agentInstance.current = new Agent(sessionId, eventBus);
16694
+ await agentInstance.current.initialize();
16695
+ appendHookEvent({
16696
+ type: "session:init",
16697
+ sessionId,
16698
+ summary: "agent initialized"
16699
+ });
16700
+ eventBus.emit("backend_message", {
16701
+ type: "status",
16702
+ status: "mcp_connected",
16703
+ tools: agentInstance.current.getAvailableTools().length
16704
+ });
16705
+ setStartupPhase("ready");
16706
+ } catch (error) {
16707
+ const errorMessage = error instanceof Error ? error.message : "Unknown error during Agent initialization.";
16708
+ setStartupPhase("failed");
16709
+ setStatusMessage(errorMessage);
16710
+ eventBus.emit("backend_message", {
16711
+ type: "error",
16712
+ message: errorMessage
16713
+ });
16714
+ }
16715
+ }, [eventBus, sessionId]);
16615
16716
  useEffect11(() => {
16616
16717
  expandPreviewHotkeyBus.on("expand", appendExpandPreviewToHistory);
16617
16718
  return () => {
@@ -16619,22 +16720,24 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
16619
16720
  };
16620
16721
  }, [appendExpandPreviewToHistory]);
16621
16722
  useEffect11(() => {
16622
- if (process.env.CI || blumaUpdateRegistryCheckStarted) return;
16723
+ if (process.env.CI || blumaUpdateRegistryCheckStarted) {
16724
+ setStartupPhase("starting-agent");
16725
+ void startAgentInitialization();
16726
+ return;
16727
+ }
16623
16728
  blumaUpdateRegistryCheckStarted = true;
16729
+ setStatusMessage("Checking for updates...");
16624
16730
  void checkForUpdates().then((msg) => {
16625
- if (!msg) return;
16626
- setHistory((prev) => {
16627
- const nextId2 = prev.length === 0 ? 1 : Math.max(...prev.map((h) => h.id), HEADER_PANEL_HISTORY_ID) + 1;
16628
- return [
16629
- ...prev,
16630
- {
16631
- id: nextId2,
16632
- component: /* @__PURE__ */ jsx27(UpdateNotice_default, { message: msg })
16633
- }
16634
- ];
16635
- });
16731
+ if (!msg) {
16732
+ setStartupPhase("starting-agent");
16733
+ void startAgentInitialization();
16734
+ return;
16735
+ }
16736
+ setStartupUpdateMessage(msg);
16737
+ setStartupPhase("update-available");
16738
+ setStatusMessage("Update available");
16636
16739
  });
16637
- }, []);
16740
+ }, [startAgentInitialization]);
16638
16741
  useEffect11(() => {
16639
16742
  setHistory((prev) => {
16640
16743
  const tail = prev.filter((h) => h.id !== HEADER_PANEL_HISTORY_ID);
@@ -16664,6 +16767,28 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
16664
16767
  ];
16665
16768
  });
16666
16769
  }, [isProcessing, eventBus]);
16770
+ const continuePastUpdateGate = useCallback4(() => {
16771
+ if (startupPhase !== "update-available") return;
16772
+ setStartupPhase("starting-agent");
16773
+ setStatusMessage("Connecting to MCP...");
16774
+ void startAgentInitialization();
16775
+ }, [startupPhase, startAgentInitialization]);
16776
+ useInput6((input, key) => {
16777
+ if (startupPhase === "update-available" && (key.return || input === "\n" || input === "\r")) {
16778
+ continuePastUpdateGate();
16779
+ return;
16780
+ }
16781
+ if (key.ctrl && key.shift && input.toLowerCase() === "w") {
16782
+ setShowWorkers((prev) => !prev);
16783
+ }
16784
+ if (key.escape && showWorkers) {
16785
+ if (zoomedWorkerSession) {
16786
+ setZoomedWorkerSession(null);
16787
+ } else {
16788
+ setShowWorkers(false);
16789
+ }
16790
+ }
16791
+ });
16667
16792
  const handleSubmit = useCallback4(
16668
16793
  (text) => {
16669
16794
  if (!text || !agentInstance.current) return;
@@ -16919,28 +17044,6 @@ Please use command_status to check the result and report back to the user.`;
16919
17044
  }
16920
17045
  }, [history.length]);
16921
17046
  useEffect11(() => {
16922
- const initializeAgent = async () => {
16923
- try {
16924
- agentInstance.current = new Agent(sessionId, eventBus);
16925
- await agentInstance.current.initialize();
16926
- appendHookEvent({
16927
- type: "session:init",
16928
- sessionId,
16929
- summary: "agent initialized"
16930
- });
16931
- eventBus.emit("backend_message", {
16932
- type: "status",
16933
- status: "mcp_connected",
16934
- tools: agentInstance.current.getAvailableTools().length
16935
- });
16936
- } catch (error) {
16937
- const errorMessage = error instanceof Error ? error.message : "Unknown error during Agent initialization.";
16938
- eventBus.emit("backend_message", {
16939
- type: "error",
16940
- message: errorMessage
16941
- });
16942
- }
16943
- };
16944
17047
  const handleBackendMessage = (parsed) => {
16945
17048
  try {
16946
17049
  const appendTurnDurationIfAny = () => {
@@ -17195,7 +17298,6 @@ Please use command_status to check the result and report back to the user.`;
17195
17298
  uiEventBus.on("user_overlay", handleUiOverlay);
17196
17299
  uiEventBus.on("input_notice", handleInputNotice);
17197
17300
  eventBus.on("backend_message", handleBackendMessage);
17198
- initializeAgent();
17199
17301
  return () => {
17200
17302
  uiEventBus.off("user_overlay", handleUiOverlay);
17201
17303
  uiEventBus.off("input_notice", handleInputNotice);
@@ -17203,6 +17305,27 @@ Please use command_status to check the result and report back to the user.`;
17203
17305
  };
17204
17306
  }, [eventBus, sessionId, handleConfirmation]);
17205
17307
  const renderInteractiveComponent = () => {
17308
+ if (startupPhase === "checking") {
17309
+ return /* @__PURE__ */ jsx27(
17310
+ SessionInfoConnectingMCP_default,
17311
+ {
17312
+ workdir,
17313
+ statusMessage: statusMessage || "Checking for updates..."
17314
+ }
17315
+ );
17316
+ }
17317
+ if (startupPhase === "update-available" && startupUpdateMessage) {
17318
+ return /* @__PURE__ */ jsx27(StartupUpdateGate_default, { message: startupUpdateMessage });
17319
+ }
17320
+ if (startupPhase === "failed") {
17321
+ return /* @__PURE__ */ jsx27(Box26, { flexDirection: "column", children: /* @__PURE__ */ jsx27(
17322
+ ErrorMessage_default,
17323
+ {
17324
+ message: statusMessage || "Startup failed",
17325
+ hint: "Fix the error above and restart BluMa."
17326
+ }
17327
+ ) });
17328
+ }
17206
17329
  if (mcpStatus !== "connected") {
17207
17330
  return /* @__PURE__ */ jsx27(
17208
17331
  SessionInfoConnectingMCP_default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",