@nomad-e/bluma-cli 0.1.32 → 0.1.33

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 (2) hide show
  1. package/dist/main.js +510 -100
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -17,7 +17,7 @@ __export(async_command_exports, {
17
17
  runCommandAsync: () => runCommandAsync,
18
18
  sendCommandInput: () => sendCommandInput
19
19
  });
20
- import os2 from "os";
20
+ import os3 from "os";
21
21
  import { spawn } from "child_process";
22
22
  import { v4 as uuidv42 } from "uuid";
23
23
  function cleanupOldCommands() {
@@ -63,7 +63,7 @@ async function runCommandAsync(args) {
63
63
  };
64
64
  }
65
65
  const commandId = uuidv42().substring(0, 8);
66
- const platform = os2.platform();
66
+ const platform = os3.platform();
67
67
  let shellCmd;
68
68
  let shellArgs;
69
69
  if (platform === "win32") {
@@ -331,7 +331,9 @@ var init_async_command = __esm({
331
331
  import React12 from "react";
332
332
  import { render } from "ink";
333
333
  import { EventEmitter as EventEmitter3 } from "events";
334
- import fs15 from "fs";
334
+ import fs16 from "fs";
335
+ import path20 from "path";
336
+ import { fileURLToPath as fileURLToPath4 } from "url";
335
337
  import { v4 as uuidv46 } from "uuid";
336
338
 
337
339
  // src/app/ui/App.tsx
@@ -339,8 +341,9 @@ import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useC
339
341
  import { Box as Box20, Text as Text19, Static } from "ink";
340
342
 
341
343
  // src/app/ui/layout.tsx
342
- import { Box, Text } from "ink";
344
+ import { Box, Text, useStdout } from "ink";
343
345
  import { memo } from "react";
346
+ import os from "os";
344
347
 
345
348
  // src/app/ui/theme/blumaTerminal.ts
346
349
  var BLUMA_TERMINAL = {
@@ -375,24 +378,106 @@ var BLUMA_TERMINAL = {
375
378
 
376
379
  // src/app/ui/layout.tsx
377
380
  import { jsx, jsxs } from "react/jsx-runtime";
381
+ function buildTopBorder(cols, leftW, rightW, titleInLeft) {
382
+ const t = titleInLeft.length <= leftW ? titleInLeft : titleInLeft.slice(0, leftW);
383
+ const leftFill = t + "\u2500".repeat(Math.max(0, leftW - t.length));
384
+ const rightFill = "\u2500".repeat(rightW);
385
+ return "\u250C" + leftFill + "\u252C" + rightFill + "\u2510";
386
+ }
387
+ function buildBottomBorder(leftW, rightW) {
388
+ return "\u2514" + "\u2500".repeat(leftW) + "\u2534" + "\u2500".repeat(rightW) + "\u2518";
389
+ }
378
390
  var HeaderComponent = ({
379
391
  sessionId,
380
- workdir
392
+ workdir,
393
+ cliVersion = "?",
394
+ recentActivitySummary
381
395
  }) => {
382
- const dirName = workdir.split("/").pop() || workdir;
383
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
384
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [
385
- /* @__PURE__ */ jsx(Text, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "BluMa" }),
386
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2014 " }),
387
- /* @__PURE__ */ jsx(Text, { children: "Base Language Unit" }),
388
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
389
- /* @__PURE__ */ jsx(Text, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "Model Agent" })
396
+ const { stdout } = useStdout();
397
+ const cols = Math.max(52, stdout?.columns ?? 80);
398
+ let username = "there";
399
+ try {
400
+ username = os.userInfo().username || username;
401
+ } catch {
402
+ }
403
+ const inner = cols - 3;
404
+ const leftW = Math.max(24, Math.floor(inner * 0.48));
405
+ const rightW = Math.max(22, inner - leftW);
406
+ const borderTitle = ` BluMa v${cliVersion} `;
407
+ const topLine = buildTopBorder(cols, leftW, rightW, borderTitle);
408
+ const bottomLine = buildBottomBorder(leftW, rightW);
409
+ const M = BLUMA_TERMINAL.brandMagenta;
410
+ const B = BLUMA_TERMINAL.panelBorder;
411
+ const ruleW = Math.max(6, rightW - 2);
412
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, width: cols, children: [
413
+ /* @__PURE__ */ jsx(Text, { color: B, children: topLine }),
414
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", width: cols, children: [
415
+ /* @__PURE__ */ jsx(Text, { color: B, children: "\u2502" }),
416
+ /* @__PURE__ */ jsxs(
417
+ Box,
418
+ {
419
+ width: leftW,
420
+ flexDirection: "column",
421
+ alignItems: "center",
422
+ paddingX: 1,
423
+ children: [
424
+ /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
425
+ /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "BluMa" }),
426
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2014 " }),
427
+ /* @__PURE__ */ jsx(Text, { bold: true, color: B, children: "Base Language Unit" }),
428
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
429
+ /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "Model Agent" })
430
+ ] }),
431
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", alignItems: "center", children: /* @__PURE__ */ jsxs(Text, { children: [
432
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Welcome back " }),
433
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: username }),
434
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "!" })
435
+ ] }) }),
436
+ /* @__PURE__ */ jsxs(Box, { marginY: 1, flexDirection: "column", alignItems: "center", children: [
437
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(13, Math.max(7, leftW - 6))) }),
438
+ /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: " \u27E8 \u03BB \u27E9 " }),
439
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(13, Math.max(7, leftW - 6))) })
440
+ ] }),
441
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignSelf: "flex-start", width: leftW - 2, children: [
442
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, wrap: "wrap", children: [
443
+ /* @__PURE__ */ jsx(Text, { color: "white", children: "auto" }),
444
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 FactorRouter \xB7 session " }),
445
+ /* @__PURE__ */ jsx(Text, { color: B, children: sessionId.slice(0, 8) })
446
+ ] }),
447
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: workdir })
448
+ ] })
449
+ ]
450
+ }
451
+ ),
452
+ /* @__PURE__ */ jsx(Text, { color: B, children: "\u2502" }),
453
+ /* @__PURE__ */ jsxs(Box, { width: rightW, flexDirection: "column", paddingX: 1, children: [
454
+ /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "Tips for getting started" }),
455
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, wrap: "wrap", children: [
456
+ "Run ",
457
+ /* @__PURE__ */ jsx(Text, { color: "white", children: "/init" }),
458
+ " to scaffold project context (BluMa.md). Use",
459
+ " ",
460
+ /* @__PURE__ */ jsx(Text, { color: "white", children: "/help" }),
461
+ ", ",
462
+ /* @__PURE__ */ jsx(Text, { color: "white", children: "/img" }),
463
+ ", ",
464
+ /* @__PURE__ */ jsx(Text, { color: "white", children: "!" }),
465
+ " ",
466
+ "as needed."
467
+ ] }),
468
+ /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(ruleW) }) }),
469
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "Recent activity" }) }),
470
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: recentActivitySummary?.trim() ? recentActivitySummary.trim() : "No recent activity." })
471
+ ] }),
472
+ /* @__PURE__ */ jsx(Text, { color: B, children: "\u2502" })
390
473
  ] }),
