@nomad-e/bluma-cli 0.1.31 → 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.
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
  /**
@@ -4305,10 +4448,19 @@ var SkillLoader = class _SkillLoader {
4305
4448
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
4306
4449
  return path14.join(process.cwd(), "dist", "config", "skills");
4307
4450
  }
4308
- const candidates = [
4451
+ const candidates = [];
4452
+ const argv1 = process.argv[1];
4453
+ if (argv1 && !argv1.startsWith("-")) {
4454
+ try {
4455
+ const scriptDir = path14.dirname(path14.resolve(argv1));
4456
+ candidates.push(path14.join(scriptDir, "config", "skills"));
4457
+ } catch {
4458
+ }
4459
+ }
4460
+ candidates.push(
4309
4461
  path14.join(process.cwd(), "dist", "config", "skills"),
4310
4462
  path14.join(process.cwd(), "node_modules", "@nomad-e", "bluma-cli", "dist", "config", "skills")
4311
- ];
4463
+ );
4312
4464
  if (typeof __dirname !== "undefined") {
4313
4465
  candidates.push(
4314
4466
  path14.join(__dirname, "config", "skills"),
@@ -4695,6 +4847,10 @@ var SYSTEM_PROMPT = `
4695
4847
  - NEVER try to \`load_skill\` with a name that isn't in \`<available_skills>\`
4696
4848
  - Your base knowledge (testing, git, docker...) is NOT a skill - it's just knowledge you have
4697
4849
 
4850
+ **Git / commits (when \`git-commit\` is listed in \`<available_skills>\`):**
4851
+ - For "commit", "push", "commit message", or "conventional commit": call \`load_skill({ skill_name: "git-commit" })\` **before** \`git commit\`, then follow the skill body (scopes, types, optional validator script).
4852
+ - The bundled skill name is exactly \`git-commit\`. Do **not** use \`git-conventional\` or other invented names.
4853
+
4698
4854
  **Progressive Disclosure (Skills with references and scripts):**
4699
4855
 
4700
4856
  Skills may include additional assets beyond the SKILL.md body:
@@ -5108,12 +5264,12 @@ In sandbox mode you are a Python-focused, non-interactive, deterministic agent t
5108
5264
  function getUnifiedSystemPrompt(availableSkills) {
5109
5265
  const cwd = process.cwd();
5110
5266
  const env = {
5111
- os_type: os8.type(),
5112
- os_version: os8.release(),
5113
- architecture: os8.arch(),
5267
+ os_type: os9.type(),
5268
+ os_version: os9.release(),
5269
+ architecture: os9.arch(),
5114
5270
  workdir: cwd,
5115
5271
  shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
5116
- username: os8.userInfo().username,
5272
+ username: os9.userInfo().username,
5117
5273
  current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
5118
5274
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
5119
5275
  is_git_repo: isGitRepo(cwd) ? "yes" : "no",
@@ -5218,7 +5374,7 @@ tools:
5218
5374
 
5219
5375
  2. READ FRONTMATTER FIRST
5220
5376
  - name: npm-publish
5221
- - depends_on: [git-conventional]
5377
+ - depends_on: [git-commit]
5222
5378
  - tools.required: [shell_command, command_status]
5223
5379
  - tools.recommended: [read_file_lines, message]
5224
5380
 
@@ -5255,17 +5411,17 @@ User: "Publish the package"
5255
5411
  2. load_skill({ skill_name: "npm-publish" })
5256
5412
 
5257
5413
  3. Read frontmatter:
5258
- - depends_on: [git-conventional]
5414
+ - depends_on: [git-commit]
5259
5415
  - tools.required: [shell_command, command_status]
5260
5416
 
5261
5417
  4. Read body -> Workflow says:
5262
- "Step 1: If uncommitted changes, delegate to git-conventional"
5418
+ "Step 1: If uncommitted changes, delegate to git-commit"
5263
5419
 
5264
5420
  5. Check git status with shell_command (required tool)
5265
5421
  Found changes
5266
5422
 
5267
- 6. Delegate: load_skill({ skill_name: "git-conventional" })
5268
- - Read git-conventional frontmatter
5423
+ 6. Delegate: load_skill({ skill_name: "git-commit" })
5424
+ - Read git-commit frontmatter
5269
5425
  - Follow its workflow for commit
5270
5426
  - Commit done
5271
5427
 
@@ -5579,7 +5735,7 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
5579
5735
  }
5580
5736
 
5581
5737
  // src/app/agent/core/llm/llm.ts
5582
- import os9 from "os";
5738
+ import os10 from "os";
5583
5739
  import OpenAI from "openai";
5584
5740
  function defaultBlumaUserContextInput(sessionId, userMessage) {
5585
5741
  const msg = String(userMessage || "").slice(0, 300);
@@ -5596,7 +5752,7 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
5596
5752
  }
5597
5753
  function getPreferredMacAddress() {
5598
5754
  try {
5599
- const ifaces = os9.networkInterfaces();
5755
+ const ifaces = os10.networkInterfaces();
5600
5756
  for (const name of Object.keys(ifaces)) {
5601
5757
  const addrs = ifaces[name];
5602
5758
  if (!addrs) continue;
@@ -5611,7 +5767,7 @@ function getPreferredMacAddress() {
5611
5767
  } catch {
5612
5768
  }
5613
5769
  try {
5614
- return `host:${os9.hostname()}`;
5770
+ return `host:${os10.hostname()}`;
5615
5771
  } catch {
5616
5772
  return "unknown";
5617
5773
  }
@@ -5621,7 +5777,7 @@ function defaultInteractiveCliUserContextInput(sessionId, userMessage) {
5621
5777
  const machineId = getPreferredMacAddress();
5622
5778
  let userName = null;
5623
5779
  try {
5624
- userName = os9.userInfo().username || null;
5780
+ userName = os10.userInfo().username || null;
5625
5781
  } catch {
5626
5782
  userName = null;
5627
5783
  }
@@ -5948,6 +6104,134 @@ var ToolCallNormalizer = class {
5948
6104
  }
5949
6105
  };
5950
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
+
5951
6235
  // src/app/agent/bluma/core/bluma.ts
5952
6236
  var BluMaAgent = class {
5953
6237
  llm;
@@ -6020,29 +6304,38 @@ var BluMaAgent = class {
6020
6304
  history: this.history,
6021
6305
  skillLoader: this.skillLoader
6022
6306
  });
6023
- if (this.history.length === 0) {
6024
- const dirs = this.skillLoader.getSkillsDirs();
6025
- this.eventBus.emit("backend_message", {
6026
- type: "info",
6027
- message: `Skills dirs \u2014 bundled: ${dirs.bundled} | project: ${dirs.project} | global: ${dirs.global}`
6028
- });
6029
- const availableSkills = this.skillLoader.listAvailable();
6030
- this.eventBus.emit("backend_message", {
6031
- type: "info",
6032
- message: `Skills loaded: ${availableSkills.length} \u2014 ${availableSkills.map((s) => `${s.name} (${s.source})`).join(", ") || "none"}`
6033
- });
6034
- if (this.skillLoader.hasConflicts()) {
6035
- for (const warning of this.skillLoader.formatConflictWarnings()) {
6036
- this.eventBus.emit("backend_message", {
6037
- type: "warning",
6038
- message: warning
6039
- });
6040
- }
6307
+ const dirs = this.skillLoader.getSkillsDirs();
6308
+ this.eventBus.emit("backend_message", {
6309
+ type: "info",
6310
+ message: `Skills dirs \u2014 bundled: ${dirs.bundled} | project: ${dirs.project} | global: ${dirs.global}`
6311
+ });
6312
+ const availableSkills = this.skillLoader.listAvailable();
6313
+ this.eventBus.emit("backend_message", {
6314
+ type: "info",
6315
+ message: `Skills loaded: ${availableSkills.length} \u2014 ${availableSkills.map((s) => `${s.name} (${s.source})`).join(", ") || "none"}`
6316
+ });
6317
+ if (this.skillLoader.hasConflicts()) {
6318
+ for (const warning of this.skillLoader.formatConflictWarnings()) {
6319
+ this.eventBus.emit("backend_message", {
6320
+ type: "warning",
6321
+ message: warning
6322
+ });
6041
6323
  }
6042
- const systemPrompt = getUnifiedSystemPrompt(availableSkills);
6324
+ }
6325
+ const systemPrompt = getUnifiedSystemPrompt(availableSkills);
6326
+ if (this.history.length === 0) {
6043
6327
  this.history.push({ role: "system", content: systemPrompt });
6044
- this.persistSession();
6328
+ } else {
6329
+ const sysIdx = this.history.findIndex(
6330
+ (m) => m.role === "system" && typeof m.content === "string" && String(m.content).includes("<identity>")
6331
+ );
6332
+ if (sysIdx >= 0) {
6333
+ this.history[sysIdx] = { ...this.history[sysIdx], content: systemPrompt };
6334
+ } else {
6335
+ this.history.unshift({ role: "system", content: systemPrompt });
6336
+ }
6045
6337
  }
6338
+ this.persistSession();
6046
6339
  }
6047
6340
  getAvailableTools() {
6048
6341
  return this.mcpClient.getAvailableTools();
@@ -6050,6 +6343,12 @@ var BluMaAgent = class {
6050
6343
  getUiToolsDetailed() {
6051
6344
  return this.mcpClient.getAvailableToolsDetailed();
6052
6345
  }
6346
+ listAvailableSkills() {
6347
+ return this.skillLoader.listAvailable();
6348
+ }
6349
+ getSkillsDirs() {
6350
+ return this.skillLoader.getSkillsDirs();
6351
+ }
6053
6352
  async processTurn(userInput, userContextInput) {
6054
6353
  this.isInterrupted = false;
6055
6354
  this.factorRouterTurnClosed = false;
@@ -6060,7 +6359,8 @@ var BluMaAgent = class {
6060
6359
  turnId,
6061
6360
  sessionId: userContextInput.sessionId || this.sessionId
6062
6361
  };
6063
- this.history.push({ role: "user", content: inputText });
6362
+ const userContent = buildUserMessageContent(inputText, process.cwd());
6363
+ this.history.push({ role: "user", content: userContent });
6064
6364
  if (inputText === "/init") {
6065
6365
  this.eventBus.emit("dispatch", inputText);
6066
6366
  }
@@ -6213,7 +6513,7 @@ var BluMaAgent = class {
6213
6513
 
6214
6514
  ${editData.error.display}`;
6215
6515
  }
6216
- const filename = path16.basename(toolArgs.file_path);
6516
+ const filename = path17.basename(toolArgs.file_path);
6217
6517
  return createDiff(filename, editData.currentContent || "", editData.newContent);
6218
6518
  } catch (e) {
6219
6519
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -6471,7 +6771,7 @@ import { v4 as uuidv45 } from "uuid";
6471
6771
  import { v4 as uuidv44 } from "uuid";
6472
6772
 
6473
6773
  // src/app/agent/subagents/init/init_system_prompt.ts
6474
- import os10 from "os";
6774
+ import os12 from "os";
6475
6775
  var SYSTEM_PROMPT2 = `
6476
6776
 
6477
6777
  ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
@@ -6634,12 +6934,12 @@ Rule Summary:
6634
6934
  function getInitPrompt() {
6635
6935
  const now = /* @__PURE__ */ new Date();
