@nomad-e/bluma-cli 0.1.32 → 0.1.34
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 +1199 -318
- package/package.json +5 -1
package/dist/main.js
CHANGED
|
@@ -17,8 +17,8 @@ __export(async_command_exports, {
|
|
|
17
17
|
runCommandAsync: () => runCommandAsync,
|
|
18
18
|
sendCommandInput: () => sendCommandInput
|
|
19
19
|
});
|
|
20
|
-
import
|
|
21
|
-
import { spawn } from "child_process";
|
|
20
|
+
import os5 from "os";
|
|
21
|
+
import { spawn as spawn2 } from "child_process";
|
|
22
22
|
import { v4 as uuidv42 } from "uuid";
|
|
23
23
|
function cleanupOldCommands() {
|
|
24
24
|
if (runningCommands.size <= MAX_STORED_COMMANDS) return;
|
|
@@ -63,7 +63,7 @@ async function runCommandAsync(args) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
const commandId = uuidv42().substring(0, 8);
|
|
66
|
-
const platform =
|
|
66
|
+
const platform = os5.platform();
|
|
67
67
|
let shellCmd;
|
|
68
68
|
let shellArgs;
|
|
69
69
|
if (platform === "win32") {
|
|
@@ -83,7 +83,7 @@ async function runCommandAsync(args) {
|
|
|
83
83
|
startTime: Date.now(),
|
|
84
84
|
process: null
|
|
85
85
|
};
|
|
86
|
-
const child =
|
|
86
|
+
const child = spawn2(shellCmd, shellArgs, {
|
|
87
87
|
cwd,
|
|
88
88
|
env: process.env,
|
|
89
89
|
windowsHide: true
|
|
@@ -331,16 +331,19 @@ 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
|
|
334
|
+
import fs17 from "fs";
|
|
335
|
+
import path21 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
|
|
338
|
-
import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useCallback as
|
|
340
|
+
import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback3, memo as memo12 } from "react";
|
|
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
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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__ */
|
|
392
|
-
|
|
393
|
-
/* @__PURE__ */ jsx(Text, {
|
|
394
|
-
/* @__PURE__ */ jsx(Text, {
|
|
395
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children:
|
|
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";
|
|
@@ -505,6 +590,13 @@ function inputReducer(state, action, viewWidth) {
|
|
|
505
590
|
}
|
|
506
591
|
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
507
592
|
}
|
|
593
|
+
case "INSERT_AT_CURSOR": {
|
|
594
|
+
const cleanInput = action.payload.replace(/(\r\n|\r)/gm, "\n");
|
|
595
|
+
const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
|
|
596
|
+
const newCursorPosition = state.cursorPosition + cleanInput.length;
|
|
597
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
598
|
+
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
599
|
+
}
|
|
508
600
|
case "NEWLINE": {
|
|
509
601
|
const newText = state.text.slice(0, state.cursorPosition) + "\n" + state.text.slice(state.cursorPosition);
|
|
510
602
|
const newCursorPosition = state.cursorPosition + 1;
|
|
@@ -590,11 +682,28 @@ function inputReducer(state, action, viewWidth) {
|
|
|
590
682
|
return state;
|
|
591
683
|
}
|
|
592
684
|
}
|
|
685
|
+
function isClipboardPasteChord(input, key) {
|
|
686
|
+
if (key.ctrl && key.shift && input === "i") {
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
if (key.meta && (input === "v" || input === "V")) {
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
if (key.ctrl && (input === "v" || input === "V")) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
if (key.ctrl && input.length >= 1 && input.charCodeAt(0) === 22) {
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
593
700
|
var useCustomInput = ({
|
|
594
701
|
onSubmit,
|
|
595
702
|
viewWidth,
|
|
596
703
|
isReadOnly,
|
|
597
|
-
onInterrupt
|
|
704
|
+
onInterrupt,
|
|
705
|
+
onChordPaste,
|
|
706
|
+
transformInputChunk
|
|
598
707
|
}) => {
|
|
599
708
|
const [state, dispatch] = useReducer(
|
|
600
709
|
(s, a) => inputReducer(s, a, viewWidth),
|
|
@@ -602,14 +711,31 @@ var useCustomInput = ({
|
|
|
602
711
|
);
|
|
603
712
|
const inputBuffer = useRef("");
|
|
604
713
|
const flushScheduled = useRef(false);
|
|
714
|
+
const applyChunkTransform = useCallback(
|
|
715
|
+
(buffered) => transformInputChunk ? transformInputChunk(buffered) : buffered,
|
|
716
|
+
[transformInputChunk]
|
|
717
|
+
);
|
|
605
718
|
const flushInputBuffer = useCallback(() => {
|
|
606
719
|
if (inputBuffer.current.length > 0) {
|
|
607
720
|
const buffered = inputBuffer.current;
|
|
608
721
|
inputBuffer.current = "";
|
|
609
|
-
dispatch({ type: "INPUT", payload: buffered });
|
|
722
|
+
dispatch({ type: "INPUT", payload: applyChunkTransform(buffered) });
|
|
610
723
|
}
|
|
611
724
|
flushScheduled.current = false;
|
|
612
|
-
}, []);
|
|
725
|
+
}, [applyChunkTransform]);
|
|
726
|
+
const insertAtCursor = useCallback(
|
|
727
|
+
(s) => {
|
|
728
|
+
if (inputBuffer.current.length > 0) {
|
|
729
|
+
const buffered = inputBuffer.current;
|
|
730
|
+
inputBuffer.current = "";
|
|
731
|
+
flushScheduled.current = false;
|
|
732
|
+
dispatch({ type: "INPUT", payload: applyChunkTransform(buffered) });
|
|
733
|
+
}
|
|
734
|
+
const clean = s.replace(/(\r\n|\r)/gm, "\n");
|
|
735
|
+
dispatch({ type: "INSERT_AT_CURSOR", payload: clean });
|
|
736
|
+
},
|
|
737
|
+
[applyChunkTransform]
|
|
738
|
+
);
|
|
613
739
|
useEffect(() => {
|
|
614
740
|
return () => {
|
|
615
741
|
if (flushScheduled.current) {
|
|
@@ -670,6 +796,13 @@ var useCustomInput = ({
|
|
|
670
796
|
if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
|
|
671
797
|
if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
672
798
|
if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
|
|
799
|
+
if (isClipboardPasteChord(input, key) && onChordPaste) {
|
|
800
|
+
if (inputBuffer.current.length > 0) {
|
|
801
|
+
flushInputBuffer();
|
|
802
|
+
}
|
|
803
|
+
void Promise.resolve(onChordPaste(insertAtCursor));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
673
806
|
if (key.ctrl || key.meta || key.tab) return;
|
|
674
807
|
inputBuffer.current += input;
|
|
675
808
|
if (!flushScheduled.current) {
|
|
@@ -704,6 +837,13 @@ var useCustomInput = ({
|
|
|
704
837
|
if (key.ctrl && input === "e") {
|
|
705
838
|
return dispatch({ type: "MOVE_LINE_END" });
|
|
706
839
|
}
|
|
840
|
+
if (isClipboardPasteChord(input, key) && onChordPaste) {
|
|
841
|
+
if (inputBuffer.current.length > 0) {
|
|
842
|
+
flushInputBuffer();
|
|
843
|
+
}
|
|
844
|
+
void Promise.resolve(onChordPaste(insertAtCursor));
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
707
847
|
if (key.ctrl || key.meta || key.tab) return;
|
|
708
848
|
inputBuffer.current += input;
|
|
709
849
|
if (!flushScheduled.current) {
|
|
@@ -732,22 +872,116 @@ var useCustomInput = ({
|
|
|
732
872
|
flushInputBuffer();
|
|
733
873
|
}
|
|
734
874
|
dispatch({ type: "SET_CURSOR", payload: pos });
|
|
735
|
-
}, [flushInputBuffer])
|
|
875
|
+
}, [flushInputBuffer]),
|
|
876
|
+
insertAtCursor
|
|
736
877
|
};
|
|
737
878
|
};
|
|
738
879
|
|
|
739
880
|
// src/app/ui/components/InputPrompt.tsx
|
|
740
|
-
import {
|
|
881
|
+
import {
|
|
882
|
+
useEffect as useEffect3,
|
|
883
|
+
useMemo,
|
|
884
|
+
useState as useState2,
|
|
885
|
+
useRef as useRef3,
|
|
886
|
+
memo as memo2,
|
|
887
|
+
useLayoutEffect,
|
|
888
|
+
useCallback as useCallback2
|
|
889
|
+
} from "react";
|
|
741
890
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
742
891
|
|
|
743
892
|
// src/app/ui/utils/slashRegistry.ts
|
|
744
893
|
var getSlashCommands = () => [
|
|
745
|
-
{
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
894
|
+
{
|
|
895
|
+
name: "Ctrl+V / Cmd+V",
|
|
896
|
+
description: "paste from clipboard: image \u2192 file path under ~/.cache/bluma/clipboard; else text (all OS via clipboardy)",
|
|
897
|
+
category: "input"
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
name: "Ctrl+Shift+I",
|
|
901
|
+
description: "same as Ctrl+V / Cmd+V (paste image or text)",
|
|
902
|
+
category: "input"
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: "/help",
|
|
906
|
+
description: "list all slash commands (grouped)",
|
|
907
|
+
category: "help"
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
name: "/clear",
|
|
911
|
+
description: "clear chat below the welcome panel (welcome + session file unchanged)",
|
|
912
|
+
category: "session"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
name: "/img",
|
|
916
|
+
description: "send local image(s) to the model: /img ./shot.png [your question]",
|
|
917
|
+
category: "agent"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "/image",
|
|
921
|
+
description: "alias of /img",
|
|
922
|
+
category: "agent"
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: "/skills",
|
|
926
|
+
description: "list load_skill modules (bundled + project + ~/.bluma)",
|
|
927
|
+
category: "inspect"
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
name: "/tools",
|
|
931
|
+
description: "list native tools (optional filter: /tools grep)",
|
|
932
|
+
category: "inspect"
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
name: "/mcp",
|
|
936
|
+
description: "list MCP tools (optional filter: /mcp fs)",
|
|
937
|
+
category: "inspect"
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: "/init",
|
|
941
|
+
description: "run init subagent \u2014 BluMa.md codebase documentation",
|
|
942
|
+
category: "agent"
|
|
943
|
+
}
|
|
750
944
|
];
|
|
945
|
+
var SLASH_ROUTE_KEYWORDS = new Set(
|
|
946
|
+
getSlashCommands().filter((c) => c.name.startsWith("/")).map((c) => c.name.slice(1).toLowerCase())
|
|
947
|
+
);
|
|
948
|
+
function isSlashRoutingLine(text) {
|
|
949
|
+
const t = text.trimStart();
|
|
950
|
+
if (!t.startsWith("/")) {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
const rest = t.slice(1).trim();
|
|
954
|
+
if (!rest) {
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
const first = rest.split(/\s+/)[0];
|
|
958
|
+
if (first.includes("/") || first.includes("\\")) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
return SLASH_ROUTE_KEYWORDS.has(first.toLowerCase());
|
|
962
|
+
}
|
|
963
|
+
var CATEGORY_LABEL = {
|
|
964
|
+
session: "Session & UI",
|
|
965
|
+
inspect: "Inspect",
|
|
966
|
+
agent: "Agent",
|
|
967
|
+
help: "Help",
|
|
968
|
+
input: "Input"
|
|
969
|
+
};
|
|
970
|
+
function formatSlashHelpLines() {
|
|
971
|
+
const cmds = getSlashCommands();
|
|
972
|
+
const byCat = (cat) => cmds.filter((c) => c.category === cat);
|
|
973
|
+
const lines = [];
|
|
974
|
+
for (const cat of ["help", "session", "input", "agent", "inspect"]) {
|
|
975
|
+
const group = byCat(cat);
|
|
976
|
+
if (group.length === 0) continue;
|
|
977
|
+
lines.push(`${CATEGORY_LABEL[cat]}:`);
|
|
978
|
+
for (const c of group) {
|
|
979
|
+
lines.push(` ${c.name.padEnd(14)} ${c.description}`);
|
|
980
|
+
}
|
|
981
|
+
lines.push("");
|
|
982
|
+
}
|
|
983
|
+
return lines;
|
|
984
|
+
}
|
|
751
985
|
var filterSlashCommands = (query) => {
|
|
752
986
|
const list = getSlashCommands();
|
|
753
987
|
const q = (query || "").toLowerCase();
|
|
@@ -942,6 +1176,390 @@ var TOOL_PREVIEW_MAX_LINES = 8;
|
|
|
942
1176
|
var EXPAND_OVERLAY_MAX_LINES = 60;
|
|
943
1177
|
var TERMINAL_RULE_CHAR = "\u2500";
|
|
944
1178
|
|
|
1179
|
+
// src/app/ui/utils/clipboardImage.ts
|
|
1180
|
+
import fs2 from "fs";
|
|
1181
|
+
import os2 from "os";
|
|
1182
|
+
import path2 from "path";
|
|
1183
|
+
import { spawn, execFile as execFileCb } from "child_process";
|
|
1184
|
+
import { promisify } from "util";
|
|
1185
|
+
var execFile = promisify(execFileCb);
|
|
1186
|
+
var CLIPBOARD_MAX_BYTES = 4 * 1024 * 1024;
|
|
1187
|
+
function extFromMagic(buf) {
|
|
1188
|
+
if (buf.length < 12) return null;
|
|
1189
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) {
|
|
1190
|
+
return ".png";
|
|
1191
|
+
}
|
|
1192
|
+
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) {
|
|
1193
|
+
return ".jpg";
|
|
1194
|
+
}
|
|
1195
|
+
if (buf.slice(0, 6).toString("ascii") === "GIF87a" || buf.slice(0, 6).toString("ascii") === "GIF89a") {
|
|
1196
|
+
return ".gif";
|
|
1197
|
+
}
|
|
1198
|
+
if (buf.slice(0, 4).toString("ascii") === "RIFF" && buf.slice(8, 12).toString("ascii") === "WEBP") {
|
|
1199
|
+
return ".webp";
|
|
1200
|
+
}
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
function runCapture(cmd, args) {
|
|
1204
|
+
return new Promise((resolve2) => {
|
|
1205
|
+
const chunks = [];
|
|
1206
|
+
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1207
|
+
child.stdout.on("data", (c) => chunks.push(c));
|
|
1208
|
+
child.stderr.on("data", () => {
|
|
1209
|
+
});
|
|
1210
|
+
child.on("error", () => resolve2(Buffer.alloc(0)));
|
|
1211
|
+
child.on("close", () => {
|
|
1212
|
+
resolve2(Buffer.concat(chunks));
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
async function tryWlPaste(mime) {
|
|
1217
|
+
const args = mime ? ["--type", mime] : [];
|
|
1218
|
+
return runCapture("wl-paste", args);
|
|
1219
|
+
}
|
|
1220
|
+
async function tryXclip(mime) {
|
|
1221
|
+
return runCapture("xclip", ["-selection", "clipboard", "-t", mime, "-o"]);
|
|
1222
|
+
}
|
|
1223
|
+
async function tryPngpaste() {
|
|
1224
|
+
return runCapture("pngpaste", ["-"]);
|
|
1225
|
+
}
|
|
1226
|
+
function writeBufferIfImage(baseDir, buf) {
|
|
1227
|
+
if (buf.length < 80 || buf.length > CLIPBOARD_MAX_BYTES) {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
const ext = extFromMagic(buf);
|
|
1231
|
+
if (!ext) {
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
const out = path2.join(
|
|
1235
|
+
baseDir,
|
|
1236
|
+
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
|
|
1237
|
+
);
|
|
1238
|
+
fs2.writeFileSync(out, buf);
|
|
1239
|
+
return out;
|
|
1240
|
+
}
|
|
1241
|
+
function unlinkQuiet(p) {
|
|
1242
|
+
try {
|
|
1243
|
+
if (fs2.existsSync(p)) {
|
|
1244
|
+
fs2.unlinkSync(p);
|
|
1245
|
+
}
|
|
1246
|
+
} catch {
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
async function tryDarwinClipboardy(baseDir) {
|
|
1250
|
+
let tmpPaths = [];
|
|
1251
|
+
try {
|
|
1252
|
+
const { default: cb } = await import("clipboardy");
|
|
1253
|
+
if (typeof cb.hasImages !== "function" || !await cb.hasImages()) {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
tmpPaths = await cb.readImages();
|
|
1257
|
+
if (!tmpPaths?.length) {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
for (const src of tmpPaths) {
|
|
1261
|
+
if (!src || !fs2.existsSync(src)) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
let st;
|
|
1265
|
+
try {
|
|
1266
|
+
st = fs2.statSync(src);
|
|
1267
|
+
} catch {
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
if (st.size < 80 || st.size > CLIPBOARD_MAX_BYTES) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const ext = path2.extname(src).toLowerCase();
|
|
1274
|
+
const safeExt = ext && /^\.(png|jpe?g|gif|webp)$/i.test(ext) ? ext : ".png";
|
|
1275
|
+
const out = path2.join(
|
|
1276
|
+
baseDir,
|
|
1277
|
+
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${safeExt}`
|
|
1278
|
+
);
|
|
1279
|
+
fs2.copyFileSync(src, out);
|
|
1280
|
+
for (const p of tmpPaths) {
|
|
1281
|
+
unlinkQuiet(p);
|
|
1282
|
+
}
|
|
1283
|
+
return out;
|
|
1284
|
+
}
|
|
1285
|
+
} catch {
|
|
1286
|
+
}
|
|
1287
|
+
for (const p of tmpPaths) {
|
|
1288
|
+
unlinkQuiet(p);
|
|
1289
|
+
}
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
async function tryWindowsPowerShell(outFile) {
|
|
1293
|
+
const ps = process.env.SystemRoot != null ? path2.join(
|
|
1294
|
+
process.env.SystemRoot,
|
|
1295
|
+
"System32",
|
|
1296
|
+
"WindowsPowerShell",
|
|
1297
|
+
"v1.0",
|
|
1298
|
+
"powershell.exe"
|
|
1299
|
+
) : "powershell.exe";
|
|
1300
|
+
if (!fs2.existsSync(ps)) {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
const script = "$ErrorActionPreference='Stop';Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$img=[System.Windows.Forms.Clipboard]::GetImage();if($null -eq $img){exit 2};$img.Save($env:BLUMA_CLIP_OUT,[System.Drawing.Imaging.ImageFormat]::Png);exit 0";
|
|
1304
|
+
try {
|
|
1305
|
+
await execFile(
|
|
1306
|
+
ps,
|
|
1307
|
+
["-NoProfile", "-Sta", "-WindowStyle", "Hidden", "-Command", script],
|
|
1308
|
+
{
|
|
1309
|
+
env: { ...process.env, BLUMA_CLIP_OUT: outFile },
|
|
1310
|
+
windowsHide: true,
|
|
1311
|
+
timeout: 25e3
|
|
1312
|
+
}
|
|
1313
|
+
);
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
const status = e && typeof e === "object" && "status" in e ? e.status : null;
|
|
1316
|
+
if (status === 2) {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
return false;
|
|
1320
|
+
}
|
|
1321
|
+
try {
|
|
1322
|
+
const st = fs2.statSync(outFile);
|
|
1323
|
+
return st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES;
|
|
1324
|
+
} catch {
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
async function tryUnixLikeClipboard(baseDir) {
|
|
1329
|
+
const attempts = [];
|
|
1330
|
+
if (process.env.WAYLAND_DISPLAY) {
|
|
1331
|
+
attempts.push(() => tryWlPaste("image/png"));
|
|
1332
|
+
attempts.push(() => tryWlPaste("image/jpeg"));
|
|
1333
|
+
attempts.push(() => tryWlPaste());
|
|
1334
|
+
}
|
|
1335
|
+
attempts.push(() => tryXclip("image/png"));
|
|
1336
|
+
attempts.push(() => tryXclip("image/jpeg"));
|
|
1337
|
+
attempts.push(() => tryXclip("image/webp"));
|
|
1338
|
+
for (const run of attempts) {
|
|
1339
|
+
try {
|
|
1340
|
+
const buf = await run();
|
|
1341
|
+
const saved = writeBufferIfImage(baseDir, buf);
|
|
1342
|
+
if (saved) {
|
|
1343
|
+
return saved;
|
|
1344
|
+
}
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
try {
|
|
1349
|
+
const buf = await tryPngpaste();
|
|
1350
|
+
return writeBufferIfImage(baseDir, buf);
|
|
1351
|
+
} catch {
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
async function readClipboardImageToTempFile() {
|
|
1356
|
+
const baseDir = path2.join(os2.homedir(), ".cache", "bluma", "clipboard");
|
|
1357
|
+
fs2.mkdirSync(baseDir, { recursive: true });
|
|
1358
|
+
if (process.platform === "darwin") {
|
|
1359
|
+
const fromCb = await tryDarwinClipboardy(baseDir);
|
|
1360
|
+
if (fromCb) {
|
|
1361
|
+
return fromCb;
|
|
1362
|
+
}
|
|
1363
|
+
try {
|
|
1364
|
+
const buf = await tryPngpaste();
|
|
1365
|
+
return writeBufferIfImage(baseDir, buf);
|
|
1366
|
+
} catch {
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (process.platform === "win32") {
|
|
1371
|
+
const outFile = path2.join(
|
|
1372
|
+
baseDir,
|
|
1373
|
+
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.png`
|
|
1374
|
+
);
|
|
1375
|
+
const ok = await tryWindowsPowerShell(outFile);
|
|
1376
|
+
return ok ? outFile : null;
|
|
1377
|
+
}
|
|
1378
|
+
return tryUnixLikeClipboard(baseDir);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/app/ui/utils/inlineImageInputLabels.ts
|
|
1382
|
+
function resolveInlineImageLabels(raw, idToPath) {
|
|
1383
|
+
return raw.replace(/\[IMAGE #(\d+)\]/g, (full, numStr) => {
|
|
1384
|
+
const id = Number.parseInt(numStr, 10);
|
|
1385
|
+
if (Number.isNaN(id)) {
|
|
1386
|
+
return full;
|
|
1387
|
+
}
|
|
1388
|
+
const p = idToPath.get(id);
|
|
1389
|
+
if (!p) {
|
|
1390
|
+
return full;
|
|
1391
|
+
}
|
|
1392
|
+
return p.includes(" ") ? `"${p}"` : p;
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/app/agent/utils/user_message_images.ts
|
|
1397
|
+
import fs3 from "fs";
|
|
1398
|
+
import os3 from "os";
|
|
1399
|
+
import path3 from "path";
|
|
1400
|
+
var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
|
|
1401
|
+
var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
1402
|
+
var MAX_IMAGES = 6;
|
|
1403
|
+
var MIME = {
|
|
1404
|
+
".png": "image/png",
|
|
1405
|
+
".jpg": "image/jpeg",
|
|
1406
|
+
".jpeg": "image/jpeg",
|
|
1407
|
+
".gif": "image/gif",
|
|
1408
|
+
".webp": "image/webp",
|
|
1409
|
+
".bmp": "image/bmp"
|
|
1410
|
+
};
|
|
1411
|
+
function expandUserPath(p) {
|
|
1412
|
+
const t = p.trim();
|
|
1413
|
+
if (t.startsWith("~")) {
|
|
1414
|
+
return path3.join(os3.homedir(), t.slice(1).replace(/^\//, ""));
|
|
1415
|
+
}
|
|
1416
|
+
return t;
|
|
1417
|
+
}
|
|
1418
|
+
function isPathAllowed(absResolved, cwd) {
|
|
1419
|
+
const resolved = path3.normalize(path3.resolve(absResolved));
|
|
1420
|
+
const cwdR = path3.normalize(path3.resolve(cwd));
|
|
1421
|
+
const homeR = path3.normalize(path3.resolve(os3.homedir()));
|
|
1422
|
+
const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path3.sep);
|
|
1423
|
+
const underHome = resolved === homeR || resolved.startsWith(homeR + path3.sep);
|
|
1424
|
+
return underCwd || underHome;
|
|
1425
|
+
}
|
|
1426
|
+
function mimeFor(abs) {
|
|
1427
|
+
const ext = path3.extname(abs).toLowerCase();
|
|
1428
|
+
return MIME[ext] || "application/octet-stream";
|
|
1429
|
+
}
|
|
1430
|
+
var IMAGE_EXT_SRC = String.raw`(?:png|jpe?g|gif|webp|bmp)`;
|
|
1431
|
+
var UNIX_PATH_WITH_IMAGE_EXT = new RegExp(
|
|
1432
|
+
String.raw`(?:^|[\s"'(])(\/(?:[^\s/'"<>|]+\/)*[^\s/'"<>|]+\.${IMAGE_EXT_SRC})\b`,
|
|
1433
|
+
"gi"
|
|
1434
|
+
);
|
|
1435
|
+
var WIN_PATH_WITH_IMAGE_EXT = new RegExp(
|
|
1436
|
+
String.raw`(?:^|[\s"'(])(([A-Za-z]:(?:\\[^\\/:*?"<>|\r\n]+)+\.${IMAGE_EXT_SRC}))\b`,
|
|
1437
|
+
"gi"
|
|
1438
|
+
);
|
|
1439
|
+
function collectImagePathStrings(raw) {
|
|
1440
|
+
const found = [];
|
|
1441
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1442
|
+
const push = (p) => {
|
|
1443
|
+
const t = p.trim();
|
|
1444
|
+
if (!t || seen.has(t)) return;
|
|
1445
|
+
seen.add(t);
|
|
1446
|
+
found.push(t);
|
|
1447
|
+
};
|
|
1448
|
+
const quoted = /(["'])((?:(?!\1).)*?\.(?:png|jpe?g|gif|webp|bmp))\1/gi;
|
|
1449
|
+
for (const m of raw.matchAll(quoted)) {
|
|
1450
|
+
push(m[2].trim());
|
|
1451
|
+
}
|
|
1452
|
+
const withoutQuoted = raw.replace(quoted, " ");
|
|
1453
|
+
const tokens = withoutQuoted.split(/\s+/);
|
|
1454
|
+
for (let tok of tokens) {
|
|
1455
|
+
tok = tok.replace(/^[([{]+/, "").replace(/[)\]},;:]+$/, "");
|
|
1456
|
+
if (!tok || !IMAGE_EXT.test(tok)) continue;
|
|
1457
|
+
push(tok);
|
|
1458
|
+
}
|
|
1459
|
+
for (const m of raw.matchAll(UNIX_PATH_WITH_IMAGE_EXT)) {
|
|
1460
|
+
if (m[1]) {
|
|
1461
|
+
push(m[1]);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
for (const m of raw.matchAll(WIN_PATH_WITH_IMAGE_EXT)) {
|
|
1465
|
+
push(m[1]);
|
|
1466
|
+
}
|
|
1467
|
+
return found;
|
|
1468
|
+
}
|
|
1469
|
+
function resolveImagePath(candidate, cwd) {
|
|
1470
|
+
const expanded = expandUserPath(candidate);
|
|
1471
|
+
const abs = path3.isAbsolute(expanded) ? path3.normalize(expanded) : path3.normalize(path3.resolve(cwd, expanded));
|
|
1472
|
+
if (!isPathAllowed(abs, cwd)) return null;
|
|
1473
|
+
try {
|
|
1474
|
+
if (!fs3.existsSync(abs) || !fs3.statSync(abs).isFile()) return null;
|
|
1475
|
+
} catch {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
if (!IMAGE_EXT.test(abs)) return null;
|
|
1479
|
+
return abs;
|
|
1480
|
+
}
|
|
1481
|
+
function tryPasteChunkAsSingleImagePath(chunk, cwd) {
|
|
1482
|
+
const normalized = chunk.replace(/\r\n/g, "\n");
|
|
1483
|
+
const lines = normalized.split("\n");
|
|
1484
|
+
const t = (lines[0] ?? "").trim();
|
|
1485
|
+
if (!t) {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
if (lines.length > 1 && lines.slice(1).some((l) => l.trim().length > 0)) {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
const paths = collectImagePathStrings(t);
|
|
1492
|
+
if (paths.length !== 1) {
|
|
1493
|
+
return null;
|
|
1494
|
+
}
|
|
1495
|
+
if (stripImagePathStrings(t, paths).trim().length > 0) {
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
const abs = resolveImagePath(paths[0], cwd);
|
|
1499
|
+
if (!abs) {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
try {
|
|
1503
|
+
const st = fs3.statSync(abs);
|
|
1504
|
+
if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
} catch {
|
|
1508
|
+
return null;
|
|
1509
|
+
}
|
|
1510
|
+
return abs;
|
|
1511
|
+
}
|
|
1512
|
+
function stripImagePathStrings(text, paths) {
|
|
1513
|
+
let out = text;
|
|
1514
|
+
for (const p of paths) {
|
|
1515
|
+
const q = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1516
|
+
out = out.replace(new RegExp(`["']${q}["']`, "gi"), " ");
|
|
1517
|
+
out = out.replace(new RegExp(`(?<=^|\\s)${q}(?=\\s|$)`, "gi"), " ");
|
|
1518
|
+
}
|
|
1519
|
+
return out.replace(/\s{2,}/g, " ").trim();
|
|
1520
|
+
}
|
|
1521
|
+
function buildUserMessageContent(raw, cwd) {
|
|
1522
|
+
const trimmed = raw.trim();
|
|
1523
|
+
if (!trimmed) return trimmed;
|
|
1524
|
+
const candidates = collectImagePathStrings(trimmed);
|
|
1525
|
+
const resolvedAbs = [];
|
|
1526
|
+
const usedStrings = [];
|
|
1527
|
+
for (const c of candidates) {
|
|
1528
|
+
if (resolvedAbs.length >= MAX_IMAGES) break;
|
|
1529
|
+
const abs = resolveImagePath(c, cwd);
|
|
1530
|
+
if (!abs) continue;
|
|
1531
|
+
try {
|
|
1532
|
+
const st = fs3.statSync(abs);
|
|
1533
|
+
if (st.size > MAX_IMAGE_BYTES) continue;
|
|
1534
|
+
} catch {
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
resolvedAbs.push(abs);
|
|
1538
|
+
usedStrings.push(c);
|
|
1539
|
+
}
|
|
1540
|
+
if (resolvedAbs.length === 0) {
|
|
1541
|
+
return trimmed;
|
|
1542
|
+
}
|
|
1543
|
+
let textPart = stripImagePathStrings(trimmed, usedStrings);
|
|
1544
|
+
if (!textPart) {
|
|
1545
|
+
textPart = "(User attached image(s) only \u2014 describe, compare, or answer using the image(s).)";
|
|
1546
|
+
}
|
|
1547
|
+
const parts = [{ type: "text", text: textPart }];
|
|
1548
|
+
for (const abs of resolvedAbs) {
|
|
1549
|
+
const buf = fs3.readFileSync(abs);
|
|
1550
|
+
const b64 = buf.toString("base64");
|
|
1551
|
+
const mime = mimeFor(abs);
|
|
1552
|
+
parts.push({
|
|
1553
|
+
type: "image_url",
|
|
1554
|
+
image_url: {
|
|
1555
|
+
url: `data:${mime};base64,${b64}`,
|
|
1556
|
+
detail: "auto"
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
return parts;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
945
1563
|
// src/app/ui/components/InputPrompt.tsx
|
|
946
1564
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
947
1565
|
var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter2();
|
|
@@ -1006,7 +1624,9 @@ var SlashSuggestions = memo2(({
|
|
|
1006
1624
|
s.name,
|
|
1007
1625
|
" ",
|
|
1008
1626
|
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1009
|
-
"
|
|
1627
|
+
"[",
|
|
1628
|
+
s.category,
|
|
1629
|
+
"] ",
|
|
1010
1630
|
s.description
|
|
1011
1631
|
] })
|
|
1012
1632
|
] })
|
|
@@ -1014,13 +1634,21 @@ var SlashSuggestions = memo2(({
|
|
|
1014
1634
|
}) }));
|
|
1015
1635
|
SlashSuggestions.displayName = "SlashSuggestions";
|
|
1016
1636
|
var InputRuleLine = memo2(() => {
|
|
1017
|
-
const { stdout } =
|
|
1637
|
+
const { stdout } = useStdout2();
|
|
1018
1638
|
const cols = stdout?.columns ?? 80;
|
|
1019
1639
|
const n = Math.max(8, cols);
|
|
1020
1640
|
return /* @__PURE__ */ jsx2(Text2, { color: "white", children: TERMINAL_RULE_CHAR.repeat(n) });
|
|
1021
1641
|
});
|
|
1022
1642
|
InputRuleLine.displayName = "InputRuleLine";
|
|
1023
|
-
var Footer = memo2(
|
|
1643
|
+
var Footer = memo2(
|
|
1644
|
+
({
|
|
1645
|
+
isReadOnly,
|
|
1646
|
+
clipHint
|
|
1647
|
+
}) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
|
|
1648
|
+
clipHint ? /* @__PURE__ */ jsx2(Box2, { marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", dimColor: true, children: clipHint }) }) : null,
|
|
1649
|
+
/* @__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 submit | Shift+Enter newline | Ctrl+V / Cmd+V paste | /help \xB7 /img \xB7 esc \xB7 Ctrl+O" }) })
|
|
1650
|
+
] })
|
|
1651
|
+
);
|
|
1024
1652
|
Footer.displayName = "Footer";
|
|
1025
1653
|
var TextLinesRenderer = memo2(({
|
|
1026
1654
|
lines,
|
|
@@ -1058,19 +1686,52 @@ var InputPrompt = memo2(({
|
|
|
1058
1686
|
onInterrupt,
|
|
1059
1687
|
disableWhileProcessing = false
|
|
1060
1688
|
}) => {
|
|
1061
|
-
const { stdout } =
|
|
1689
|
+
const { stdout } = useStdout2();
|
|
1062
1690
|
const [viewWidth] = useState2(() => stdout.columns - 6);
|
|
1063
1691
|
const [slashOpen, setSlashOpen] = useState2(false);
|
|
1064
1692
|
const [slashIndex, setSlashIndex] = useState2(0);
|
|
1693
|
+
const [clipHint, setClipHint] = useState2(null);
|
|
1694
|
+
const textRef = useRef3("");
|
|
1695
|
+
const cursorRef = useRef3(0);
|
|
1696
|
+
const inlineImageByIdRef = useRef3(/* @__PURE__ */ new Map());
|
|
1697
|
+
const nextInlineImageIdRef = useRef3(1);
|
|
1698
|
+
const clearInlineImageSlots = () => {
|
|
1699
|
+
inlineImageByIdRef.current.clear();
|
|
1700
|
+
nextInlineImageIdRef.current = 1;
|
|
1701
|
+
};
|
|
1702
|
+
const cwd = process.cwd();
|
|
1703
|
+
const transformInputChunk = useCallback2(
|
|
1704
|
+
(chunk) => {
|
|
1705
|
+
const abs = tryPasteChunkAsSingleImagePath(chunk, cwd);
|
|
1706
|
+
if (!abs) {
|
|
1707
|
+
return chunk;
|
|
1708
|
+
}
|
|
1709
|
+
const id = nextInlineImageIdRef.current++;
|
|
1710
|
+
inlineImageByIdRef.current.set(id, abs);
|
|
1711
|
+
return /\n$/.test(chunk) ? `[IMAGE #${id}]
|
|
1712
|
+
` : `[IMAGE #${id}] `;
|
|
1713
|
+
},
|
|
1714
|
+
[cwd]
|
|
1715
|
+
);
|
|
1065
1716
|
const permissiveOnSubmit = (value) => {
|
|
1066
1717
|
const trimmed = (value || "").trim();
|
|
1718
|
+
const map = inlineImageByIdRef.current;
|
|
1719
|
+
const resolved = resolveInlineImageLabels(value, map);
|
|
1067
1720
|
if (isReadOnly) {
|
|
1068
1721
|
if (trimmed.length > 0) {
|
|
1069
|
-
uiEventBus.emit("user_overlay", {
|
|
1722
|
+
uiEventBus.emit("user_overlay", {
|
|
1723
|
+
kind: "message",
|
|
1724
|
+
payload: resolved.trim(),
|
|
1725
|
+
ts: Date.now()
|
|
1726
|
+
});
|
|
1727
|
+
clearInlineImageSlots();
|
|
1070
1728
|
}
|
|
1071
1729
|
return;
|
|
1072
1730
|
}
|
|
1073
|
-
|
|
1731
|
+
if (trimmed.length > 0) {
|
|
1732
|
+
onSubmit(resolved);
|
|
1733
|
+
clearInlineImageSlots();
|
|
1734
|
+
}
|
|
1074
1735
|
};
|
|
1075
1736
|
const { text, cursorPosition, setText } = useCustomInput({
|
|
1076
1737
|
onSubmit: (value) => {
|
|
@@ -1080,8 +1741,48 @@ var InputPrompt = memo2(({
|
|
|
1080
1741
|
},
|
|
1081
1742
|
viewWidth,
|
|
1082
1743
|
isReadOnly,
|
|
1083
|
-
onInterrupt
|
|
1744
|
+
onInterrupt,
|
|
1745
|
+
transformInputChunk: !disableWhileProcessing ? transformInputChunk : void 0,
|
|
1746
|
+
onChordPaste: !disableWhileProcessing ? (insertAtCursor) => {
|
|
1747
|
+
void (async () => {
|
|
1748
|
+
const padBefore = () => {
|
|
1749
|
+
const t = textRef.current;
|
|
1750
|
+
const c = cursorRef.current;
|
|
1751
|
+
return t.length > 0 && c > 0 && t[c - 1] !== "\n" && !/\s/.test(t[c - 1]) ? " " : "";
|
|
1752
|
+
};
|
|
1753
|
+
const filePath = await readClipboardImageToTempFile();
|
|
1754
|
+
if (filePath) {
|
|
1755
|
+
const id = nextInlineImageIdRef.current++;
|
|
1756
|
+
inlineImageByIdRef.current.set(id, filePath);
|
|
1757
|
+
insertAtCursor(`${padBefore()}[IMAGE #${id}] `);
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
try {
|
|
1761
|
+
const { default: cb } = await import("clipboardy");
|
|
1762
|
+
const pasted = await cb.read();
|
|
1763
|
+
const t = pasted != null ? String(pasted).replace(/\r\n/g, "\n") : "";
|
|
1764
|
+
if (t.length > 0) {
|
|
1765
|
+
const abs = tryPasteChunkAsSingleImagePath(t, cwd);
|
|
1766
|
+
if (abs) {
|
|
1767
|
+
const id = nextInlineImageIdRef.current++;
|
|
1768
|
+
inlineImageByIdRef.current.set(id, abs);
|
|
1769
|
+
insertAtCursor(`${padBefore()}[IMAGE #${id}] `);
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
insertAtCursor(`${padBefore()}${t}`);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
} catch {
|
|
1776
|
+
}
|
|
1777
|
+
setClipHint("Clipboard: empty or unreadable (GUI session; Linux: wl-clipboard/xclip).");
|
|
1778
|
+
setTimeout(() => setClipHint(null), 4e3);
|
|
1779
|
+
})();
|
|
1780
|
+
} : void 0
|
|
1084
1781
|
});
|
|
1782
|
+
useLayoutEffect(() => {
|
|
1783
|
+
textRef.current = text;
|
|
1784
|
+
cursorRef.current = cursorPosition;
|
|
1785
|
+
}, [text, cursorPosition]);
|
|
1085
1786
|
const linesData = useMemo(() => {
|
|
1086
1787
|
const lines = text.split("\n");
|
|
1087
1788
|
let remainingChars = cursorPosition;
|
|
@@ -1148,7 +1849,6 @@ var InputPrompt = memo2(({
|
|
|
1148
1849
|
delete globalThis.__BLUMA_FORCE_CURSOR_END__;
|
|
1149
1850
|
}
|
|
1150
1851
|
}, [text, setText]);
|
|
1151
|
-
const cwd = process.cwd();
|
|
1152
1852
|
const pathAutocomplete = useAtCompletion({ cwd, text, cursorPosition, setText });
|
|
1153
1853
|
useInput2((input, key) => {
|
|
1154
1854
|
if (key.backspace || key.delete || key.ctrl || key.meta) return;
|
|
@@ -1208,7 +1908,7 @@ var InputPrompt = memo2(({
|
|
|
1208
1908
|
slashOpen && slashSuggestions.length > 0 && /* @__PURE__ */ jsx2(SlashSuggestions, { suggestions: slashSuggestions, selectedIndex: slashIndex })
|
|
1209
1909
|
] }) }),
|
|
1210
1910
|
/* @__PURE__ */ jsx2(InputRuleLine, {}),
|
|
1211
|
-
/* @__PURE__ */ jsx2(Footer, { isReadOnly })
|
|
1911
|
+
/* @__PURE__ */ jsx2(Footer, { isReadOnly, clipHint })
|
|
1212
1912
|
] });
|
|
1213
1913
|
});
|
|
1214
1914
|
InputPrompt.displayName = "InputPrompt";
|
|
@@ -1266,7 +1966,7 @@ var InteractiveMenu = memo3(InteractiveMenuComponent);
|
|
|
1266
1966
|
|
|
1267
1967
|
// src/app/ui/components/promptRenderers.tsx
|
|
1268
1968
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
1269
|
-
import
|
|
1969
|
+
import path4 from "path";
|
|
1270
1970
|
|
|
1271
1971
|
// src/app/ui/components/SimpleDiff.tsx
|
|
1272
1972
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
@@ -1312,7 +2012,7 @@ var SimpleDiff = ({ text, maxHeight }) => {
|
|
|
1312
2012
|
// src/app/ui/components/promptRenderers.tsx
|
|
1313
2013
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1314
2014
|
var getBasePath = (filePath) => {
|
|
1315
|
-
return
|
|
2015
|
+
return path4.basename(filePath);
|
|
1316
2016
|
};
|
|
1317
2017
|
var renderShellCommand = ({ toolCall }) => {
|
|
1318
2018
|
let command = "";
|
|
@@ -1526,6 +2226,25 @@ var promptRenderers = {
|
|
|
1526
2226
|
|
|
1527
2227
|
// src/app/ui/theme/m3Layout.tsx
|
|
1528
2228
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
2229
|
+
|
|
2230
|
+
// src/app/ui/utils/formatTurnDurationMs.ts
|
|
2231
|
+
function formatTurnDurationMs(ms) {
|
|
2232
|
+
if (ms < 0) {
|
|
2233
|
+
return "0s";
|
|
2234
|
+
}
|
|
2235
|
+
const secTotal = Math.round(ms / 1e3);
|
|
2236
|
+
if (secTotal < 60) {
|
|
2237
|
+
if (ms < 1e4) {
|
|
2238
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
2239
|
+
}
|
|
2240
|
+
return `${secTotal}s`;
|
|
2241
|
+
}
|
|
2242
|
+
const min = Math.floor(secTotal / 60);
|
|
2243
|
+
const sec = secTotal % 60;
|
|
2244
|
+
return `${min}min \u25CF ${sec}s`;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// src/app/ui/theme/m3Layout.tsx
|
|
1529
2248
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1530
2249
|
function ChatBlock({
|
|
1531
2250
|
children,
|
|
@@ -1533,6 +2252,24 @@ function ChatBlock({
|
|
|
1533
2252
|
}) {
|
|
1534
2253
|
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginBottom, children });
|
|
1535
2254
|
}
|
|
2255
|
+
function ChatUserImageBlock({
|
|
2256
|
+
imageCount,
|
|
2257
|
+
caption,
|
|
2258
|
+
captionDim = false
|
|
2259
|
+
}) {
|
|
2260
|
+
if (imageCount < 1) {
|
|
2261
|
+
return null;
|
|
2262
|
+
}
|
|
2263
|
+
const cap = caption?.trim() ?? "";
|
|
2264
|
+
const capLines = cap.length > 0 ? cap.split("\n") : [];
|
|
2265
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 0, children: [
|
|
2266
|
+
Array.from({ length: imageCount }, (_, i) => /* @__PURE__ */ jsx6(Text6, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: `[IMAGE #${i + 1}]` }, `img-${i}`)),
|
|
2267
|
+
capLines.map((line, i) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", flexWrap: "wrap", children: [
|
|
2268
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: i === 0 ? "\u2514\u2500 " : " " }),
|
|
2269
|
+
captionDim ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "wrap", children: line }) : /* @__PURE__ */ jsx6(Text6, { wrap: "wrap", children: line })
|
|
2270
|
+
] }, `cap-${i}`))
|
|
2271
|
+
] });
|
|
2272
|
+
}
|
|
1536
2273
|
function ChatUserMessage({ children }) {
|
|
1537
2274
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap", alignItems: "flex-start", children: [
|
|
1538
2275
|
/* @__PURE__ */ jsxs6(Text6, { color: "white", bold: true, children: [
|
|
@@ -1554,13 +2291,10 @@ function ChatStatusRow({ children }) {
|
|
|
1554
2291
|
children
|
|
1555
2292
|
] });
|
|
1556
2293
|
}
|
|
1557
|
-
function ChatTurnDuration({
|
|
2294
|
+
function ChatTurnDuration({ durationMs }) {
|
|
1558
2295
|
return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
1559
2296
|
/* @__PURE__ */ jsx6(Text6, { color: BLUMA_TERMINAL.brandBlue, children: "\xB7 " }),
|
|
1560
|
-
/* @__PURE__ */
|
|
1561
|
-
secondsFormatted,
|
|
1562
|
-
"s"
|
|
1563
|
-
] })
|
|
2297
|
+
/* @__PURE__ */ jsx6(Text6, { color: BLUMA_TERMINAL.brandMagenta, children: formatTurnDurationMs(durationMs) })
|
|
1564
2298
|
] }) });
|
|
1565
2299
|
}
|
|
1566
2300
|
var M3StatusStrip = ChatStatusRow;
|
|
@@ -1583,25 +2317,25 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
|
|
|
1583
2317
|
|
|
1584
2318
|
// src/app/agent/agent.ts
|
|
1585
2319
|
import * as dotenv from "dotenv";
|
|
1586
|
-
import
|
|
1587
|
-
import
|
|
2320
|
+
import path19 from "path";
|
|
2321
|
+
import os14 from "os";
|
|
1588
2322
|
|
|
1589
2323
|
// src/app/agent/tool_invoker.ts
|
|
1590
|
-
import { promises as
|
|
1591
|
-
import
|
|
2324
|
+
import { promises as fs11 } from "fs";
|
|
2325
|
+
import path13 from "path";
|
|
1592
2326
|
import { fileURLToPath } from "url";
|
|
1593
2327
|
|
|
1594
2328
|
// src/app/agent/tools/natives/edit.ts
|
|
1595
|
-
import
|
|
1596
|
-
import
|
|
1597
|
-
import { promises as
|
|
2329
|
+
import path5 from "path";
|
|
2330
|
+
import os4 from "os";
|
|
2331
|
+
import { promises as fs4 } from "fs";
|
|
1598
2332
|
import { diffLines } from "diff";
|
|
1599
2333
|
var MAX_DIFF_SIZE = 5e4;
|
|
1600
2334
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
1601
2335
|
function normalizePath(filePath) {
|
|
1602
2336
|
try {
|
|
1603
2337
|
filePath = filePath.trim();
|
|
1604
|
-
if (
|
|
2338
|
+
if (os4.platform() === "win32") {
|
|
1605
2339
|
const winDriveRegex = /^\/([a-zA-Z])[:/]/;
|
|
1606
2340
|
const match = filePath.match(winDriveRegex);
|
|
1607
2341
|
if (match) {
|
|
@@ -1611,7 +2345,7 @@ function normalizePath(filePath) {
|
|
|
1611
2345
|
}
|
|
1612
2346
|
filePath = filePath.replace(/\//g, "\\");
|
|
1613
2347
|
}
|
|
1614
|
-
return
|
|
2348
|
+
return path5.normalize(path5.resolve(filePath));
|
|
1615
2349
|
} catch (e) {
|
|
1616
2350
|
throw new Error(`Failed to normalize path "${filePath}": ${e.message}`);
|
|
1617
2351
|
}
|
|
@@ -1735,7 +2469,7 @@ async function calculateEdit(filePath, oldString, newString, expectedReplacement
|
|
|
1735
2469
|
let normalizedOldString = oldString.replace(/\r\n/g, "\n");
|
|
1736
2470
|
let occurrences = 0;
|
|
1737
2471
|
try {
|
|
1738
|
-
const stats = await
|
|
2472
|
+
const stats = await fs4.stat(normalizedFilePath);
|
|
1739
2473
|
if (stats.size > MAX_FILE_SIZE) {
|
|
1740
2474
|
error = {
|
|
1741
2475
|
display: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum allowed: ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
@@ -1743,7 +2477,7 @@ async function calculateEdit(filePath, oldString, newString, expectedReplacement
|
|
|
1743
2477
|
};
|
|
1744
2478
|
return { currentContent, newContent: "", occurrences: 0, error, isNewFile };
|
|
1745
2479
|
}
|
|
1746
|
-
currentContent = await
|
|
2480
|
+
currentContent = await fs4.readFile(normalizedFilePath, "utf-8");
|
|
1747
2481
|
currentContent = currentContent.replace(/\r\n/g, "\n");
|
|
1748
2482
|
} catch (e) {
|
|
1749
2483
|
if (e.code !== "ENOENT") {
|
|
@@ -1872,7 +2606,7 @@ async function editTool(args) {
|
|
|
1872
2606
|
};
|
|
1873
2607
|
}
|
|
1874
2608
|
const cwd = process.cwd();
|
|
1875
|
-
if (!normalizedFilePath.startsWith(cwd) && !
|
|
2609
|
+
if (!normalizedFilePath.startsWith(cwd) && !path5.isAbsolute(file_path)) {
|
|
1876
2610
|
return {
|
|
1877
2611
|
success: false,
|
|
1878
2612
|
error: `Invalid parameters: file_path must be within the current working directory or be an absolute path.`,
|
|
@@ -1893,11 +2627,11 @@ async function editTool(args) {
|
|
|
1893
2627
|
file_path: normalizedFilePath
|
|
1894
2628
|
};
|
|
1895
2629
|
}
|
|
1896
|
-
const dirPath =
|
|
1897
|
-
await
|
|
1898
|
-
await
|
|
1899
|
-
const relativePath =
|
|
1900
|
-
const filename =
|
|
2630
|
+
const dirPath = path5.dirname(normalizedFilePath);
|
|
2631
|
+
await fs4.mkdir(dirPath, { recursive: true });
|
|
2632
|
+
await fs4.writeFile(normalizedFilePath, editData.newContent, "utf-8");
|
|
2633
|
+
const relativePath = path5.relative(process.cwd(), normalizedFilePath);
|
|
2634
|
+
const filename = path5.basename(normalizedFilePath);
|
|
1901
2635
|
if (editData.isNewFile) {
|
|
1902
2636
|
return {
|
|
1903
2637
|
success: true,
|
|
@@ -1951,8 +2685,8 @@ function message(args) {
|
|
|
1951
2685
|
}
|
|
1952
2686
|
|
|
1953
2687
|
// src/app/agent/tools/natives/ls.ts
|
|
1954
|
-
import { promises as
|
|
1955
|
-
import
|
|
2688
|
+
import { promises as fs5 } from "fs";
|
|
2689
|
+
import path6 from "path";
|
|
1956
2690
|
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
1957
2691
|
".git",
|
|
1958
2692
|
".gitignore",
|
|
@@ -1980,8 +2714,8 @@ async function ls(args) {
|
|
|
1980
2714
|
max_depth
|
|
1981
2715
|
} = args;
|
|
1982
2716
|
try {
|
|
1983
|
-
const basePath =
|
|
1984
|
-
if (!(await
|
|
2717
|
+
const basePath = path6.resolve(directory_path);
|
|
2718
|
+
if (!(await fs5.stat(basePath)).isDirectory()) {
|
|
1985
2719
|
throw new Error(`Directory '${directory_path}' not found.`);
|
|
1986
2720
|
}
|
|
1987
2721
|
const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
|
|
@@ -1990,11 +2724,11 @@ async function ls(args) {
|
|
|
1990
2724
|
const allDirs = [];
|
|
1991
2725
|
const walk = async (currentDir, currentDepth) => {
|
|
1992
2726
|
if (max_depth !== void 0 && currentDepth > max_depth) return;
|
|
1993
|
-
const entries = await
|
|
2727
|
+
const entries = await fs5.readdir(currentDir, { withFileTypes: true });
|
|
1994
2728
|
for (const entry of entries) {
|
|
1995
2729
|
const entryName = entry.name;
|
|
1996
|
-
const fullPath =
|
|
1997
|
-
const posixPath = fullPath.split(
|
|
2730
|
+
const fullPath = path6.join(currentDir, entryName);
|
|
2731
|
+
const posixPath = fullPath.split(path6.sep).join("/");
|
|
1998
2732
|
const isHidden = entryName.startsWith(".");
|
|
1999
2733
|
if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
|
|
2000
2734
|
continue;
|
|
@@ -2005,7 +2739,7 @@ async function ls(args) {
|
|
|
2005
2739
|
await walk(fullPath, currentDepth + 1);
|
|
2006
2740
|
}
|
|
2007
2741
|
} else if (entry.isFile()) {
|
|
2008
|
-
if (!normalizedExtensions || normalizedExtensions.includes(
|
|
2742
|
+
if (!normalizedExtensions || normalizedExtensions.includes(path6.extname(entryName).toLowerCase())) {
|
|
2009
2743
|
allFiles.push(posixPath);
|
|
2010
2744
|
}
|
|
2011
2745
|
}
|
|
@@ -2016,7 +2750,7 @@ async function ls(args) {
|
|
|
2016
2750
|
allDirs.sort();
|
|
2017
2751
|
return {
|
|
2018
2752
|
success: true,
|
|
2019
|
-
path: basePath.split(
|
|
2753
|
+
path: basePath.split(path6.sep).join("/"),
|
|
2020
2754
|
recursive,
|
|
2021
2755
|
total_files: allFiles.length,
|
|
2022
2756
|
total_directories: allDirs.length,
|
|
@@ -2032,17 +2766,17 @@ async function ls(args) {
|
|
|
2032
2766
|
}
|
|
2033
2767
|
|
|
2034
2768
|
// src/app/agent/tools/natives/readLines.ts
|
|
2035
|
-
import { promises as
|
|
2769
|
+
import { promises as fs6 } from "fs";
|
|
2036
2770
|
async function readLines(args) {
|
|
2037
2771
|
const { filepath, start_line, end_line } = args;
|
|
2038
2772
|
try {
|
|
2039
|
-
if (!(await
|
|
2773
|
+
if (!(await fs6.stat(filepath)).isFile()) {
|
|
2040
2774
|
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
2041
2775
|
}
|
|
2042
2776
|
if (start_line < 1 || end_line < start_line) {
|
|
2043
2777
|
throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
|
|
2044
2778
|
}
|
|
2045
|
-
const fileContent = await
|
|
2779
|
+
const fileContent = await fs6.readFile(filepath, "utf-8");
|
|
2046
2780
|
const lines = fileContent.split("\n");
|
|
2047
2781
|
const total_lines = lines.length;
|
|
2048
2782
|
const startIndex = start_line - 1;
|
|
@@ -2070,12 +2804,12 @@ async function readLines(args) {
|
|
|
2070
2804
|
|
|
2071
2805
|
// src/app/agent/tools/natives/count_lines.ts
|
|
2072
2806
|
import { createReadStream } from "fs";
|
|
2073
|
-
import { promises as
|
|
2807
|
+
import { promises as fs7 } from "fs";
|
|
2074
2808
|
import readline from "readline";
|
|
2075
2809
|
async function countLines(args) {
|
|
2076
2810
|
const { filepath } = args;
|
|
2077
2811
|
try {
|
|
2078
|
-
if (!(await
|
|
2812
|
+
if (!(await fs7.stat(filepath)).isFile()) {
|
|
2079
2813
|
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
2080
2814
|
}
|
|
2081
2815
|
const fileStream = createReadStream(filepath);
|
|
@@ -2094,18 +2828,18 @@ async function countLines(args) {
|
|
|
2094
2828
|
}
|
|
2095
2829
|
|
|
2096
2830
|
// src/app/agent/tools/natives/todo.ts
|
|
2097
|
-
import * as
|
|
2098
|
-
import * as
|
|
2831
|
+
import * as fs8 from "fs";
|
|
2832
|
+
import * as path7 from "path";
|
|
2099
2833
|
var taskStore = [];
|
|
2100
2834
|
var nextId = 1;
|
|
2101
2835
|
function getTodoFilePath() {
|
|
2102
|
-
return
|
|
2836
|
+
return path7.join(process.cwd(), ".bluma", "todo.json");
|
|
2103
2837
|
}
|
|
2104
2838
|
function loadTasksFromFile() {
|
|
2105
2839
|
try {
|
|
2106
2840
|
const filePath = getTodoFilePath();
|
|
2107
|
-
if (
|
|
2108
|
-
const data =
|
|
2841
|
+
if (fs8.existsSync(filePath)) {
|
|
2842
|
+
const data = fs8.readFileSync(filePath, "utf-8");
|
|
2109
2843
|
const parsed = JSON.parse(data);
|
|
2110
2844
|
if (Array.isArray(parsed.tasks)) {
|
|
2111
2845
|
taskStore = parsed.tasks;
|
|
@@ -2118,11 +2852,11 @@ function loadTasksFromFile() {
|
|
|
2118
2852
|
function saveTasksToFile() {
|
|
2119
2853
|
try {
|
|
2120
2854
|
const filePath = getTodoFilePath();
|
|
2121
|
-
const dir =
|
|
2122
|
-
if (!
|
|
2123
|
-
|
|
2855
|
+
const dir = path7.dirname(filePath);
|
|
2856
|
+
if (!fs8.existsSync(dir)) {
|
|
2857
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
2124
2858
|
}
|
|
2125
|
-
|
|
2859
|
+
fs8.writeFileSync(filePath, JSON.stringify({
|
|
2126
2860
|
tasks: taskStore,
|
|
2127
2861
|
nextId,
|
|
2128
2862
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2297,7 +3031,7 @@ async function todo(args) {
|
|
|
2297
3031
|
}
|
|
2298
3032
|
|
|
2299
3033
|
// src/app/agent/tools/natives/find_by_name.ts
|
|
2300
|
-
import
|
|
3034
|
+
import path8 from "path";
|
|
2301
3035
|
import { promises as fsPromises } from "fs";
|
|
2302
3036
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
2303
3037
|
"node_modules",
|
|
@@ -2337,7 +3071,7 @@ function shouldIgnore(name, ignorePatterns, includeHidden) {
|
|
|
2337
3071
|
}
|
|
2338
3072
|
function matchesExtensions(filename, extensions) {
|
|
2339
3073
|
if (!extensions || extensions.length === 0) return true;
|
|
2340
|
-
const ext =
|
|
3074
|
+
const ext = path8.extname(filename).toLowerCase();
|
|
2341
3075
|
return extensions.some((e) => {
|
|
2342
3076
|
const normalizedExt = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
|
|
2343
3077
|
return ext === normalizedExt;
|
|
@@ -2359,8 +3093,8 @@ async function searchDirectory(dir, pattern, baseDir, options, results) {
|
|
|
2359
3093
|
if (shouldIgnore(name, options.ignorePatterns, options.includeHidden)) {
|
|
2360
3094
|
continue;
|
|
2361
3095
|
}
|
|
2362
|
-
const fullPath =
|
|
2363
|
-
const relativePath =
|
|
3096
|
+
const fullPath = path8.join(dir, name);
|
|
3097
|
+
const relativePath = path8.relative(baseDir, fullPath);
|
|
2364
3098
|
if (entry.isDirectory()) {
|
|
2365
3099
|
if (pattern.test(name)) {
|
|
2366
3100
|
results.push({
|
|
@@ -2416,7 +3150,7 @@ async function findByName(args) {
|
|
|
2416
3150
|
error: "Pattern is required and must be a string"
|
|
2417
3151
|
};
|
|
2418
3152
|
}
|
|
2419
|
-
const resolvedDir =
|
|
3153
|
+
const resolvedDir = path8.resolve(directory);
|
|
2420
3154
|
try {
|
|
2421
3155
|
const stats = await fsPromises.stat(resolvedDir);
|
|
2422
3156
|
if (!stats.isDirectory()) {
|
|
@@ -2477,7 +3211,7 @@ async function findByName(args) {
|
|
|
2477
3211
|
}
|
|
2478
3212
|
|
|
2479
3213
|
// src/app/agent/tools/natives/grep_search.ts
|
|
2480
|
-
import
|
|
3214
|
+
import path9 from "path";
|
|
2481
3215
|
import { promises as fsPromises2 } from "fs";
|
|
2482
3216
|
var MAX_RESULTS3 = 100;
|
|
2483
3217
|
var MAX_FILE_SIZE2 = 1024 * 1024;
|
|
@@ -2560,8 +3294,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2560
3294
|
"Rakefile"
|
|
2561
3295
|
]);
|
|
2562
3296
|
function isTextFile(filepath) {
|
|
2563
|
-
const ext =
|
|
2564
|
-
const basename =
|
|
3297
|
+
const ext = path9.extname(filepath).toLowerCase();
|
|
3298
|
+
const basename = path9.basename(filepath);
|
|
2565
3299
|
if (TEXT_EXTENSIONS.has(ext)) return true;
|
|
2566
3300
|
if (TEXT_EXTENSIONS.has(basename)) return true;
|
|
2567
3301
|
if (basename.startsWith(".") && !ext) return true;
|
|
@@ -2606,7 +3340,7 @@ async function searchFile(filepath, baseDir, pattern, contextLines, matches, max
|
|
|
2606
3340
|
if (stats.size > MAX_FILE_SIZE2) return 0;
|
|
2607
3341
|
const content = await fsPromises2.readFile(filepath, "utf-8");
|
|
2608
3342
|
const lines = content.split("\n");
|
|
2609
|
-
const relativePath =
|
|
3343
|
+
const relativePath = path9.relative(baseDir, filepath);
|
|
2610
3344
|
for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
|
|
2611
3345
|
const line = lines[i];
|
|
2612
3346
|
pattern.lastIndex = 0;
|
|
@@ -2646,7 +3380,7 @@ async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextL
|
|
|
2646
3380
|
if (matches.length >= maxResults) break;
|
|
2647
3381
|
const name = entry.name;
|
|
2648
3382
|
if (shouldIgnore2(name)) continue;
|
|
2649
|
-
const fullPath =
|
|
3383
|
+
const fullPath = path9.join(dir, name);
|
|
2650
3384
|
if (entry.isDirectory()) {
|
|
2651
3385
|
await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats);
|
|
2652
3386
|
} else if (entry.isFile()) {
|
|
@@ -2695,7 +3429,7 @@ async function grepSearch(args) {
|
|
|
2695
3429
|
error: "Search path is required"
|
|
2696
3430
|
};
|
|
2697
3431
|
}
|
|
2698
|
-
const resolvedPath =
|
|
3432
|
+
const resolvedPath = path9.resolve(searchPath);
|
|
2699
3433
|
let stats;
|
|
2700
3434
|
try {
|
|
2701
3435
|
stats = await fsPromises2.stat(resolvedPath);
|
|
@@ -2743,7 +3477,7 @@ async function grepSearch(args) {
|
|
|
2743
3477
|
);
|
|
2744
3478
|
} else if (stats.isFile()) {
|
|
2745
3479
|
searchStats.filesSearched = 1;
|
|
2746
|
-
const found = await searchFile(resolvedPath,
|
|
3480
|
+
const found = await searchFile(resolvedPath, path9.dirname(resolvedPath), pattern, context_lines, matches, max_results);
|
|
2747
3481
|
if (found > 0) searchStats.filesWithMatches = 1;
|
|
2748
3482
|
}
|
|
2749
3483
|
return {
|
|
@@ -2772,7 +3506,7 @@ async function grepSearch(args) {
|
|
|
2772
3506
|
}
|
|
2773
3507
|
|
|
2774
3508
|
// src/app/agent/tools/natives/view_file_outline.ts
|
|
2775
|
-
import
|
|
3509
|
+
import path10 from "path";
|
|
2776
3510
|
import { promises as fsPromises3 } from "fs";
|
|
2777
3511
|
var LANGUAGE_MAP = {
|
|
2778
3512
|
".ts": "typescript",
|
|
@@ -2886,7 +3620,7 @@ var PATTERNS = {
|
|
|
2886
3620
|
]
|
|
2887
3621
|
};
|
|
2888
3622
|
function detectLanguage(filepath) {
|
|
2889
|
-
const ext =
|
|
3623
|
+
const ext = path10.extname(filepath).toLowerCase();
|
|
2890
3624
|
return LANGUAGE_MAP[ext] || "unknown";
|
|
2891
3625
|
}
|
|
2892
3626
|
function determineItemType(line, language) {
|
|
@@ -2982,7 +3716,7 @@ async function viewFileOutline(args) {
|
|
|
2982
3716
|
error: "file_path is required and must be a string"
|
|
2983
3717
|
};
|
|
2984
3718
|
}
|
|
2985
|
-
const resolvedPath =
|
|
3719
|
+
const resolvedPath = path10.resolve(file_path);
|
|
2986
3720
|
let content;
|
|
2987
3721
|
try {
|
|
2988
3722
|
content = await fsPromises3.readFile(resolvedPath, "utf-8");
|
|
@@ -3026,23 +3760,23 @@ async function viewFileOutline(args) {
|
|
|
3026
3760
|
init_async_command();
|
|
3027
3761
|
|
|
3028
3762
|
// src/app/agent/tools/natives/task_boundary.ts
|
|
3029
|
-
import
|
|
3030
|
-
import { promises as
|
|
3031
|
-
import
|
|
3763
|
+
import path11 from "path";
|
|
3764
|
+
import { promises as fs9 } from "fs";
|
|
3765
|
+
import os6 from "os";
|
|
3032
3766
|
var currentTask = null;
|
|
3033
3767
|
var artifactsDir = null;
|
|
3034
3768
|
async function getArtifactsDir() {
|
|
3035
3769
|
if (artifactsDir) return artifactsDir;
|
|
3036
|
-
const homeDir =
|
|
3037
|
-
const baseDir =
|
|
3770
|
+
const homeDir = os6.homedir();
|
|
3771
|
+
const baseDir = path11.join(homeDir, ".bluma", "artifacts");
|
|
3038
3772
|
const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
|
3039
|
-
artifactsDir =
|
|
3040
|
-
await
|
|
3773
|
+
artifactsDir = path11.join(baseDir, sessionId);
|
|
3774
|
+
await fs9.mkdir(artifactsDir, { recursive: true });
|
|
3041
3775
|
return artifactsDir;
|
|
3042
3776
|
}
|
|
3043
3777
|
async function updateTaskFile(task) {
|
|
3044
3778
|
const dir = await getArtifactsDir();
|
|
3045
|
-
const taskFile =
|
|
3779
|
+
const taskFile = path11.join(dir, "task.md");
|
|
3046
3780
|
const content = `# ${task.taskName}
|
|
3047
3781
|
|
|
3048
3782
|
**Mode:** ${task.mode}
|
|
@@ -3054,7 +3788,7 @@ async function updateTaskFile(task) {
|
|
|
3054
3788
|
## Summary
|
|
3055
3789
|
${task.summary}
|
|
3056
3790
|
`;
|
|
3057
|
-
await
|
|
3791
|
+
await fs9.writeFile(taskFile, content, "utf-8");
|
|
3058
3792
|
}
|
|
3059
3793
|
async function taskBoundary(args) {
|
|
3060
3794
|
try {
|
|
@@ -3134,8 +3868,8 @@ async function createArtifact(args) {
|
|
|
3134
3868
|
return { success: false, error: "content is required" };
|
|
3135
3869
|
}
|
|
3136
3870
|
const dir = await getArtifactsDir();
|
|
3137
|
-
const filepath =
|
|
3138
|
-
await
|
|
3871
|
+
const filepath = path11.join(dir, filename);
|
|
3872
|
+
await fs9.writeFile(filepath, content, "utf-8");
|
|
3139
3873
|
return {
|
|
3140
3874
|
success: true,
|
|
3141
3875
|
path: filepath
|
|
@@ -3154,8 +3888,8 @@ async function readArtifact(args) {
|
|
|
3154
3888
|
return { success: false, error: "filename is required" };
|
|
3155
3889
|
}
|
|
3156
3890
|
const dir = await getArtifactsDir();
|
|
3157
|
-
const filepath =
|
|
3158
|
-
const content = await
|
|
3891
|
+
const filepath = path11.join(dir, filename);
|
|
3892
|
+
const content = await fs9.readFile(filepath, "utf-8");
|
|
3159
3893
|
return {
|
|
3160
3894
|
success: true,
|
|
3161
3895
|
content
|
|
@@ -3524,9 +4258,9 @@ ${skill.content}`;
|
|
|
3524
4258
|
}
|
|
3525
4259
|
|
|
3526
4260
|
// src/app/agent/tools/natives/coding_memory.ts
|
|
3527
|
-
import * as
|
|
3528
|
-
import * as
|
|
3529
|
-
import
|
|
4261
|
+
import * as fs10 from "fs";
|
|
4262
|
+
import * as path12 from "path";
|
|
4263
|
+
import os7 from "os";
|
|
3530
4264
|
var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
|
|
3531
4265
|
var PROMPT_DEFAULT_MAX_NOTES = 25;
|
|
3532
4266
|
var PROMPT_DEFAULT_PREVIEW = 500;
|
|
@@ -3534,14 +4268,14 @@ function readCodingMemoryForPrompt(options) {
|
|
|
3534
4268
|
const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
|
|
3535
4269
|
const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
|
|
3536
4270
|
const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
|
|
3537
|
-
const globalPath =
|
|
3538
|
-
const legacyPath =
|
|
4271
|
+
const globalPath = path12.join(os7.homedir(), ".bluma", "coding_memory.json");
|
|
4272
|
+
const legacyPath = path12.join(process.cwd(), ".bluma", "coding_memory.json");
|
|
3539
4273
|
let raw = null;
|
|
3540
4274
|
try {
|
|
3541
|
-
if (
|
|
3542
|
-
raw =
|
|
3543
|
-
} else if (
|
|
3544
|
-
raw =
|
|
4275
|
+
if (fs10.existsSync(globalPath)) {
|
|
4276
|
+
raw = fs10.readFileSync(globalPath, "utf-8");
|
|
4277
|
+
} else if (path12.resolve(globalPath) !== path12.resolve(legacyPath) && fs10.existsSync(legacyPath)) {
|
|
4278
|
+
raw = fs10.readFileSync(legacyPath, "utf-8");
|
|
3545
4279
|
}
|
|
3546
4280
|
} catch {
|
|
3547
4281
|
return "";
|
|
@@ -3579,10 +4313,10 @@ var memoryStore = [];
|
|
|
3579
4313
|
var nextId2 = 1;
|
|
3580
4314
|
var loaded = false;
|
|
3581
4315
|
function getMemoryFilePath() {
|
|
3582
|
-
return
|
|
4316
|
+
return path12.join(os7.homedir(), ".bluma", "coding_memory.json");
|
|
3583
4317
|
}
|
|
3584
4318
|
function getLegacyMemoryFilePath() {
|
|
3585
|
-
return
|
|
4319
|
+
return path12.join(process.cwd(), ".bluma", "coding_memory.json");
|
|
3586
4320
|
}
|
|
3587
4321
|
function loadMemoryFromFile() {
|
|
3588
4322
|
if (loaded) return;
|
|
@@ -3592,19 +4326,19 @@ function loadMemoryFromFile() {
|
|
|
3592
4326
|
try {
|
|
3593
4327
|
const filePath = getMemoryFilePath();
|
|
3594
4328
|
const legacy = getLegacyMemoryFilePath();
|
|
3595
|
-
const legacyDistinct =
|
|
4329
|
+
const legacyDistinct = path12.resolve(legacy) !== path12.resolve(filePath);
|
|
3596
4330
|
const readIntoStore = (p) => {
|
|
3597
|
-
const raw =
|
|
4331
|
+
const raw = fs10.readFileSync(p, "utf-8");
|
|
3598
4332
|
const parsed = JSON.parse(raw);
|
|
3599
4333
|
if (Array.isArray(parsed.entries)) {
|
|
3600
4334
|
memoryStore = parsed.entries;
|
|
3601
4335
|
nextId2 = typeof parsed.nextId === "number" ? parsed.nextId : memoryStore.length + 1;
|
|
3602
4336
|
}
|
|
3603
4337
|
};
|
|
3604
|
-
if (
|
|
4338
|
+
if (fs10.existsSync(filePath)) {
|
|
3605
4339
|
readIntoStore(filePath);
|
|
3606
4340
|
}
|
|
3607
|
-
if (memoryStore.length === 0 && legacyDistinct &&
|
|
4341
|
+
if (memoryStore.length === 0 && legacyDistinct && fs10.existsSync(legacy)) {
|
|
3608
4342
|
readIntoStore(legacy);
|
|
3609
4343
|
if (memoryStore.length > 0) {
|
|
3610
4344
|
saveMemoryToFile();
|
|
@@ -3618,16 +4352,16 @@ function loadMemoryFromFile() {
|
|
|
3618
4352
|
function saveMemoryToFile() {
|
|
3619
4353
|
try {
|
|
3620
4354
|
const filePath = getMemoryFilePath();
|
|
3621
|
-
const dir =
|
|
3622
|
-
if (!
|
|
3623
|
-
|
|
4355
|
+
const dir = path12.dirname(filePath);
|
|
4356
|
+
if (!fs10.existsSync(dir)) {
|
|
4357
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
3624
4358
|
}
|
|
3625
4359
|
const payload = {
|
|
3626
4360
|
entries: memoryStore,
|
|
3627
4361
|
nextId: nextId2,
|
|
3628
4362
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3629
4363
|
};
|
|
3630
|
-
|
|
4364
|
+
fs10.writeFileSync(filePath, JSON.stringify(payload, null, 2));
|
|
3631
4365
|
} catch {
|
|
3632
4366
|
}
|
|
3633
4367
|
}
|
|
@@ -3824,9 +4558,9 @@ var ToolInvoker = class {
|
|
|
3824
4558
|
async initialize() {
|
|
3825
4559
|
try {
|
|
3826
4560
|
const __filename = fileURLToPath(import.meta.url);
|
|
3827
|
-
const __dirname2 =
|
|
3828
|
-
const configPath =
|
|
3829
|
-
const fileContent = await
|
|
4561
|
+
const __dirname2 = path13.dirname(__filename);
|
|
4562
|
+
const configPath = path13.resolve(__dirname2, "config", "native_tools.json");
|
|
4563
|
+
const fileContent = await fs11.readFile(configPath, "utf-8");
|
|
3830
4564
|
const config2 = JSON.parse(fileContent);
|
|
3831
4565
|
this.toolDefinitions = config2.nativeTools;
|
|
3832
4566
|
} catch (error) {
|
|
@@ -3887,9 +4621,9 @@ var ToolInvoker = class {
|
|
|
3887
4621
|
};
|
|
3888
4622
|
|
|
3889
4623
|
// src/app/agent/tools/mcp/mcp_client.ts
|
|
3890
|
-
import { promises as
|
|
3891
|
-
import
|
|
3892
|
-
import
|
|
4624
|
+
import { promises as fs12 } from "fs";
|
|
4625
|
+
import path14 from "path";
|
|
4626
|
+
import os8 from "os";
|
|
3893
4627
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3894
4628
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3895
4629
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -3916,9 +4650,9 @@ var MCPClient = class {
|
|
|
3916
4650
|
});
|
|
3917
4651
|
}
|
|
3918
4652
|
const __filename = fileURLToPath2(import.meta.url);
|
|
3919
|
-
const __dirname2 =
|
|
3920
|
-
const defaultConfigPath =
|
|
3921
|
-
const userConfigPath =
|
|
4653
|
+
const __dirname2 = path14.dirname(__filename);
|
|
4654
|
+
const defaultConfigPath = path14.resolve(__dirname2, "config", "bluma-mcp.json");
|
|
4655
|
+
const userConfigPath = path14.join(os8.homedir(), ".bluma", "bluma-mcp.json");
|
|
3922
4656
|
const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
|
|
3923
4657
|
const userConfig = await this.loadMcpConfig(userConfigPath, "User");
|
|
3924
4658
|
const mergedConfig = {
|
|
@@ -3952,7 +4686,7 @@ var MCPClient = class {
|
|
|
3952
4686
|
}
|
|
3953
4687
|
async loadMcpConfig(configPath, configType) {
|
|
3954
4688
|
try {
|
|
3955
|
-
const fileContent = await
|
|
4689
|
+
const fileContent = await fs12.readFile(configPath, "utf-8");
|
|
3956
4690
|
const processedContent = this.replaceEnvPlaceholders(fileContent);
|
|
3957
4691
|
return JSON.parse(processedContent);
|
|
3958
4692
|
} catch (error) {
|
|
@@ -3971,7 +4705,7 @@ var MCPClient = class {
|
|
|
3971
4705
|
async connectToStdioServer(serverName, config2) {
|
|
3972
4706
|
let commandToExecute = config2.command;
|
|
3973
4707
|
let argsToExecute = config2.args || [];
|
|
3974
|
-
const isWindows =
|
|
4708
|
+
const isWindows = os8.platform() === "win32";
|
|
3975
4709
|
if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
|
|
3976
4710
|
if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
|
|
3977
4711
|
commandToExecute = argsToExecute[1];
|
|
@@ -4087,13 +4821,13 @@ var AdvancedFeedbackSystem = class {
|
|
|
4087
4821
|
};
|
|
4088
4822
|
|
|
4089
4823
|
// src/app/agent/bluma/core/bluma.ts
|
|
4090
|
-
import
|
|
4824
|
+
import path18 from "path";
|
|
4091
4825
|
import { v4 as uuidv43 } from "uuid";
|
|
4092
4826
|
|
|
4093
4827
|
// src/app/agent/session_manager/session_manager.ts
|
|
4094
|
-
import
|
|
4095
|
-
import
|
|
4096
|
-
import { promises as
|
|
4828
|
+
import path15 from "path";
|
|
4829
|
+
import os9 from "os";
|
|
4830
|
+
import { promises as fs13 } from "fs";
|
|
4097
4831
|
var fileLocks = /* @__PURE__ */ new Map();
|
|
4098
4832
|
async function withFileLock(file, fn) {
|
|
4099
4833
|
const prev = fileLocks.get(file) || Promise.resolve();
|
|
@@ -4129,13 +4863,13 @@ function debouncedSave(sessionFile, history, memory) {
|
|
|
4129
4863
|
function expandHome(p) {
|
|
4130
4864
|
if (!p) return p;
|
|
4131
4865
|
if (p.startsWith("~")) {
|
|
4132
|
-
return
|
|
4866
|
+
return path15.join(os9.homedir(), p.slice(1));
|
|
4133
4867
|
}
|
|
4134
4868
|
return p;
|
|
4135
4869
|
}
|
|
4136
4870
|
function getPreferredAppDir() {
|
|
4137
|
-
const fixed =
|
|
4138
|
-
return
|
|
4871
|
+
const fixed = path15.join(os9.homedir(), ".bluma");
|
|
4872
|
+
return path15.resolve(expandHome(fixed));
|
|
4139
4873
|
}
|
|
4140
4874
|
async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
4141
4875
|
let attempt = 0;
|
|
@@ -4143,10 +4877,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
4143
4877
|
const isWin = process.platform === "win32";
|
|
4144
4878
|
while (attempt <= maxRetries) {
|
|
4145
4879
|
try {
|
|
4146
|
-
const dir =
|
|
4147
|
-
await
|
|
4880
|
+
const dir = path15.dirname(dest);
|
|
4881
|
+
await fs13.mkdir(dir, { recursive: true }).catch(() => {
|
|
4148
4882
|
});
|
|
4149
|
-
await
|
|
4883
|
+
await fs13.rename(src, dest);
|
|
4150
4884
|
return;
|
|
4151
4885
|
} catch (e) {
|
|
4152
4886
|
lastErr = e;
|
|
@@ -4159,13 +4893,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
4159
4893
|
}
|
|
4160
4894
|
}
|
|
4161
4895
|
try {
|
|
4162
|
-
await
|
|
4163
|
-
const data = await
|
|
4164
|
-
const dir =
|
|
4165
|
-
await
|
|
4896
|
+
await fs13.access(src);
|
|
4897
|
+
const data = await fs13.readFile(src);
|
|
4898
|
+
const dir = path15.dirname(dest);
|
|
4899
|
+
await fs13.mkdir(dir, { recursive: true }).catch(() => {
|
|
4166
4900
|
});
|
|
4167
|
-
await
|
|
4168
|
-
await
|
|
4901
|
+
await fs13.writeFile(dest, data);
|
|
4902
|
+
await fs13.unlink(src).catch(() => {
|
|
4169
4903
|
});
|
|
4170
4904
|
return;
|
|
4171
4905
|
} catch (fallbackErr) {
|
|
@@ -4178,16 +4912,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
4178
4912
|
}
|
|
4179
4913
|
async function ensureSessionDir() {
|
|
4180
4914
|
const appDir = getPreferredAppDir();
|
|
4181
|
-
const sessionDir =
|
|
4182
|
-
await
|
|
4915
|
+
const sessionDir = path15.join(appDir, "sessions");
|
|
4916
|
+
await fs13.mkdir(sessionDir, { recursive: true });
|
|
4183
4917
|
return sessionDir;
|
|
4184
4918
|
}
|
|
4185
4919
|
async function loadOrcreateSession(sessionId) {
|
|
4186
4920
|
const sessionDir = await ensureSessionDir();
|
|
4187
|
-
const sessionFile =
|
|
4921
|
+
const sessionFile = path15.join(sessionDir, `${sessionId}.json`);
|
|
4188
4922
|
try {
|
|
4189
|
-
await
|
|
4190
|
-
const fileContent = await
|
|
4923
|
+
await fs13.access(sessionFile);
|
|
4924
|
+
const fileContent = await fs13.readFile(sessionFile, "utf-8");
|
|
4191
4925
|
const sessionData = JSON.parse(fileContent);
|
|
4192
4926
|
const memory = {
|
|
4193
4927
|
historyAnchor: sessionData.history_anchor ?? null,
|
|
@@ -4200,7 +4934,7 @@ async function loadOrcreateSession(sessionId) {
|
|
|
4200
4934
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4201
4935
|
conversation_history: []
|
|
4202
4936
|
};
|
|
4203
|
-
await
|
|
4937
|
+
await fs13.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
|
|
4204
4938
|
const emptyMemory = {
|
|
4205
4939
|
historyAnchor: null,
|
|
4206
4940
|
compressedTurnSliceCount: 0
|
|
@@ -4212,12 +4946,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
4212
4946
|
await withFileLock(sessionFile, async () => {
|
|
4213
4947
|
let sessionData;
|
|
4214
4948
|
try {
|
|
4215
|
-
const dir =
|
|
4216
|
-
await
|
|
4949
|
+
const dir = path15.dirname(sessionFile);
|
|
4950
|
+
await fs13.mkdir(dir, { recursive: true });
|
|
4217
4951
|
} catch {
|
|
4218
4952
|
}
|
|
4219
4953
|
try {
|
|
4220
|
-
const fileContent = await
|
|
4954
|
+
const fileContent = await fs13.readFile(sessionFile, "utf-8");
|
|
4221
4955
|
sessionData = JSON.parse(fileContent);
|
|
4222
4956
|
} catch (error) {
|
|
4223
4957
|
const code = error && error.code;
|
|
@@ -4228,14 +4962,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
4228
4962
|
console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
|
|
4229
4963
|
}
|
|
4230
4964
|
}
|
|
4231
|
-
const sessionId =
|
|
4965
|
+
const sessionId = path15.basename(sessionFile, ".json");
|
|
4232
4966
|
sessionData = {
|
|
4233
4967
|
session_id: sessionId,
|
|
4234
4968
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4235
4969
|
conversation_history: []
|
|
4236
4970
|
};
|
|
4237
4971
|
try {
|
|
4238
|
-
await
|
|
4972
|
+
await fs13.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
4239
4973
|
} catch {
|
|
4240
4974
|
}
|
|
4241
4975
|
}
|
|
@@ -4251,7 +4985,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
4251
4985
|
}
|
|
4252
4986
|
const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
|
|
4253
4987
|
try {
|
|
4254
|
-
await
|
|
4988
|
+
await fs13.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
4255
4989
|
await safeRenameWithRetry(tempSessionFile, sessionFile);
|
|
4256
4990
|
} catch (writeError) {
|
|
4257
4991
|
if (writeError instanceof Error) {
|
|
@@ -4260,7 +4994,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
4260
4994
|
console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
|
|
4261
4995
|
}
|
|
4262
4996
|
try {
|
|
4263
|
-
await
|
|
4997
|
+
await fs13.unlink(tempSessionFile);
|
|
4264
4998
|
} catch {
|
|
4265
4999
|
}
|
|
4266
5000
|
}
|
|
@@ -4277,15 +5011,15 @@ async function saveSessionHistory(sessionFile, history, memory) {
|
|
|
4277
5011
|
}
|
|
4278
5012
|
|
|
4279
5013
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
4280
|
-
import
|
|
4281
|
-
import
|
|
4282
|
-
import
|
|
5014
|
+
import os11 from "os";
|
|
5015
|
+
import fs15 from "fs";
|
|
5016
|
+
import path17 from "path";
|
|
4283
5017
|
import { execSync } from "child_process";
|
|
4284
5018
|
|
|
4285
5019
|
// src/app/agent/skills/skill_loader.ts
|
|
4286
|
-
import
|
|
4287
|
-
import
|
|
4288
|
-
import
|
|
5020
|
+
import fs14 from "fs";
|
|
5021
|
+
import path16 from "path";
|
|
5022
|
+
import os10 from "os";
|
|
4289
5023
|
var SkillLoader = class _SkillLoader {
|
|
4290
5024
|
bundledSkillsDir;
|
|
4291
5025
|
projectSkillsDir;
|
|
@@ -4293,8 +5027,8 @@ var SkillLoader = class _SkillLoader {
|
|
|
4293
5027
|
cache = /* @__PURE__ */ new Map();
|
|
4294
5028
|
conflicts = [];
|
|
4295
5029
|
constructor(projectRoot, bundledDir) {
|
|
4296
|
-
this.projectSkillsDir =
|
|
4297
|
-
this.globalSkillsDir =
|
|
5030
|
+
this.projectSkillsDir = path16.join(projectRoot, ".bluma", "skills");
|
|
5031
|
+
this.globalSkillsDir = path16.join(os10.homedir(), ".bluma", "skills");
|
|
4298
5032
|
this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
|
|
4299
5033
|
}
|
|
4300
5034
|
/**
|
|
@@ -4303,34 +5037,34 @@ var SkillLoader = class _SkillLoader {
|
|
|
4303
5037
|
*/
|
|
4304
5038
|
static resolveBundledDir() {
|
|
4305
5039
|
if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
|
|
4306
|
-
return
|
|
5040
|
+
return path16.join(process.cwd(), "dist", "config", "skills");
|
|
4307
5041
|
}
|
|
4308
5042
|
const candidates = [];
|
|
4309
5043
|
const argv1 = process.argv[1];
|
|
4310
5044
|
if (argv1 && !argv1.startsWith("-")) {
|
|
4311
5045
|
try {
|
|
4312
|
-
const scriptDir =
|
|
4313
|
-
candidates.push(
|
|
5046
|
+
const scriptDir = path16.dirname(path16.resolve(argv1));
|
|
5047
|
+
candidates.push(path16.join(scriptDir, "config", "skills"));
|
|
4314
5048
|
} catch {
|
|
4315
5049
|
}
|
|
4316
5050
|
}
|
|
4317
5051
|
candidates.push(
|
|
4318
|
-
|
|
4319
|
-
|
|
5052
|
+
path16.join(process.cwd(), "dist", "config", "skills"),
|
|
5053
|
+
path16.join(process.cwd(), "node_modules", "@nomad-e", "bluma-cli", "dist", "config", "skills")
|
|
4320
5054
|
);
|
|
4321
5055
|
if (typeof __dirname !== "undefined") {
|
|
4322
5056
|
candidates.push(
|
|
4323
|
-
|
|
4324
|
-
|
|
5057
|
+
path16.join(__dirname, "config", "skills"),
|
|
5058
|
+
path16.join(__dirname, "..", "..", "..", "config", "skills")
|
|
4325
5059
|
);
|
|
4326
5060
|
}
|
|
4327
5061
|
for (const c of candidates) {
|
|
4328
|
-
const abs =
|
|
4329
|
-
if (
|
|
5062
|
+
const abs = path16.resolve(c);
|
|
5063
|
+
if (fs14.existsSync(abs)) {
|
|
4330
5064
|
return abs;
|
|
4331
5065
|
}
|
|
4332
5066
|
}
|
|
4333
|
-
return
|
|
5067
|
+
return path16.join(process.cwd(), "dist", "config", "skills");
|
|
4334
5068
|
}
|
|
4335
5069
|
/**
|
|
4336
5070
|
* Lista skills disponíveis de todas as fontes.
|
|
@@ -4358,8 +5092,8 @@ var SkillLoader = class _SkillLoader {
|
|
|
4358
5092
|
this.conflicts.push({
|
|
4359
5093
|
name: skill.name,
|
|
4360
5094
|
userSource: source,
|
|
4361
|
-
userPath:
|
|
4362
|
-
bundledPath:
|
|
5095
|
+
userPath: path16.join(dir, skill.name, "SKILL.md"),
|
|
5096
|
+
bundledPath: path16.join(this.bundledSkillsDir, skill.name, "SKILL.md")
|
|
4363
5097
|
});
|
|
4364
5098
|
continue;
|
|
4365
5099
|
}
|
|
@@ -4367,20 +5101,20 @@ var SkillLoader = class _SkillLoader {
|
|
|
4367
5101
|
}
|
|
4368
5102
|
}
|
|
4369
5103
|
listFromDir(dir, source) {
|
|
4370
|
-
if (!
|
|
5104
|
+
if (!fs14.existsSync(dir)) return [];
|
|
4371
5105
|
try {
|
|
4372
|
-
return
|
|
4373
|
-
const fullPath =
|
|
4374
|
-
return
|
|
4375
|
-
}).map((d) => this.loadMetadataFromPath(
|
|
5106
|
+
return fs14.readdirSync(dir).filter((d) => {
|
|
5107
|
+
const fullPath = path16.join(dir, d);
|
|
5108
|
+
return fs14.statSync(fullPath).isDirectory() && fs14.existsSync(path16.join(fullPath, "SKILL.md"));
|
|
5109
|
+
}).map((d) => this.loadMetadataFromPath(path16.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
|
|
4376
5110
|
} catch {
|
|
4377
5111
|
return [];
|
|
4378
5112
|
}
|
|
4379
5113
|
}
|
|
4380
5114
|
loadMetadataFromPath(skillPath, skillName, source) {
|
|
4381
|
-
if (!
|
|
5115
|
+
if (!fs14.existsSync(skillPath)) return null;
|
|
4382
5116
|
try {
|
|
4383
|
-
const raw =
|
|
5117
|
+
const raw = fs14.readFileSync(skillPath, "utf-8");
|
|
4384
5118
|
const parsed = this.parseFrontmatter(raw);
|
|
4385
5119
|
return {
|
|
4386
5120
|
name: parsed.name || skillName,
|
|
@@ -4402,12 +5136,12 @@ var SkillLoader = class _SkillLoader {
|
|
|
4402
5136
|
*/
|
|
4403
5137
|
load(name) {
|
|
4404
5138
|
if (this.cache.has(name)) return this.cache.get(name);
|
|
4405
|
-
const bundledPath =
|
|
4406
|
-
const projectPath =
|
|
4407
|
-
const globalPath =
|
|
4408
|
-
const existsBundled =
|
|
4409
|
-
const existsProject =
|
|
4410
|
-
const existsGlobal =
|
|
5139
|
+
const bundledPath = path16.join(this.bundledSkillsDir, name, "SKILL.md");
|
|
5140
|
+
const projectPath = path16.join(this.projectSkillsDir, name, "SKILL.md");
|
|
5141
|
+
const globalPath = path16.join(this.globalSkillsDir, name, "SKILL.md");
|
|
5142
|
+
const existsBundled = fs14.existsSync(bundledPath);
|
|
5143
|
+
const existsProject = fs14.existsSync(projectPath);
|
|
5144
|
+
const existsGlobal = fs14.existsSync(globalPath);
|
|
4411
5145
|
if (existsBundled && (existsProject || existsGlobal)) {
|
|
4412
5146
|
const conflictSource = existsProject ? "project" : "global";
|
|
4413
5147
|
const conflictPath = existsProject ? projectPath : globalPath;
|
|
@@ -4446,9 +5180,9 @@ var SkillLoader = class _SkillLoader {
|
|
|
4446
5180
|
}
|
|
4447
5181
|
loadFromPath(skillPath, name, source) {
|
|
4448
5182
|
try {
|
|
4449
|
-
const raw =
|
|
5183
|
+
const raw = fs14.readFileSync(skillPath, "utf-8");
|
|
4450
5184
|
const parsed = this.parseFrontmatter(raw);
|
|
4451
|
-
const skillDir =
|
|
5185
|
+
const skillDir = path16.dirname(skillPath);
|
|
4452
5186
|
return {
|
|
4453
5187
|
name: parsed.name || name,
|
|
4454
5188
|
description: parsed.description || "",
|
|
@@ -4457,22 +5191,22 @@ var SkillLoader = class _SkillLoader {
|
|
|
4457
5191
|
version: parsed.version,
|
|
4458
5192
|
author: parsed.author,
|
|
4459
5193
|
license: parsed.license,
|
|
4460
|
-
references: this.scanAssets(
|
|
4461
|
-
scripts: this.scanAssets(
|
|
5194
|
+
references: this.scanAssets(path16.join(skillDir, "references")),
|
|
5195
|
+
scripts: this.scanAssets(path16.join(skillDir, "scripts"))
|
|
4462
5196
|
};
|
|
4463
5197
|
} catch {
|
|
4464
5198
|
return null;
|
|
4465
5199
|
}
|
|
4466
5200
|
}
|
|
4467
5201
|
scanAssets(dir) {
|
|
4468
|
-
if (!
|
|
5202
|
+
if (!fs14.existsSync(dir)) return [];
|
|
4469
5203
|
try {
|
|
4470
|
-
return
|
|
4471
|
-
const fp =
|
|
4472
|
-
return
|
|
5204
|
+
return fs14.readdirSync(dir).filter((f) => {
|
|
5205
|
+
const fp = path16.join(dir, f);
|
|
5206
|
+
return fs14.statSync(fp).isFile();
|
|
4473
5207
|
}).map((f) => ({
|
|
4474
5208
|
name: f,
|
|
4475
|
-
path:
|
|
5209
|
+
path: path16.resolve(dir, f)
|
|
4476
5210
|
}));
|
|
4477
5211
|
} catch {
|
|
4478
5212
|
return [];
|
|
@@ -4529,10 +5263,10 @@ var SkillLoader = class _SkillLoader {
|
|
|
4529
5263
|
this.cache.clear();
|
|
4530
5264
|
}
|
|
4531
5265
|
exists(name) {
|
|
4532
|
-
const bundledPath =
|
|
4533
|
-
const projectPath =
|
|
4534
|
-
const globalPath =
|
|
4535
|
-
return
|
|
5266
|
+
const bundledPath = path16.join(this.bundledSkillsDir, name, "SKILL.md");
|
|
5267
|
+
const projectPath = path16.join(this.projectSkillsDir, name, "SKILL.md");
|
|
5268
|
+
const globalPath = path16.join(this.globalSkillsDir, name, "SKILL.md");
|
|
5269
|
+
return fs14.existsSync(bundledPath) || fs14.existsSync(projectPath) || fs14.existsSync(globalPath);
|
|
4536
5270
|
}
|
|
4537
5271
|
/**
|
|
4538
5272
|
* Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
|
|
@@ -4591,10 +5325,10 @@ function getGitBranch(dir) {
|
|
|
4591
5325
|
}
|
|
4592
5326
|
function getPackageManager(dir) {
|
|
4593
5327
|
try {
|
|
4594
|
-
if (
|
|
4595
|
-
if (
|
|
4596
|
-
if (
|
|
4597
|
-
if (
|
|
5328
|
+
if (fs15.existsSync(path17.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
5329
|
+
if (fs15.existsSync(path17.join(dir, "yarn.lock"))) return "yarn";
|
|
5330
|
+
if (fs15.existsSync(path17.join(dir, "bun.lockb"))) return "bun";
|
|
5331
|
+
if (fs15.existsSync(path17.join(dir, "package-lock.json"))) return "npm";
|
|
4598
5332
|
return "unknown";
|
|
4599
5333
|
} catch {
|
|
4600
5334
|
return "unknown";
|
|
@@ -4602,9 +5336,9 @@ function getPackageManager(dir) {
|
|
|
4602
5336
|
}
|
|
4603
5337
|
function getProjectType(dir) {
|
|
4604
5338
|
try {
|
|
4605
|
-
const files =
|
|
5339
|
+
const files = fs15.readdirSync(dir);
|
|
4606
5340
|
if (files.includes("package.json")) {
|
|
4607
|
-
const pkg = JSON.parse(
|
|
5341
|
+
const pkg = JSON.parse(fs15.readFileSync(path17.join(dir, "package.json"), "utf-8"));
|
|
4608
5342
|
if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
|
|
4609
5343
|
if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
|
|
4610
5344
|
if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
|
|
@@ -4623,9 +5357,9 @@ function getProjectType(dir) {
|
|
|
4623
5357
|
}
|
|
4624
5358
|
function getTestFramework(dir) {
|
|
4625
5359
|
try {
|
|
4626
|
-
const pkgPath =
|
|
4627
|
-
if (
|
|
4628
|
-
const pkg = JSON.parse(
|
|
5360
|
+
const pkgPath = path17.join(dir, "package.json");
|
|
5361
|
+
if (fs15.existsSync(pkgPath)) {
|
|
5362
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
4629
5363
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4630
5364
|
if (deps.jest) return "jest";
|
|
4631
5365
|
if (deps.vitest) return "vitest";
|
|
@@ -4634,7 +5368,7 @@ function getTestFramework(dir) {
|
|
|
4634
5368
|
if (deps["@playwright/test"]) return "playwright";
|
|
4635
5369
|
if (deps.cypress) return "cypress";
|
|
4636
5370
|
}
|
|
4637
|
-
if (
|
|
5371
|
+
if (fs15.existsSync(path17.join(dir, "pytest.ini")) || fs15.existsSync(path17.join(dir, "conftest.py"))) return "pytest";
|
|
4638
5372
|
return "unknown";
|
|
4639
5373
|
} catch {
|
|
4640
5374
|
return "unknown";
|
|
@@ -4642,9 +5376,9 @@ function getTestFramework(dir) {
|
|
|
4642
5376
|
}
|
|
4643
5377
|
function getTestCommand(dir) {
|
|
4644
5378
|
try {
|
|
4645
|
-
const pkgPath =
|
|
4646
|
-
if (
|
|
4647
|
-
const pkg = JSON.parse(
|
|
5379
|
+
const pkgPath = path17.join(dir, "package.json");
|
|
5380
|
+
if (fs15.existsSync(pkgPath)) {
|
|
5381
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
4648
5382
|
if (pkg.scripts?.test) return `npm test`;
|
|
4649
5383
|
if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
|
|
4650
5384
|
}
|
|
@@ -5121,12 +5855,12 @@ In sandbox mode you are a Python-focused, non-interactive, deterministic agent t
|
|
|
5121
5855
|
function getUnifiedSystemPrompt(availableSkills) {
|
|
5122
5856
|
const cwd = process.cwd();
|
|
5123
5857
|
const env = {
|
|
5124
|
-
os_type:
|
|
5125
|
-
os_version:
|
|
5126
|
-
architecture:
|
|
5858
|
+
os_type: os11.type(),
|
|
5859
|
+
os_version: os11.release(),
|
|
5860
|
+
architecture: os11.arch(),
|
|
5127
5861
|
workdir: cwd,
|
|
5128
5862
|
shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
|
|
5129
|
-
username:
|
|
5863
|
+
username: os11.userInfo().username,
|
|
5130
5864
|
current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
5131
5865
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
5132
5866
|
is_git_repo: isGitRepo(cwd) ? "yes" : "no",
|
|
@@ -5311,8 +6045,8 @@ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use codi
|
|
|
5311
6045
|
}
|
|
5312
6046
|
function isGitRepo(dir) {
|
|
5313
6047
|
try {
|
|
5314
|
-
const gitPath =
|
|
5315
|
-
return
|
|
6048
|
+
const gitPath = path17.join(dir, ".git");
|
|
6049
|
+
return fs15.existsSync(gitPath) && fs15.lstatSync(gitPath).isDirectory();
|
|
5316
6050
|
} catch {
|
|
5317
6051
|
return false;
|
|
5318
6052
|
}
|
|
@@ -5521,21 +6255,9 @@ function partitionConversationIntoTurnSlices(conversationHistory) {
|
|
|
5521
6255
|
}
|
|
5522
6256
|
return turns;
|
|
5523
6257
|
}
|
|
5524
|
-
var
|
|
5525
|
-
var
|
|
5526
|
-
var
|
|
5527
|
-
function readContextOptionInt(envKey, fallback) {
|
|
5528
|
-
const raw = (process.env[envKey] ?? "").trim();
|
|
5529
|
-
if (!raw) return fallback;
|
|
5530
|
-
const n = Number(raw);
|
|
5531
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
5532
|
-
}
|
|
5533
|
-
function readContextOptionFloat(envKey, fallback) {
|
|
5534
|
-
const raw = (process.env[envKey] ?? "").trim();
|
|
5535
|
-
if (!raw) return fallback;
|
|
5536
|
-
const n = Number(raw);
|
|
5537
|
-
return Number.isFinite(n) && n > 0 && n <= 1 ? n : fallback;
|
|
5538
|
-
}
|
|
6258
|
+
var CONTEXT_TOKEN_BUDGET = 24e4;
|
|
6259
|
+
var COMPRESS_THRESHOLD = 0.7;
|
|
6260
|
+
var KEEP_RECENT_TURNS = 10;
|
|
5539
6261
|
function buildContextMessages(systemMessages, anchor, pendingRaw, recentFlat) {
|
|
5540
6262
|
const anchorMsg = anchor ? [anchorToSystemMessage(anchor)] : [];
|
|
5541
6263
|
return [...systemMessages, ...anchorMsg, ...pendingRaw, ...recentFlat];
|
|
@@ -5548,9 +6270,9 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
|
|
|
5548
6270
|
newCompressedTurnSliceCount: compressedTurnSliceCount
|
|
5549
6271
|
};
|
|
5550
6272
|
}
|
|
5551
|
-
const tokenBudget = options?.tokenBudget ??
|
|
5552
|
-
const compressThreshold = options?.compressThreshold ??
|
|
5553
|
-
const keepRecentTurns = options?.keepRecentTurns ??
|
|
6273
|
+
const tokenBudget = options?.tokenBudget ?? CONTEXT_TOKEN_BUDGET;
|
|
6274
|
+
const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
|
|
6275
|
+
const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
|
|
5554
6276
|
const systemMessages = [];
|
|
5555
6277
|
let historyStartIndex = 0;
|
|
5556
6278
|
while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
|
|
@@ -5592,7 +6314,7 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
|
|
|
5592
6314
|
}
|
|
5593
6315
|
|
|
5594
6316
|
// src/app/agent/core/llm/llm.ts
|
|
5595
|
-
import
|
|
6317
|
+
import os12 from "os";
|
|
5596
6318
|
import OpenAI from "openai";
|
|
5597
6319
|
function defaultBlumaUserContextInput(sessionId, userMessage) {
|
|
5598
6320
|
const msg = String(userMessage || "").slice(0, 300);
|
|
@@ -5609,7 +6331,7 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
|
|
|
5609
6331
|
}
|
|
5610
6332
|
function getPreferredMacAddress() {
|
|
5611
6333
|
try {
|
|
5612
|
-
const ifaces =
|
|
6334
|
+
const ifaces = os12.networkInterfaces();
|
|
5613
6335
|
for (const name of Object.keys(ifaces)) {
|
|
5614
6336
|
const addrs = ifaces[name];
|
|
5615
6337
|
if (!addrs) continue;
|
|
@@ -5624,7 +6346,7 @@ function getPreferredMacAddress() {
|
|
|
5624
6346
|
} catch {
|
|
5625
6347
|
}
|
|
5626
6348
|
try {
|
|
5627
|
-
return `host:${
|
|
6349
|
+
return `host:${os12.hostname()}`;
|
|
5628
6350
|
} catch {
|
|
5629
6351
|
return "unknown";
|
|
5630
6352
|
}
|
|
@@ -5634,7 +6356,7 @@ function defaultInteractiveCliUserContextInput(sessionId, userMessage) {
|
|
|
5634
6356
|
const machineId = getPreferredMacAddress();
|
|
5635
6357
|
let userName = null;
|
|
5636
6358
|
try {
|
|
5637
|
-
userName =
|
|
6359
|
+
userName = os12.userInfo().username || null;
|
|
5638
6360
|
} catch {
|
|
5639
6361
|
userName = null;
|
|
5640
6362
|
}
|
|
@@ -6072,6 +6794,12 @@ var BluMaAgent = class {
|
|
|
6072
6794
|
getUiToolsDetailed() {
|
|
6073
6795
|
return this.mcpClient.getAvailableToolsDetailed();
|
|
6074
6796
|
}
|
|
6797
|
+
listAvailableSkills() {
|
|
6798
|
+
return this.skillLoader.listAvailable();
|
|
6799
|
+
}
|
|
6800
|
+
getSkillsDirs() {
|
|
6801
|
+
return this.skillLoader.getSkillsDirs();
|
|
6802
|
+
}
|
|
6075
6803
|
async processTurn(userInput, userContextInput) {
|
|
6076
6804
|
this.isInterrupted = false;
|
|
6077
6805
|
this.factorRouterTurnClosed = false;
|
|
@@ -6082,7 +6810,8 @@ var BluMaAgent = class {
|
|
|
6082
6810
|
turnId,
|
|
6083
6811
|
sessionId: userContextInput.sessionId || this.sessionId
|
|
6084
6812
|
};
|
|
6085
|
-
|
|
6813
|
+
const userContent = buildUserMessageContent(inputText, process.cwd());
|
|
6814
|
+
this.history.push({ role: "user", content: userContent });
|
|
6086
6815
|
if (inputText === "/init") {
|
|
6087
6816
|
this.eventBus.emit("dispatch", inputText);
|
|
6088
6817
|
}
|
|
@@ -6235,7 +6964,7 @@ var BluMaAgent = class {
|
|
|
6235
6964
|
|
|
6236
6965
|
${editData.error.display}`;
|
|
6237
6966
|
}
|
|
6238
|
-
const filename =
|
|
6967
|
+
const filename = path18.basename(toolArgs.file_path);
|
|
6239
6968
|
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
6240
6969
|
} catch (e) {
|
|
6241
6970
|
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
@@ -6493,7 +7222,7 @@ import { v4 as uuidv45 } from "uuid";
|
|
|
6493
7222
|
import { v4 as uuidv44 } from "uuid";
|
|
6494
7223
|
|
|
6495
7224
|
// src/app/agent/subagents/init/init_system_prompt.ts
|
|
6496
|
-
import
|
|
7225
|
+
import os13 from "os";
|
|
6497
7226
|
var SYSTEM_PROMPT2 = `
|
|
6498
7227
|
|
|
6499
7228
|
### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
@@ -6656,12 +7385,12 @@ Rule Summary:
|
|
|
6656
7385
|
function getInitPrompt() {
|
|
6657
7386
|
const now = /* @__PURE__ */ new Date();
|
|
6658
7387
|
const collectedData = {
|
|
6659
|
-
os_type:
|
|
6660
|
-
os_version:
|
|
6661
|
-
architecture:
|
|
7388
|
+
os_type: os13.type(),
|
|
7389
|
+
os_version: os13.release(),
|
|
7390
|
+
architecture: os13.arch(),
|
|
6662
7391
|
workdir: process.cwd(),
|
|
6663
7392
|
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
6664
|
-
username:
|
|
7393
|
+
username: os13.userInfo().username || "Unknown",
|
|
6665
7394
|
current_date: now.toISOString().split("T")[0],
|
|
6666
7395
|
// Formato YYYY-MM-DD
|
|
6667
7396
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
@@ -6942,14 +7671,14 @@ var RouteManager = class {
|
|
|
6942
7671
|
this.subAgents = subAgents;
|
|
6943
7672
|
this.core = core;
|
|
6944
7673
|
}
|
|
6945
|
-
registerRoute(
|
|
6946
|
-
this.routeHandlers.set(
|
|
7674
|
+
registerRoute(path22, handler) {
|
|
7675
|
+
this.routeHandlers.set(path22, handler);
|
|
6947
7676
|
}
|
|
6948
7677
|
async handleRoute(payload) {
|
|
6949
7678
|
const inputText = String(payload.content || "").trim();
|
|
6950
7679
|
const { userContext } = payload;
|
|
6951
|
-
for (const [
|
|
6952
|
-
if (inputText ===
|
|
7680
|
+
for (const [path22, handler] of this.routeHandlers) {
|
|
7681
|
+
if (inputText === path22 || inputText.startsWith(`${path22} `)) {
|
|
6953
7682
|
return handler({ content: inputText, userContext });
|
|
6954
7683
|
}
|
|
6955
7684
|
}
|
|
@@ -6958,7 +7687,7 @@ var RouteManager = class {
|
|
|
6958
7687
|
};
|
|
6959
7688
|
|
|
6960
7689
|
// src/app/agent/agent.ts
|
|
6961
|
-
var globalEnvPath =
|
|
7690
|
+
var globalEnvPath = path19.join(os14.homedir(), ".bluma", ".env");
|
|
6962
7691
|
dotenv.config({ path: globalEnvPath });
|
|
6963
7692
|
var Agent = class {
|
|
6964
7693
|
sessionId;
|
|
@@ -7069,6 +7798,13 @@ var Agent = class {
|
|
|
7069
7798
|
getUiToolsDetailed() {
|
|
7070
7799
|
return this.core.getUiToolsDetailed();
|
|
7071
7800
|
}
|
|
7801
|
+
/** Skills list for UI (/skills) — same source as system prompt & load_skill. */
|
|
7802
|
+
listAvailableSkills() {
|
|
7803
|
+
return this.core.listAvailableSkills();
|
|
7804
|
+
}
|
|
7805
|
+
getSkillsDirs() {
|
|
7806
|
+
return this.core.getSkillsDirs();
|
|
7807
|
+
}
|
|
7072
7808
|
async processTurn(userInput, userContextInput) {
|
|
7073
7809
|
const inputText = String(userInput.content || "").trim();
|
|
7074
7810
|
const resolvedUserContext = userContextInput ?? defaultInteractiveCliUserContextInput(this.sessionId, inputText.slice(0, 300));
|
|
@@ -7175,12 +7911,12 @@ var renderShellCommand2 = ({ args }) => {
|
|
|
7175
7911
|
};
|
|
7176
7912
|
var renderLsTool2 = ({ args }) => {
|
|
7177
7913
|
const parsed = parseArgs(args);
|
|
7178
|
-
const
|
|
7914
|
+
const path22 = parsed.directory_path || ".";
|
|
7179
7915
|
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
7180
7916
|
/* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
|
|
7181
7917
|
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
7182
7918
|
" ",
|
|
7183
|
-
|
|
7919
|
+
path22
|
|
7184
7920
|
] })
|
|
7185
7921
|
] });
|
|
7186
7922
|
};
|
|
@@ -7316,7 +8052,7 @@ var renderFindByName = ({ args }) => {
|
|
|
7316
8052
|
var renderGrepSearch = ({ args }) => {
|
|
7317
8053
|
const parsed = parseArgs(args);
|
|
7318
8054
|
const query = parsed.query || "";
|
|
7319
|
-
const
|
|
8055
|
+
const path22 = parsed.path || ".";
|
|
7320
8056
|
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
7321
8057
|
/* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
|
|
7322
8058
|
/* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
|
|
@@ -7326,7 +8062,7 @@ var renderGrepSearch = ({ args }) => {
|
|
|
7326
8062
|
] }),
|
|
7327
8063
|
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
7328
8064
|
" ",
|
|
7329
|
-
|
|
8065
|
+
path22
|
|
7330
8066
|
] })
|
|
7331
8067
|
] });
|
|
7332
8068
|
};
|
|
@@ -7895,10 +8631,10 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
|
|
|
7895
8631
|
] }),
|
|
7896
8632
|
matches.slice(0, 5).map((m, i) => {
|
|
7897
8633
|
const row = m;
|
|
7898
|
-
const
|
|
8634
|
+
const path22 = row.file || row.path || row.name || m;
|
|
7899
8635
|
const line = row.line;
|
|
7900
8636
|
return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
7901
|
-
String(
|
|
8637
|
+
String(path22),
|
|
7902
8638
|
line != null ? `:${line}` : ""
|
|
7903
8639
|
] }, i);
|
|
7904
8640
|
}),
|
|
@@ -8030,8 +8766,18 @@ var SessionInfoConnectingMCP_default = SessionInfoConnectingMCP;
|
|
|
8030
8766
|
|
|
8031
8767
|
// src/app/ui/components/SlashCommands.tsx
|
|
8032
8768
|
import { Box as Box14, Text as Text13 } from "ink";
|
|
8769
|
+
|
|
8770
|
+
// src/app/ui/constants/historyLayout.ts
|
|
8771
|
+
var HEADER_PANEL_HISTORY_ID = 0;
|
|
8772
|
+
|
|
8773
|
+
// src/app/ui/components/SlashCommands.tsx
|
|
8033
8774
|
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
8034
|
-
var SlashCommands = ({
|
|
8775
|
+
var SlashCommands = ({
|
|
8776
|
+
input,
|
|
8777
|
+
setHistory,
|
|
8778
|
+
agentRef,
|
|
8779
|
+
onClearRecent
|
|
8780
|
+
}) => {
|
|
8035
8781
|
const [cmd, ...args] = input.slice(1).trim().split(/\s+/);
|
|
8036
8782
|
const outBox = (children) => /* @__PURE__ */ jsx14(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Box14, { paddingLeft: 1, flexDirection: "column", children }) });
|
|
8037
8783
|
const render2 = () => {
|
|
@@ -8039,19 +8785,59 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
|
|
|
8039
8785
|
return null;
|
|
8040
8786
|
}
|
|
8041
8787
|
if (cmd === "help") {
|
|
8042
|
-
const
|
|
8788
|
+
const lines = formatSlashHelpLines();
|
|
8789
|
+
return outBox(
|
|
8790
|
+
/* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
8791
|
+
/* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
|
|
8792
|
+
/* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Slash commands" }),
|
|
8793
|
+
/* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \xB7 " }),
|
|
8794
|
+
/* @__PURE__ */ jsx14(Text13, { dimColor: true, children: "put .png/.jpg/.webp paths in a normal message to attach images (project dir or ~)" })
|
|
8795
|
+
] }),
|
|
8796
|
+
/* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx14(Text13, { dimColor: line.trim().length > 0, children: line || " " }, i)) })
|
|
8797
|
+
] })
|
|
8798
|
+
);
|
|
8799
|
+
}
|
|
8800
|
+
if (cmd === "skills") {
|
|
8801
|
+
const list = agentRef.current?.listAvailableSkills?.() || [];
|
|
8802
|
+
const dirs = agentRef.current?.getSkillsDirs?.();
|
|
8043
8803
|
return outBox(
|
|
8044
8804
|
/* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
8045
|
-
/* @__PURE__ */
|
|
8046
|
-
|
|
8047
|
-
/* @__PURE__ */
|
|
8048
|
-
|
|
8805
|
+
/* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
|
|
8806
|
+
/* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Skills (load_skill)" }),
|
|
8807
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
8808
|
+
" \xB7 ",
|
|
8809
|
+
list.length,
|
|
8810
|
+
" available"
|
|
8811
|
+
] })
|
|
8812
|
+
] }),
|
|
8813
|
+
dirs ? /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, flexDirection: "column", children: [
|
|
8814
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
8815
|
+
"bundled: ",
|
|
8816
|
+
String(dirs.bundled || "")
|
|
8817
|
+
] }),
|
|
8818
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
8819
|
+
"project: ",
|
|
8820
|
+
String(dirs.project || "")
|
|
8821
|
+
] }),
|
|
8822
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
8823
|
+
"global: ",
|
|
8824
|
+
String(dirs.global || "")
|
|
8825
|
+
] })
|
|
8826
|
+
] }) : null,
|
|
8827
|
+
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: [
|
|
8828
|
+
/* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: s.name }),
|
|
8829
|
+
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
8830
|
+
s.source,
|
|
8831
|
+
" \u2014 ",
|
|
8832
|
+
s.description || "\u2014"
|
|
8833
|
+
] })
|
|
8049
8834
|
] }, i)) })
|
|
8050
8835
|
] })
|
|
8051
8836
|
);
|
|
8052
8837
|
}
|
|
8053
8838
|
if (cmd === "clear") {
|
|
8054
|
-
|
|
8839
|
+
onClearRecent?.();
|
|
8840
|
+
setHistory((prev) => prev.filter((item) => item.id === HEADER_PANEL_HISTORY_ID));
|
|
8055
8841
|
return outBox(
|
|
8056
8842
|
/* @__PURE__ */ jsxs13(Box14, { children: [
|
|
8057
8843
|
/* @__PURE__ */ jsx14(Text13, { color: "green", children: "[ok]" }),
|
|
@@ -8195,16 +8981,16 @@ var SlashCommands_default = SlashCommands;
|
|
|
8195
8981
|
// src/app/agent/utils/update_check.ts
|
|
8196
8982
|
import updateNotifier from "update-notifier";
|
|
8197
8983
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8198
|
-
import
|
|
8199
|
-
import
|
|
8984
|
+
import path20 from "path";
|
|
8985
|
+
import fs16 from "fs";
|
|
8200
8986
|
var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
|
|
8201
8987
|
function findBlumaPackageJson(startDir) {
|
|
8202
8988
|
let dir = startDir;
|
|
8203
8989
|
for (let i = 0; i < 10; i++) {
|
|
8204
|
-
const candidate =
|
|
8205
|
-
if (
|
|
8990
|
+
const candidate = path20.join(dir, "package.json");
|
|
8991
|
+
if (fs16.existsSync(candidate)) {
|
|
8206
8992
|
try {
|
|
8207
|
-
const raw =
|
|
8993
|
+
const raw = fs16.readFileSync(candidate, "utf8");
|
|
8208
8994
|
const parsed = JSON.parse(raw);
|
|
8209
8995
|
if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
|
|
8210
8996
|
return { name: parsed.name, version: parsed.version };
|
|
@@ -8212,7 +8998,7 @@ function findBlumaPackageJson(startDir) {
|
|
|
8212
8998
|
} catch {
|
|
8213
8999
|
}
|
|
8214
9000
|
}
|
|
8215
|
-
const parent =
|
|
9001
|
+
const parent = path20.dirname(dir);
|
|
8216
9002
|
if (parent === dir) break;
|
|
8217
9003
|
dir = parent;
|
|
8218
9004
|
}
|
|
@@ -8225,12 +9011,12 @@ async function checkForUpdates() {
|
|
|
8225
9011
|
}
|
|
8226
9012
|
const binPath = process.argv?.[1];
|
|
8227
9013
|
let pkg = null;
|
|
8228
|
-
if (binPath &&
|
|
8229
|
-
pkg = findBlumaPackageJson(
|
|
9014
|
+
if (binPath && fs16.existsSync(binPath)) {
|
|
9015
|
+
pkg = findBlumaPackageJson(path20.dirname(binPath));
|
|
8230
9016
|
}
|
|
8231
9017
|
if (!pkg) {
|
|
8232
9018
|
const __filename = fileURLToPath3(import.meta.url);
|
|
8233
|
-
const __dirname2 =
|
|
9019
|
+
const __dirname2 = path20.dirname(__filename);
|
|
8234
9020
|
pkg = findBlumaPackageJson(__dirname2);
|
|
8235
9021
|
}
|
|
8236
9022
|
if (!pkg) {
|
|
@@ -8460,7 +9246,42 @@ var SAFE_AUTO_APPROVE_TOOLS = [
|
|
|
8460
9246
|
// Status de comandos (read-only)
|
|
8461
9247
|
"command_status"
|
|
8462
9248
|
];
|
|
8463
|
-
|
|
9249
|
+
function trimRecentActivity(s, max = 72) {
|
|
9250
|
+
const t = String(s ?? "").replace(/\s+/g, " ").trim();
|
|
9251
|
+
if (!t) return "";
|
|
9252
|
+
return t.length <= max ? t : `${t.slice(0, max - 1)}\u2026`;
|
|
9253
|
+
}
|
|
9254
|
+
function UserMessageWithOptionalImages({
|
|
9255
|
+
raw,
|
|
9256
|
+
variant
|
|
9257
|
+
}) {
|
|
9258
|
+
if (variant === "slash-img") {
|
|
9259
|
+
const pathStrs = collectImagePathStrings(raw);
|
|
9260
|
+
const stripped2 = pathStrs.length > 0 ? stripImagePathStrings(raw, pathStrs) : raw;
|
|
9261
|
+
const cap = stripped2.trim();
|
|
9262
|
+
const capDisp = cap.length > 800 ? `${cap.slice(0, 800)}\u2026` : cap;
|
|
9263
|
+
const fallbackDisp = raw.length > 800 ? `${raw.slice(0, 800)}\u2026` : raw;
|
|
9264
|
+
return /* @__PURE__ */ jsx20(ChatUserMessage, { children: pathStrs.length > 0 ? /* @__PURE__ */ jsx20(
|
|
9265
|
+
ChatUserImageBlock,
|
|
9266
|
+
{
|
|
9267
|
+
imageCount: pathStrs.length,
|
|
9268
|
+
caption: cap.length > 0 ? capDisp : null,
|
|
9269
|
+
captionDim: true
|
|
9270
|
+
}
|
|
9271
|
+
) : /* @__PURE__ */ jsx20(Text19, { dimColor: true, wrap: "wrap", children: fallbackDisp }) });
|
|
9272
|
+
}
|
|
9273
|
+
const displayRaw = raw.length > 1e4 ? `${raw.substring(0, 1e4)}...` : raw;
|
|
9274
|
+
const paths = collectImagePathStrings(displayRaw);
|
|
9275
|
+
const stripped = paths.length > 0 ? stripImagePathStrings(displayRaw, paths) : displayRaw;
|
|
9276
|
+
return /* @__PURE__ */ jsx20(ChatUserMessage, { children: paths.length > 0 ? /* @__PURE__ */ jsx20(
|
|
9277
|
+
ChatUserImageBlock,
|
|
9278
|
+
{
|
|
9279
|
+
imageCount: paths.length,
|
|
9280
|
+
caption: stripped.trim().length > 0 ? stripped : null
|
|
9281
|
+
}
|
|
9282
|
+
) : /* @__PURE__ */ jsx20(Text19, { wrap: "wrap", children: displayRaw }) });
|
|
9283
|
+
}
|
|
9284
|
+
var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
|
|
8464
9285
|
const agentInstance = useRef5(null);
|
|
8465
9286
|
const [history, setHistory] = useState6([]);
|
|
8466
9287
|
const [statusMessage, setStatusMessage] = useState6(
|
|
@@ -8478,11 +9299,12 @@ var AppComponent = ({ eventBus, sessionId }) => {
|
|
|
8478
9299
|
null
|
|
8479
9300
|
);
|
|
8480
9301
|
const [isInitAgentActive, setIsInitAgentActive] = useState6(false);
|
|
9302
|
+
const [recentActivityLine, setRecentActivityLine] = useState6(null);
|
|
8481
9303
|
const alwaysAcceptList = useRef5([]);
|
|
8482
9304
|
const workdir = process.cwd();
|
|
8483
9305
|
const updateCheckRan = useRef5(false);
|
|
8484
9306
|
const turnStartedAtRef = useRef5(null);
|
|
8485
|
-
const appendExpandPreviewToHistory =
|
|
9307
|
+
const appendExpandPreviewToHistory = useCallback3(() => {
|
|
8486
9308
|
const p = peekLatestExpandable();
|
|
8487
9309
|
setHistory((prev) => {
|
|
8488
9310
|
const id = prev.length;
|
|
@@ -8510,7 +9332,27 @@ var AppComponent = ({ eventBus, sessionId }) => {
|
|
|
8510
9332
|
expandPreviewHotkeyBus.off("expand", appendExpandPreviewToHistory);
|
|
8511
9333
|
};
|
|
8512
9334
|
}, [appendExpandPreviewToHistory]);
|
|
8513
|
-
|
|
9335
|
+
useEffect7(() => {
|
|
9336
|
+
setHistory((prev) => {
|
|
9337
|
+
const tail = prev.filter((h) => h.id !== HEADER_PANEL_HISTORY_ID);
|
|
9338
|
+
return [
|
|
9339
|
+
{
|
|
9340
|
+
id: HEADER_PANEL_HISTORY_ID,
|
|
9341
|
+
component: /* @__PURE__ */ jsx20(
|
|
9342
|
+
Header,
|
|
9343
|
+
{
|
|
9344
|
+
sessionId,
|
|
9345
|
+
workdir,
|
|
9346
|
+
cliVersion,
|
|
9347
|
+
recentActivitySummary: recentActivityLine
|
|
9348
|
+
}
|
|
9349
|
+
)
|
|
9350
|
+
},
|
|
9351
|
+
...tail
|
|
9352
|
+
];
|
|
9353
|
+
});
|
|
9354
|
+
}, [sessionId, workdir, cliVersion, recentActivityLine]);
|
|
9355
|
+
const handleInterrupt = useCallback3(() => {
|
|
8514
9356
|
if (!isProcessing) return;
|
|
8515
9357
|
eventBus.emit("user_interrupt");
|
|
8516
9358
|
turnStartedAtRef.current = null;
|
|
@@ -8523,15 +9365,41 @@ var AppComponent = ({ eventBus, sessionId }) => {
|
|
|
8523
9365
|
}
|
|
8524
9366
|
]);
|
|
8525
9367
|
}, [isProcessing, eventBus]);
|
|
8526
|
-
const handleSubmit =
|
|
9368
|
+
const handleSubmit = useCallback3(
|
|
8527
9369
|
(text) => {
|
|
8528
9370
|
if (!text || isProcessing || !agentInstance.current) return;
|
|
8529
|
-
if (text.
|
|
9371
|
+
if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
|
|
9372
|
+
const payload = text.replace(/^\/img\s+/i, "").replace(/^\/image\s+/i, "").trim();
|
|
9373
|
+
if (!payload) {
|
|
9374
|
+
setHistory((prev) => [
|
|
9375
|
+
...prev,
|
|
9376
|
+
{
|
|
9377
|
+
id: prev.length,
|
|
9378
|
+
component: /* @__PURE__ */ jsx20(ChatMeta, { children: "Usage: /img ./screenshot.png \u2014 optional text after the path is sent too" })
|
|
9379
|
+
}
|
|
9380
|
+
]);
|
|
9381
|
+
return;
|
|
9382
|
+
}
|
|
9383
|
+
setIsProcessing(true);
|
|
9384
|
+
turnStartedAtRef.current = Date.now();
|
|
9385
|
+
setHistory((prev) => [
|
|
9386
|
+
...prev,
|
|
9387
|
+
{
|
|
9388
|
+
id: prev.length,
|
|
9389
|
+
component: /* @__PURE__ */ jsx20(UserMessageWithOptionalImages, { raw: payload, variant: "slash-img" })
|
|
9390
|
+
}
|
|
9391
|
+
]);
|
|
9392
|
+
setRecentActivityLine(`Prompt: ${trimRecentActivity(payload)}`);
|
|
9393
|
+
agentInstance.current.processTurn({ content: payload });
|
|
9394
|
+
return;
|
|
9395
|
+
}
|
|
9396
|
+
if (isSlashRoutingLine(text)) {
|
|
8530
9397
|
const [cmd] = text.slice(1).trim().split(/\s+/);
|
|
8531
9398
|
if (!cmd) {
|
|
8532
9399
|
setIsProcessing(false);
|
|
8533
9400
|
return;
|
|
8534
9401
|
}
|
|
9402
|
+
setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
|
|
8535
9403
|
if (cmd === "init") {
|
|
8536
9404
|
setIsInitAgentActive(true);
|
|
8537
9405
|
setIsProcessing(true);
|
|
@@ -8553,7 +9421,8 @@ var AppComponent = ({ eventBus, sessionId }) => {
|
|
|
8553
9421
|
{
|
|
8554
9422
|
input: text,
|
|
8555
9423
|
setHistory,
|
|
8556
|
-
agentRef: agentInstance
|
|
9424
|
+
agentRef: agentInstance,
|
|
9425
|
+
onClearRecent: () => setRecentActivityLine(null)
|
|
8557
9426
|
}
|
|
8558
9427
|
)
|
|
8559
9428
|
}
|
|
@@ -8578,6 +9447,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
|
|
|
8578
9447
|
] }) })
|
|
8579
9448
|
}
|
|
8580
9449
|
]);
|
|
9450
|
+
setRecentActivityLine(`Shell: ${trimRecentActivity(command)}`);
|
|
8581
9451
|
Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
|
|
8582
9452
|
try {
|
|
8583
9453
|
const result = await runCommandAsync2({ command, cwd: workdir });
|
|
@@ -8621,19 +9491,19 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8621
9491
|
}
|
|
8622
9492
|
setIsProcessing(true);
|
|
8623
9493
|
turnStartedAtRef.current = Date.now();
|
|
8624
|
-
const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
|
|
8625
9494
|
setHistory((prev) => [
|
|
8626
9495
|
...prev,
|
|
8627
9496
|
{
|
|
8628
9497
|
id: prev.length,
|
|
8629
|
-
component: /* @__PURE__ */ jsx20(
|
|
9498
|
+
component: /* @__PURE__ */ jsx20(UserMessageWithOptionalImages, { raw: text, variant: "plain" })
|
|
8630
9499
|
}
|
|
8631
9500
|
]);
|
|
9501
|
+
setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
|
|
8632
9502
|
agentInstance.current.processTurn({ content: text });
|
|
8633
9503
|
},
|
|
8634
9504
|
[isProcessing]
|
|
8635
9505
|
);
|
|
8636
|
-
const handleConfirmation =
|
|
9506
|
+
const handleConfirmation = useCallback3(
|
|
8637
9507
|
(decision, toolCalls) => {
|
|
8638
9508
|
if (!agentInstance.current) return;
|
|
8639
9509
|
setPendingConfirmation(null);
|
|
@@ -8655,7 +9525,6 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8655
9525
|
[]
|
|
8656
9526
|
);
|
|
8657
9527
|
useEffect7(() => {
|
|
8658
|
-
setHistory([{ id: 0, component: /* @__PURE__ */ jsx20(Header, { sessionId, workdir }) }]);
|
|
8659
9528
|
const initializeAgent = async () => {
|
|
8660
9529
|
try {
|
|
8661
9530
|
agentInstance.current = new Agent(sessionId, eventBus);
|
|
@@ -8680,12 +9549,11 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8680
9549
|
if (t == null) return;
|
|
8681
9550
|
turnStartedAtRef.current = null;
|
|
8682
9551
|
const ms = Date.now() - t;
|
|
8683
|
-
const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
|
|
8684
9552
|
setHistory((prev) => [
|
|
8685
9553
|
...prev,
|
|
8686
9554
|
{
|
|
8687
9555
|
id: prev.length,
|
|
8688
|
-
component: /* @__PURE__ */ jsx20(ChatTurnDuration, {
|
|
9556
|
+
component: /* @__PURE__ */ jsx20(ChatTurnDuration, { durationMs: ms })
|
|
8689
9557
|
}
|
|
8690
9558
|
]);
|
|
8691
9559
|
};
|
|
@@ -8724,10 +9592,6 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8724
9592
|
setToolsCount(parsed.tools);
|
|
8725
9593
|
setMcpStatus("connected");
|
|
8726
9594
|
setIsProcessing(false);
|
|
8727
|
-
setHistory((prev) => {
|
|
8728
|
-
const newHistory = [...prev];
|
|
8729
|
-
return newHistory;
|
|
8730
|
-
});
|
|
8731
9595
|
if (!updateCheckRan.current) {
|
|
8732
9596
|
updateCheckRan.current = true;
|
|
8733
9597
|
Promise.resolve().then(() => checkForUpdates()).then((msg) => {
|
|
@@ -8766,7 +9630,9 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8766
9630
|
}
|
|
8767
9631
|
);
|
|
8768
9632
|
} else if (parsed.type === "tool_call") {
|
|
8769
|
-
|
|
9633
|
+
if (parsed.tool_name) {
|
|
9634
|
+
setRecentActivityLine(`Tool: ${String(parsed.tool_name)}`);
|
|
9635
|
+
}
|
|
8770
9636
|
newComponent = /* @__PURE__ */ jsx20(
|
|
8771
9637
|
ToolCallDisplay,
|
|
8772
9638
|
{
|
|
@@ -8784,6 +9650,11 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8784
9650
|
}
|
|
8785
9651
|
);
|
|
8786
9652
|
} else if (parsed.type === "user_overlay") {
|
|
9653
|
+
if (parsed.payload != null && String(parsed.payload).trim()) {
|
|
9654
|
+
setRecentActivityLine(
|
|
9655
|
+
`Context: ${trimRecentActivity(String(parsed.payload))}`
|
|
9656
|
+
);
|
|
9657
|
+
}
|
|
8787
9658
|
newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.payload }) });
|
|
8788
9659
|
} else if (parsed.type === "reasoning") {
|
|
8789
9660
|
newComponent = /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning: parsed.content });
|
|
@@ -8806,10 +9677,9 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
8806
9677
|
if (t != null) {
|
|
8807
9678
|
turnStartedAtRef.current = null;
|
|
8808
9679
|
const ms = Date.now() - t;
|
|
8809
|
-
const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
|
|
8810
9680
|
next.push({
|
|
8811
9681
|
id: next.length,
|
|
8812
|
-
component: /* @__PURE__ */ jsx20(ChatTurnDuration, {
|
|
9682
|
+
component: /* @__PURE__ */ jsx20(ChatTurnDuration, { durationMs: ms })
|
|
8813
9683
|
});
|
|
8814
9684
|
}
|
|
8815
9685
|
}
|
|
@@ -8974,9 +9844,9 @@ async function runAgentMode() {
|
|
|
8974
9844
|
try {
|
|
8975
9845
|
if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
|
|
8976
9846
|
const filePath = args[inputFileIndex + 1];
|
|
8977
|
-
rawPayload =
|
|
9847
|
+
rawPayload = fs17.readFileSync(filePath, "utf-8");
|
|
8978
9848
|
} else {
|
|
8979
|
-
rawPayload =
|
|
9849
|
+
rawPayload = fs17.readFileSync(0, "utf-8");
|
|
8980
9850
|
}
|
|
8981
9851
|
} catch (err) {
|
|
8982
9852
|
writeJsonl({
|
|
@@ -9142,6 +10012,16 @@ async function runAgentMode() {
|
|
|
9142
10012
|
process.exit(1);
|
|
9143
10013
|
}
|
|
9144
10014
|
}
|
|
10015
|
+
function readCliPackageVersion() {
|
|
10016
|
+
try {
|
|
10017
|
+
const base = path21.dirname(fileURLToPath4(import.meta.url));
|
|
10018
|
+
const pkgPath = path21.join(base, "..", "package.json");
|
|
10019
|
+
const j = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
|
|
10020
|
+
return String(j.version || "0.0.0");
|
|
10021
|
+
} catch {
|
|
10022
|
+
return "0.0.0";
|
|
10023
|
+
}
|
|
10024
|
+
}
|
|
9145
10025
|
function runCliMode() {
|
|
9146
10026
|
const BLUMA_TITLE = process.env.BLUMA_TITLE || "BluMa - NomadEngenuity";
|
|
9147
10027
|
startTitleKeeper(BLUMA_TITLE);
|
|
@@ -9149,7 +10029,8 @@ function runCliMode() {
|
|
|
9149
10029
|
const sessionId = uuidv46();
|
|
9150
10030
|
const props = {
|
|
9151
10031
|
eventBus,
|
|
9152
|
-
sessionId
|
|
10032
|
+
sessionId,
|
|
10033
|
+
cliVersion: readCliPackageVersion()
|
|
9153
10034
|
};
|
|
9154
10035
|
render(React12.createElement(App_default, props));
|
|
9155
10036
|
}
|