391
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [
392
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cwd " }),
393
- /* @__PURE__ */ jsx(Text, { color: BLUMA_TERMINAL.brandBlue, children: dirName }),
394
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 session " }),
395
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: sessionId.slice(0, 8) })
474
+ /* @__PURE__ */ jsx(Text, { color: B, children: bottomLine }),
475
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
476
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 " }),
477
+ /* @__PURE__ */ jsx(Text, { color: B, bold: true, children: "NomadEngenuity" }),
478
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 Factor stack \xB7 " }),
479
+ /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "/help" }),
480
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " for shortcuts" })
396
481
  ] })
397
482
  ] });
398
483
  };
@@ -441,7 +526,7 @@ var TaskStatusBarComponent = ({
441
526
  var TaskStatusBar = memo(TaskStatusBarComponent);
442
527
 
443
528
  // src/app/ui/components/InputPrompt.tsx
444
- import { Box as Box2, Text as Text2, useStdout, useInput as useInput2 } from "ink";
529
+ import { Box as Box2, Text as Text2, useStdout as useStdout2, useInput as useInput2 } from "ink";
445
530
 
446
531
  // src/app/ui/utils/useSimpleInputBuffer.ts
447
532
  import { useReducer, useRef, useCallback, useEffect } from "react";
@@ -742,12 +827,68 @@ import { EventEmitter as EventEmitter2 } from "events";
742
827
 
743
828
  // src/app/ui/utils/slashRegistry.ts
744
829
  var getSlashCommands = () => [
745
- { name: "/help", description: "list commands" },
746
- { name: "/mcp", description: "list tools connected via MCP" },
747
- { name: "/tools", description: "list native tools" },
748
- { name: "/init", description: "create a new BluMa.md file with codebase documentation" },
749
- { name: "/clear", description: "clear history" }
830
+ {
831
+ name: "/help",
832
+ description: "list all slash commands (grouped)",
833
+ category: "help"
834
+ },
835
+ {
836
+ name: "/clear",
837
+ description: "clear chat below the welcome panel (welcome + session file unchanged)",
838
+ category: "session"
839
+ },
840
+ {
841
+ name: "/img",
842
+ description: "send local image(s) to the model: /img ./shot.png [your question]",
843
+ category: "agent"
844
+ },
845
+ {
846
+ name: "/image",
847
+ description: "alias of /img",
848
+ category: "agent"
849
+ },
850
+ {
851
+ name: "/skills",
852
+ description: "list load_skill modules (bundled + project + ~/.bluma)",
853
+ category: "inspect"
854
+ },
855
+ {
856
+ name: "/tools",
857
+ description: "list native tools (optional filter: /tools grep)",
858
+ category: "inspect"
859
+ },
860
+ {
861
+ name: "/mcp",
862
+ description: "list MCP tools (optional filter: /mcp fs)",
863
+ category: "inspect"
864
+ },
865
+ {
866
+ name: "/init",
867
+ description: "run init subagent \u2014 BluMa.md codebase documentation",
868
+ category: "agent"
869
+ }
750
870
  ];
871
+ var CATEGORY_LABEL = {
872
+ session: "Session & UI",
873
+ inspect: "Inspect",
874
+ agent: "Agent",
875
+ help: "Help"
876
+ };
877
+ function formatSlashHelpLines() {
878
+ const cmds = getSlashCommands();
879
+ const byCat = (cat) => cmds.filter((c) => c.category === cat);
880
+ const lines = [];
881
+ for (const cat of ["help", "session", "agent", "inspect"]) {
882
+ const group = byCat(cat);
883
+ if (group.length === 0) continue;
884
+ lines.push(`${CATEGORY_LABEL[cat]}:`);
885
+ for (const c of group) {
886
+ lines.push(` ${c.name.padEnd(14)} ${c.description}`);
887
+ }
888
+ lines.push("");
889
+ }
890
+ return lines;
891
+ }
751
892
  var filterSlashCommands = (query) => {
752
893
  const list = getSlashCommands();
753
894
  const q = (query || "").toLowerCase();
@@ -1006,7 +1147,9 @@ var SlashSuggestions = memo2(({
1006
1147
  s.name,
1007
1148
  " ",
1008
1149
  /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1009
- "- ",
1150
+ "[",
1151
+ s.category,
1152
+ "] ",
1010
1153
  s.description
1011
1154
  ] })
1012
1155
  ] })
@@ -1014,13 +1157,13 @@ var SlashSuggestions = memo2(({
1014
1157
  }) }));
1015
1158
  SlashSuggestions.displayName = "SlashSuggestions";
1016
1159
  var InputRuleLine = memo2(() => {
1017
- const { stdout } = useStdout();
1160
+ const { stdout } = useStdout2();
1018
1161
  const cols = stdout?.columns ?? 80;
1019
1162
  const n = Math.max(8, cols);
1020
1163
  return /* @__PURE__ */ jsx2(Text2, { color: "white", children: TERMINAL_RULE_CHAR.repeat(n) });
1021
1164
  });
1022
1165
  InputRuleLine.displayName = "InputRuleLine";
1023
- var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt | Ctrl+O expand last clip" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | /help commands | esc interrupt | Ctrl+O expand last clip" }) }));
1166
+ var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt | Ctrl+O expand last clip" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | ? /help \xB7 /img \xB7 esc \xB7 Ctrl+O expand" }) }));
1024
1167
  Footer.displayName = "Footer";
