@semalt-ai/code 1.7.0 → 1.8.1
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/.claude/settings.local.json +8 -0
- package/ARCHITECTURE.md +99 -0
- package/CLAUDE.md +349 -0
- package/index.js +69 -7
- package/lib/agent.js +577 -39
- package/lib/api.js +285 -79
- package/lib/args.js +31 -0
- package/lib/audit.js +31 -0
- package/lib/commands.js +1006 -307
- package/lib/config.js +51 -5
- package/lib/constants.js +72 -0
- package/lib/context.js +2 -6
- package/lib/metrics.js +94 -0
- package/lib/permissions.js +180 -49
- package/lib/prompts.js +96 -13
- package/lib/storage.js +96 -0
- package/lib/tools.js +1009 -35
- package/lib/ui/ansi.js +65 -0
- package/lib/ui/chat-history.js +217 -0
- package/lib/ui/create-ui.js +474 -0
- package/lib/ui/diff.js +243 -0
- package/lib/ui/input-field.js +1176 -0
- package/lib/ui/layout.js +53 -0
- package/lib/ui/legacy.js +130 -0
- package/lib/ui/status-bar.js +131 -0
- package/lib/ui/stream.js +158 -0
- package/lib/ui/utils.js +45 -0
- package/lib/ui.js +42 -598
- package/package.json +1 -1
- package/path +1 -0
package/lib/ui/ansi.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const RST = '\x1b[0m';
|
|
4
|
+
const BOLD = '\x1b[1m';
|
|
5
|
+
const DIM = '\x1b[2m';
|
|
6
|
+
|
|
7
|
+
const THEME = {
|
|
8
|
+
user: '\x1b[36m',
|
|
9
|
+
agent: '\x1b[32m',
|
|
10
|
+
sys: '\x1b[33m',
|
|
11
|
+
error: '\x1b[31;1m',
|
|
12
|
+
warn: '\x1b[33;1m',
|
|
13
|
+
tool: '\x1b[35m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const FG_GRAY = '\x1b[38;5;245m';
|
|
19
|
+
const FG_DARK = '\x1b[38;5;240m';
|
|
20
|
+
const FG_BLUE = '\x1b[38;5;75m';
|
|
21
|
+
const FG_CYAN = '\x1b[38;5;116m';
|
|
22
|
+
const FG_GREEN = '\x1b[38;5;114m';
|
|
23
|
+
const FG_YELLOW = '\x1b[38;5;222m';
|
|
24
|
+
const FG_RED = '\x1b[38;5;203m';
|
|
25
|
+
const FG_TEAL = '\x1b[38;5;73m';
|
|
26
|
+
|
|
27
|
+
const BOX_H = '─';
|
|
28
|
+
const BOX_V = '│';
|
|
29
|
+
const BOX_TL = '╭';
|
|
30
|
+
const BOX_TR = '╮';
|
|
31
|
+
const BOX_BL = '╰';
|
|
32
|
+
const BOX_BR = '╯';
|
|
33
|
+
|
|
34
|
+
const FG_CODE_BG = '\x1b[48;5;236m';
|
|
35
|
+
const BG_SELECTED = '\x1b[48;5;237m';
|
|
36
|
+
const FG_CODE_BORDER = '\x1b[38;5;240m';
|
|
37
|
+
const FG_CODE_LANG = '\x1b[38;5;75m';
|
|
38
|
+
const FG_TAG = '\x1b[38;5;176m';
|
|
39
|
+
const FG_FILEPATH = '\x1b[38;5;222m';
|
|
40
|
+
|
|
41
|
+
const KEYWORDS = new Set([
|
|
42
|
+
'def', 'class', 'import', 'from', 'return', 'if', 'else', 'elif',
|
|
43
|
+
'for', 'while', 'try', 'except', 'finally', 'with', 'as', 'in',
|
|
44
|
+
'not', 'and', 'or', 'is', 'None', 'True', 'False', 'async', 'await',
|
|
45
|
+
'function', 'const', 'let', 'var', 'export', 'require', 'func',
|
|
46
|
+
'type', 'struct', 'interface', 'package', 'fn', 'pub', 'use',
|
|
47
|
+
'mod', 'impl', 'match', 'enum', 'self', 'print', 'len', 'range',
|
|
48
|
+
'yield', 'lambda', 'raise', 'pass', 'break', 'continue', 'del',
|
|
49
|
+
'global', 'nonlocal', 'assert',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const SPINNER_DEFS = {
|
|
53
|
+
thinking: { frames: ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'], color: '\x1b[36m' },
|
|
54
|
+
streaming: { frames: ['▁','▂','▃','▄','▅','▆','▇','█','▇','▆','▅','▄','▃','▂'], color: '\x1b[32m' },
|
|
55
|
+
tool: { frames: ['⣾','⣽','⣻','⢿','⡿','⣟','⣯','⣷'], color: '\x1b[33m' },
|
|
56
|
+
waiting_download: { frames: ['⬇ ','⬇⠂','⬇⠆','⬇⠇','⬇⠧','⬇⠷','⬇⠿','⬇⠾','⬇⠼','⬇⠸','⬇⠰','⬇⠠'], color: '\x1b[38;5;75m' },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
RST, BOLD, DIM, THEME,
|
|
61
|
+
FG_GRAY, FG_DARK, FG_BLUE, FG_CYAN, FG_GREEN, FG_YELLOW, FG_RED, FG_TEAL,
|
|
62
|
+
BOX_H, BOX_V, BOX_TL, BOX_TR, BOX_BL, BOX_BR,
|
|
63
|
+
FG_CODE_BG, BG_SELECTED, FG_CODE_BORDER, FG_CODE_LANG, FG_TAG, FG_FILEPATH,
|
|
64
|
+
KEYWORDS, SPINNER_DEFS,
|
|
65
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { RST, DIM, BOLD, FG_CYAN, FG_GREEN, FG_YELLOW, FG_RED, FG_DARK, FG_GRAY } = require('./ansi');
|
|
4
|
+
const { getCols, stripAnsi } = require('./utils');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function safeContent(text) {
|
|
8
|
+
if (!text) return '';
|
|
9
|
+
return text.replace(/<\/?[a-zA-Z_][a-zA-Z0-9_]*(\s[^>]*)?>/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const TOOL_ICONS = {
|
|
13
|
+
exec: '▶', shell: '▶', run_command: '▶', run: '▶',
|
|
14
|
+
read_file: '[R]', write_file: '✎',
|
|
15
|
+
list_dir: '[D]', search_files: '[D]',
|
|
16
|
+
http_get: '↓', download: '↓',
|
|
17
|
+
ask_user: '?',
|
|
18
|
+
store_memory: '◆', recall_memory: '◆', list_memories: '◆',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function _fmtTime(ts) {
|
|
22
|
+
const d = ts instanceof Date ? ts : (ts ? new Date(ts) : new Date());
|
|
23
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const BG_USER = '\x1b[48;5;237m';
|
|
27
|
+
|
|
28
|
+
function _printUser(content, ts) {
|
|
29
|
+
const time = _fmtTime(ts);
|
|
30
|
+
process.stdout.write(`\n${FG_CYAN}▸ You${RST} ${DIM}${time}${RST}\n`);
|
|
31
|
+
for (const line of (content || '').split('\n')) {
|
|
32
|
+
process.stdout.write(`${BG_USER} ${line}\x1b[K${RST}\n`);
|
|
33
|
+
}
|
|
34
|
+
process.stdout.write('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _printAI(content, ts) {
|
|
38
|
+
const time = _fmtTime(ts);
|
|
39
|
+
process.stdout.write(`\n${FG_GREEN}▸ AI-agent${RST} ${DIM}${time}${RST}\n`);
|
|
40
|
+
for (const line of (content || '').split('\n')) {
|
|
41
|
+
process.stdout.write(` ${line}\n`);
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const MAX_TOOL_DISPLAY = 15;
|
|
47
|
+
|
|
48
|
+
class ChatHistory {
|
|
49
|
+
constructor() {
|
|
50
|
+
this._streamWritten = false;
|
|
51
|
+
this._streamStart = null;
|
|
52
|
+
this._msgById = {};
|
|
53
|
+
this._msgLineCount = {};
|
|
54
|
+
this._pendingWs = '';
|
|
55
|
+
this._didStream = false;
|
|
56
|
+
this._toolExpanded = {};
|
|
57
|
+
this._lastExpandableId = null;
|
|
58
|
+
this._toolIdCounter = 0;
|
|
59
|
+
this._messages = [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_flush() {
|
|
63
|
+
this._pendingWs = '';
|
|
64
|
+
if (this._streamWritten) {
|
|
65
|
+
process.stdout.write('\n\n');
|
|
66
|
+
this._streamWritten = false;
|
|
67
|
+
this._streamStart = null;
|
|
68
|
+
this._didStream = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
addMessage(msg) {
|
|
73
|
+
if (msg.role === 'think') return;
|
|
74
|
+
const content = safeContent(msg.content || '');
|
|
75
|
+
if ((msg.role === 'assistant' || msg.role === 'system') && !content.trim()) return;
|
|
76
|
+
|
|
77
|
+
this._flush();
|
|
78
|
+
|
|
79
|
+
if (msg.id) this._msgById[msg.id] = msg;
|
|
80
|
+
|
|
81
|
+
if (msg.role === 'user') {
|
|
82
|
+
_printUser(content, msg.ts);
|
|
83
|
+
} else if (msg.role === 'assistant') {
|
|
84
|
+
_printAI(content, msg.ts);
|
|
85
|
+
} else if (msg.role === 'shell') {
|
|
86
|
+
const cmd = msg.cmd || '';
|
|
87
|
+
const out = safeContent(msg.content || '');
|
|
88
|
+
process.stdout.write(`\n${DIM} $ ${cmd}${RST}\n`);
|
|
89
|
+
if (out.trim()) {
|
|
90
|
+
for (const line of out.split('\n')) {
|
|
91
|
+
if (line.trim()) process.stdout.write(`${DIM} ${line}${RST}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
process.stdout.write('\n');
|
|
95
|
+
} else if (msg.role === 'tool') {
|
|
96
|
+
const icon = TOOL_ICONS[msg.tag] || '⚙';
|
|
97
|
+
process.stdout.write(`${DIM} ${icon} ${content}${RST}\n`);
|
|
98
|
+
let lc = 1;
|
|
99
|
+
if (msg.output) {
|
|
100
|
+
// Wrap long lines so char-heavy single-line output (e.g. minified HTML)
|
|
101
|
+
// is treated the same as multi-line output for truncation purposes.
|
|
102
|
+
const wrapAt = Math.max(60, getCols() - 8);
|
|
103
|
+
const outLines = [];
|
|
104
|
+
for (const raw of msg.output.split('\n')) {
|
|
105
|
+
if (!raw.trim()) continue;
|
|
106
|
+
if (raw.length <= wrapAt) {
|
|
107
|
+
outLines.push(raw);
|
|
108
|
+
} else {
|
|
109
|
+
for (let i = 0; i < raw.length; i += wrapAt) {
|
|
110
|
+
outLines.push(raw.slice(i, i + wrapAt));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const truncatable = outLines.length > MAX_TOOL_DISPLAY;
|
|
115
|
+
|
|
116
|
+
if (truncatable && !msg.id) {
|
|
117
|
+
msg.id = `tool-${++this._toolIdCounter}`;
|
|
118
|
+
}
|
|
119
|
+
if (truncatable && msg.id) {
|
|
120
|
+
this._msgById[msg.id] = msg;
|
|
121
|
+
this._lastExpandableId = msg.id;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const expanded = truncatable && msg.id && this._toolExpanded[msg.id];
|
|
125
|
+
const visible = (truncatable && !expanded) ? outLines.slice(0, MAX_TOOL_DISPLAY) : outLines;
|
|
126
|
+
const remaining = outLines.length - visible.length;
|
|
127
|
+
|
|
128
|
+
for (const ol of visible) { process.stdout.write(`${DIM} ${ol}${RST}\n`); lc++; }
|
|
129
|
+
if (remaining > 0) {
|
|
130
|
+
process.stdout.write(`${DIM} … ${remaining} more lines ${FG_DARK}(ctrl+o to expand ${remaining} rows)${RST}\n`);
|
|
131
|
+
lc++;
|
|
132
|
+
} else if (truncatable && expanded) {
|
|
133
|
+
process.stdout.write(`${DIM} ${FG_DARK}(ctrl+o to collapse)${RST}\n`);
|
|
134
|
+
lc++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (msg.id) this._msgLineCount[msg.id] = lc;
|
|
138
|
+
} else if (msg.role === 'permission') {
|
|
139
|
+
process.stdout.write(`\n${FG_YELLOW} ⚠ Permission required: ${content}${RST}\n`);
|
|
140
|
+
} else {
|
|
141
|
+
const isErr = !!msg.isError && !msg.isWarning;
|
|
142
|
+
const color = isErr ? FG_RED : FG_YELLOW;
|
|
143
|
+
const prefix = isErr ? '✕' : '⚠';
|
|
144
|
+
const lines = content.split('\n');
|
|
145
|
+
process.stdout.write(`${color} ${prefix} ${lines[0]}${RST}\n`);
|
|
146
|
+
for (let i = 1; i < lines.length; i++) {
|
|
147
|
+
process.stdout.write(`${color} ${lines[i]}${RST}\n`);
|
|
148
|
+
}
|
|
149
|
+
if (msg.id) this._msgLineCount[msg.id] = lines.length;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
streamToken(token) {
|
|
154
|
+
if (!this._streamWritten) {
|
|
155
|
+
if (!token.trim()) {
|
|
156
|
+
this._pendingWs += token;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const time = _fmtTime(new Date());
|
|
160
|
+
process.stdout.write(`\n${FG_GREEN}▸ AI-agent${RST} ${DIM}${time}${RST}\n `);
|
|
161
|
+
this._streamWritten = true;
|
|
162
|
+
this._streamStart = new Date();
|
|
163
|
+
this._pendingWs = '';
|
|
164
|
+
}
|
|
165
|
+
process.stdout.write(token.replace(/\n/g, '\n '));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
clearStreamingContent() {
|
|
169
|
+
this._flush();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
finalizeLastMessage(cleanContent) {
|
|
173
|
+
if (this._streamWritten) {
|
|
174
|
+
this._flush();
|
|
175
|
+
} else if (!this._didStream && cleanContent && cleanContent.trim()) {
|
|
176
|
+
_printAI(cleanContent, new Date());
|
|
177
|
+
}
|
|
178
|
+
this._didStream = false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
clearMessages() {
|
|
182
|
+
this._streamWritten = false;
|
|
183
|
+
this._pendingWs = '';
|
|
184
|
+
this._didStream = false;
|
|
185
|
+
this._msgById = {};
|
|
186
|
+
this._msgLineCount = {};
|
|
187
|
+
this._toolExpanded = {};
|
|
188
|
+
this._lastExpandableId = null;
|
|
189
|
+
this._messages = [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
toggleLastExpand() {
|
|
193
|
+
const id = this._lastExpandableId;
|
|
194
|
+
if (!id) return;
|
|
195
|
+
const msg = this._msgById[id];
|
|
196
|
+
if (!msg) return;
|
|
197
|
+
this._toolExpanded[id] = !this._toolExpanded[id];
|
|
198
|
+
this.addMessage(msg);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
rerenderById(id) {
|
|
202
|
+
const msg = this._msgById[id];
|
|
203
|
+
if (!msg) return;
|
|
204
|
+
this.addMessage(msg);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
removeById(id) {
|
|
208
|
+
delete this._msgById[id];
|
|
209
|
+
delete this._msgLineCount[id];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
invalidateCache() {}
|
|
213
|
+
scrollUp() {}
|
|
214
|
+
scrollDown() {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { ChatHistory, safeContent, TOOL_ICONS };
|