@nomad-e/bluma-cli 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -140
- package/assets/bg.png +0 -0
- package/assets/bg1.png +0 -0
- package/assets/bluma.png +0 -0
- package/assets/image.png +0 -0
- package/dist/assets/bg.png +0 -0
- package/dist/assets/bg1.png +0 -0
- package/dist/assets/bluma.png +0 -0
- package/dist/assets/image.png +0 -0
- package/dist/config/native_tools.json +12 -3
- package/dist/main.js +7435 -3285
- package/package.json +7 -6
- package/dist/WorkingTimer.js +0 -130
- package/dist/components/StreamingText.js +0 -113
- package/dist/components/reasoningText.js +0 -18
- package/dist/components/streamingTextFlush.js +0 -11
- package/dist/config/skills/xlsx/scripts/office/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/pack.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/soffice.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/unpack.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/validate.cpython-312.pyc +0 -0
- package/dist/constants/toolUiSymbols.js +0 -10
- package/dist/theme/blumaTerminal.js +0 -79
- package/dist/theme/m3Layout.js +0 -68
- package/dist/utils/formatTurnDurationMs.js +0 -16
- package/dist/utils/toolActionLabels.js +0 -167
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomad-e/bluma-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "BluMa independent agent for automation and advanced software engineering.",
|
|
5
5
|
"author": "Alex Fonseca",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"husky": "^9.1.7",
|
|
35
35
|
"ink-testing-library": "^4.0.0",
|
|
36
36
|
"jest": "^30.0.5",
|
|
37
|
-
"lodash-es": "^4.18.1",
|
|
38
37
|
"nodemon": "^3.1.10",
|
|
39
38
|
"supports-hyperlinks": "^4.4.0",
|
|
40
39
|
"ts-node-dev": "^2.0.0",
|
|
@@ -44,10 +43,10 @@
|
|
|
44
43
|
"type": "module",
|
|
45
44
|
"main": "dist/main.js",
|
|
46
45
|
"scripts": {
|
|
47
|
-
"build": "node scripts/build.js",
|
|
48
|
-
"build:native": "cd native && npm run build",
|
|
46
|
+
"build": "npx tsc --noEmit && node scripts/build.js",
|
|
47
|
+
"build:native": "npx tsc --noEmit && cd native && npm run build",
|
|
49
48
|
"build:all": "npm run build:native && npm run build",
|
|
50
|
-
"start": "
|
|
49
|
+
"start": "npm run build && clear && node dist/main.js",
|
|
51
50
|
"test": "jest",
|
|
52
51
|
"test:watch": "jest --watch",
|
|
53
52
|
"test:parallel": "node scripts/test-parallel.js",
|
|
@@ -83,6 +82,7 @@
|
|
|
83
82
|
"jquery": "^4.0.0",
|
|
84
83
|
"js-tiktoken": "^1.0.21",
|
|
85
84
|
"latest-version": "^9.0.0",
|
|
85
|
+
"lodash-es": "^4.18.1",
|
|
86
86
|
"marked": "^16.1.2",
|
|
87
87
|
"openai": "^4.47.3",
|
|
88
88
|
"react": "^19.2.5",
|
|
@@ -94,7 +94,8 @@
|
|
|
94
94
|
"yoga-layout": "^3.2.1"
|
|
95
95
|
},
|
|
96
96
|
"files": [
|
|
97
|
-
"dist/"
|
|
97
|
+
"dist/",
|
|
98
|
+
"assets/"
|
|
98
99
|
],
|
|
99
100
|
"publishConfig": {
|
|
100
101
|
"access": "public"
|
package/dist/WorkingTimer.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Indicador de trabalho em curso — pulso minimalista + shimmer suave.
|
|
4
|
-
*/
|
|
5
|
-
import { useState, useEffect, memo, useRef } from "react";
|
|
6
|
-
import { Box, Text } from "ink";
|
|
7
|
-
import { BLUMA_TERMINAL as T } from "./theme/blumaTerminal.js";
|
|
8
|
-
import { TOOL_DETAIL_PREFIX } from "./constants/toolUiSymbols.js";
|
|
9
|
-
import { formatTurnDurationMs } from "./utils/formatTurnDurationMs.js";
|
|
10
|
-
import { getToolActionLabel } from "./utils/toolActionLabels.js";
|
|
11
|
-
// ─── Pulse ────────────────────────────────────────────────────────────────────
|
|
12
|
-
// Frames com "peso visual" crescente/decrescente → sensação de respiração
|
|
13
|
-
const PULSE_FRAMES = ["·", "○", "◌", "◎", "●", "◎", "◌", "○"];
|
|
14
|
-
const PULSE_INTERVAL_MS = 200; // Increased from 110ms to reduce screen flicker
|
|
15
|
-
// ─── Shimmer ──────────────────────────────────────────────────────────────────
|
|
16
|
-
// Paleta de 5 níveis de brilho (sem mudar hue, apenas luminosidade percebida)
|
|
17
|
-
// Usamos gray scale para ficar neutro com qualquer tema
|
|
18
|
-
const SHIMMER_PALETTE = [
|
|
19
|
-
"#4a4a4a", // escuro
|
|
20
|
-
"#6e6e6e",
|
|
21
|
-
"#9a9a9a", // mid
|
|
22
|
-
"#c8c8c8",
|
|
23
|
-
"#f0f0f0", // quase branco
|
|
24
|
-
"#ffffff", // pico de brilho (branco puro)
|
|
25
|
-
"#f0f0f0",
|
|
26
|
-
"#c8c8c8",
|
|
27
|
-
"#9a9a9a",
|
|
28
|
-
"#6e6e6e",
|
|
29
|
-
];
|
|
30
|
-
const SHIMMER_CYCLE_MS = 2000; // Increased from 1000ms to reduce flicker (slower shimmer)
|
|
31
|
-
/** Shimmer com uma única sombra que percorre o texto da esquerda para a direita.
|
|
32
|
-
* Cada passada (início → fim) demora SHIMMER_CYCLE_MS (1s).
|
|
33
|
-
* Quando chega ao fim, volta instantaneamente ao início e recomeça.
|
|
34
|
-
* Usa ref para animação fluída sem re-render quando o texto muda. */
|
|
35
|
-
const ShimmerText = memo(({ text, baseColor = "#9a9a9a" }) => {
|
|
36
|
-
const posRef = useRef(0);
|
|
37
|
-
const [, forceUpdate] = useState({});
|
|
38
|
-
const textRef = useRef(text);
|
|
39
|
-
const intervalMsRef = useRef(50); // valor padrão inicial
|
|
40
|
-
// Atualiza ref do texto sem afetar a animação
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
textRef.current = text;
|
|
43
|
-
// Recalcula o intervalo para que a passada (início → fim) demore SHIMMER_CYCLE_MS
|
|
44
|
-
const cycleSteps = Math.max(1, text.length);
|
|
45
|
-
intervalMsRef.current = SHIMMER_CYCLE_MS / cycleSteps;
|
|
46
|
-
}, [text]);
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
const rafId = setInterval(() => {
|
|
49
|
-
const len = textRef.current.length;
|
|
50
|
-
// Posição vai de 0 a len-1 (apenas ida, esquerda → direita)
|
|
51
|
-
// Quando chega ao fim, volta ao início (0)
|
|
52
|
-
posRef.current = (posRef.current + 1) % (len || 1);
|
|
53
|
-
forceUpdate({}); // Força re-render mínimo apenas para atualizar cores
|
|
54
|
-
}, intervalMsRef.current);
|
|
55
|
-
return () => clearInterval(rafId);
|
|
56
|
-
}, []);
|
|
57
|
-
const currentText = textRef.current;
|
|
58
|
-
const currentPos = posRef.current;
|
|
59
|
-
// Calcula a cor baseada na distância do ponto de brilho
|
|
60
|
-
// O caractere na currentPos tem o brilho máximo (índice 5 = branco)
|
|
61
|
-
// Os adjacentes têm brilho decrescente
|
|
62
|
-
const getColorForIndex = (i) => {
|
|
63
|
-
const dist = Math.abs(i - currentPos);
|
|
64
|
-
if (dist === 0)
|
|
65
|
-
return SHIMMER_PALETTE[5]; // branco puro no pico
|
|
66
|
-
if (dist === 1)
|
|
67
|
-
return SHIMMER_PALETTE[4]; // quase branco
|
|
68
|
-
if (dist === 2)
|
|
69
|
-
return SHIMMER_PALETTE[3]; // claro
|
|
70
|
-
if (dist === 3)
|
|
71
|
-
return SHIMMER_PALETTE[2]; // mid
|
|
72
|
-
return baseColor; // cor base para o resto
|
|
73
|
-
};
|
|
74
|
-
return (_jsx(_Fragment, { children: currentText.split("").map((char, i) => (_jsx(Text, { color: getColorForIndex(i), children: char }, i))) }));
|
|
75
|
-
});
|
|
76
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
77
|
-
function trimLine(s, max = 76) {
|
|
78
|
-
const t = String(s ?? "")
|
|
79
|
-
.replace(/\s+/g, " ")
|
|
80
|
-
.trim();
|
|
81
|
-
if (!t)
|
|
82
|
-
return "";
|
|
83
|
-
return t.length <= max ? t : `${t.slice(0, max - 1)}…`;
|
|
84
|
-
}
|
|
85
|
-
// ─── WorkingTimer ─────────────────────────────────────────────────────────────
|
|
86
|
-
const WorkingTimerComponent = ({ eventBus, taskName, taskStatus, liveToolName, liveToolArgs, isReasoning, startedAtMs, }) => {
|
|
87
|
-
const [currentAction, setCurrentAction] = useState("working");
|
|
88
|
-
const [pulseFrame, setPulseFrame] = useState(0);
|
|
89
|
-
const [nowTick, setNowTick] = useState(() => Date.now());
|
|
90
|
-
const dynamicActionLabel = liveToolName
|
|
91
|
-
? getToolActionLabel(liveToolName, liveToolArgs)
|
|
92
|
-
: null;
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
if (!eventBus)
|
|
95
|
-
return;
|
|
96
|
-
const handleActionStatus = (data) => {
|
|
97
|
-
if (data.action)
|
|
98
|
-
setCurrentAction(data.action);
|
|
99
|
-
};
|
|
100
|
-
eventBus.on("action_status", handleActionStatus);
|
|
101
|
-
return () => { eventBus.off("action_status", handleActionStatus); };
|
|
102
|
-
}, [eventBus]);
|
|
103
|
-
// Pulse — frame rate próprio, independente do shimmer
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
const id = setInterval(() => {
|
|
106
|
-
setPulseFrame((prev) => (prev + 1) % PULSE_FRAMES.length);
|
|
107
|
-
}, PULSE_INTERVAL_MS);
|
|
108
|
-
return () => clearInterval(id);
|
|
109
|
-
}, []);
|
|
110
|
-
// Elapsed timer
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (startedAtMs == null)
|
|
113
|
-
return;
|
|
114
|
-
setNowTick(Date.now());
|
|
115
|
-
const id = setInterval(() => setNowTick(Date.now()), 500);
|
|
116
|
-
return () => clearInterval(id);
|
|
117
|
-
}, [startedAtMs]);
|
|
118
|
-
const displayAction = dynamicActionLabel ||
|
|
119
|
-
(taskStatus || (isReasoning ? "thinking" : currentAction)).trim() ||
|
|
120
|
-
"working";
|
|
121
|
-
const actionLine = `${displayAction}…`;
|
|
122
|
-
const elapsedMs = startedAtMs != null ? Math.max(0, nowTick - startedAtMs) : 0;
|
|
123
|
-
const elapsedLabel = startedAtMs != null ? formatTurnDurationMs(elapsedMs) : null;
|
|
124
|
-
// Intensidade do pulse: frame central (●) = full, bordas = dim
|
|
125
|
-
const pulseSymbol = PULSE_FRAMES[pulseFrame];
|
|
126
|
-
const isPulsePeak = pulseFrame === 4; // "●"
|
|
127
|
-
const isPulseDim = pulseFrame === 0; // "·"
|
|
128
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 0.5, children: [_jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsx(Text, { color: T.onSurfaceVariant, dimColor: isPulseDim, bold: isPulsePeak, children: pulseSymbol }), _jsx(Text, { children: " " }), _jsx(ShimmerText, { text: actionLine, baseColor: T.onSurfaceVariant ?? "#6e6e6e" }), elapsedLabel ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: T.m3OnSurface, dimColor: true, children: " \u00B7 " }), _jsx(Text, { color: T.m3OnSurface, dimColor: true, children: elapsedLabel })] })) : null] }), taskName ? (_jsx(Box, { paddingLeft: TOOL_DETAIL_PREFIX.length, children: _jsx(Text, { color: T.m3OnSurface, dimColor: true, wrap: "truncate", children: trimLine(taskName) }) })) : null] }));
|
|
129
|
-
};
|
|
130
|
-
export const WorkingTimer = memo(WorkingTimerComponent);
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useRef, memo } from "react";
|
|
3
|
-
import { Box, Text } from "ink";
|
|
4
|
-
import { ChatBlock, MessageResponse } from "../theme/m3Layout.js";
|
|
5
|
-
import { BLUMA_TERMINAL as T } from "../theme/blumaTerminal.js";
|
|
6
|
-
import { applyStreamEndFlush } from "./streamingTextFlush.js";
|
|
7
|
-
import { collapseRepeatedReasoningLines } from "./reasoningText.js";
|
|
8
|
-
const THROTTLE_MS = 500; // Increased from 300ms to reduce Ink re-render flicker
|
|
9
|
-
const MAX_VISIBLE_LINES = 20;
|
|
10
|
-
const StreamingTextComponent = ({ eventBus, onReasoningComplete, onAssistantContentComplete, }) => {
|
|
11
|
-
const [reasoning, setReasoning] = useState("");
|
|
12
|
-
const [assistantContent, setAssistantContent] = useState("");
|
|
13
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
14
|
-
const reasoningRef = useRef("");
|
|
15
|
-
const contentRef = useRef("");
|
|
16
|
-
const flushTimerRef = useRef(null);
|
|
17
|
-
const onReasoningCompleteRef = useRef(onReasoningComplete);
|
|
18
|
-
onReasoningCompleteRef.current = onReasoningComplete;
|
|
19
|
-
const onAssistantContentCompleteRef = useRef(onAssistantContentComplete);
|
|
20
|
-
onAssistantContentCompleteRef.current = onAssistantContentComplete;
|
|
21
|
-
const streamEndHandledRef = useRef(false);
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const syncVisible = () => {
|
|
24
|
-
setReasoning(reasoningRef.current);
|
|
25
|
-
setAssistantContent(contentRef.current);
|
|
26
|
-
};
|
|
27
|
-
const startFlushTimer = () => {
|
|
28
|
-
if (flushTimerRef.current)
|
|
29
|
-
return;
|
|
30
|
-
flushTimerRef.current = setInterval(() => {
|
|
31
|
-
setReasoning(reasoningRef.current);
|
|
32
|
-
setAssistantContent(contentRef.current);
|
|
33
|
-
}, THROTTLE_MS);
|
|
34
|
-
};
|
|
35
|
-
const handleStart = () => {
|
|
36
|
-
streamEndHandledRef.current = false;
|
|
37
|
-
reasoningRef.current = "";
|
|
38
|
-
contentRef.current = "";
|
|
39
|
-
setReasoning("");
|
|
40
|
-
setAssistantContent("");
|
|
41
|
-
setIsStreaming(true);
|
|
42
|
-
startFlushTimer();
|
|
43
|
-
};
|
|
44
|
-
const handleReasoningChunk = (data) => {
|
|
45
|
-
if (data.delta) {
|
|
46
|
-
reasoningRef.current += data.delta;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const handleContentChunk = (data) => {
|
|
50
|
-
if (data.delta) {
|
|
51
|
-
contentRef.current += data.delta;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const handleEnd = (payload) => {
|
|
55
|
-
if (streamEndHandledRef.current)
|
|
56
|
-
return;
|
|
57
|
-
streamEndHandledRef.current = true;
|
|
58
|
-
// Clear timers first
|
|
59
|
-
if (flushTimerRef.current) {
|
|
60
|
-
clearInterval(flushTimerRef.current);
|
|
61
|
-
flushTimerRef.current = null;
|
|
62
|
-
}
|
|
63
|
-
// IMMEDIATELY clear the streaming UI before adding to history
|
|
64
|
-
// This prevents the reasoning from appearing twice
|
|
65
|
-
const finalReasoning = reasoningRef.current;
|
|
66
|
-
const finalContent = contentRef.current;
|
|
67
|
-
setReasoning("");
|
|
68
|
-
setAssistantContent("");
|
|
69
|
-
reasoningRef.current = "";
|
|
70
|
-
contentRef.current = "";
|
|
71
|
-
setIsStreaming(false);
|
|
72
|
-
// NOW add to history (after UI is cleared)
|
|
73
|
-
applyStreamEndFlush({
|
|
74
|
-
finalReasoning,
|
|
75
|
-
finalContent,
|
|
76
|
-
payload,
|
|
77
|
-
onReasoningComplete: onReasoningCompleteRef.current,
|
|
78
|
-
onAssistantContentComplete: onAssistantContentCompleteRef.current,
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
eventBus.on("stream_start", handleStart);
|
|
82
|
-
eventBus.on("stream_reasoning_chunk", handleReasoningChunk);
|
|
83
|
-
eventBus.on("stream_chunk", handleContentChunk);
|
|
84
|
-
eventBus.on("stream_end", handleEnd);
|
|
85
|
-
return () => {
|
|
86
|
-
eventBus.off("stream_start", handleStart);
|
|
87
|
-
eventBus.off("stream_reasoning_chunk", handleReasoningChunk);
|
|
88
|
-
eventBus.off("stream_chunk", handleContentChunk);
|
|
89
|
-
eventBus.off("stream_end", handleEnd);
|
|
90
|
-
if (flushTimerRef.current) {
|
|
91
|
-
clearInterval(flushTimerRef.current);
|
|
92
|
-
flushTimerRef.current = null;
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}, [eventBus]);
|
|
96
|
-
if (!isStreaming || (!reasoning && !assistantContent)) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
const renderLines = (text, dim) => {
|
|
100
|
-
const normalized = collapseRepeatedReasoningLines(text);
|
|
101
|
-
const lines = normalized.split("\n");
|
|
102
|
-
let displayLines = lines;
|
|
103
|
-
let truncatedCount = 0;
|
|
104
|
-
if (lines.length > MAX_VISIBLE_LINES) {
|
|
105
|
-
truncatedCount = lines.length - MAX_VISIBLE_LINES;
|
|
106
|
-
displayLines = lines.slice(-MAX_VISIBLE_LINES);
|
|
107
|
-
}
|
|
108
|
-
return (_jsxs(Box, { flexDirection: "column", children: [truncatedCount > 0 ? (_jsxs(Text, { dimColor: true, children: ["\u2026 ", truncatedCount, " lines above hidden"] })) : null, displayLines.map((line, i) => (_jsx(Text, { dimColor: dim, color: dim ? undefined : T.m3OnSurface, children: line }, i)))] }));
|
|
109
|
-
};
|
|
110
|
-
return (_jsxs(ChatBlock, { marginBottom: 0, children: [reasoning ? (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: renderLines(reasoning, true) })) : null, assistantContent ? (_jsx(MessageResponse, { children: renderLines(assistantContent, false) })) : null] }));
|
|
111
|
-
};
|
|
112
|
-
export const StreamingText = memo(StreamingTextComponent);
|
|
113
|
-
export default StreamingText;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function collapseRepeatedReasoningLines(text) {
|
|
2
|
-
const raw = String(text ?? "").replace(/\r\n/g, "\n");
|
|
3
|
-
if (!raw.trim())
|
|
4
|
-
return "";
|
|
5
|
-
const lines = raw.split("\n");
|
|
6
|
-
const collapsed = [];
|
|
7
|
-
for (const line of lines) {
|
|
8
|
-
const current = line.trimEnd();
|
|
9
|
-
if (current === "" && collapsed[collapsed.length - 1] === "") {
|
|
10
|
-
continue;
|
|
11
|
-
}
|
|
12
|
-
if (collapsed.length > 0 && collapsed[collapsed.length - 1] === current) {
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
collapsed.push(current);
|
|
16
|
-
}
|
|
17
|
-
return collapsed.join("\n").trim();
|
|
18
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export function applyStreamEndFlush(params) {
|
|
2
|
-
const { finalReasoning, finalContent, payload, onReasoningComplete, onAssistantContentComplete, } = params;
|
|
3
|
-
if (finalReasoning && onReasoningComplete) {
|
|
4
|
-
onReasoningComplete(finalReasoning);
|
|
5
|
-
}
|
|
6
|
-
const trimmed = finalContent.trim();
|
|
7
|
-
const skipAssistant = payload?.omitAssistantFlush === true;
|
|
8
|
-
if (trimmed && onAssistantContentComplete && !skipAssistant) {
|
|
9
|
-
onAssistantContentComplete(finalContent);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Marcador de invocação de tool — bullet compacto (U+2022 •).
|
|
3
|
-
*/
|
|
4
|
-
export const TOOL_INVOCATION_MARK = "\u2022 ";
|
|
5
|
-
/** Indentação do detalhe da tool. */
|
|
6
|
-
export const TOOL_DETAIL_PREFIX = " ";
|
|
7
|
-
/**
|
|
8
|
-
* Prefixo da linha de resultado sob a invocação (• List / path) — ramo `└` em dim, estilo BluMa.
|
|
9
|
-
*/
|
|
10
|
-
export const RESULT_LINE_GUTTER = " \u2514 ";
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Paleta BluMa — magenta #FF70FE, ciano #53B2D2, laranja (#d77757).
|
|
3
|
-
* Chaves semânticas (`accent`, `suggestion`, …) mantêm compat com o resto da UI.
|
|
4
|
-
*/
|
|
5
|
-
export const BLUMA_TERMINAL = {
|
|
6
|
-
/** Tríade principal (marca) */
|
|
7
|
-
magenta: "#FF70FE",
|
|
8
|
-
blue: "#53B2D2",
|
|
9
|
-
/** Laranja (darkTheme) */
|
|
10
|
-
orange: "#d77757",
|
|
11
|
-
accent: "#d77757",
|
|
12
|
-
accentShimmer: "#eb9f7f",
|
|
13
|
-
/** UI estrutural, links, secções — ciano */
|
|
14
|
-
permission: "#53B2D2",
|
|
15
|
-
permissionShimmer: "#7ecce3",
|
|
16
|
-
promptBorder: "#53B2D2",
|
|
17
|
-
promptBorderShimmer: "#8fd4e8",
|
|
18
|
-
suggestion: "#53B2D2",
|
|
19
|
-
panelBorder: "#53B2D2",
|
|
20
|
-
link: "#53B2D2",
|
|
21
|
-
codeLabel: "#53B2D2",
|
|
22
|
-
heading1: "#53B2D2",
|
|
23
|
-
heading2: "#53B2D2",
|
|
24
|
-
m3Outline: "#53B2D2",
|
|
25
|
-
m3Rail: "#53B2D2",
|
|
26
|
-
/** Ferramentas, bullets fortes — magenta */
|
|
27
|
-
toolLabel: "#FF70FE",
|
|
28
|
-
headingDeep: "#FF70FE",
|
|
29
|
-
listBullet: "#FF70FE",
|
|
30
|
-
listBulletSub: "#53B2D2",
|
|
31
|
-
m3TonalOutline: "#FF70FE",
|
|
32
|
-
merged: "#d896ff",
|
|
33
|
-
subtle: "#505050",
|
|
34
|
-
inactive: "#999999",
|
|
35
|
-
text: "#ffffff",
|
|
36
|
-
success: "#4eba65",
|
|
37
|
-
err: "#ff6b80",
|
|
38
|
-
warn: "#ffc107",
|
|
39
|
-
diffAdded: "#225a2b",
|
|
40
|
-
diffRemoved: "#7a2936",
|
|
41
|
-
diffAddedWord: "#38a660",
|
|
42
|
-
diffRemovedWord: "#b3596b",
|
|
43
|
-
brandBlue: "#53B2D2",
|
|
44
|
-
brandMagenta: "#FF70FE",
|
|
45
|
-
/**
|
|
46
|
-
* Rampa neutra para shimmer no texto “a trabalhar”.
|
|
47
|
-
* O texto base fica branco e o brilho entra em cinzas suaves.
|
|
48
|
-
*/
|
|
49
|
-
workingShimmerRamp: [
|
|
50
|
-
"#6b6b6b", // cinza base
|
|
51
|
-
"#7b7b7b", // cinza base+
|
|
52
|
-
"#8f8f8f", // cinza médio
|
|
53
|
-
"#a8a8a8", // cinza claro
|
|
54
|
-
"#e6e6e6", // brilho máximo
|
|
55
|
-
"#a8a8a8", // cinza claro
|
|
56
|
-
"#8f8f8f", // cinza médio
|
|
57
|
-
"#7b7b7b", // cinza base+
|
|
58
|
-
],
|
|
59
|
-
muted: "#999999",
|
|
60
|
-
dim: "#999999",
|
|
61
|
-
code: "#999999",
|
|
62
|
-
linkUnderline: true,
|
|
63
|
-
toolMeta: "#999999",
|
|
64
|
-
rule: "#505050",
|
|
65
|
-
m3Label: "#999999",
|
|
66
|
-
m3OnSurface: "#ffffff",
|
|
67
|
-
// Material Design 3 surface colors
|
|
68
|
-
surface: "#1E1E1E",
|
|
69
|
-
surfaceVariant: "#2D2D2D",
|
|
70
|
-
surfaceContainer: "#333333",
|
|
71
|
-
onSurfaceVariant: "#B0B0B0",
|
|
72
|
-
outline: "#909090",
|
|
73
|
-
outlineVariant: "#707070",
|
|
74
|
-
// Cores semânticas adicionais para compatibilidade
|
|
75
|
-
primary: "#7C4DFF",
|
|
76
|
-
primaryVariant: "#6C3DCC",
|
|
77
|
-
onPrimary: "#FFFFFF",
|
|
78
|
-
warning: "#ffc107",
|
|
79
|
-
};
|
package/dist/theme/m3Layout.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Layout chat — MessageResponse: indentação suave (RESULT_LINE_GUTTER) + conteúdo.
|
|
4
|
-
* Contexto evita repetir o gutter quando há MessageResponse aninhados.
|
|
5
|
-
*/
|
|
6
|
-
import { createContext, useContext } from "react";
|
|
7
|
-
import { Box, Text } from "ink";
|
|
8
|
-
import { BLUMA_TERMINAL as T } from "./blumaTerminal.js";
|
|
9
|
-
import { RESULT_LINE_GUTTER } from "../constants/toolUiSymbols.js";
|
|
10
|
-
import { formatTurnDurationMs } from "../utils/formatTurnDurationMs.js";
|
|
11
|
-
export { formatTurnDurationMs };
|
|
12
|
-
const MessageResponseNestedContext = createContext(false);
|
|
13
|
-
export function MessageResponse({ children, height, }) {
|
|
14
|
-
const nested = useContext(MessageResponseNestedContext);
|
|
15
|
-
if (nested) {
|
|
16
|
-
return _jsx(_Fragment, { children: children });
|
|
17
|
-
}
|
|
18
|
-
return (_jsx(MessageResponseNestedContext.Provider, { value: true, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", height: height, overflow: height !== undefined ? "hidden" : undefined, children: [_jsx(Text, { dimColor: true, children: RESULT_LINE_GUTTER }), _jsx(Box, { flexShrink: 1, flexGrow: 1, flexDirection: "column", children: children })] }) }));
|
|
19
|
-
}
|
|
20
|
-
export function ChatBlock({ children, marginBottom = 1, }) {
|
|
21
|
-
return (_jsx(Box, { flexDirection: "column", marginBottom: marginBottom, children: children }));
|
|
22
|
-
}
|
|
23
|
-
export function ChatUserImageBlock({ imageCount, caption, captionDim = false, }) {
|
|
24
|
-
if (imageCount < 1)
|
|
25
|
-
return null;
|
|
26
|
-
const cap = caption?.trim() ?? "";
|
|
27
|
-
const capLines = cap.length > 0 ? cap.split("\n") : [];
|
|
28
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [Array.from({ length: imageCount }, (_, i) => (_jsx(Text, { bold: true, color: T.orange, children: `[IMAGE #${i + 1}]` }, `img-${i}`))), capLines.map((line, i) => (_jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [_jsx(Text, { dimColor: true, children: i === 0 ? "└─ " : " " }), captionDim ? (_jsx(Text, { dimColor: true, bold: true, wrap: "wrap", children: line })) : (_jsx(Text, { color: T.m3OnSurface, bold: true, wrap: "wrap", children: line }))] }, `cap-${i}`)))] }));
|
|
29
|
-
}
|
|
30
|
-
/** @deprecated usar ChatUserImageBlock */
|
|
31
|
-
export function ChatUserImageTree({ count }) {
|
|
32
|
-
return _jsx(ChatUserImageBlock, { imageCount: count });
|
|
33
|
-
}
|
|
34
|
-
/** Mensagem do utilizador: moldura só topo/fundo (como o InputPrompt), sem `>`. */
|
|
35
|
-
export function ChatUserMessage({ children }) {
|
|
36
|
-
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: T.m3OnSurface, borderLeft: false, borderRight: false, paddingX: 1, children: children }) }));
|
|
37
|
-
}
|
|
38
|
-
export function ChatMeta({ children }) {
|
|
39
|
-
return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: children }) }));
|
|
40
|
-
}
|
|
41
|
-
export function ChatStatusRow({ children }) {
|
|
42
|
-
return (_jsxs(Box, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap", children: [_jsxs(Text, { color: T.orange, bold: true, children: ["*", " "] }), children] }));
|
|
43
|
-
}
|
|
44
|
-
export function ChatTurnDuration({ durationMs }) {
|
|
45
|
-
return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: T.blue, children: "\u00B7 " }), _jsx(Text, { color: T.magenta, children: formatTurnDurationMs(durationMs) })] }) }));
|
|
46
|
-
}
|
|
47
|
-
export function Divider({ width, title, color, }) {
|
|
48
|
-
const w = width ?? 60;
|
|
49
|
-
const char = "─";
|
|
50
|
-
if (title) {
|
|
51
|
-
const titleWidth = title.length + 2;
|
|
52
|
-
const sideWidth = Math.max(0, w - titleWidth);
|
|
53
|
-
const leftWidth = Math.floor(sideWidth / 2);
|
|
54
|
-
const rightWidth = sideWidth - leftWidth;
|
|
55
|
-
return (_jsxs(Text, { color: color, dimColor: !color, children: [char.repeat(leftWidth), " ", _jsx(Text, { dimColor: true, children: title }), " ", char.repeat(rightWidth)] }));
|
|
56
|
-
}
|
|
57
|
-
return _jsx(Text, { color: color, dimColor: !color, children: char.repeat(w) });
|
|
58
|
-
}
|
|
59
|
-
export function M3Surface({ children, marginBottom = 1, }) {
|
|
60
|
-
return _jsx(ChatBlock, { marginBottom: marginBottom, children: children });
|
|
61
|
-
}
|
|
62
|
-
export const M3UserBubble = ChatUserMessage;
|
|
63
|
-
export const M3SystemRow = ChatMeta;
|
|
64
|
-
export const M3InputDock = ({ children }) => (_jsx(Box, { flexDirection: "column", marginTop: 1, children: children }));
|
|
65
|
-
export const M3StatusStrip = ChatStatusRow;
|
|
66
|
-
export function TerminalRule({ width = 48 }) {
|
|
67
|
-
return _jsx(Text, { dimColor: true, children: "─".repeat(Math.max(8, width)) });
|
|
68
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/** E.g. 211s → `3min · 31s`; under 60s → `45s` or `9.3s` if under 10s. */
|
|
2
|
-
export function formatTurnDurationMs(ms) {
|
|
3
|
-
if (ms < 0) {
|
|
4
|
-
return "0s";
|
|
5
|
-
}
|
|
6
|
-
const secTotal = Math.round(ms / 1000);
|
|
7
|
-
if (secTotal < 60) {
|
|
8
|
-
if (ms < 10_000) {
|
|
9
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
10
|
-
}
|
|
11
|
-
return `${secTotal}s`;
|
|
12
|
-
}
|
|
13
|
-
const min = Math.floor(secTotal / 60);
|
|
14
|
-
const sec = secTotal % 60;
|
|
15
|
-
return `${min}min · ${sec}s`;
|
|
16
|
-
}
|