1025
1168
  var TextLinesRenderer = memo2(({
1026
1169
  lines,
@@ -1058,7 +1201,7 @@ var InputPrompt = memo2(({
1058
1201
  onInterrupt,
1059
1202
  disableWhileProcessing = false
1060
1203
  }) => {
1061
- const { stdout } = useStdout();
1204
+ const { stdout } = useStdout2();
1062
1205
  const [viewWidth] = useState2(() => stdout.columns - 6);
1063
1206
  const [slashOpen, setSlashOpen] = useState2(false);
1064
1207
  const [slashIndex, setSlashIndex] = useState2(0);
@@ -1583,8 +1726,8 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
1583
1726
 
1584
1727
  // src/app/agent/agent.ts
1585
1728
  import * as dotenv from "dotenv";
1586
- import path17 from "path";
1587
- import os11 from "os";
1729
+ import path18 from "path";
1730
+ import os13 from "os";
1588
1731
 
1589
1732
  // src/app/agent/tool_invoker.ts
1590
1733
  import { promises as fs9 } from "fs";
@@ -1593,7 +1736,7 @@ import { fileURLToPath } from "url";
1593
1736
 
1594
1737
  // src/app/agent/tools/natives/edit.ts
1595
1738
  import path3 from "path";
1596
- import os from "os";
1739
+ import os2 from "os";
1597
1740
  import { promises as fs2 } from "fs";
1598
1741
  import { diffLines } from "diff";
1599
1742
  var MAX_DIFF_SIZE = 5e4;
@@ -1601,7 +1744,7 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
1601
1744
  function normalizePath(filePath) {
1602
1745
  try {
1603
1746
  filePath = filePath.trim();
1604
- if (os.platform() === "win32") {
1747
+ if (os2.platform() === "win32") {
1605
1748
  const winDriveRegex = /^\/([a-zA-Z])[:/]/;
1606
1749
  const match = filePath.match(winDriveRegex);
1607
1750
  if (match) {
@@ -3028,12 +3171,12 @@ init_async_command();
3028
3171
  // src/app/agent/tools/natives/task_boundary.ts
3029
3172
  import path9 from "path";
3030
3173
  import { promises as fs7 } from "fs";
3031
- import os3 from "os";
3174
+ import os4 from "os";
3032
3175
  var currentTask = null;
3033
3176
  var artifactsDir = null;
3034
3177
  async function getArtifactsDir() {
3035
3178
  if (artifactsDir) return artifactsDir;
3036
- const homeDir = os3.homedir();
3179
+ const homeDir = os4.homedir();
3037
3180
  const baseDir = path9.join(homeDir, ".bluma", "artifacts");
3038
3181
  const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
3039
3182
  artifactsDir = path9.join(baseDir, sessionId);
@@ -3526,7 +3669,7 @@ ${skill.content}`;
3526
3669
  // src/app/agent/tools/natives/coding_memory.ts
3527
3670
  import * as fs8 from "fs";
3528
3671
  import * as path10 from "path";
3529
- import os4 from "os";
3672
+ import os5 from "os";
3530
3673
  var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
3531
3674
  var PROMPT_DEFAULT_MAX_NOTES = 25;
3532
3675
  var PROMPT_DEFAULT_PREVIEW = 500;
@@ -3534,7 +3677,7 @@ function readCodingMemoryForPrompt(options) {
3534
3677
  const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
3535
3678
  const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
3536
3679
  const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
3537
- const globalPath = path10.join(os4.homedir(), ".bluma", "coding_memory.json");
3680
+ const globalPath = path10.join(os5.homedir(), ".bluma", "coding_memory.json");
3538
3681
  const legacyPath = path10.join(process.cwd(), ".bluma", "coding_memory.json");
3539
3682
  let raw = null;
3540
3683
  try {
@@ -3579,7 +3722,7 @@ var memoryStore = [];
3579
3722
  var nextId2 = 1;
3580
3723
  var loaded = false;
3581
3724
  function getMemoryFilePath() {
3582
- return path10.join(os4.homedir(), ".bluma", "coding_memory.json");
3725
+ return path10.join(os5.homedir(), ".bluma", "coding_memory.json");
3583
3726
  }
3584
3727
  function getLegacyMemoryFilePath() {
3585
3728
  return path10.join(process.cwd(), ".bluma", "coding_memory.json");
@@ -3889,7 +4032,7 @@ var ToolInvoker = class {
3889
4032
  // src/app/agent/tools/mcp/mcp_client.ts
3890
4033
  import { promises as fs10 } from "fs";
3891
4034
  import path12 from "path";
3892
- import os5 from "os";
4035
+ import os6 from "os";
3893
4036
  import { fileURLToPath as fileURLToPath2 } from "url";
3894
4037
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3895
4038
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -3918,7 +4061,7 @@ var MCPClient = class {
3918
4061
  const __filename = fileURLToPath2(import.meta.url);
3919
4062
  const __dirname2 = path12.dirname(__filename);
3920
4063
  const defaultConfigPath = path12.resolve(__dirname2, "config", "bluma-mcp.json");
3921
- const userConfigPath = path12.join(os5.homedir(), ".bluma", "bluma-mcp.json");
4064
+ const userConfigPath = path12.join(os6.homedir(), ".bluma", "bluma-mcp.json");
3922
4065
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
3923
4066
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
3924
4067
  const mergedConfig = {
@@ -3971,7 +4114,7 @@ var MCPClient = class {
3971
4114
  async connectToStdioServer(serverName, config2) {
3972
4115
  let commandToExecute = config2.command;
3973
4116
  let argsToExecute = config2.args || [];
3974
- const isWindows = os5.platform() === "win32";
4117
+ const isWindows = os6.platform() === "win32";
3975
4118
  if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
3976
4119
  if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
3977
4120
  commandToExecute = argsToExecute[1];
@@ -4087,12 +4230,12 @@ var AdvancedFeedbackSystem = class {
4087
4230
  };
4088
4231
 
4089
4232
  // src/app/agent/bluma/core/bluma.ts
4090
- import path16 from "path";
4233
+ import path17 from "path";
4091
4234
  import { v4 as uuidv43 } from "uuid";
4092
4235
 
4093
4236
  // src/app/agent/session_manager/session_manager.ts
4094
4237
  import path13 from "path";
4095
- import os6 from "os";
4238
+ import os7 from "os";
4096
4239
  import { promises as fs11 } from "fs";
4097
4240
  var fileLocks = /* @__PURE__ */ new Map();
4098
4241
  async function withFileLock(file, fn) {
@@ -4129,12 +4272,12 @@ function debouncedSave(sessionFile, history, memory) {
4129
4272
  function expandHome(p) {
4130
4273
  if (!p) return p;
4131
4274
  if (p.startsWith("~")) {
4132
- return path13.join(os6.homedir(), p.slice(1));
4275
+ return path13.join(os7.homedir(), p.slice(1));
4133
4276
  }
4134
4277
  return p;
4135
4278
  }
4136
4279
  function getPreferredAppDir() {
4137
- const fixed = path13.join(os6.homedir(), ".bluma");
4280
+ const fixed = path13.join(os7.homedir(), ".bluma");
4138
4281
  return path13.resolve(expandHome(fixed));
4139
4282
  }
4140
4283
  async function safeRenameWithRetry(src, dest, maxRetries = 6) {
@@ -4277,7 +4420,7 @@ async function saveSessionHistory(sessionFile, history, memory) {
4277
4420
  }
4278
4421
 
4279
4422
  // src/app/agent/core/prompt/prompt_builder.ts
4280
- import os8 from "os";
4423
+ import os9 from "os";
4281
4424
  import fs13 from "fs";
4282
4425
  import path15 from "path";
4283
4426
  import { execSync } from "child_process";
@@ -4285,7 +4428,7 @@ import { execSync } from "child_process";
4285
4428
  // src/app/agent/skills/skill_loader.ts
4286
4429
  import fs12 from "fs";
4287
4430
  import path14 from "path";
4288
- import os7 from "os";
4431
+ import os8 from "os";
4289
4432
  var SkillLoader = class _SkillLoader {
4290
4433
  bundledSkillsDir;
4291
4434
  projectSkillsDir;
@@ -4294,7 +4437,7 @@ var SkillLoader = class _SkillLoader {
4294
4437
  conflicts = [];
4295
4438
  constructor(projectRoot, bundledDir) {
4296
4439
  this.projectSkillsDir = path14.join(projectRoot, ".bluma", "skills");
4297
- this.globalSkillsDir = path14.join(os7.homedir(), ".bluma", "skills");
4440
+ this.globalSkillsDir = path14.join(os8.homedir(), ".bluma", "skills");
4298
4441
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
4299
4442
  }
4300
4443
  /**
@@ -5121,12 +5264,12 @@ In sandbox mode you are a Python-focused, non-interactive, deterministic agent t
5121
5264
  function getUnifiedSystemPrompt(availableSkills) {
5122
5265
  const cwd = process.cwd();
5123
5266
  const env = {
5124
- os_type: os8.type(),
5125
- os_version: os8.release(),
5126
- architecture: os8.arch(),
5267
+ os_type: os9.type(),
5268
+ os_version: os9.release(),
5269
+ architecture: os9.arch(),
5127
5270
  workdir: cwd,
5128
5271
  shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
5129
- username: os8.userInfo().username,
5272
+ username: os9.userInfo().username,
5130
5273
  current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
5131
5274
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
5132
5275
  is_git_repo: isGitRepo(cwd) ? "yes" : "no",
@@ -5592,7 +5735,7 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
5592
5735
  }
5593
5736
 
5594
5737
  // src/app/agent/core/llm/llm.ts
5595
- import os9 from "os";
5738
+ import os10 from "os";
5596
5739
  import OpenAI from "openai";
5597
5740
  function defaultBlumaUserContextInput(sessionId, userMessage) {
5598
5741
  const msg = String(userMessage || "").slice(0, 300);
@@ -5609,7 +5752,7 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
5609
5752
  }
5610
5753
  function getPreferredMacAddress() {
5611
5754
  try {
5612
- const ifaces = os9.networkInterfaces();
5755
+ const ifaces = os10.networkInterfaces();
5613
5756
  for (const name of Object.keys(ifaces)) {
5614
5757
  const addrs = ifaces[name];
5615
5758
  if (!addrs) continue;
@@ -5624,7 +5767,7 @@ function getPreferredMacAddress() {
5624
5767
  } catch {
5625
5768
  }
5626
5769
  try {
5627
- return `host:${os9.hostname()}`;
5770
+ return `host:${os10.hostname()}`;
5628
5771
  } catch {
5629
5772
  return "unknown";
5630
5773
  }
@@ -5634,7 +5777,7 @@ function defaultInteractiveCliUserContextInput(sessionId, userMessage) {
5634
5777
  const machineId = getPreferredMacAddress();
5635
5778
  let userName = null;
5636
5779
  try {
5637
- userName = os9.userInfo().username || null;
5780
+ userName = os10.userInfo().username || null;
5638
5781
  } catch {
5639
5782
  userName = null;
5640
5783
  }
@@ -5961,6 +6104,134 @@ var ToolCallNormalizer = class {
5961
6104
  }
5962
6105
  };
5963
6106
 
6107
+ // src/app/agent/utils/user_message_images.ts
6108
+ import fs14 from "fs";
6109
+ import os11 from "os";
6110
+ import path16 from "path";
6111
+ var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
6112
+ var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
6113
+ var MAX_IMAGES = 6;
6114
+ var MIME = {
6115
+ ".png": "image/png",
6116
+ ".jpg": "image/jpeg",
6117
+ ".jpeg": "image/jpeg",
6118
+ ".gif": "image/gif",
6119
+ ".webp": "image/webp",
6120
+ ".bmp": "image/bmp"
6121
+ };
6122
+ function expandUserPath(p) {
6123
+ const t = p.trim();
6124
+ if (t.startsWith("~")) {
6125
+ return path16.join(os11.homedir(), t.slice(1).replace(/^\//, ""));
6126
+ }
6127
+ return t;
6128
+ }
6129
+ function isPathAllowed(absResolved, cwd) {
6130
+ const resolved = path16.normalize(path16.resolve(absResolved));
6131
+ const cwdR = path16.normalize(path16.resolve(cwd));
6132
+ const homeR = path16.normalize(path16.resolve(os11.homedir()));
6133
+ const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path16.sep);
6134
+ const underHome = resolved === homeR || resolved.startsWith(homeR + path16.sep);
6135
+ return underCwd || underHome;
6136
+ }
6137
+ function mimeFor(abs) {
6138
+ const ext = path16.extname(abs).toLowerCase();
6139
+ return MIME[ext] || "application/octet-stream";
6140
+ }
6141
+ function collectImagePathStrings(raw) {
6142
+ const found = [];
6143
+ const seen = /* @__PURE__ */ new Set();
6144
+ const quoted = /(["'])((?:(?!\1).)*?\.(?:png|jpe?g|gif|webp|bmp))\1/gi;
6145
+ for (const m of raw.matchAll(quoted)) {
6146
+ const p = m[2].trim();
6147
+ if (p && !seen.has(p)) {
6148
+ seen.add(p);
6149
+ found.push(p);
6150
+ }
6151
+ }
6152
+ const withoutQuoted = raw.replace(quoted, " ");
6153
+ const tokens = withoutQuoted.split(/\s+/);
6154
+ for (let tok of tokens) {
6155
+ tok = tok.replace(/^[([{]+/, "").replace(/[)\]},;:]+$/, "");
6156
+ if (!tok || !IMAGE_EXT.test(tok)) continue;
6157
+ if (!seen.has(tok)) {
6158
+ seen.add(tok);
6159
+ found.push(tok);
6160
+ }
6161
+ }
6162
+ return found;
6163
+ }
6164
+ function resolveImagePath(candidate, cwd) {
6165
+ const expanded = expandUserPath(candidate);
6166
+ const abs = path16.isAbsolute(expanded) ? path16.normalize(expanded) : path16.normalize(path16.resolve(cwd, expanded));
6167
+ if (!isPathAllowed(abs, cwd)) return null;
6168
+ try {
6169
+ if (!fs14.existsSync(abs) || !fs14.statSync(abs).isFile()) return null;
6170
+ } catch {
6171
+ return null;
6172
+ }
6173
+ if (!IMAGE_EXT.test(abs)) return null;
6174
+ return abs;
6175
+ }
6176
+ function stripImagePathStrings(text, paths) {
6177
+ let out = text;
6178
+ for (const p of paths) {
6179
+ const q = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6180
+ out = out.replace(new RegExp(`["']${q}["']`, "gi"), " ");
6181
+ out = out.replace(new RegExp(`(?<=^|\\s)${q}(?=\\s|$)`, "gi"), " ");
6182
+ }
6183
+ return out.replace(/\s{2,}/g, " ").trim();
6184
+ }
6185
+ function buildUserMessageContent(raw, cwd) {
6186
+ const trimmed = raw.trim();
6187
+ if (!trimmed) return trimmed;
6188
+ const candidates = collectImagePathStrings(trimmed);
6189
+ const resolvedAbs = [];
6190
+ const usedStrings = [];
6191
+ for (const c of candidates) {
6192
+ if (resolvedAbs.length >= MAX_IMAGES) break;
6193
+ const abs = resolveImagePath(c, cwd);
6194
+ if (!abs) continue;
6195
+ try {
6196
+ const st = fs14.statSync(abs);
6197
+ if (st.size > MAX_IMAGE_BYTES) continue;
6198
+ } catch {
6199
+ continue;
6200
+ }
6201
+ resolvedAbs.push(abs);
6202
+ usedStrings.push(c);
6203
+ }
6204
+ if (resolvedAbs.length === 0) {
6205
+ return trimmed;
6206
+ }
6207
+ let textPart = stripImagePathStrings(trimmed, usedStrings);
6208
+ if (!textPart) {
6209
+ textPart = "(User attached image(s) only \u2014 describe, compare, or answer using the image(s).)";
6210
+ }
6211
+ const note = resolvedAbs.map((p) => path16.basename(p)).join(", ");
6212
+ const parts = [
6213
+ {
6214
+ type: "text",
6215
+ text: `${textPart}
6216
+
6217
+ [Attached local image file(s): ${note}]`
6218
+ }
6219
+ ];
6220
+ for (const abs of resolvedAbs) {
6221
+ const buf = fs14.readFileSync(abs);
6222
+ const b64 = buf.toString("base64");
6223
+ const mime = mimeFor(abs);
6224
+ parts.push({
6225
+ type: "image_url",
6226
+ image_url: {
6227
+ url: `data:${mime};base64,${b64}`,
6228
+ detail: "auto"
6229
+ }
6230
+ });
6231
+ }
6232
+ return parts;
6233
+ }
6234
+
5964
6235
  // src/app/agent/bluma/core/bluma.ts
5965
6236
  var BluMaAgent = class {
5966
6237
  llm;
@@ -6072,6 +6343,12 @@ var BluMaAgent = class {
6072
6343
  getUiToolsDetailed() {
6073
6344
  return this.mcpClient.getAvailableToolsDetailed();
6074
6345
  }
6346
+ listAvailableSkills() {
6347
+ return this.skillLoader.listAvailable();
6348
+ }
6349
+ getSkillsDirs() {
6350
+ return this.skillLoader.getSkillsDirs();
6351
+ }
6075
6352
  async processTurn(userInput, userContextInput) {
6076
6353
  this.isInterrupted = false;
6077
6354
  this.factorRouterTurnClosed = false;
@@ -6082,7 +6359,8 @@ var BluMaAgent = class {
6082
6359
  turnId,
6083
6360
  sessionId: userContextInput.sessionId || this.sessionId
6084
6361
  };
6085
- this.history.push({ role: "user", content: inputText });
6362
+ const userContent = buildUserMessageContent(inputText, process.cwd());
6363
+ this.history.push({ role: "user", content: userContent });
6086
6364
  if (inputText === "/init") {
6087
6365
  this.eventBus.emit("dispatch", inputText);
6088
6366
  }
@@ -6235,7 +6513,7 @@ var BluMaAgent = class {
6235
6513
 
6236
6514
  ${editData.error.display}`;
6237
6515
  }
6238
- const filename = path16.basename(toolArgs.file_path);
6516
+ const filename = path17.basename(toolArgs.file_path);
6239
6517
  return createDiff(filename, editData.currentContent || "", editData.newContent);
6240
6518
  } catch (e) {
6241
6519
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -6493,7 +6771,7 @@ import { v4 as uuidv45 } from "uuid";
6493
6771
  import { v4 as uuidv44 } from "uuid";
6494
6772
 
6495
6773
  // src/app/agent/subagents/init/init_system_prompt.ts
6496
- import os10 from "os";
6774
+ import os12 from "os";
6497
6775
  var SYSTEM_PROMPT2 = `
6498
6776
 
6499
6777
  ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
@@ -6656,12 +6934,12 @@ Rule Summary:
6656
6934
  function getInitPrompt() {
6657
6935
  const now = /* @__PURE__ */ new Date();
6658
6936
  const collectedData = {
6659
- os_type: os10.type(),
6660
- os_version: os10.release(),
6661
- architecture: os10.arch(),
6937
+ os_type: os12.type(),
6938
+ os_version: os12.release(),
6939
+ architecture: os12.arch(),
6662
6940
  workdir: process.cwd(),
6663
6941
  shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
6664
- username: os10.userInfo().username || "Unknown",
6942
+ username: os12.userInfo().username || "Unknown",
6665
6943
  current_date: now.toISOString().split("T")[0],
6666
6944
  // Formato YYYY-MM-DD
6667
6945
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
@@ -6942,14 +7220,14 @@ var RouteManager = class {
6942
7220
  this.subAgents = subAgents;
6943
7221
  this.core = core;
6944
7222
  }
6945
- registerRoute(path19, handler) {
6946
- this.routeHandlers.set(path19, handler);
7223
+ registerRoute(path21, handler) {
7224
+ this.routeHandlers.set(path21, handler);
6947
7225
  }
6948
7226
  async handleRoute(payload) {
6949
7227
  const inputText = String(payload.content || "").trim();
6950
7228
  const { userContext } = payload;
6951
- for (const [path19, handler] of this.routeHandlers) {
6952
- if (inputText === path19 || inputText.startsWith(`${path19} `)) {
7229
+ for (const [path21, handler] of this.routeHandlers) {
7230
+ if (inputText === path21 || inputText.startsWith(`${path21} `)) {
6953
7231
  return handler({ content: inputText, userContext });
6954
7232
  }
6955
7233
  }
@@ -6958,7 +7236,7 @@ var RouteManager = class {
6958
7236
  };
6959
7237
 
6960
7238
  // src/app/agent/agent.ts
6961
- var globalEnvPath = path17.join(os11.homedir(), ".bluma", ".env");
7239
+ var globalEnvPath = path18.join(os13.homedir(), ".bluma", ".env");
6962
7240
  dotenv.config({ path: globalEnvPath });
6963
7241
  var Agent = class {
6964
7242
  sessionId;
@@ -7069,6 +7347,13 @@ var Agent = class {
7069
7347
  getUiToolsDetailed() {
7070
7348
  return this.core.getUiToolsDetailed();
7071
7349
  }
7350
+ /** Skills list for UI (/skills) — same source as system prompt & load_skill. */
7351
+ listAvailableSkills() {
7352
+ return this.core.listAvailableSkills();
7353
+ }
7354
+ getSkillsDirs() {
7355
+ return this.core.getSkillsDirs();
7356
+ }
7072
7357
  async processTurn(userInput, userContextInput) {
7073
7358
  const inputText = String(userInput.content || "").trim();
7074
7359
  const resolvedUserContext = userContextInput ?? defaultInteractiveCliUserContextInput(this.sessionId, inputText.slice(0, 300));
@@ -7175,12 +7460,12 @@ var renderShellCommand2 = ({ args }) => {
7175
7460
  };
7176
7461
  var renderLsTool2 = ({ args }) => {
7177
7462
  const parsed = parseArgs(args);
7178
- const path19 = parsed.directory_path || ".";
7463
+ const path21 = parsed.directory_path || ".";
7179
7464
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7180
7465
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
7181
7466
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7182
7467
  " ",
7183
- path19
7468
+ path21
7184
7469
  ] })
7185
7470
  ] });
7186
7471
  };
@@ -7316,7 +7601,7 @@ var renderFindByName = ({ args }) => {
7316
7601
  var renderGrepSearch = ({ args }) => {
7317
7602
  const parsed = parseArgs(args);
7318
7603
  const query = parsed.query || "";
7319
- const path19 = parsed.path || ".";
7604
+ const path21 = parsed.path || ".";
7320
7605
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7321
7606
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
7322
7607
  /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
@@ -7326,7 +7611,7 @@ var renderGrepSearch = ({ args }) => {
7326
7611
  ] }),
7327
7612
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7328
7613
  " ",
7329
- path19
7614
+ path21
7330
7615
  ] })
7331
7616
  ] });
7332
7617
  };
@@ -7895,10 +8180,10 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
7895
8180
  ] }),
7896
8181
  matches.slice(0, 5).map((m, i) => {
7897
8182
  const row = m;
7898
- const path19 = row.file || row.path || row.name || m;
8183
+ const path21 = row.file || row.path || row.name || m;
7899
8184
  const line = row.line;
7900
8185
  return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7901
- String(path19),
8186
+ String(path21),
7902
8187
  line != null ? `:${line}` : ""
7903
8188
  ] }, i);
7904
8189
  }),
@@ -8030,8 +8315,18 @@ var SessionInfoConnectingMCP_default = SessionInfoConnectingMCP;
8030
8315
 
8031
8316
  // src/app/ui/components/SlashCommands.tsx
8032
8317
  import { Box as Box14, Text as Text13 } from "ink";
8318
+
8319
+ // src/app/ui/constants/historyLayout.ts
8320
+ var HEADER_PANEL_HISTORY_ID = 0;
8321
+
8322
+ // src/app/ui/components/SlashCommands.tsx
8033
8323
  import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
8034
- var SlashCommands = ({ input, setHistory, agentRef }) => {
8324
+ var SlashCommands = ({
8325
+ input,
8326
+ setHistory,
8327
+ agentRef,
8328
+ onClearRecent
8329
+ }) => {
8035
8330
  const [cmd, ...args] = input.slice(1).trim().split(/\s+/);
8036
8331
  const outBox = (children) => /* @__PURE__ */ jsx14(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Box14, { paddingLeft: 1, flexDirection: "column", children }) });
8037
8332
  const render2 = () => {
@@ -8039,19 +8334,59 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
8039
8334
  return null;
8040
8335
  }
8041
8336
  if (cmd === "help") {
8042
- const cmds = getSlashCommands();
8337
+ const lines = formatSlashHelpLines();
8338
+ return outBox(
8339
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8340
+ /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
8341
+ /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Slash commands" }),
8342
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \xB7 " }),
8343
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: "put .png/.jpg/.webp paths in a normal message to attach images (project dir or ~)" })
8344
+ ] }),
8345
+ /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx14(Text13, { dimColor: line.trim().length > 0, children: line || " " }, i)) })
8346
+ ] })
8347
+ );
8348
+ }
8349
+ if (cmd === "skills") {
8350
+ const list = agentRef.current?.listAvailableSkills?.() || [];
8351
+ const dirs = agentRef.current?.getSkillsDirs?.();
8043
8352
  return outBox(
8044
8353
  /* @__PURE__ */ jsxs13(Fragment2, { children: [
8045
- /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Available Commands" }) }),
8046
- /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: cmds.map((c, i) => /* @__PURE__ */ jsxs13(Box14, { children: [
8047
- /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, children: c.name.padEnd(12) }),
8048
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: c.description })
8354
+ /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
8355
+ /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Skills (load_skill)" }),
8356
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
8357
+ " \xB7 ",
8358
+ list.length,
8359
+ " available"
8360
+ ] })
8361
+ ] }),
8362
+ dirs ? /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, flexDirection: "column", children: [
8363
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
8364
+ "bundled: ",
8365
+ String(dirs.bundled || "")
8366
+ ] }),
8367
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
8368
+ "project: ",
8369
+ String(dirs.project || "")
8370
+ ] }),
8371
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
8372
+ "global: ",
8373
+ String(dirs.global || "")
8374
+ ] })
8375
+ ] }) : null,
8376
+ list.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No skills found (check bundled dist/config/skills)." }) : /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: list.map((s, i) => /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
8377
+ /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: s.name }),
8378
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
8379
+ s.source,
8380
+ " \u2014 ",
8381
+ s.description || "\u2014"
8382
+ ] })
8049
8383
  ] }, i)) })