6636
6936
  const collectedData = {
6637
- os_type: os10.type(),
6638
- os_version: os10.release(),
6639
- architecture: os10.arch(),
6937
+ os_type: os12.type(),
6938
+ os_version: os12.release(),
6939
+ architecture: os12.arch(),
6640
6940
  workdir: process.cwd(),
6641
6941
  shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
6642
- username: os10.userInfo().username || "Unknown",
6942
+ username: os12.userInfo().username || "Unknown",
6643
6943
  current_date: now.toISOString().split("T")[0],
6644
6944
  // Formato YYYY-MM-DD
6645
6945
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
@@ -6920,14 +7220,14 @@ var RouteManager = class {
6920
7220
  this.subAgents = subAgents;
6921
7221
  this.core = core;
6922
7222
  }
6923
- registerRoute(path19, handler) {
6924
- this.routeHandlers.set(path19, handler);
7223
+ registerRoute(path21, handler) {
7224
+ this.routeHandlers.set(path21, handler);
6925
7225
  }
6926
7226
  async handleRoute(payload) {
6927
7227
  const inputText = String(payload.content || "").trim();
6928
7228
  const { userContext } = payload;
6929
- for (const [path19, handler] of this.routeHandlers) {
6930
- if (inputText === path19 || inputText.startsWith(`${path19} `)) {
7229
+ for (const [path21, handler] of this.routeHandlers) {
7230
+ if (inputText === path21 || inputText.startsWith(`${path21} `)) {
6931
7231
  return handler({ content: inputText, userContext });
6932
7232
  }
6933
7233
  }
@@ -6936,7 +7236,7 @@ var RouteManager = class {
6936
7236
  };
6937
7237
 
6938
7238
  // src/app/agent/agent.ts
6939
- var globalEnvPath = path17.join(os11.homedir(), ".bluma", ".env");
7239
+ var globalEnvPath = path18.join(os13.homedir(), ".bluma", ".env");
6940
7240
  dotenv.config({ path: globalEnvPath });
6941
7241
  var Agent = class {
6942
7242
  sessionId;
@@ -7047,6 +7347,13 @@ var Agent = class {
7047
7347
  getUiToolsDetailed() {
7048
7348
  return this.core.getUiToolsDetailed();
7049
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
+ }
7050
7357
  async processTurn(userInput, userContextInput) {
7051
7358
  const inputText = String(userInput.content || "").trim();
7052
7359
  const resolvedUserContext = userContextInput ?? defaultInteractiveCliUserContextInput(this.sessionId, inputText.slice(0, 300));
@@ -7153,12 +7460,12 @@ var renderShellCommand2 = ({ args }) => {
7153
7460
  };
7154
7461
  var renderLsTool2 = ({ args }) => {
7155
7462
  const parsed = parseArgs(args);
7156
- const path19 = parsed.directory_path || ".";
7463
+ const path21 = parsed.directory_path || ".";
7157
7464
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7158
7465
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
7159
7466
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7160
7467
  " ",
7161
- path19
7468
+ path21
7162
7469
  ] })
7163
7470
  ] });
7164
7471
  };
@@ -7294,7 +7601,7 @@ var renderFindByName = ({ args }) => {
7294
7601
  var renderGrepSearch = ({ args }) => {
7295
7602
  const parsed = parseArgs(args);
7296
7603
  const query = parsed.query || "";
7297
- const path19 = parsed.path || ".";
7604
+ const path21 = parsed.path || ".";
7298
7605
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7299
7606
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
7300
7607
  /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
@@ -7304,7 +7611,7 @@ var renderGrepSearch = ({ args }) => {
7304
7611
  ] }),
7305
7612
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7306
7613
  " ",
7307
- path19
7614
+ path21
7308
7615
  ] })
