@semalt-ai/code 1.3.1 → 1.4.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 +22 -1
- package/index.js +135 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,10 @@ semalt-code [command] [options]
|
|
|
95
95
|
Runs a shell command with approval prompts.
|
|
96
96
|
|
|
97
97
|
- `semalt-code models`
|
|
98
|
-
Lists
|
|
98
|
+
Lists all saved model profiles.
|
|
99
|
+
|
|
100
|
+
- `semalt-code models add`
|
|
101
|
+
Opens an interactive flow to add an API base URL, API key, and model ID as a reusable model profile.
|
|
99
102
|
|
|
100
103
|
- `semalt-code init`
|
|
101
104
|
Creates or updates the local config file.
|
|
@@ -134,7 +137,9 @@ Available interactive commands:
|
|
|
134
137
|
|
|
135
138
|
- `/help`
|
|
136
139
|
- `/file <path>`
|
|
140
|
+
- `/model`
|
|
137
141
|
- `/model <name>`
|
|
142
|
+
- `/models`
|
|
138
143
|
- `/clear`
|
|
139
144
|
- `/compact`
|
|
140
145
|
- `/cost`
|
|
@@ -205,6 +210,22 @@ semalt-code shell -a "npm test"
|
|
|
205
210
|
semalt-code models
|
|
206
211
|
```
|
|
207
212
|
|
|
213
|
+
### Add a saved model profile
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
semalt-code models add
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The CLI will ask for:
|
|
220
|
+
|
|
221
|
+
- API Base URL
|
|
222
|
+
- API Key
|
|
223
|
+
- Model ID
|
|
224
|
+
|
|
225
|
+
Each saved profile is appended to the profile list.
|
|
226
|
+
|
|
227
|
+
Saved profiles can then be selected inside chat mode with `/model` or `/models`.
|
|
228
|
+
|
|
208
229
|
### Show the current version
|
|
209
230
|
|
|
210
231
|
```bash
|
package/index.js
CHANGED
|
@@ -21,23 +21,43 @@ const DEFAULT_CONFIG = {
|
|
|
21
21
|
temperature: 0.7,
|
|
22
22
|
max_tokens: 4096,
|
|
23
23
|
stream: true,
|
|
24
|
+
models: [],
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
const CONFIG_PATH = path.join(os.homedir(), '.semalt-ai', 'config.json');
|
|
27
28
|
|
|
29
|
+
function normalizeConfig(cfg = {}) {
|
|
30
|
+
const merged = { ...DEFAULT_CONFIG, ...cfg };
|
|
31
|
+
merged.models = Array.isArray(cfg.models)
|
|
32
|
+
? cfg.models
|
|
33
|
+
.filter((entry) => entry &&
|
|
34
|
+
typeof entry.api_base === 'string' &&
|
|
35
|
+
typeof entry.api_key === 'string' &&
|
|
36
|
+
typeof entry.model === 'string' &&
|
|
37
|
+
entry.api_base.trim() &&
|
|
38
|
+
entry.model.trim())
|
|
39
|
+
.map((entry) => ({
|
|
40
|
+
api_base: entry.api_base.trim(),
|
|
41
|
+
api_key: entry.api_key,
|
|
42
|
+
model: entry.model.trim(),
|
|
43
|
+
}))
|
|
44
|
+
: [];
|
|
45
|
+
return merged;
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
function loadConfig() {
|
|
29
49
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
30
50
|
try {
|
|
31
51
|
const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
32
|
-
return
|
|
52
|
+
return normalizeConfig(data);
|
|
33
53
|
} catch {}
|
|
34
54
|
}
|
|
35
|
-
return
|
|
55
|
+
return normalizeConfig();
|
|
36
56
|
}
|
|
37
57
|
|
|
38
58
|
function saveConfig(cfg) {
|
|
39
59
|
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
40
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
60
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(normalizeConfig(cfg), null, 2));
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
let config = loadConfig();
|
|
@@ -424,6 +444,52 @@ function apiUrl(urlPath) {
|
|
|
424
444
|
return `${normalizedBase}${normalizedPath}`;
|
|
425
445
|
}
|
|
426
446
|
|
|
447
|
+
function describeModelProfile(profile) {
|
|
448
|
+
return `${profile.model} @ ${profile.api_base}`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function setActiveModelProfile(profile) {
|
|
452
|
+
config.api_base = profile.api_base;
|
|
453
|
+
config.api_key = profile.api_key;
|
|
454
|
+
config.default_model = profile.model;
|
|
455
|
+
saveConfig(config);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function chooseSavedModelProfile(rl, currentModel, cwd, onDone) {
|
|
459
|
+
if (!config.models.length) {
|
|
460
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}No saved model profiles. Use semalt-code models add first.${RST}`);
|
|
461
|
+
onDone(currentModel);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log();
|
|
466
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Saved Models${RST}`);
|
|
467
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
468
|
+
config.models.forEach((profile, index) => {
|
|
469
|
+
const active = profile.api_base === config.api_base &&
|
|
470
|
+
profile.api_key === config.api_key &&
|
|
471
|
+
profile.model === currentModel;
|
|
472
|
+
const marker = active ? `${FG_GREEN}●${RST}` : `${FG_DARK}○${RST}`;
|
|
473
|
+
console.log(` ${marker} ${FG_CYAN}${index + 1}.${RST} ${describeModelProfile(profile)}`);
|
|
474
|
+
});
|
|
475
|
+
console.log();
|
|
476
|
+
|
|
477
|
+
rl.question(` ${FG_TEAL}${BOLD}Select model>${RST} `, (answer) => {
|
|
478
|
+
const selected = Number((answer || '').trim());
|
|
479
|
+
if (!Number.isInteger(selected) || selected < 1 || selected > config.models.length) {
|
|
480
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Invalid selection${RST}`);
|
|
481
|
+
onDone(currentModel);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const profile = config.models[selected - 1];
|
|
486
|
+
setActiveModelProfile(profile);
|
|
487
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model profile → ${describeModelProfile(profile)}${RST}`);
|
|
488
|
+
printStatusBar(profile.model, cwd);
|
|
489
|
+
onDone(profile.model);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
427
493
|
function estimateTokens(text) {
|
|
428
494
|
return Math.floor((text || '').length / 4);
|
|
429
495
|
}
|
|
@@ -831,7 +897,9 @@ async function cmdChat(opts) {
|
|
|
831
897
|
console.log(`
|
|
832
898
|
${FG_BLUE}${BOLD}Commands:${RST}
|
|
833
899
|
${FG_CYAN}/file <path>${RST} ${FG_GRAY}Load file or dir into context${RST}
|
|
834
|
-
${FG_CYAN}/model
|
|
900
|
+
${FG_CYAN}/model${RST} ${FG_GRAY}Choose saved model profile${RST}
|
|
901
|
+
${FG_CYAN}/model <name>${RST} ${FG_GRAY}Switch model manually${RST}
|
|
902
|
+
${FG_CYAN}/models${RST} ${FG_GRAY}Choose saved model profile${RST}
|
|
835
903
|
${FG_CYAN}/clear${RST} ${FG_GRAY}Clear conversation${RST}
|
|
836
904
|
${FG_CYAN}/compact${RST} ${FG_GRAY}Show token usage${RST}
|
|
837
905
|
${FG_CYAN}/shell <cmd>${RST} ${FG_GRAY}Run shell command directly${RST}
|
|
@@ -852,6 +920,14 @@ async function cmdChat(opts) {
|
|
|
852
920
|
return prompt();
|
|
853
921
|
}
|
|
854
922
|
|
|
923
|
+
if (text === '/model' || text === '/models') {
|
|
924
|
+
chooseSavedModelProfile(rl, currentModel, cwd, (nextModel) => {
|
|
925
|
+
currentModel = nextModel;
|
|
926
|
+
prompt();
|
|
927
|
+
});
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
855
931
|
if (text.startsWith('/model ')) {
|
|
856
932
|
currentModel = text.slice(7).trim();
|
|
857
933
|
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model → ${currentModel}${RST}`);
|
|
@@ -987,41 +1063,60 @@ async function cmdShell(opts, commandArgs) {
|
|
|
987
1063
|
}
|
|
988
1064
|
|
|
989
1065
|
async function cmdModels() {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
try {
|
|
993
|
-
res = await httpRequest(url, {
|
|
994
|
-
method: 'GET',
|
|
995
|
-
headers: { 'Authorization': `Bearer ${config.api_key}` },
|
|
996
|
-
timeout: 10000,
|
|
997
|
-
}, null);
|
|
998
|
-
} catch (e) {
|
|
999
|
-
console.log(` ${FG_RED}✗ ${e.message}${RST}`);
|
|
1066
|
+
if (!config.models.length) {
|
|
1067
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}No saved model profiles. Use semalt-code models add first.${RST}`);
|
|
1000
1068
|
return;
|
|
1001
1069
|
}
|
|
1002
1070
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
console.log(` ${FG_RED}✗ ${e.message}${RST}`);
|
|
1022
|
-
resolve();
|
|
1023
|
-
});
|
|
1071
|
+
console.log();
|
|
1072
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Saved Models${RST}`);
|
|
1073
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
1074
|
+
config.models.forEach((profile, index) => {
|
|
1075
|
+
const active = profile.api_base === config.api_base &&
|
|
1076
|
+
profile.api_key === config.api_key &&
|
|
1077
|
+
profile.model === config.default_model;
|
|
1078
|
+
const marker = active ? `${FG_GREEN}●${RST}` : `${FG_DARK}○${RST}`;
|
|
1079
|
+
console.log(` ${marker} ${FG_CYAN}${index + 1}.${RST} ${describeModelProfile(profile)}`);
|
|
1080
|
+
});
|
|
1081
|
+
console.log();
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
async function cmdModelsAdd() {
|
|
1085
|
+
const rl = readline.createInterface({
|
|
1086
|
+
input: process.stdin,
|
|
1087
|
+
output: process.stdout,
|
|
1088
|
+
terminal: true,
|
|
1024
1089
|
});
|
|
1090
|
+
|
|
1091
|
+
function ask(question) {
|
|
1092
|
+
return new Promise((resolve) => {
|
|
1093
|
+
rl.question(question, (answer) => resolve((answer || '').trim()));
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
console.log();
|
|
1098
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Add Model Profile${RST}`);
|
|
1099
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
1100
|
+
|
|
1101
|
+
const apiBase = await ask(` ${FG_CYAN}API Base URL:${RST} `);
|
|
1102
|
+
const apiKey = await ask(` ${FG_CYAN}API Key:${RST} `);
|
|
1103
|
+
const modelId = await ask(` ${FG_CYAN}Model ID:${RST} `);
|
|
1104
|
+
rl.close();
|
|
1105
|
+
|
|
1106
|
+
if (!apiBase || !modelId) {
|
|
1107
|
+
console.log(`\n ${FG_RED}✗${RST} ${FG_GRAY}API Base URL and Model ID are required.${RST}\n`);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const profile = {
|
|
1112
|
+
api_base: apiBase,
|
|
1113
|
+
api_key: apiKey || 'any',
|
|
1114
|
+
model: modelId,
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
config.models.push(profile);
|
|
1118
|
+
setActiveModelProfile(profile);
|
|
1119
|
+
console.log(`\n ${FG_GREEN}✓${RST} Saved model profile: ${describeModelProfile(profile)}\n`);
|
|
1025
1120
|
}
|
|
1026
1121
|
|
|
1027
1122
|
function cmdInit(opts) {
|
|
@@ -1032,6 +1127,7 @@ function cmdInit(opts) {
|
|
|
1032
1127
|
temperature: 0.7,
|
|
1033
1128
|
max_tokens: 4096,
|
|
1034
1129
|
stream: true,
|
|
1130
|
+
models: config.models,
|
|
1035
1131
|
};
|
|
1036
1132
|
saveConfig(cfg);
|
|
1037
1133
|
config = cfg;
|
|
@@ -1085,7 +1181,8 @@ Commands:
|
|
|
1085
1181
|
code <prompt> Generate code from a prompt
|
|
1086
1182
|
edit <file> <instruction> Edit a file with AI
|
|
1087
1183
|
shell <command> Run and optionally analyze a shell command
|
|
1088
|
-
models List
|
|
1184
|
+
models List saved model profiles
|
|
1185
|
+
models add Add a saved model profile
|
|
1089
1186
|
init Initialize config
|
|
1090
1187
|
|
|
1091
1188
|
Options:
|
|
@@ -1121,7 +1218,8 @@ Config: ${CONFIG_PATH}
|
|
|
1121
1218
|
const { opts, positional } = parseArgs(rawArgs.slice(1));
|
|
1122
1219
|
await cmdShell(opts, positional);
|
|
1123
1220
|
} else if (command === 'models') {
|
|
1124
|
-
await
|
|
1221
|
+
if (rawArgs[1] === 'add') await cmdModelsAdd();
|
|
1222
|
+
else await cmdModels();
|
|
1125
1223
|
} else if (command === 'init') {
|
|
1126
1224
|
const { opts } = parseArgs(rawArgs.slice(1));
|
|
1127
1225
|
cmdInit(opts);
|