8050
8384
  ] })
8051
8385
  );
8052
8386
  }
8053
8387
  if (cmd === "clear") {
8054
- setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
8388
+ onClearRecent?.();
8389
+ setHistory((prev) => prev.filter((item) => item.id === HEADER_PANEL_HISTORY_ID));
8055
8390
  return outBox(
8056
8391
  /* @__PURE__ */ jsxs13(Box14, { children: [
8057
8392
  /* @__PURE__ */ jsx14(Text13, { color: "green", children: "[ok]" }),
@@ -8195,16 +8530,16 @@ var SlashCommands_default = SlashCommands;
8195
8530
  // src/app/agent/utils/update_check.ts
8196
8531
  import updateNotifier from "update-notifier";
8197
8532
  import { fileURLToPath as fileURLToPath3 } from "url";
8198
- import path18 from "path";
8199
- import fs14 from "fs";
8533
+ import path19 from "path";
8534
+ import fs15 from "fs";
8200
8535
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
8201
8536
  function findBlumaPackageJson(startDir) {
8202
8537
  let dir = startDir;
8203
8538
  for (let i = 0; i < 10; i++) {
8204
- const candidate = path18.join(dir, "package.json");
8205
- if (fs14.existsSync(candidate)) {
8539
+ const candidate = path19.join(dir, "package.json");
8540
+ if (fs15.existsSync(candidate)) {
8206
8541
  try {
8207
- const raw = fs14.readFileSync(candidate, "utf8");
8542
+ const raw = fs15.readFileSync(candidate, "utf8");
8208
8543
  const parsed = JSON.parse(raw);
8209
8544
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
8210
8545
  return { name: parsed.name, version: parsed.version };
@@ -8212,7 +8547,7 @@ function findBlumaPackageJson(startDir) {
8212
8547
  } catch {
8213
8548
  }
8214
8549
  }
8215
- const parent = path18.dirname(dir);
8550
+ const parent = path19.dirname(dir);
8216
8551
  if (parent === dir) break;
8217
8552
  dir = parent;
8218
8553
  }
@@ -8225,12 +8560,12 @@ async function checkForUpdates() {
8225
8560
  }
8226
8561
  const binPath = process.argv?.[1];
8227
8562
  let pkg = null;
8228
- if (binPath && fs14.existsSync(binPath)) {
8229
- pkg = findBlumaPackageJson(path18.dirname(binPath));
8563
+ if (binPath && fs15.existsSync(binPath)) {
8564
+ pkg = findBlumaPackageJson(path19.dirname(binPath));
8230
8565
  }
8231
8566
  if (!pkg) {
8232
8567
  const __filename = fileURLToPath3(import.meta.url);
8233
- const __dirname2 = path18.dirname(__filename);
8568
+ const __dirname2 = path19.dirname(__filename);
8234
8569
  pkg = findBlumaPackageJson(__dirname2);
8235
8570
  }
8236
8571
  if (!pkg) {
@@ -8460,7 +8795,12 @@ var SAFE_AUTO_APPROVE_TOOLS = [
8460
8795
  // Status de comandos (read-only)
8461
8796
  "command_status"
8462
8797
  ];
8463
- var AppComponent = ({ eventBus, sessionId }) => {
8798
+ function trimRecentActivity(s, max = 72) {
8799
+ const t = String(s ?? "").replace(/\s+/g, " ").trim();
8800
+ if (!t) return "";
8801
+ return t.length <= max ? t : `${t.slice(0, max - 1)}\u2026`;
8802
+ }
8803
+ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
8464
8804
  const agentInstance = useRef5(null);
8465
8805
  const [history, setHistory] = useState6([]);
8466
8806
  const [statusMessage, setStatusMessage] = useState6(
@@ -8478,6 +8818,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
8478
8818
  null
8479
8819
  );
8480
8820
  const [isInitAgentActive, setIsInitAgentActive] = useState6(false);
8821
+ const [recentActivityLine, setRecentActivityLine] = useState6(null);
8481
8822
  const alwaysAcceptList = useRef5([]);
8482
8823
  const workdir = process.cwd();
8483
8824
  const updateCheckRan = useRef5(false);
@@ -8510,6 +8851,26 @@ var AppComponent = ({ eventBus, sessionId }) => {
8510
8851
  expandPreviewHotkeyBus.off("expand", appendExpandPreviewToHistory);
8511
8852
  };
8512
8853
  }, [appendExpandPreviewToHistory]);
8854
+ useEffect7(() => {
8855
+ setHistory((prev) => {
8856
+ const tail = prev.filter((h) => h.id !== HEADER_PANEL_HISTORY_ID);
8857
+ return [
8858
+ {
8859
+ id: HEADER_PANEL_HISTORY_ID,
8860
+ component: /* @__PURE__ */ jsx20(
8861
+ Header,
8862
+ {
8863
+ sessionId,
8864
+ workdir,
8865
+ cliVersion,
8866
+ recentActivitySummary: recentActivityLine
8867
+ }
8868
+ )
8869
+ },
8870
+ ...tail
8871
+ ];
8872
+ });
8873
+ }, [sessionId, workdir, cliVersion, recentActivityLine]);
8513
8874
  const handleInterrupt = useCallback2(() => {
8514
8875
  if (!isProcessing) return;
8515
8876
  eventBus.emit("user_interrupt");
@@ -8526,12 +8887,45 @@ var AppComponent = ({ eventBus, sessionId }) => {
8526
8887
  const handleSubmit = useCallback2(
8527
8888
  (text) => {
8528
8889
  if (!text || isProcessing || !agentInstance.current) return;
8890
+ if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
8891
+ const payload = text.replace(/^\/img\s+/i, "").replace(/^\/image\s+/i, "").trim();
8892
+ if (!payload) {
8893
+ setHistory((prev) => [
8894
+ ...prev,
8895
+ {
8896
+ id: prev.length,
8897
+ component: /* @__PURE__ */ jsx20(ChatMeta, { children: "Usage: /img ./screenshot.png \u2014 optional text after the path is sent too" })
8898
+ }
8899
+ ]);
8900
+ return;
8901
+ }
8902
+ setIsProcessing(true);
8903
+ turnStartedAtRef.current = Date.now();
8904
+ const shown = payload.length > 800 ? `${payload.slice(0, 800)}\u2026` : payload;
8905
+ setHistory((prev) => [
8906
+ ...prev,
8907
+ {
8908
+ id: prev.length,
8909
+ component: /* @__PURE__ */ jsxs19(ChatUserMessage, { children: [
8910
+ /* @__PURE__ */ jsxs19(Text19, { bold: true, color: "white", children: [
8911
+ "/img",
8912
+ " "
8913
+ ] }),
8914
+ /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: shown })
8915
+ ] })
8916
+ }
8917
+ ]);
8918
+ setRecentActivityLine(`Prompt: ${trimRecentActivity(payload)}`);
8919
+ agentInstance.current.processTurn({ content: payload });
8920
+ return;
8921
+ }
8529
8922
  if (text.startsWith("/")) {
8530
8923
  const [cmd] = text.slice(1).trim().split(/\s+/);
8531
8924
  if (!cmd) {
8532
8925
  setIsProcessing(false);
8533
8926
  return;
8534
8927
  }
8928
+ setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
8535
8929
  if (cmd === "init") {
8536
8930
  setIsInitAgentActive(true);
8537
8931
  setIsProcessing(true);
@@ -8553,7 +8947,8 @@ var AppComponent = ({ eventBus, sessionId }) => {
8553
8947
  {
8554
8948
  input: text,
8555
8949
  setHistory,
8556
- agentRef: agentInstance
8950
+ agentRef: agentInstance,
8951
+ onClearRecent: () => setRecentActivityLine(null)
8557
8952
  }
8558
8953
  )
8559
8954
  }
@@ -8578,6 +8973,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
8578
8973
  ] }) })
8579
8974
  }
8580
8975
  ]);
8976
+ setRecentActivityLine(`Shell: ${trimRecentActivity(command)}`);
8581
8977
  Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
8582
8978
  try {
8583
8979
  const result = await runCommandAsync2({ command, cwd: workdir });
@@ -8629,6 +9025,7 @@ Please use command_status to check the result and report back to the user.`;
8629
9025
  component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { children: displayText }) })
8630
9026
  }
8631
9027
  ]);
9028
+ setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
8632
9029
  agentInstance.current.processTurn({ content: text });
8633
9030
  },
8634
9031
  [isProcessing]
@@ -8655,7 +9052,6 @@ Please use command_status to check the result and report back to the user.`;
8655
9052
  []
8656
9053
  );
8657
9054
  useEffect7(() => {
8658
- setHistory([{ id: 0, component: /* @__PURE__ */ jsx20(Header, { sessionId, workdir }) }]);
8659
9055
  const initializeAgent = async () => {
8660
9056
  try {
8661
9057
  agentInstance.current = new Agent(sessionId, eventBus);
@@ -8724,10 +9120,6 @@ Please use command_status to check the result and report back to the user.`;
8724
9120
  setToolsCount(parsed.tools);
8725
9121
  setMcpStatus("connected");
8726
9122
  setIsProcessing(false);
8727
- setHistory((prev) => {
8728
- const newHistory = [...prev];
8729
- return newHistory;
8730
- });
8731
9123
  if (!updateCheckRan.current) {
8732
9124
  updateCheckRan.current = true;
8733
9125
  Promise.resolve().then(() => checkForUpdates()).then((msg) => {
@@ -8766,7 +9158,9 @@ Please use command_status to check the result and report back to the user.`;
8766
9158
  }
8767
9159
  );
8768
9160
  } else if (parsed.type === "tool_call") {
8769
- const nextId3 = history.length;
9161
+ if (parsed.tool_name) {
9162
+ setRecentActivityLine(`Tool: ${String(parsed.tool_name)}`);
9163
+ }
8770
9164
  newComponent = /* @__PURE__ */ jsx20(
8771
9165
  ToolCallDisplay,
8772
9166
  {
@@ -8784,6 +9178,11 @@ Please use command_status to check the result and report back to the user.`;
8784
9178
  }
8785
9179
  );
8786
9180
  } else if (parsed.type === "user_overlay") {
9181
+ if (parsed.payload != null && String(parsed.payload).trim()) {
9182
+ setRecentActivityLine(
9183
+ `Context: ${trimRecentActivity(String(parsed.payload))}`
9184
+ );
9185
+ }
8787
9186
  newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.payload }) });
8788
9187
  } else if (parsed.type === "reasoning") {
8789
9188
  newComponent = /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning: parsed.content });
@@ -8974,9 +9373,9 @@ async function runAgentMode() {
8974
9373
  try {
8975
9374
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
8976
9375
  const filePath = args[inputFileIndex + 1];
8977
- rawPayload = fs15.readFileSync(filePath, "utf-8");
9376
+ rawPayload = fs16.readFileSync(filePath, "utf-8");
8978
9377
  } else {
8979
- rawPayload = fs15.readFileSync(0, "utf-8");
9378
+ rawPayload = fs16.readFileSync(0, "utf-8");
8980
9379
  }
8981
9380
  } catch (err) {
8982
9381
  writeJsonl({
@@ -9142,6 +9541,16 @@ async function runAgentMode() {
9142
9541
  process.exit(1);
9143
9542
  }
9144
9543
  }
9544
+ function readCliPackageVersion() {
9545
+ try {
9546
+ const base = path20.dirname(fileURLToPath4(import.meta.url));
9547
+ const pkgPath = path20.join(base, "..", "package.json");
9548
+ const j = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
9549
+ return String(j.version || "0.0.0");
9550
+ } catch {
9551
+ return "0.0.0";
9552
+ }
9553
+ }
9145
9554
  function runCliMode() {
9146
9555
  const BLUMA_TITLE = process.env.BLUMA_TITLE || "BluMa - NomadEngenuity";
9147
9556
  startTitleKeeper(BLUMA_TITLE);
@@ -9149,7 +9558,8 @@ function runCliMode() {
9149
9558
  const sessionId = uuidv46();
9150
9559
  const props = {
9151
9560
  eventBus,
9152
- sessionId
9561
+ sessionId,
9562
+ cliVersion: readCliPackageVersion()
9153
9563
  };
9154
9564
  render(React12.createElement(App_default, props));
9155
9565
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",