@nomad-e/bluma-cli 0.2.1 → 0.3.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.
@@ -0,0 +1,79 @@
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
+ };
@@ -0,0 +1,68 @@
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
+ }
@@ -0,0 +1,16 @@
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
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Mapeia tool names + argumentos para labels de ação em tempo real (WorkingTimer).
3
+ * Exemplo: shell_command → "Executing command", edit_tool → "Editing file", etc.
4
+ */
5
+ function parseArgsRecord(args) {
6
+ if (args == null)
7
+ return {};
8
+ if (typeof args === "string") {
9
+ try {
10
+ return JSON.parse(args);
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ }
16
+ if (typeof args === "object") {
17
+ return args;
18
+ }
19
+ return {};
20
+ }
21
+ /**
22
+ * Gera label de ação para o WorkingTimer baseado na tool e argumentos.
23
+ * Retorna string curta e descritiva (ex.: "Executing command", "Writing 2 files").
24
+ */
25
+ export function getToolActionLabel(toolName, args) {
26
+ const p = parseArgsRecord(args);
27
+ switch (toolName) {
28
+ case "shell_command":
29
+ case "run_command": {
30
+ const cmd = typeof p.command === "string" ? p.command : "";
31
+ const truncated = cmd.length > 40 ? `${cmd.slice(0, 40)}…` : cmd;
32
+ return truncated ? `Executing: ${truncated}` : "Executing command";
33
+ }
34
+ case "command_status":
35
+ return "Checking command status";
36
+ case "send_command_input":
37
+ return "Sending input to command";
38
+ case "kill_command":
39
+ return "Terminating command";
40
+ case "read_file_lines": {
41
+ const filepath = typeof p.filepath === "string" ? p.filepath : "";
42
+ const file = filepath.split("/").pop() || filepath;
43
+ return file ? `Reading: ${file}` : "Reading file";
44
+ }
45
+ case "count_file_lines": {
46
+ const filepath = typeof p.filepath === "string" ? p.filepath : "";
47
+ const file = filepath.split("/").pop() || filepath;
48
+ return file ? `Counting: ${file}` : "Counting lines";
49
+ }
50
+ case "edit_tool": {
51
+ const edits = p.edits;
52
+ const count = Array.isArray(edits) ? edits.length : 1;
53
+ const filepath = typeof p.file_path === "string" ? p.file_path : (Array.isArray(edits) && edits[0]?.file_path ? edits[0].file_path : "");
54
+ const file = filepath ? filepath.split("/").pop() : "file";
55
+ return count === 1 ? `Editing: ${file}` : `Editing ${count} changes`;
56
+ }
57
+ case "file_write": {
58
+ const filepath = typeof p.filepath === "string" ? p.filepath : "";
59
+ const file = filepath.split("/").pop() || filepath;
60
+ return file ? `Writing: ${file}` : "Writing file";
61
+ }
62
+ case "grep_search": {
63
+ const query = typeof p.query === "string" ? p.query : "";
64
+ const truncated = query.length > 30 ? `${query.slice(0, 30)}…` : query;
65
+ return truncated ? `Searching: "${truncated}"` : "Searching";
66
+ }
67
+ case "find_by_name": {
68
+ const pattern = typeof p.pattern === "string" ? p.pattern : "";
69
+ return pattern ? `Finding: ${pattern}` : "Finding files";
70
+ }
71
+ case "view_file_outline": {
72
+ const filepath = typeof p.file_path === "string" ? p.file_path : "";
73
+ const file = filepath.split("/").pop() || filepath;
74
+ return file ? `Outline: ${file}` : "Reading outline";
75
+ }
76
+ case "web_fetch": {
77
+ const url = typeof p.url === "string" ? p.url : "";
78
+ const truncated = url.length > 40 ? `${url.slice(0, 40)}…` : url;
79
+ return truncated ? `Fetching: ${truncated}` : "Fetching URL";
80
+ }
81
+ case "search_web": {
82
+ const query = typeof p.query === "string" ? p.query : "";
83
+ const truncated = query.length > 30 ? `${query.slice(0, 30)}…` : query;
84
+ return truncated ? `Web search: "${truncated}"` : "Searching web";
85
+ }
86
+ case "spawn_agent": {
87
+ const title = typeof p.title === "string" ? p.title : "";
88
+ const task = typeof p.task === "string" ? p.task : "";
89
+ const label = title || (task ? task.slice(0, 40) : "task");
90
+ return `Spawning agent: ${label}`;
91
+ }
92
+ case "wait_agent":
93
+ return "Waiting for agent";
94
+ case "list_agents":
95
+ return "Listing agents";
96
+ case "todo": {
97
+ const action = typeof p.action === "string" ? p.action : "update";
98
+ return `Updating todo: ${action}`;
99
+ }
100
+ case "task_boundary": {
101
+ const mode = typeof p.mode === "string" ? p.mode : "";
102
+ const taskName = typeof p.task_name === "string" ? p.task_name : "";
103
+ return taskName ? `${mode}: ${taskName}` : `Task ${mode}`;
104
+ }
105
+ case "task_create":
106
+ case "task_list":
107
+ case "task_get":
108
+ case "task_update":
109
+ case "task_stop": {
110
+ const title = typeof p.title === "string" ? p.title : "";
111
+ return title ? `Task: ${title}` : "Managing task";
112
+ }
113
+ case "load_skill": {
114
+ const skill = typeof p.skill_name === "string" ? p.skill_name : "";
115
+ return skill ? `Loading skill: ${skill}` : "Loading skill";
116
+ }
117
+ case "coding_memory": {
118
+ const action = typeof p.action === "string" ? p.action : "update";
119
+ return `Coding memory: ${action}`;
120
+ }
121
+ case "create_artifact": {
122
+ const filename = typeof p.filename === "string" ? p.filename : "";
123
+ return filename ? `Creating: ${filename}` : "Creating artifact";
124
+ }
125
+ case "read_artifact": {
126
+ const filename = typeof p.filename === "string" ? p.filename : "";
127
+ return filename ? `Reading: ${filename}` : "Reading artifact";
128
+ }
129
+ case "message":
130
+ return "Writing message";
131
+ case "ask_user_question":
132
+ return "Asking question";
133
+ case "enter_plan_mode":
134
+ return "Entering plan mode";
135
+ case "exit_plan_mode":
136
+ return "Exiting plan mode";
137
+ case "list_mcp_resources":
138
+ return "Listing MCP resources";
139
+ case "read_mcp_resource": {
140
+ const server = typeof p.server === "string" ? p.server : "";
141
+ const uri = typeof p.uri === "string" ? p.uri : "";
142
+ return `Reading MCP: ${server || uri || "resource"}`;
143
+ }
144
+ case "cron_create":
145
+ return "Scheduling reminder";
146
+ case "cron_list":
147
+ return "Listing reminders";
148
+ case "cron_delete":
149
+ return "Canceling reminder";
150
+ case "notebook_edit": {
151
+ const filepath = typeof p.filepath === "string" ? p.filepath : "";
152
+ const file = filepath.split("/").pop() || filepath;
153
+ return file ? `Editing notebook: ${file}` : "Editing notebook";
154
+ }
155
+ case "lsp_query": {
156
+ const operation = typeof p.operation === "string" ? p.operation : "";
157
+ return `LSP: ${operation || "query"}`;
158
+ }
159
+ default: {
160
+ // Fallback: prettify snake_case
161
+ const pretty = toolName
162
+ .replace(/_/g, " ")
163
+ .replace(/\b\w/g, (c) => c.toUpperCase());
164
+ return pretty || "Working";
165
+ }
166
+ }
167
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",
@@ -14,8 +14,12 @@
14
14
  "@types/diff": "^7.0.2",
15
15
  "@types/glob": "^8.1.0",
16
16
  "@types/jest": "^30.0.0",
17
+ "@types/lodash-es": "^4.17.12",
17
18
  "@types/node": "^20.14.2",
18
- "@types/react": "^18.3.3",
19
+ "@types/react": "^19.2.14",
20
+ "@types/react-reconciler": "^0.33.0",
21
+ "@types/semver": "^7.7.1",
22
+ "@types/signal-exit": "^3.0.4",
19
23
  "@types/uuid": "^9.0.8",
20
24
  "@typescript-eslint/eslint-plugin": "^8.58.2",
21
25
  "@typescript-eslint/parser": "^8.58.2",
@@ -27,17 +31,20 @@
27
31
  "eslint-plugin-react": "^7.37.5",
28
32
  "eslint-plugin-react-hooks": "^7.0.1",
29
33
  "glob": "^10.5.0",
34
+ "husky": "^9.1.7",
30
35
  "ink-testing-library": "^4.0.0",
31
36
  "jest": "^30.0.5",
37
+ "lodash-es": "^4.18.1",
32
38
  "nodemon": "^3.1.10",
33
- "react": "^18.3.1",
39
+ "supports-hyperlinks": "^4.4.0",
34
40
  "ts-node-dev": "^2.0.0",
35
- "typescript": "^5.4.5"
41
+ "typescript": "^5.4.5",
42
+ "usehooks-ts": "^3.1.1"
36
43
  },
37
44
  "type": "module",
38
45
  "main": "dist/main.js",
39
46
  "scripts": {
40
- "build": "npx tsc --noEmit && node scripts/build.js",
47
+ "build": "node scripts/build.js",
41
48
  "build:native": "cd native && npm run build",
42
49
  "build:all": "npm run build:native && npm run build",
43
50
  "start": "npx tsc --noEmit && node scripts/build.js && clear && node dist/main.js",
@@ -47,28 +54,44 @@
47
54
  "test:parallel:fast": "TEST_WORKERS=8 node scripts/test-parallel.js",
48
55
  "lint": "eslint src --ext .ts,.tsx",
49
56
  "lint:fix": "eslint src --ext .ts,.tsx --fix",
50
- "prepack": "npm run build"
57
+ "prepack": "npm run build",
58
+ "precommit": "node .pre-commit.cjs",
59
+ "prepare": "husky"
51
60
  },
52
61
  "keywords": [],
53
62
  "dependencies": {
63
+ "@alcalzone/ansi-tokenize": "^0.3.0",
64
+ "@anthropic-ai/sdk": "^0.92.0",
54
65
  "@modelcontextprotocol/sdk": "^1.17.0",
55
66
  "@nomad-e/bluma-cli": "^0.1.68",
67
+ "@opentelemetry/api": "^1.9.1",
68
+ "@opentelemetry/api-logs": "^0.216.0",
69
+ "@opentelemetry/sdk-logs": "^0.216.0",
70
+ "@opentelemetry/sdk-metrics": "^2.7.1",
71
+ "@opentelemetry/sdk-trace-base": "^2.7.1",
72
+ "@types/bun": "^1.3.13",
73
+ "@types/jquery": "^4.0.0",
74
+ "@types/react-dom": "^19.2.3",
75
+ "auto-bind": "^5.0.1",
76
+ "axios": "^1.16.0",
77
+ "bidi-js": "^1.0.3",
56
78
  "chalk": "^5.5.0",
57
79
  "clipboardy": "^5.3.1",
58
80
  "diff": "^8.0.2",
59
81
  "dotenv": "^16.4.5",
60
- "ink": "^5.2.1",
61
- "ink-big-text": "^2.0.0",
62
- "ink-spinner": "^5.0.0",
63
- "ink-text-input": "^6.0.0",
82
+ "env-paths": "^4.0.0",
83
+ "jquery": "^4.0.0",
64
84
  "js-tiktoken": "^1.0.21",
65
85
  "latest-version": "^9.0.0",
66
86
  "marked": "^16.1.2",
67
87
  "openai": "^4.47.3",
68
- "react-devtools-core": "^4.28.5",
88
+ "react": "^19.2.5",
89
+ "react-dom": "^19.2.5",
90
+ "react-reconciler": "^0.31.0",
69
91
  "read-package-up": "^11.0.0",
70
92
  "semver": "^7.7.4",
71
- "uuid": "^9.0.1"
93
+ "uuid": "^9.0.1",
94
+ "yoga-layout": "^3.2.1"
72
95
  },
73
96
  "files": [
74
97
  "dist/"