@semalt-ai/code 1.4.3 → 1.4.4
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/index.js +245 -110
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -105,53 +105,184 @@ function boxLine(text, width) {
|
|
|
105
105
|
return ` ${FG_DARK}${BOX_V}${RST} ${text}${' '.repeat(pad)}${FG_DARK}${BOX_V}${RST}`;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
function dropLastChar(text) {
|
|
109
|
+
const chars = Array.from(text || '');
|
|
110
|
+
chars.pop();
|
|
111
|
+
return chars.join('');
|
|
112
|
+
}
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
function insertCharAt(text, index, value) {
|
|
115
|
+
const chars = Array.from(text || '');
|
|
116
|
+
chars.splice(index, 0, value);
|
|
117
|
+
return chars.join('');
|
|
118
|
+
}
|
|
112
119
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
function removeCharAt(text, index) {
|
|
121
|
+
const chars = Array.from(text || '');
|
|
122
|
+
chars.splice(index, 1);
|
|
123
|
+
return chars.join('');
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
function
|
|
126
|
+
function isPrintableKey(str, key = {}) {
|
|
127
|
+
if (!str || key.ctrl || key.meta) return false;
|
|
128
|
+
if (key.name === 'return' || key.name === 'enter' || key.name === 'tab') return false;
|
|
129
|
+
if (key.name && ['up', 'down', 'left', 'right', 'home', 'end', 'pageup', 'pagedown', 'escape', 'delete', 'backspace'].includes(key.name)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return !/[\x00-\x1f\x7f]/.test(str);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readInteractiveInput(promptText, options = {}) {
|
|
136
|
+
const {
|
|
137
|
+
allowed = null,
|
|
138
|
+
immediate = false,
|
|
139
|
+
trim = false,
|
|
140
|
+
allowCursorNavigation = false,
|
|
141
|
+
} = options;
|
|
142
|
+
|
|
120
143
|
return new Promise((resolve) => {
|
|
121
144
|
if (!process.stdin.isTTY) {
|
|
122
145
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
123
|
-
rl.question(
|
|
146
|
+
rl.question(promptText, (answer) => {
|
|
124
147
|
rl.close();
|
|
125
|
-
resolve((answer || '').trim());
|
|
148
|
+
resolve({ type: 'submit', value: trim ? (answer || '').trim() : (answer || '') });
|
|
126
149
|
});
|
|
127
150
|
return;
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
const wasRaw = typeof process.stdin.isRaw === 'boolean' ? process.stdin.isRaw : false;
|
|
154
|
+
let buffer = '';
|
|
155
|
+
let cursor = 0;
|
|
156
|
+
let done = false;
|
|
157
|
+
|
|
131
158
|
readline.emitKeypressEvents(process.stdin);
|
|
132
159
|
process.stdin.setRawMode(true);
|
|
133
160
|
process.stdin.resume();
|
|
134
|
-
|
|
161
|
+
|
|
162
|
+
const render = () => {
|
|
163
|
+
readline.cursorTo(process.stdout, 0);
|
|
164
|
+
readline.clearLine(process.stdout, 0);
|
|
165
|
+
process.stdout.write(`${promptText}${buffer}`);
|
|
166
|
+
const promptWidth = stripAnsi(promptText).length;
|
|
167
|
+
readline.cursorTo(process.stdout, promptWidth + cursor);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const finish = (result, addNewline = true) => {
|
|
171
|
+
if (done) return;
|
|
172
|
+
done = true;
|
|
173
|
+
process.stdin.setRawMode(wasRaw);
|
|
174
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
175
|
+
if (addNewline) process.stdout.write('\n');
|
|
176
|
+
resolve(result);
|
|
177
|
+
};
|
|
135
178
|
|
|
136
179
|
const onKeypress = (str, key = {}) => {
|
|
137
180
|
if (key.ctrl && key.name === 'c') {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
181
|
+
if (buffer) {
|
|
182
|
+
buffer = '';
|
|
183
|
+
cursor = 0;
|
|
184
|
+
render();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
finish({ type: 'sigint' }, false);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (key.ctrl && key.name === 'd') {
|
|
192
|
+
if (!buffer) {
|
|
193
|
+
finish({ type: 'eof' }, false);
|
|
194
|
+
}
|
|
142
195
|
return;
|
|
143
196
|
}
|
|
144
197
|
|
|
145
|
-
|
|
146
|
-
|
|
198
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
199
|
+
finish({ type: 'submit', value: trim ? buffer.trim() : buffer });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
147
202
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
203
|
+
if (key.name === 'backspace' || key.name === 'delete') {
|
|
204
|
+
if (key.name === 'backspace' && cursor > 0) {
|
|
205
|
+
buffer = removeCharAt(buffer, cursor - 1);
|
|
206
|
+
cursor--;
|
|
207
|
+
render();
|
|
208
|
+
} else if (key.name === 'delete' && cursor < Array.from(buffer).length) {
|
|
209
|
+
buffer = removeCharAt(buffer, cursor);
|
|
210
|
+
render();
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (allowCursorNavigation && key.name === 'left') {
|
|
216
|
+
if (cursor > 0) {
|
|
217
|
+
cursor--;
|
|
218
|
+
render();
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (allowCursorNavigation && key.name === 'right') {
|
|
224
|
+
if (cursor < Array.from(buffer).length) {
|
|
225
|
+
cursor++;
|
|
226
|
+
render();
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (allowCursorNavigation && key.name === 'home') {
|
|
232
|
+
cursor = 0;
|
|
233
|
+
render();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (allowCursorNavigation && key.name === 'end') {
|
|
238
|
+
cursor = Array.from(buffer).length;
|
|
239
|
+
render();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (key.name && ['up', 'down', 'left', 'right', 'home', 'end', 'pageup', 'pagedown', 'escape', 'tab'].includes(key.name)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!isPrintableKey(str, key)) return;
|
|
248
|
+
|
|
249
|
+
if (allowed && !allowed.includes(str)) return;
|
|
250
|
+
|
|
251
|
+
if (immediate) {
|
|
252
|
+
buffer = str;
|
|
253
|
+
cursor = Array.from(buffer).length;
|
|
254
|
+
render();
|
|
255
|
+
finish({ type: 'submit', value: str });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
buffer = insertCharAt(buffer, cursor, str);
|
|
260
|
+
cursor++;
|
|
261
|
+
render();
|
|
152
262
|
};
|
|
153
263
|
|
|
154
264
|
process.stdin.on('keypress', onKeypress);
|
|
265
|
+
render();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Permission system ─────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
let AUTO_APPROVE_SHELL = false;
|
|
272
|
+
let AUTO_APPROVE_FILE = false;
|
|
273
|
+
|
|
274
|
+
function askPermissionLine(actionType) {
|
|
275
|
+
return actionType === 'shell'
|
|
276
|
+
? ' 1. Yes 2. Yes, always for shell 3. No'
|
|
277
|
+
: ' 1. Yes 2. Yes, always for files 3. No';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function readPermissionChoice() {
|
|
281
|
+
return readInteractiveInput(` ${FG_YELLOW}?${RST} `, {
|
|
282
|
+
allowed: ['1', '2', '3'],
|
|
283
|
+
immediate: true,
|
|
284
|
+
onEmptyCtrlC: 'signal',
|
|
285
|
+
trim: true,
|
|
155
286
|
});
|
|
156
287
|
}
|
|
157
288
|
|
|
@@ -173,8 +304,14 @@ function askPermission(actionType, description) {
|
|
|
173
304
|
console.log(` ${FG_CYAN}${askPermissionLine(actionType)}${RST}`);
|
|
174
305
|
console.log();
|
|
175
306
|
|
|
176
|
-
readPermissionChoice().then((
|
|
177
|
-
|
|
307
|
+
readPermissionChoice().then((result) => {
|
|
308
|
+
if (result.type === 'sigint' || result.type === 'eof') {
|
|
309
|
+
console.log(` ${FG_RED}✗${RST} ${FG_DARK}Denied${RST}`);
|
|
310
|
+
resolve(false);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const choice = (result.value || '').trim().toLowerCase();
|
|
178
315
|
if (choice === '1' || choice === 'y' || choice === 'yes') {
|
|
179
316
|
resolve(true);
|
|
180
317
|
} else if (choice === '2' || choice === 'a' || choice === 'always') {
|
|
@@ -912,38 +1049,35 @@ async function cmdChat(opts) {
|
|
|
912
1049
|
let messages = [{ role: 'system', content: getSystemPrompt() }];
|
|
913
1050
|
const cols = getCols();
|
|
914
1051
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1052
|
+
while (true) {
|
|
1053
|
+
const inputResult = await readInteractiveInput(` ${FG_TEAL}${BOLD}>${RST} `, {
|
|
1054
|
+
trim: false,
|
|
1055
|
+
allowCursorNavigation: true,
|
|
1056
|
+
});
|
|
920
1057
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1058
|
+
if (inputResult.type === 'eof') {
|
|
1059
|
+
console.log(`\n ${FG_GRAY}Goodbye!${RST}\n`);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
925
1062
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1063
|
+
if (inputResult.type === 'sigint') {
|
|
1064
|
+
if (!isRunningAgent) {
|
|
1065
|
+
console.log(`\n ${FG_YELLOW}Use Ctrl+D or type exit to quit.${RST}`);
|
|
1066
|
+
}
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
931
1069
|
|
|
932
|
-
|
|
933
|
-
rl.setPrompt(` ${FG_TEAL}${BOLD}>${RST} `);
|
|
934
|
-
rl.question(rl.getPrompt(), async (input) => {
|
|
935
|
-
const text = (input || '').trim();
|
|
1070
|
+
const text = (inputResult.value || '').trim();
|
|
936
1071
|
|
|
937
|
-
|
|
1072
|
+
if (!text) continue;
|
|
938
1073
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
}
|
|
1074
|
+
if (['exit', 'quit', '/exit', '/quit'].includes(text.toLowerCase())) {
|
|
1075
|
+
console.log(`\n ${FG_GRAY}Goodbye!${RST}\n`);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
944
1078
|
|
|
945
|
-
|
|
946
|
-
|
|
1079
|
+
if (text === '/help') {
|
|
1080
|
+
console.log(`
|
|
947
1081
|
${FG_BLUE}${BOLD}Commands:${RST}
|
|
948
1082
|
${FG_CYAN}/file <path>${RST} ${FG_GRAY}Load file or dir into context${RST}
|
|
949
1083
|
${FG_CYAN}/model${RST} ${FG_GRAY}Choose saved model profile${RST}
|
|
@@ -959,82 +1093,83 @@ async function cmdChat(opts) {
|
|
|
959
1093
|
|
|
960
1094
|
${FG_DARK}The AI can execute commands — you'll be asked to approve each one.${RST}
|
|
961
1095
|
`);
|
|
962
|
-
|
|
963
|
-
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
964
1098
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1099
|
+
if (text.startsWith('/file ')) {
|
|
1100
|
+
const fp = text.slice(6).trim();
|
|
1101
|
+
const ctx = readFileContext([fp]);
|
|
1102
|
+
if (ctx) messages.push({ role: 'user', content: `Here is the file context:\n${ctx}` });
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
971
1105
|
|
|
972
|
-
|
|
1106
|
+
if (text === '/model' || text === '/models') {
|
|
1107
|
+
await new Promise((resolve) => {
|
|
1108
|
+
const rl = readline.createInterface({
|
|
1109
|
+
input: process.stdin,
|
|
1110
|
+
output: process.stdout,
|
|
1111
|
+
terminal: true,
|
|
1112
|
+
});
|
|
973
1113
|
chooseSavedModelProfile(rl, currentModel, cwd, (nextModel) => {
|
|
974
1114
|
currentModel = nextModel;
|
|
975
|
-
|
|
1115
|
+
rl.close();
|
|
1116
|
+
resolve();
|
|
976
1117
|
});
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (text.startsWith('/model ')) {
|
|
981
|
-
currentModel = text.slice(7).trim();
|
|
982
|
-
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model → ${currentModel}${RST}`);
|
|
983
|
-
printStatusBar(currentModel, cwd);
|
|
984
|
-
return prompt();
|
|
985
|
-
}
|
|
1118
|
+
});
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
986
1121
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
1122
|
+
if (text.startsWith('/model ')) {
|
|
1123
|
+
currentModel = text.slice(7).trim();
|
|
1124
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model → ${currentModel}${RST}`);
|
|
1125
|
+
printStatusBar(currentModel, cwd);
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
994
1128
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}
|
|
1129
|
+
if (text === '/clear') {
|
|
1130
|
+
messages = [{ role: 'system', content: getSystemPrompt() }];
|
|
1131
|
+
AUTO_APPROVE_SHELL = false;
|
|
1132
|
+
AUTO_APPROVE_FILE = false;
|
|
1133
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Conversation and approvals cleared${RST}\n`);
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1000
1136
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1137
|
+
if (text === '/compact' || text === '/cost') {
|
|
1138
|
+
const total = messages.reduce((s, m) => s + estimateTokens(m.content), 0);
|
|
1139
|
+
console.log(` ${FG_GRAY}${messages.length - 1} messages · ~${total} tokens${RST}\n`);
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1005
1142
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const color = AUTO_APPROVE_SHELL ? FG_GREEN : FG_RED;
|
|
1011
|
-
console.log(` ${color}●${RST} ${FG_GRAY}Auto-approve: ${state}${RST}\n`);
|
|
1012
|
-
return prompt();
|
|
1013
|
-
}
|
|
1143
|
+
if (text === '/config') {
|
|
1144
|
+
console.log(` ${FG_GRAY}${JSON.stringify(config, null, 2)}${RST}\n`);
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1014
1147
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1148
|
+
if (text === '/approve') {
|
|
1149
|
+
AUTO_APPROVE_SHELL = !AUTO_APPROVE_SHELL;
|
|
1150
|
+
AUTO_APPROVE_FILE = !AUTO_APPROVE_FILE;
|
|
1151
|
+
const state = AUTO_APPROVE_SHELL ? 'ON' : 'OFF';
|
|
1152
|
+
const color = AUTO_APPROVE_SHELL ? FG_GREEN : FG_RED;
|
|
1153
|
+
console.log(` ${color}●${RST} ${FG_GRAY}Auto-approve: ${state}${RST}\n`);
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1020
1156
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1157
|
+
if (text.startsWith('/shell ') || text.startsWith('!')) {
|
|
1158
|
+
const cmd = text.startsWith('/shell ') ? text.slice(7).trim() : text.slice(1).trim();
|
|
1159
|
+
await agentExecShell(cmd);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1023
1162
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
messages = await runAgentLoop(messages, currentModel);
|
|
1027
|
-
isRunningAgent = false;
|
|
1028
|
-
rl.resume();
|
|
1163
|
+
messages.push({ role: 'user', content: text });
|
|
1164
|
+
console.log(` ${FG_DARK}${'─'.repeat(Math.min(cols, 70) - 4)}${RST}`);
|
|
1029
1165
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1166
|
+
isRunningAgent = true;
|
|
1167
|
+
messages = await runAgentLoop(messages, currentModel);
|
|
1168
|
+
isRunningAgent = false;
|
|
1032
1169
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1170
|
+
console.log(` ${FG_DARK}${'━'.repeat(Math.min(cols, 70) - 4)}${RST}`);
|
|
1171
|
+
console.log();
|
|
1035
1172
|
}
|
|
1036
|
-
|
|
1037
|
-
prompt();
|
|
1038
1173
|
}
|
|
1039
1174
|
|
|
1040
1175
|
async function cmdCode(opts, promptArgs) {
|