@stevezhou/sisu 0.1.10 → 0.1.11
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/dist/commands.js +61 -2
- package/dist/main.js +11 -0
- package/dist/pager/app.js +87 -2
- package/dist/pager/model.js +6 -0
- package/dist/store.js +1 -0
- package/dist/transport.js +2 -0
- package/dist/tui.js +8 -0
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -18,6 +18,10 @@ exports.execCommand = execCommand;
|
|
|
18
18
|
exports.listConversationsCommand = listConversationsCommand;
|
|
19
19
|
exports.openConversationCommand = openConversationCommand;
|
|
20
20
|
exports.setTrainingCommand = setTrainingCommand;
|
|
21
|
+
exports.fetchModelCatalog = fetchModelCatalog;
|
|
22
|
+
exports.resolveCatalogModel = resolveCatalogModel;
|
|
23
|
+
exports.listModelsCommand = listModelsCommand;
|
|
24
|
+
exports.setModelCommand = setModelCommand;
|
|
21
25
|
const child_process_1 = require("child_process");
|
|
22
26
|
const fs_1 = __importDefault(require("fs"));
|
|
23
27
|
const path_1 = __importDefault(require("path"));
|
|
@@ -274,7 +278,7 @@ async function execCommand(prompt, options = {}, http = http_1.defaultHttp) {
|
|
|
274
278
|
headers: (0, http_1.authHeaders)(auth.token),
|
|
275
279
|
body: JSON.stringify({
|
|
276
280
|
title: text.slice(0, 50),
|
|
277
|
-
model: options.model || undefined,
|
|
281
|
+
model: options.model || (0, store_1.readSession)().last_model || undefined,
|
|
278
282
|
project_id: options.projectId || (0, store_1.readSession)().last_project_id || undefined,
|
|
279
283
|
client: stamp.client,
|
|
280
284
|
client_version: stamp.client_version,
|
|
@@ -293,7 +297,7 @@ async function execCommand(prompt, options = {}, http = http_1.defaultHttp) {
|
|
|
293
297
|
body: JSON.stringify({
|
|
294
298
|
conversation_id: conversationId,
|
|
295
299
|
message: text,
|
|
296
|
-
model: options.model || undefined,
|
|
300
|
+
model: options.model || (0, store_1.readSession)().last_model || undefined,
|
|
297
301
|
task_category: 'coding',
|
|
298
302
|
client: stamp.client,
|
|
299
303
|
client_version: stamp.client_version,
|
|
@@ -348,3 +352,58 @@ async function setTrainingCommand(optIn, http = http_1.defaultHttp) {
|
|
|
348
352
|
throw new Error((0, http_1.errorDetail)(body, `training update failed (${response.status})`));
|
|
349
353
|
return optIn ? 'training opt-in on (new turns may be used if eligible)' : 'training opt-in off';
|
|
350
354
|
}
|
|
355
|
+
function normalizeModelKey(value) {
|
|
356
|
+
return value.toLowerCase().replace(/[-_.\s]/g, '');
|
|
357
|
+
}
|
|
358
|
+
async function fetchModelCatalog(http = http_1.defaultHttp) {
|
|
359
|
+
const auth = (0, store_1.requireAuth)();
|
|
360
|
+
const response = await http(`${auth.api_base}/api/chat/models`, { headers: (0, http_1.authHeaders)(auth.token) });
|
|
361
|
+
const body = await response.json().catch(() => ({}));
|
|
362
|
+
if (!response.ok)
|
|
363
|
+
throw new Error((0, http_1.errorDetail)(body, `models failed (${response.status})`));
|
|
364
|
+
const rows = Array.isArray(body?.models) ? body.models : [];
|
|
365
|
+
const models = rows
|
|
366
|
+
.map((row) => {
|
|
367
|
+
const name = String(row?.name || '').trim();
|
|
368
|
+
if (!name)
|
|
369
|
+
return null;
|
|
370
|
+
return { name, label: String(row.display_name || row.label || name) };
|
|
371
|
+
})
|
|
372
|
+
.filter((row) => Boolean(row));
|
|
373
|
+
return { models, defaultModel: String(body?.default_model || '') };
|
|
374
|
+
}
|
|
375
|
+
function resolveCatalogModel(query, models) {
|
|
376
|
+
const needle = normalizeModelKey(query);
|
|
377
|
+
if (!needle)
|
|
378
|
+
return undefined;
|
|
379
|
+
return (models.find((row) => normalizeModelKey(row.name) === needle) ||
|
|
380
|
+
models.find((row) => normalizeModelKey(row.label) === needle) ||
|
|
381
|
+
models.find((row) => normalizeModelKey(row.name).includes(needle) || normalizeModelKey(row.label).includes(needle)));
|
|
382
|
+
}
|
|
383
|
+
async function listModelsCommand(http = http_1.defaultHttp) {
|
|
384
|
+
const { models, defaultModel } = await fetchModelCatalog(http);
|
|
385
|
+
const current = (0, store_1.readSession)().last_model || defaultModel;
|
|
386
|
+
if (!current && !models.length)
|
|
387
|
+
return 'no models available';
|
|
388
|
+
const lines = models.map((row) => {
|
|
389
|
+
const mark = row.name === current ? '* ' : ' ';
|
|
390
|
+
const extra = row.label !== row.name ? ` ${row.label}` : '';
|
|
391
|
+
return `${mark}${row.name}${extra}`;
|
|
392
|
+
});
|
|
393
|
+
if (current && !models.some((row) => row.name === current)) {
|
|
394
|
+
lines.unshift(`* ${current}`);
|
|
395
|
+
}
|
|
396
|
+
return lines.join('\n') || `* ${current}`;
|
|
397
|
+
}
|
|
398
|
+
async function setModelCommand(query, http = http_1.defaultHttp) {
|
|
399
|
+
const wanted = query.trim();
|
|
400
|
+
if (!wanted)
|
|
401
|
+
return listModelsCommand(http);
|
|
402
|
+
const { models, defaultModel } = await fetchModelCatalog(http);
|
|
403
|
+
const match = resolveCatalogModel(wanted, models);
|
|
404
|
+
const name = match?.name || (normalizeModelKey(wanted) === normalizeModelKey(defaultModel) ? defaultModel : '');
|
|
405
|
+
if (!name)
|
|
406
|
+
throw new Error(`unknown model ${wanted}`);
|
|
407
|
+
(0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_model: name });
|
|
408
|
+
return `model ${name}`;
|
|
409
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -25,6 +25,8 @@ Usage:
|
|
|
25
25
|
sisu open <dir> --project <project-id>
|
|
26
26
|
sisu ls [--project <project-id>]
|
|
27
27
|
sisu exec "<prompt>" [--project <id>] [--model <name>] [--new]
|
|
28
|
+
sisu models
|
|
29
|
+
sisu model <name>
|
|
28
30
|
sisu history
|
|
29
31
|
sisu thread <conversation-id>
|
|
30
32
|
sisu training --on|--off
|
|
@@ -146,6 +148,15 @@ async function runCli(argv, deps = {}) {
|
|
|
146
148
|
process.stdout.write(`${(0, commands_1.openConversationCommand)(id)}\n`);
|
|
147
149
|
return 0;
|
|
148
150
|
}
|
|
151
|
+
if (command === 'models') {
|
|
152
|
+
process.stdout.write(`${await (0, commands_1.listModelsCommand)(http)}\n`);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
if (command === 'model') {
|
|
156
|
+
const name = args.find((item) => !item.startsWith('--')) || '';
|
|
157
|
+
process.stdout.write(`${await (0, commands_1.setModelCommand)(name, http)}\n`);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
149
160
|
if (command === 'training') {
|
|
150
161
|
if (args.includes('--on')) {
|
|
151
162
|
process.stdout.write(`${await (0, commands_1.setTrainingCommand)(true)}\n`);
|
package/dist/pager/app.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.formatChromeStatus = formatChromeStatus;
|
|
4
7
|
exports.chromeShortQuota = chromeShortQuota;
|
|
5
8
|
exports.runPager = runPager;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
6
11
|
const store_1 = require("../store");
|
|
7
12
|
const input_1 = require("./input");
|
|
8
13
|
const model_1 = require("./model");
|
|
9
14
|
const history_1 = require("./history");
|
|
10
15
|
const logo_1 = require("../logo");
|
|
16
|
+
const text_1 = require("./text");
|
|
11
17
|
const render_1 = require("./render");
|
|
12
18
|
const ALT_ENTER = '\x1b[?1049h\x1b[?25l';
|
|
13
19
|
const ALT_LEAVE = '\x1b[?1049l\x1b[?25h';
|
|
@@ -17,13 +23,16 @@ function shortConversationId(id) {
|
|
|
17
23
|
return '';
|
|
18
24
|
return conv.length > 12 ? conv.slice(0, 8) : conv;
|
|
19
25
|
}
|
|
20
|
-
function formatChromeStatus(email, quota, conversationId = '') {
|
|
26
|
+
function formatChromeStatus(email, quota, conversationId = '', model = '') {
|
|
21
27
|
const who = (email || '').trim();
|
|
22
28
|
const conv = shortConversationId(conversationId);
|
|
29
|
+
const modelName = (model || '').trim();
|
|
23
30
|
if (!who) {
|
|
24
31
|
return conv ? `sisu · not signed in · ${conv}` : 'sisu · not signed in';
|
|
25
32
|
}
|
|
26
33
|
const parts = [who];
|
|
34
|
+
if (modelName)
|
|
35
|
+
parts.push(modelName);
|
|
27
36
|
const quotaText = (quota || '').trim();
|
|
28
37
|
if (quotaText && quotaText !== 'quota unavailable')
|
|
29
38
|
parts.push(quotaText);
|
|
@@ -74,6 +83,8 @@ function commandHead(text) {
|
|
|
74
83
|
return '/new';
|
|
75
84
|
if (token === '/history')
|
|
76
85
|
return '/resume';
|
|
86
|
+
if (token === '/m')
|
|
87
|
+
return '/model';
|
|
77
88
|
return token;
|
|
78
89
|
}
|
|
79
90
|
async function resolveText(value) {
|
|
@@ -85,9 +96,10 @@ async function runPager(io, transport, options = {}) {
|
|
|
85
96
|
let theme = options.theme ?? 'dark';
|
|
86
97
|
let quotaLine = 'quota unavailable';
|
|
87
98
|
let chromeEmail = (options.email || '').trim();
|
|
99
|
+
let chromeModel = ((0, store_1.readSession)().last_model || '').trim();
|
|
88
100
|
const withChrome = (next) => ({
|
|
89
101
|
...next,
|
|
90
|
-
statusLine: formatChromeStatus(chromeEmail, quotaLine, next.conversationId),
|
|
102
|
+
statusLine: formatChromeStatus(chromeEmail, quotaLine, next.conversationId, chromeModel),
|
|
91
103
|
});
|
|
92
104
|
const refreshQuota = async () => {
|
|
93
105
|
if (!options.quota)
|
|
@@ -295,6 +307,79 @@ async function runPager(io, transport, options = {}) {
|
|
|
295
307
|
paint();
|
|
296
308
|
return;
|
|
297
309
|
}
|
|
310
|
+
if (head === '/logout') {
|
|
311
|
+
options.logout?.();
|
|
312
|
+
chromeEmail = '';
|
|
313
|
+
chromeModel = '';
|
|
314
|
+
state = withChrome(state);
|
|
315
|
+
state = pushEntry(state, 'status', 'signed out');
|
|
316
|
+
paint();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (head === '/models' || (head === '/model' && !text.replace(/^\S+\s*/, '').trim())) {
|
|
320
|
+
const body = options.models ? await resolveText(options.models()) : 'not wired';
|
|
321
|
+
if (!running)
|
|
322
|
+
return;
|
|
323
|
+
state = pushEntry(state, 'status', body);
|
|
324
|
+
paint();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (head === '/model') {
|
|
328
|
+
const wanted = text.replace(/^\S+\s*/, '').trim();
|
|
329
|
+
if (!options.setModel) {
|
|
330
|
+
state = pushEntry(state, 'status', 'not wired');
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
try {
|
|
334
|
+
const body = await resolveText(options.setModel(wanted));
|
|
335
|
+
if (!running)
|
|
336
|
+
return;
|
|
337
|
+
chromeModel = ((0, store_1.readSession)().last_model || wanted).trim();
|
|
338
|
+
state = withChrome(state);
|
|
339
|
+
state = pushEntry(state, 'status', body);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
if (!running)
|
|
343
|
+
return;
|
|
344
|
+
state = pushEntry(state, 'status', err instanceof Error ? err.message : String(err));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
paint();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (head === '/copy') {
|
|
351
|
+
const last = [...state.entries].reverse().find((entry) => entry.kind === 'assistant');
|
|
352
|
+
const body = last ? (0, text_1.visibleAssistantText)(last.text).trim() : '';
|
|
353
|
+
if (!body) {
|
|
354
|
+
state = pushEntry(state, 'status', 'nothing to copy');
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
const dest = path_1.default.join((0, store_1.getSisuHome)(), 'last-copy.txt');
|
|
358
|
+
fs_1.default.mkdirSync((0, store_1.getSisuHome)(), { recursive: true });
|
|
359
|
+
fs_1.default.writeFileSync(dest, `${body}\n`, 'utf8');
|
|
360
|
+
state = pushEntry(state, 'status', `copied to ${dest}`);
|
|
361
|
+
}
|
|
362
|
+
paint();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (head === '/export') {
|
|
366
|
+
const dest = text.replace(/^\S+\s*/, '').trim() || path_1.default.join((0, store_1.getSisuHome)(), 'export.md');
|
|
367
|
+
const body = state.entries
|
|
368
|
+
.map((entry) => {
|
|
369
|
+
if (entry.kind === 'user')
|
|
370
|
+
return `## You\n\n${entry.text}`;
|
|
371
|
+
if (entry.kind === 'assistant')
|
|
372
|
+
return `## SiSu\n\n${(0, text_1.visibleAssistantText)(entry.text)}`;
|
|
373
|
+
return '';
|
|
374
|
+
})
|
|
375
|
+
.filter(Boolean)
|
|
376
|
+
.join('\n\n');
|
|
377
|
+
fs_1.default.mkdirSync(path_1.default.dirname(path_1.default.resolve(dest)), { recursive: true });
|
|
378
|
+
fs_1.default.writeFileSync(path_1.default.resolve(dest), `${body}\n`, 'utf8');
|
|
379
|
+
state = pushEntry(state, 'status', `exported ${path_1.default.resolve(dest)}`);
|
|
380
|
+
paint();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
298
383
|
if (head === '/status') {
|
|
299
384
|
const body = options.status ? await resolveText(options.status()) : 'not wired';
|
|
300
385
|
if (!running)
|
package/dist/pager/model.js
CHANGED
|
@@ -9,8 +9,13 @@ exports.appendText = appendText;
|
|
|
9
9
|
exports.applyKey = applyKey;
|
|
10
10
|
exports.SLASH_COMMANDS = [
|
|
11
11
|
{ name: '/login', hint: 'Sign in with the browser' },
|
|
12
|
+
{ name: '/logout', hint: 'Sign out of this terminal' },
|
|
12
13
|
{ name: '/new', hint: 'Start a new conversation (alias: /clear)' },
|
|
13
14
|
{ name: '/resume', hint: 'Resume a conversation (alias: /history)' },
|
|
15
|
+
{ name: '/model', hint: 'Switch model (alias: /m)' },
|
|
16
|
+
{ name: '/models', hint: 'List models available to your account' },
|
|
17
|
+
{ name: '/copy', hint: 'Copy the last assistant reply' },
|
|
18
|
+
{ name: '/export', hint: 'Write this conversation to a markdown file' },
|
|
14
19
|
{ name: '/status', hint: 'Show session status' },
|
|
15
20
|
{ name: '/ls', hint: 'List local workspace files' },
|
|
16
21
|
{ name: '/training', hint: 'Training mode' },
|
|
@@ -23,6 +28,7 @@ const SLASH_ALIASES = {
|
|
|
23
28
|
'/clear': '/new',
|
|
24
29
|
'/history': '/resume',
|
|
25
30
|
'/exit': '/quit',
|
|
31
|
+
'/m': '/model',
|
|
26
32
|
};
|
|
27
33
|
let nextEntryId = 1;
|
|
28
34
|
function entryId() {
|
package/dist/store.js
CHANGED
package/dist/transport.js
CHANGED
|
@@ -64,6 +64,7 @@ function createFastApiTransport(http) {
|
|
|
64
64
|
headers: (0, http_1.authHeaders)(auth.token),
|
|
65
65
|
body: JSON.stringify({
|
|
66
66
|
title: text.slice(0, 50),
|
|
67
|
+
model: session.last_model || undefined,
|
|
67
68
|
project_id: session.last_project_id || undefined,
|
|
68
69
|
client: stamp.client,
|
|
69
70
|
client_version: stamp.client_version,
|
|
@@ -87,6 +88,7 @@ function createFastApiTransport(http) {
|
|
|
87
88
|
body: JSON.stringify({
|
|
88
89
|
conversation_id: conversationId,
|
|
89
90
|
message: text,
|
|
91
|
+
model: session.last_model || undefined,
|
|
90
92
|
task_category: 'coding',
|
|
91
93
|
client: stamp.client,
|
|
92
94
|
client_version: stamp.client_version,
|
package/dist/tui.js
CHANGED
|
@@ -171,6 +171,11 @@ async function promptLogin(io, login) {
|
|
|
171
171
|
function tuiHelp() {
|
|
172
172
|
return [
|
|
173
173
|
'/login sign in with the browser',
|
|
174
|
+
'/logout sign out',
|
|
175
|
+
'/model switch model (alias /m)',
|
|
176
|
+
'/models list available models',
|
|
177
|
+
'/copy copy last reply to ~/.sisu/last-copy.txt',
|
|
178
|
+
'/export write the thread to a markdown file',
|
|
174
179
|
'/status account and quota',
|
|
175
180
|
'/ls local workspace files',
|
|
176
181
|
'/history saved cloud conversations',
|
|
@@ -224,6 +229,9 @@ async function runTui(io, deps = {}) {
|
|
|
224
229
|
columns,
|
|
225
230
|
email: account?.email,
|
|
226
231
|
login: startWebLogin,
|
|
232
|
+
logout: commands_1.logoutCommand,
|
|
233
|
+
models: () => (0, commands_1.listModelsCommand)(http),
|
|
234
|
+
setModel: (name) => (0, commands_1.setModelCommand)(name, http),
|
|
227
235
|
intro: animate,
|
|
228
236
|
sleep: deps.sleep,
|
|
229
237
|
quota: async () => (0, commands_1.formatQuota)(await (0, commands_1.fetchBalance)(http)),
|