@semalt-ai/code 1.4.2 → 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 +272 -94
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -105,11 +105,187 @@ 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
|
+
function dropLastChar(text) {
|
|
109
|
+
const chars = Array.from(text || '');
|
|
110
|
+
chars.pop();
|
|
111
|
+
return chars.join('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function insertCharAt(text, index, value) {
|
|
115
|
+
const chars = Array.from(text || '');
|
|
116
|
+
chars.splice(index, 0, value);
|
|
117
|
+
return chars.join('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function removeCharAt(text, index) {
|
|
121
|
+
const chars = Array.from(text || '');
|
|
122
|
+
chars.splice(index, 1);
|
|
123
|
+
return chars.join('');
|
|
124
|
+
}
|
|
125
|
+
|
|
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
|
+
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
if (!process.stdin.isTTY) {
|
|
145
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
146
|
+
rl.question(promptText, (answer) => {
|
|
147
|
+
rl.close();
|
|
148
|
+
resolve({ type: 'submit', value: trim ? (answer || '').trim() : (answer || '') });
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const wasRaw = typeof process.stdin.isRaw === 'boolean' ? process.stdin.isRaw : false;
|
|
154
|
+
let buffer = '';
|
|
155
|
+
let cursor = 0;
|
|
156
|
+
let done = false;
|
|
157
|
+
|
|
158
|
+
readline.emitKeypressEvents(process.stdin);
|
|
159
|
+
process.stdin.setRawMode(true);
|
|
160
|
+
process.stdin.resume();
|
|
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
|
+
};
|
|
178
|
+
|
|
179
|
+
const onKeypress = (str, key = {}) => {
|
|
180
|
+
if (key.ctrl && key.name === 'c') {
|
|
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
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
199
|
+
finish({ type: 'submit', value: trim ? buffer.trim() : buffer });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
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();
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
process.stdin.on('keypress', onKeypress);
|
|
265
|
+
render();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
108
269
|
// ── Permission system ─────────────────────────────────────────────────────────
|
|
109
270
|
|
|
110
271
|
let AUTO_APPROVE_SHELL = false;
|
|
111
272
|
let AUTO_APPROVE_FILE = false;
|
|
112
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,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
113
289
|
function askPermission(actionType, description) {
|
|
114
290
|
return new Promise((resolve) => {
|
|
115
291
|
if (actionType === 'shell' && AUTO_APPROVE_SHELL) {
|
|
@@ -125,16 +301,20 @@ function askPermission(actionType, description) {
|
|
|
125
301
|
console.log(` ${FG_YELLOW}${BOLD}⚠ Permission required${RST}`);
|
|
126
302
|
console.log(` ${FG_GRAY}${actionType}: ${description}${RST}`);
|
|
127
303
|
console.log();
|
|
128
|
-
console.log(` ${FG_CYAN}
|
|
304
|
+
console.log(` ${FG_CYAN}${askPermissionLine(actionType)}${RST}`);
|
|
129
305
|
console.log();
|
|
130
306
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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();
|
|
315
|
+
if (choice === '1' || choice === 'y' || choice === 'yes') {
|
|
136
316
|
resolve(true);
|
|
137
|
-
} else if (choice === 'a' || choice === 'always') {
|
|
317
|
+
} else if (choice === '2' || choice === 'a' || choice === 'always') {
|
|
138
318
|
if (actionType === 'shell') AUTO_APPROVE_SHELL = true;
|
|
139
319
|
else AUTO_APPROVE_FILE = true;
|
|
140
320
|
console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approve enabled for ${actionType} operations${RST}`);
|
|
@@ -869,38 +1049,35 @@ async function cmdChat(opts) {
|
|
|
869
1049
|
let messages = [{ role: 'system', content: getSystemPrompt() }];
|
|
870
1050
|
const cols = getCols();
|
|
871
1051
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1052
|
+
while (true) {
|
|
1053
|
+
const inputResult = await readInteractiveInput(` ${FG_TEAL}${BOLD}>${RST} `, {
|
|
1054
|
+
trim: false,
|
|
1055
|
+
allowCursorNavigation: true,
|
|
1056
|
+
});
|
|
877
1057
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1058
|
+
if (inputResult.type === 'eof') {
|
|
1059
|
+
console.log(`\n ${FG_GRAY}Goodbye!${RST}\n`);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
882
1062
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
}
|
|
888
1069
|
|
|
889
|
-
|
|
890
|
-
rl.setPrompt(` ${FG_TEAL}${BOLD}>${RST} `);
|
|
891
|
-
rl.question(rl.getPrompt(), async (input) => {
|
|
892
|
-
const text = (input || '').trim();
|
|
1070
|
+
const text = (inputResult.value || '').trim();
|
|
893
1071
|
|
|
894
|
-
|
|
1072
|
+
if (!text) continue;
|
|
895
1073
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
1074
|
+
if (['exit', 'quit', '/exit', '/quit'].includes(text.toLowerCase())) {
|
|
1075
|
+
console.log(`\n ${FG_GRAY}Goodbye!${RST}\n`);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
901
1078
|
|
|
902
|
-
|
|
903
|
-
|
|
1079
|
+
if (text === '/help') {
|
|
1080
|
+
console.log(`
|
|
904
1081
|
${FG_BLUE}${BOLD}Commands:${RST}
|
|
905
1082
|
${FG_CYAN}/file <path>${RST} ${FG_GRAY}Load file or dir into context${RST}
|
|
906
1083
|
${FG_CYAN}/model${RST} ${FG_GRAY}Choose saved model profile${RST}
|
|
@@ -916,82 +1093,83 @@ async function cmdChat(opts) {
|
|
|
916
1093
|
|
|
917
1094
|
${FG_DARK}The AI can execute commands — you'll be asked to approve each one.${RST}
|
|
918
1095
|
`);
|
|
919
|
-
|
|
920
|
-
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
921
1098
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
+
}
|
|
928
1105
|
|
|
929
|
-
|
|
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
|
+
});
|
|
930
1113
|
chooseSavedModelProfile(rl, currentModel, cwd, (nextModel) => {
|
|
931
1114
|
currentModel = nextModel;
|
|
932
|
-
|
|
1115
|
+
rl.close();
|
|
1116
|
+
resolve();
|
|
933
1117
|
});
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (text.startsWith('/model ')) {
|
|
938
|
-
currentModel = text.slice(7).trim();
|
|
939
|
-
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model → ${currentModel}${RST}`);
|
|
940
|
-
printStatusBar(currentModel, cwd);
|
|
941
|
-
return prompt();
|
|
942
|
-
}
|
|
1118
|
+
});
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
943
1121
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
}
|
|
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
|
+
}
|
|
951
1128
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
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
|
+
}
|
|
957
1136
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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
|
+
}
|
|
962
1142
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const color = AUTO_APPROVE_SHELL ? FG_GREEN : FG_RED;
|
|
968
|
-
console.log(` ${color}●${RST} ${FG_GRAY}Auto-approve: ${state}${RST}\n`);
|
|
969
|
-
return prompt();
|
|
970
|
-
}
|
|
1143
|
+
if (text === '/config') {
|
|
1144
|
+
console.log(` ${FG_GRAY}${JSON.stringify(config, null, 2)}${RST}\n`);
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
971
1147
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
+
}
|
|
977
1156
|
|
|
978
|
-
|
|
979
|
-
|
|
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
|
+
}
|
|
980
1162
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
messages = await runAgentLoop(messages, currentModel);
|
|
984
|
-
isRunningAgent = false;
|
|
985
|
-
rl.resume();
|
|
1163
|
+
messages.push({ role: 'user', content: text });
|
|
1164
|
+
console.log(` ${FG_DARK}${'─'.repeat(Math.min(cols, 70) - 4)}${RST}`);
|
|
986
1165
|
|
|
987
|
-
|
|
988
|
-
|
|
1166
|
+
isRunningAgent = true;
|
|
1167
|
+
messages = await runAgentLoop(messages, currentModel);
|
|
1168
|
+
isRunningAgent = false;
|
|
989
1169
|
|
|
990
|
-
|
|
991
|
-
|
|
1170
|
+
console.log(` ${FG_DARK}${'━'.repeat(Math.min(cols, 70) - 4)}${RST}`);
|
|
1171
|
+
console.log();
|
|
992
1172
|
}
|
|
993
|
-
|
|
994
|
-
prompt();
|
|
995
1173
|
}
|
|
996
1174
|
|
|
997
1175
|
async function cmdCode(opts, promptArgs) {
|