@semalt-ai/code 1.6.0 → 1.7.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 +16 -2
- package/index.js +11 -1
- package/lib/api.js +124 -0
- package/lib/args.js +3 -0
- package/lib/commands.js +209 -26
- package/lib/config.js +17 -0
- package/lib/constants.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ semalt-code
|
|
|
49
49
|
Create the CLI config:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
semalt-code init --api-base http://127.0.0.1:8800 --api-key any --default-model default
|
|
52
|
+
semalt-code init --api-base http://127.0.0.1:8800 --api-key any --dashboard-url https://cli.semalt.ai --default-model default
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
This writes configuration to:
|
|
@@ -64,6 +64,8 @@ Example config:
|
|
|
64
64
|
{
|
|
65
65
|
"api_base": "http://127.0.0.1:8800",
|
|
66
66
|
"api_key": "any",
|
|
67
|
+
"dashboard_url": "https://cli.semalt.ai",
|
|
68
|
+
"auth_token": "",
|
|
67
69
|
"default_model": "default",
|
|
68
70
|
"temperature": 0.7,
|
|
69
71
|
"request_timeout_ms": 900000,
|
|
@@ -94,8 +96,17 @@ semalt-code [command] [options]
|
|
|
94
96
|
- `semalt-code shell <command>`
|
|
95
97
|
Runs a shell command with approval prompts.
|
|
96
98
|
|
|
99
|
+
- `semalt-code login`
|
|
100
|
+
Starts browser-based login and stores the confirmed CLI token in `~/.semalt-ai/config.json`.
|
|
101
|
+
|
|
102
|
+
- `semalt-code whoami`
|
|
103
|
+
Shows the current user associated with the saved CLI auth token.
|
|
104
|
+
|
|
105
|
+
- `semalt-code logout`
|
|
106
|
+
Logs out the current CLI user and clears the saved local auth token.
|
|
107
|
+
|
|
97
108
|
- `semalt-code models`
|
|
98
|
-
Lists
|
|
109
|
+
Lists your models from the dashboard and lets you choose the current one for the CLI.
|
|
99
110
|
|
|
100
111
|
- `semalt-code models add`
|
|
101
112
|
Opens an interactive flow to add an API base URL, API key, and model ID as a reusable model profile.
|
|
@@ -137,7 +148,10 @@ Available interactive commands:
|
|
|
137
148
|
|
|
138
149
|
- `/help`
|
|
139
150
|
- `/file <path>`
|
|
151
|
+
- `/whoami`
|
|
152
|
+
- `/logout`
|
|
140
153
|
- `/model`
|
|
154
|
+
- `/login`
|
|
141
155
|
- `/model <name>`
|
|
142
156
|
- `/models`
|
|
143
157
|
- `/clear`
|
package/index.js
CHANGED
|
@@ -74,7 +74,10 @@ Commands:
|
|
|
74
74
|
code <prompt> Generate code from a prompt
|
|
75
75
|
edit <file> <instruction> Edit a file with AI
|
|
76
76
|
shell <command> Run and optionally analyze a shell command
|
|
77
|
-
|
|
77
|
+
login Authorize CLI via browser
|
|
78
|
+
whoami Show current authorized user
|
|
79
|
+
logout Clear current CLI login
|
|
80
|
+
models Choose one of your dashboard models
|
|
78
81
|
models add Add a saved model profile
|
|
79
82
|
init Initialize config
|
|
80
83
|
|
|
@@ -85,6 +88,7 @@ Options:
|
|
|
85
88
|
--dry-run Don't save changes (edit command)
|
|
86
89
|
--api-base <url> API base URL (init)
|
|
87
90
|
--api-key <key> API key (init)
|
|
91
|
+
--dashboard-url <url> Dashboard URL (init)
|
|
88
92
|
--default-model <name> Default model (init)
|
|
89
93
|
-v, --version Show CLI version
|
|
90
94
|
|
|
@@ -110,6 +114,12 @@ Config: ${CONFIG_PATH}
|
|
|
110
114
|
} else if (command === 'shell') {
|
|
111
115
|
const { opts, positional } = parseArgs(rawArgs.slice(1));
|
|
112
116
|
await commands.cmdShell(opts, positional);
|
|
117
|
+
} else if (command === 'login') {
|
|
118
|
+
await commands.cmdLogin();
|
|
119
|
+
} else if (command === 'whoami') {
|
|
120
|
+
await commands.cmdWhoAmI();
|
|
121
|
+
} else if (command === 'logout') {
|
|
122
|
+
await commands.cmdLogout();
|
|
113
123
|
} else if (command === 'models') {
|
|
114
124
|
if (rawArgs[1] === 'add') await commands.cmdModelsAdd();
|
|
115
125
|
else await commands.cmdModels();
|
package/lib/api.js
CHANGED
|
@@ -27,10 +27,27 @@ function createApiClient({ getConfig, saveConfig, ui }) {
|
|
|
27
27
|
return `${normalizedBase}${normalizedPath}`;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function dashboardUrl(urlPath) {
|
|
31
|
+
const config = getConfig();
|
|
32
|
+
const base = (config.dashboard_url || '').replace(/\/$/, '');
|
|
33
|
+
const normalizedPath = urlPath.startsWith('/') ? urlPath : `/${urlPath}`;
|
|
34
|
+
return `${base}${normalizedPath}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
function describeModelProfile(profile) {
|
|
31
38
|
return `${profile.model} @ ${profile.api_base}`;
|
|
32
39
|
}
|
|
33
40
|
|
|
41
|
+
function requireAuthToken() {
|
|
42
|
+
const config = getConfig();
|
|
43
|
+
if (!config.auth_token) {
|
|
44
|
+
const error = new Error('Not logged in. Run semalt login first.');
|
|
45
|
+
error.statusCode = 401;
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
return config.auth_token;
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
function setActiveModelProfile(profile) {
|
|
35
52
|
const config = getConfig();
|
|
36
53
|
config.api_base = profile.api_base;
|
|
@@ -105,6 +122,107 @@ function createApiClient({ getConfig, saveConfig, ui }) {
|
|
|
105
122
|
});
|
|
106
123
|
}
|
|
107
124
|
|
|
125
|
+
async function requestJson(urlStr, { method = 'GET', timeout, headers = {}, body } = {}) {
|
|
126
|
+
const requestBody = body === undefined ? undefined : JSON.stringify(body);
|
|
127
|
+
const finalHeaders = { ...headers };
|
|
128
|
+
if (requestBody !== undefined) {
|
|
129
|
+
finalHeaders['Content-Type'] = 'application/json';
|
|
130
|
+
finalHeaders['Content-Length'] = Buffer.byteLength(requestBody);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const res = await httpRequest(urlStr, {
|
|
134
|
+
method,
|
|
135
|
+
timeout: timeout || getConfig().request_timeout_ms,
|
|
136
|
+
headers: finalHeaders,
|
|
137
|
+
}, requestBody);
|
|
138
|
+
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
let data = '';
|
|
141
|
+
res.setEncoding('utf8');
|
|
142
|
+
res.on('data', (chunk) => {
|
|
143
|
+
data += chunk;
|
|
144
|
+
});
|
|
145
|
+
res.on('end', () => {
|
|
146
|
+
let parsed = null;
|
|
147
|
+
try {
|
|
148
|
+
parsed = data ? JSON.parse(data) : null;
|
|
149
|
+
} catch {
|
|
150
|
+
parsed = data ? { error: data } : null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
154
|
+
const error = new Error((parsed && parsed.error) || `HTTP ${res.statusCode}`);
|
|
155
|
+
error.statusCode = res.statusCode;
|
|
156
|
+
error.data = parsed;
|
|
157
|
+
reject(error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
resolve(parsed);
|
|
162
|
+
});
|
|
163
|
+
res.on('error', reject);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function requestCliLogin() {
|
|
168
|
+
return requestJson(dashboardUrl('/api/auth/cli/request'), {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
timeout: 15000,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getCliLoginStatus(id, hash) {
|
|
175
|
+
return requestJson(dashboardUrl('/api/auth/cli/status'), {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
timeout: 15000,
|
|
178
|
+
body: { id, hash },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function dashboardWhoAmI() {
|
|
183
|
+
const authToken = requireAuthToken();
|
|
184
|
+
return requestJson(dashboardUrl('/api/auth/me'), {
|
|
185
|
+
method: 'GET',
|
|
186
|
+
timeout: 15000,
|
|
187
|
+
headers: {
|
|
188
|
+
'Authorization': `Bearer ${authToken}`,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function dashboardLogout() {
|
|
194
|
+
const authToken = requireAuthToken();
|
|
195
|
+
return requestJson(dashboardUrl('/api/auth/logout'), {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
timeout: 15000,
|
|
198
|
+
headers: {
|
|
199
|
+
'Authorization': `Bearer ${authToken}`,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function dashboardListModels() {
|
|
205
|
+
const authToken = requireAuthToken();
|
|
206
|
+
return requestJson(dashboardUrl('/api/models'), {
|
|
207
|
+
method: 'GET',
|
|
208
|
+
timeout: 15000,
|
|
209
|
+
headers: {
|
|
210
|
+
'Authorization': `Bearer ${authToken}`,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function dashboardGetModelForCli(id) {
|
|
216
|
+
const authToken = requireAuthToken();
|
|
217
|
+
return requestJson(dashboardUrl(`/api/models/${encodeURIComponent(String(id))}/cli`), {
|
|
218
|
+
method: 'GET',
|
|
219
|
+
timeout: 15000,
|
|
220
|
+
headers: {
|
|
221
|
+
'Authorization': `Bearer ${authToken}`,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
108
226
|
async function chatStream(messages, { model, temperature, maxTokens } = {}) {
|
|
109
227
|
const config = getConfig();
|
|
110
228
|
const payload = {
|
|
@@ -271,8 +389,14 @@ function createApiClient({ getConfig, saveConfig, ui }) {
|
|
|
271
389
|
chatStream,
|
|
272
390
|
chatSync,
|
|
273
391
|
chooseSavedModelProfile,
|
|
392
|
+
dashboardGetModelForCli,
|
|
393
|
+
dashboardListModels,
|
|
394
|
+
dashboardLogout,
|
|
395
|
+
dashboardWhoAmI,
|
|
274
396
|
describeModelProfile,
|
|
275
397
|
estimateTokens,
|
|
398
|
+
getCliLoginStatus,
|
|
399
|
+
requestCliLogin,
|
|
276
400
|
setActiveModelProfile,
|
|
277
401
|
};
|
|
278
402
|
}
|
package/lib/args.js
CHANGED
package/lib/commands.js
CHANGED
|
@@ -36,12 +36,24 @@ function createCommands({
|
|
|
36
36
|
const {
|
|
37
37
|
chatStream,
|
|
38
38
|
chatSync,
|
|
39
|
-
|
|
39
|
+
dashboardGetModelForCli,
|
|
40
|
+
dashboardListModels,
|
|
41
|
+
dashboardLogout,
|
|
42
|
+
dashboardWhoAmI,
|
|
40
43
|
describeModelProfile,
|
|
41
44
|
estimateTokens,
|
|
45
|
+
getCliLoginStatus,
|
|
46
|
+
requestCliLogin,
|
|
42
47
|
setActiveModelProfile,
|
|
43
48
|
} = apiClient;
|
|
44
49
|
|
|
50
|
+
const LOGIN_POLL_INTERVAL_MS = 2000;
|
|
51
|
+
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
52
|
+
|
|
53
|
+
function formatUserLine(label, value) {
|
|
54
|
+
return ` ${FG_CYAN}${label}:${RST} ${FG_GRAY}${value}${RST}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
async function cmdChat(opts) {
|
|
46
58
|
printBanner();
|
|
47
59
|
const cwd = process.cwd();
|
|
@@ -87,9 +99,12 @@ function createCommands({
|
|
|
87
99
|
console.log(`
|
|
88
100
|
${FG_BLUE}${BOLD}Commands:${RST}
|
|
89
101
|
${FG_CYAN}/file <path>${RST} ${FG_GRAY}Load file or dir into context${RST}
|
|
90
|
-
${FG_CYAN}/
|
|
102
|
+
${FG_CYAN}/login${RST} ${FG_GRAY}Authorize this CLI via browser${RST}
|
|
103
|
+
${FG_CYAN}/whoami${RST} ${FG_GRAY}Show current authorized user${RST}
|
|
104
|
+
${FG_CYAN}/logout${RST} ${FG_GRAY}Clear current CLI login${RST}
|
|
105
|
+
${FG_CYAN}/model${RST} ${FG_GRAY}Show or set current model id${RST}
|
|
91
106
|
${FG_CYAN}/model <name>${RST} ${FG_GRAY}Switch model manually${RST}
|
|
92
|
-
${FG_CYAN}/models${RST} ${FG_GRAY}Choose
|
|
107
|
+
${FG_CYAN}/models${RST} ${FG_GRAY}Choose one of your dashboard models${RST}
|
|
93
108
|
${FG_CYAN}/clear${RST} ${FG_GRAY}Clear conversation${RST}
|
|
94
109
|
${FG_CYAN}/compact${RST} ${FG_GRAY}Show token usage${RST}
|
|
95
110
|
${FG_CYAN}/shell <cmd>${RST} ${FG_GRAY}Run shell command directly${RST}
|
|
@@ -103,6 +118,23 @@ function createCommands({
|
|
|
103
118
|
continue;
|
|
104
119
|
}
|
|
105
120
|
|
|
121
|
+
if (text === '/login') {
|
|
122
|
+
await cmdLogin();
|
|
123
|
+
printStatusBar(currentModel, cwd);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (text === '/whoami') {
|
|
128
|
+
await cmdWhoAmI();
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (text === '/logout') {
|
|
133
|
+
await cmdLogout();
|
|
134
|
+
printStatusBar(currentModel, cwd);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
106
138
|
if (text.startsWith('/file ')) {
|
|
107
139
|
const fp = text.slice(6).trim();
|
|
108
140
|
const ctx = readFileContext([fp], ui);
|
|
@@ -110,19 +142,13 @@ function createCommands({
|
|
|
110
142
|
continue;
|
|
111
143
|
}
|
|
112
144
|
|
|
113
|
-
if (text === '/
|
|
114
|
-
await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
chooseSavedModelProfile(rl, currentModel, cwd, (nextModel) => {
|
|
121
|
-
currentModel = nextModel;
|
|
122
|
-
rl.close();
|
|
123
|
-
resolve();
|
|
124
|
-
});
|
|
125
|
-
});
|
|
145
|
+
if (text === '/models') {
|
|
146
|
+
currentModel = await cmdModels(currentModel, cwd);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (text === '/model') {
|
|
151
|
+
console.log(` ${FG_GRAY}Current model: ${currentModel}${RST}\n`);
|
|
126
152
|
continue;
|
|
127
153
|
}
|
|
128
154
|
|
|
@@ -255,22 +281,71 @@ function createCommands({
|
|
|
255
281
|
|
|
256
282
|
async function cmdModels() {
|
|
257
283
|
const config = getConfig();
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
284
|
+
let response;
|
|
285
|
+
try {
|
|
286
|
+
response = await dashboardListModels();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}${error.message}${RST}\n`);
|
|
289
|
+
return config.default_model;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const models = Array.isArray(response && response.models) ? response.models : [];
|
|
293
|
+
if (!models.length) {
|
|
294
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}No models found in your dashboard account.${RST}\n`);
|
|
295
|
+
return config.default_model;
|
|
261
296
|
}
|
|
262
297
|
|
|
263
298
|
console.log();
|
|
264
|
-
console.log(` ${FG_TEAL}${BOLD}◆
|
|
265
|
-
console.log(` ${FG_DARK}${'─'.repeat(
|
|
266
|
-
|
|
267
|
-
const active =
|
|
268
|
-
profile.api_key === config.api_key &&
|
|
269
|
-
profile.model === config.default_model;
|
|
299
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Your Models${RST}`);
|
|
300
|
+
console.log(` ${FG_DARK}${'─'.repeat(60)}${RST}`);
|
|
301
|
+
models.forEach((model, index) => {
|
|
302
|
+
const active = model.base_url === config.api_base && model.model_id === config.default_model;
|
|
270
303
|
const marker = active ? `${FG_GREEN}●${RST}` : `${FG_DARK}○${RST}`;
|
|
271
|
-
console.log(` ${marker} ${FG_CYAN}${index + 1}.${RST} ${
|
|
304
|
+
console.log(` ${marker} ${FG_CYAN}${index + 1}.${RST} ${FG_GRAY}${model.name} · ${model.model_id} @ ${model.base_url}${RST}`);
|
|
272
305
|
});
|
|
273
306
|
console.log();
|
|
307
|
+
|
|
308
|
+
const rl = readline.createInterface({
|
|
309
|
+
input: process.stdin,
|
|
310
|
+
output: process.stdout,
|
|
311
|
+
terminal: true,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const selectedIndex = await new Promise((resolve) => {
|
|
315
|
+
rl.question(` ${FG_TEAL}${BOLD}Select model>${RST} `, (answer) => {
|
|
316
|
+
rl.close();
|
|
317
|
+
resolve(Number((answer || '').trim()));
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > models.length) {
|
|
322
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Invalid selection${RST}\n`);
|
|
323
|
+
return config.default_model;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const selectedModel = models[selectedIndex - 1];
|
|
327
|
+
let credentialsResponse;
|
|
328
|
+
try {
|
|
329
|
+
credentialsResponse = await dashboardGetModelForCli(selectedModel.id);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}${error.message}${RST}\n`);
|
|
332
|
+
return config.default_model;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const model = credentialsResponse && credentialsResponse.model ? credentialsResponse.model : null;
|
|
336
|
+
if (!model) {
|
|
337
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Unable to load selected model.${RST}\n`);
|
|
338
|
+
return config.default_model;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
setConfig({
|
|
342
|
+
...config,
|
|
343
|
+
api_base: model.base_url,
|
|
344
|
+
api_key: model.api_key,
|
|
345
|
+
default_model: model.model_id,
|
|
346
|
+
});
|
|
347
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Current model → ${model.name} (${model.model_id})${RST}\n`);
|
|
348
|
+
return model.model_id;
|
|
274
349
|
}
|
|
275
350
|
|
|
276
351
|
async function cmdModelsAdd() {
|
|
@@ -317,6 +392,8 @@ function createCommands({
|
|
|
317
392
|
const cfg = {
|
|
318
393
|
api_base: opts.apiBase || 'http://127.0.0.1:8800',
|
|
319
394
|
api_key: opts.apiKey || 'any',
|
|
395
|
+
dashboard_url: opts.dashboardUrl || current.dashboard_url,
|
|
396
|
+
auth_token: current.auth_token || '',
|
|
320
397
|
default_model: opts.defaultModel || 'default',
|
|
321
398
|
temperature: 0.7,
|
|
322
399
|
request_timeout_ms: DEFAULT_API_TIMEOUT_MS,
|
|
@@ -328,14 +405,120 @@ function createCommands({
|
|
|
328
405
|
console.log(` ${FG_GRAY}${JSON.stringify(cfg, null, 2)}${RST}\n`);
|
|
329
406
|
}
|
|
330
407
|
|
|
408
|
+
async function cmdLogin() {
|
|
409
|
+
console.log();
|
|
410
|
+
console.log(` ${FG_TEAL}${BOLD}◆ CLI Login${RST}`);
|
|
411
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
412
|
+
|
|
413
|
+
let loginRequest;
|
|
414
|
+
try {
|
|
415
|
+
loginRequest = await requestCliLogin();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Unable to start login via ${getConfig().dashboard_url}: ${error.message}${RST}\n`);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
console.log(` ${FG_GRAY}Open this URL in your browser and confirm the login:${RST}`);
|
|
422
|
+
console.log(` ${FG_CYAN}${loginRequest.verification_url}${RST}`);
|
|
423
|
+
console.log(` ${FG_DARK}Waiting for confirmation...${RST}`);
|
|
424
|
+
|
|
425
|
+
const startedAt = Date.now();
|
|
426
|
+
while (Date.now() - startedAt < LOGIN_TIMEOUT_MS) {
|
|
427
|
+
await new Promise((resolve) => setTimeout(resolve, LOGIN_POLL_INTERVAL_MS));
|
|
428
|
+
|
|
429
|
+
let status;
|
|
430
|
+
try {
|
|
431
|
+
status = await getCliLoginStatus(loginRequest.id, loginRequest.hash);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error.statusCode === 404 || error.statusCode === 410) {
|
|
434
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Login token is no longer valid.${RST}\n`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (status.status === 'authorized') {
|
|
441
|
+
const config = getConfig();
|
|
442
|
+
setConfig({
|
|
443
|
+
...config,
|
|
444
|
+
dashboard_url: config.dashboard_url,
|
|
445
|
+
auth_token: loginRequest.token,
|
|
446
|
+
});
|
|
447
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}CLI token saved to ${CONFIG_PATH}${RST}\n`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (status.status === 'expired') {
|
|
452
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Login token expired. Run semalt login again.${RST}\n`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
console.log(` ${FG_YELLOW}⚠${RST} ${FG_GRAY}Login timed out. The URL may still work for a short time.${RST}\n`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function cmdWhoAmI() {
|
|
461
|
+
let response;
|
|
462
|
+
try {
|
|
463
|
+
response = await dashboardWhoAmI();
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}${error.message}${RST}\n`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const user = response && response.user ? response.user : null;
|
|
470
|
+
if (!user) {
|
|
471
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Unable to load current user.${RST}\n`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
console.log();
|
|
476
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Current User${RST}`);
|
|
477
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
478
|
+
console.log(formatUserLine('ID', user.id));
|
|
479
|
+
console.log(formatUserLine('Email', user.email || '-'));
|
|
480
|
+
console.log(formatUserLine('Name', user.name || '-'));
|
|
481
|
+
console.log(formatUserLine('Provider', user.provider || '-'));
|
|
482
|
+
if (user.avatar_url) {
|
|
483
|
+
console.log(formatUserLine('Avatar', user.avatar_url));
|
|
484
|
+
}
|
|
485
|
+
console.log();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function cmdLogout() {
|
|
489
|
+
const config = getConfig();
|
|
490
|
+
if (!config.auth_token) {
|
|
491
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Not logged in. Run semalt login first.${RST}\n`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
await dashboardLogout();
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (error.statusCode !== 401) {
|
|
499
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}${error.message}${RST}\n`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
setConfig({
|
|
505
|
+
...config,
|
|
506
|
+
auth_token: '',
|
|
507
|
+
});
|
|
508
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Logged out and cleared local CLI token.${RST}\n`);
|
|
509
|
+
}
|
|
510
|
+
|
|
331
511
|
return {
|
|
332
512
|
cmdChat,
|
|
333
513
|
cmdCode,
|
|
334
514
|
cmdEdit,
|
|
335
515
|
cmdInit,
|
|
516
|
+
cmdLogin,
|
|
336
517
|
cmdModels,
|
|
337
518
|
cmdModelsAdd,
|
|
338
519
|
cmdShell,
|
|
520
|
+
cmdLogout,
|
|
521
|
+
cmdWhoAmI,
|
|
339
522
|
};
|
|
340
523
|
}
|
|
341
524
|
|
package/lib/config.js
CHANGED
|
@@ -7,6 +7,23 @@ const { CONFIG_PATH, DEFAULT_CONFIG } = require('./constants');
|
|
|
7
7
|
|
|
8
8
|
function normalizeConfig(cfg = {}) {
|
|
9
9
|
const merged = { ...DEFAULT_CONFIG, ...cfg };
|
|
10
|
+
const legacyDashboardUrl = typeof cfg.semalt_base_url === 'string' && cfg.semalt_base_url.trim()
|
|
11
|
+
? cfg.semalt_base_url.trim()
|
|
12
|
+
: '';
|
|
13
|
+
const requestedDashboardUrl = typeof cfg.dashboard_url === 'string' ? cfg.dashboard_url.trim() : '';
|
|
14
|
+
merged.api_base = typeof merged.api_base === 'string' ? merged.api_base.trim() : DEFAULT_CONFIG.api_base;
|
|
15
|
+
merged.api_key = typeof merged.api_key === 'string' ? merged.api_key : DEFAULT_CONFIG.api_key;
|
|
16
|
+
if (requestedDashboardUrl) {
|
|
17
|
+
merged.dashboard_url = requestedDashboardUrl;
|
|
18
|
+
} else if (legacyDashboardUrl) {
|
|
19
|
+
merged.dashboard_url = legacyDashboardUrl;
|
|
20
|
+
} else {
|
|
21
|
+
merged.dashboard_url = DEFAULT_CONFIG.dashboard_url;
|
|
22
|
+
}
|
|
23
|
+
merged.auth_token = typeof merged.auth_token === 'string' ? merged.auth_token : '';
|
|
24
|
+
merged.default_model = typeof merged.default_model === 'string' && merged.default_model.trim()
|
|
25
|
+
? merged.default_model.trim()
|
|
26
|
+
: DEFAULT_CONFIG.default_model;
|
|
10
27
|
merged.models = Array.isArray(cfg.models)
|
|
11
28
|
? cfg.models
|
|
12
29
|
.filter((entry) => entry &&
|
package/lib/constants.js
CHANGED
|
@@ -10,6 +10,8 @@ const DEFAULT_API_TIMEOUT_MS = 15 * 60 * 1000;
|
|
|
10
10
|
const DEFAULT_CONFIG = {
|
|
11
11
|
api_base: 'http://127.0.0.1:8800',
|
|
12
12
|
api_key: 'any',
|
|
13
|
+
dashboard_url: 'https://cli.semalt.ai',
|
|
14
|
+
auth_token: '',
|
|
13
15
|
default_model: 'default',
|
|
14
16
|
temperature: 0.7,
|
|
15
17
|
request_timeout_ms: DEFAULT_API_TIMEOUT_MS,
|