7309
7616
  ] });
7310
7617
  };
@@ -7873,10 +8180,10 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
7873
8180
  ] }),
7874
8181
  matches.slice(0, 5).map((m, i) => {
7875
8182
  const row = m;
7876
- const path19 = row.file || row.path || row.name || m;
8183
+ const path21 = row.file || row.path || row.name || m;
7877
8184
  const line = row.line;
7878
8185
  return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7879
- String(path19),
8186
+ String(path21),
7880
8187
  line != null ? `:${line}` : ""
7881
8188
  ] }, i);
7882
8189
  }),
@@ -8008,8 +8315,18 @@ var SessionInfoConnectingMCP_default = SessionInfoConnectingMCP;
8008
8315
 
8009
8316
  // src/app/ui/components/SlashCommands.tsx
8010
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
8011
8323
  import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
8012
- var SlashCommands = ({ input, setHistory, agentRef }) => {
8324
+ var SlashCommands = ({
8325
+ input,
8326
+ setHistory,
8327
+ agentRef,
8328
+ onClearRecent
8329
+ }) => {
8013
8330
  const [cmd, ...args] = input.slice(1).trim().split(/\s+/);
8014
8331
  const outBox = (children) => /* @__PURE__ */ jsx14(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Box14, { paddingLeft: 1, flexDirection: "column", children }) });
