@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 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)
@@ -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
@@ -140,5 +140,6 @@ function describeStatus() {
140
140
  plan_code: auth?.plan_code || '',
141
141
  api_base: auth?.api_base || process.env.SISU_API_BASE || exports.DEFAULT_API_BASE,
142
142
  workspaces: readWorkspaces(),
143
+ model: readSession().last_model || '',
143
144
  };
144
145
  }
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)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stevezhou/sisu",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "SiSu CLI — one login, cloud quota, local workspace",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://www.sisu.chat",