8015
8332
  const render2 = () => {
@@ -8017,19 +8334,59 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
8017
8334
  return null;
8018
8335
  }
8019
8336
  if (cmd === "help") {
8020
- 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?.();
8021
8352
  return outBox(
8022
8353
  /* @__PURE__ */ jsxs13(Fragment2, { children: [
8023
- /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Available Commands" }) }),
8024
- /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: cmds.map((c, i) => /* @__PURE__ */ jsxs13(Box14, { children: [
8025
- /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, children: c.name.padEnd(12) }),
8026
- /* @__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
+ ] })
8027
8383
  ] }, i)) })
8028
8384
  ] })
8029
8385
  );
8030
8386
  }
8031
8387
  if (cmd === "clear") {
8032
- 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));
8033
8390
  return outBox(
8034
8391
  /* @__PURE__ */ jsxs13(Box14, { children: [
8035
8392
  /* @__PURE__ */ jsx14(Text13, { color: "green", children: "[ok]" }),
@@ -8173,16 +8530,16 @@ var SlashCommands_default = SlashCommands;
8173
8530
  // src/app/agent/utils/update_check.ts
8174
8531
  import updateNotifier from "update-notifier";
8175
8532
  import { fileURLToPath as fileURLToPath3 } from "url";
8176
- import path18 from "path";
8177
- import fs14 from "fs";
8533
+ import path19 from "path";
8534
+ import fs15 from "fs";
8178
8535
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
8179
8536
  function findBlumaPackageJson(startDir) {
8180
8537
  let dir = startDir;
8181
8538
  for (let i = 0; i < 10; i++) {
8182
- const candidate = path18.join(dir, "package.json");
8183
- if (fs14.existsSync(candidate)) {
8539
+ const candidate = path19.join(dir, "package.json");
8540
+ if (fs15.existsSync(candidate)) {
8184
8541
  try {
8185
- const raw = fs14.readFileSync(candidate, "utf8");
8542
+ const raw = fs15.readFileSync(candidate, "utf8");
8186
8543
  const parsed = JSON.parse(raw);
8187
8544
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
8188
8545
  return { name: parsed.name, version: parsed.version };
@@ -8190,7 +8547,7 @@ function findBlumaPackageJson(startDir) {
8190
8547
  } catch {
8191
8548
  }
8192
8549
  }
8193
- const parent = path18.dirname(dir);
8550
+ const parent = path19.dirname(dir);
8194
8551
  if (parent === dir) break;
8195
8552
  dir = parent;
8196
8553
  }
@@ -8203,12 +8560,12 @@ async function checkForUpdates() {
8203
8560
  }
8204
8561
  const binPath = process.argv?.[1];
8205
8562
  let pkg = null;
8206
- if (binPath && fs14.existsSync(binPath)) {
8207
- pkg = findBlumaPackageJson(path18.dirname(binPath));
8563
+ if (binPath && fs15.existsSync(binPath)) {
8564
+ pkg = findBlumaPackageJson(path19.dirname(binPath));
8208
8565
  }
8209
8566
  if (!pkg) {
8210
8567
  const __filename = fileURLToPath3(import.meta.url);
8211
- const __dirname2 = path18.dirname(__filename);
8568
+ const __dirname2 = path19.dirname(__filename);
8212
8569
  pkg = findBlumaPackageJson(__dirname2);
8213
8570
  }
8214
8571
  if (!pkg) {
@@ -8438,7 +8795,12 @@ var SAFE_AUTO_APPROVE_TOOLS = [
8438
8795
  // Status de comandos (read-only)
8439
8796
  "command_status"
8440
8797
  ];
8441
- 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 }) => {
8442
8804
  const agentInstance = useRef5(null);
8443
8805
  const [history, setHistory] = useState6([]);
8444
8806
  const [statusMessage, setStatusMessage] = useState6(
@@ -8456,6 +8818,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
8456
8818
  null
8457
8819
  );
8458
8820
  const [isInitAgentActive, setIsInitAgentActive] = useState6(false);
8821
+ const [recentActivityLine, setRecentActivityLine] = useState6(null);
8459
8822
  const alwaysAcceptList = useRef5([]);
8460
8823
  const workdir = process.cwd();
8461
8824
  const updateCheckRan = useRef5(false);
@@ -8488,6 +8851,26 @@ var AppComponent = ({ eventBus, sessionId }) => {
8488
8851
  expandPreviewHotkeyBus.off("expand", appendExpandPreviewToHistory);
8489
8852
  };
8490
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]);
8491
8874
  const handleInterrupt = useCallback2(() => {
8492
8875
  if (!isProcessing) return;
8493
8876
  eventBus.emit("user_interrupt");
@@ -8504,12 +8887,45 @@ var AppComponent = ({ eventBus, sessionId }) => {
8504
8887
  const handleSubmit = useCallback2(
8505
8888
  (text) => {
8506
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
+ }
8507
8922
  if (text.startsWith("/")) {
8508
8923
  const [cmd] = text.slice(1).trim().split(/\s+/);
8509
8924
  if (!cmd) {
8510
8925
  setIsProcessing(false);
8511
8926
  return;
8512
8927
  }
8928
+ setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
8513
8929
  if (cmd === "init") {
8514
8930
  setIsInitAgentActive(true);
8515
8931
  setIsProcessing(true);
@@ -8531,7 +8947,8 @@ var AppComponent = ({ eventBus, sessionId }) => {
8531
8947
  {
8532
8948
  input: text,
8533
8949
  setHistory,
8534
- agentRef: agentInstance
8950
+ agentRef: agentInstance,
8951
+ onClearRecent: () => setRecentActivityLine(null)
8535
8952
  }
8536
8953
  )
8537
8954
  }
@@ -8556,6 +8973,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
8556
8973
  ] }) })
8557
8974
  }
8558
8975
  ]);
8976
+ setRecentActivityLine(`Shell: ${trimRecentActivity(command)}`);
8559
8977
  Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
8560
8978
  try {
8561
8979
  const result = await runCommandAsync2({ command, cwd: workdir });
@@ -8607,6 +9025,7 @@ Please use command_status to check the result and report back to the user.`;
8607
9025
  component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { children: displayText }) })
8608
9026
  }
8609
9027
  ]);
9028
+ setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
8610
9029
  agentInstance.current.processTurn({ content: text });
8611
9030
  },
8612
9031
  [isProcessing]
@@ -8633,7 +9052,6 @@ Please use command_status to check the result and report back to the user.`;
8633
9052
  []
8634
9053
  );
8635
9054
  useEffect7(() => {
8636
- setHistory([{ id: 0, component: /* @__PURE__ */ jsx20(Header, { sessionId, workdir }) }]);
8637
9055
  const initializeAgent = async () => {
8638
9056
  try {
8639
9057
  agentInstance.current = new Agent(sessionId, eventBus);
@@ -8702,10 +9120,6 @@ Please use command_status to check the result and report back to the user.`;
8702
9120
  setToolsCount(parsed.tools);
8703
9121
  setMcpStatus("connected");
8704
9122
  setIsProcessing(false);
8705
- setHistory((prev) => {
8706
- const newHistory = [...prev];
8707
- return newHistory;
8708
- });
8709
9123
  if (!updateCheckRan.current) {
8710
9124
  updateCheckRan.current = true;
8711
9125
  Promise.resolve().then(() => checkForUpdates()).then((msg) => {
@@ -8744,7 +9158,9 @@ Please use command_status to check the result and report back to the user.`;
8744
9158
  }
8745
9159
  );
8746
9160
  } else if (parsed.type === "tool_call") {
8747
- const nextId3 = history.length;
9161
+ if (parsed.tool_name) {
9162
+ setRecentActivityLine(`Tool: ${String(parsed.tool_name)}`);
9163
+ }
8748
9164
  newComponent = /* @__PURE__ */ jsx20(
8749
9165
  ToolCallDisplay,
8750
9166
  {
@@ -8762,6 +9178,11 @@ Please use command_status to check the result and report back to the user.`;
8762
9178
  }
8763
9179
  );
8764
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
+ }
8765
9186
  newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.payload }) });
8766
9187
  } else if (parsed.type === "reasoning") {
8767
9188
  newComponent = /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning: parsed.content });
@@ -8952,9 +9373,9 @@ async function runAgentMode() {
8952
9373
  try {
8953
9374
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
8954
9375
  const filePath = args[inputFileIndex + 1];
8955
- rawPayload = fs15.readFileSync(filePath, "utf-8");
9376
+ rawPayload = fs16.readFileSync(filePath, "utf-8");
8956
9377
  } else {
8957
- rawPayload = fs15.readFileSync(0, "utf-8");
9378
+ rawPayload = fs16.readFileSync(0, "utf-8");
8958
9379
  }
8959
9380
  } catch (err) {
8960
9381
  writeJsonl({
@@ -9120,6 +9541,16 @@ async function runAgentMode() {
9120
9541
  process.exit(1);
9121
9542
  }
9122
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
+ }
9123
9554
  function runCliMode() {
9124
9555
  const BLUMA_TITLE = process.env.BLUMA_TITLE || "BluMa - NomadEngenuity";
9125
9556
  startTitleKeeper(BLUMA_TITLE);
@@ -9127,7 +9558,8 @@ function runCliMode() {
9127
9558
  const sessionId = uuidv46();
9128
9559
  const props = {
9129
9560
  eventBus,
9130
- sessionId
9561
+ sessionId,
9562
+ cliVersion: readCliPackageVersion()
9131
9563
  };
9132
9564
  render(React12.createElement(App_default, props));
9133
9565
  }