@stevezhou/sisu 0.1.0 → 0.1.2
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 +8 -1
- package/dist/logo.js +1 -4
- package/dist/mobius.js +3 -3
- package/dist/pager/app.js +49 -7
- package/dist/pager/model.js +1 -0
- package/dist/pager/render.js +26 -3
- package/dist/tui.js +27 -18
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -126,8 +126,15 @@ async function webLoginCommand(input = {}, http = http_1.defaultHttp) {
|
|
|
126
126
|
}
|
|
127
127
|
const started = await http(`${apiBase}/api/auth/cli/device`, { method: 'POST' });
|
|
128
128
|
const startBody = await started.json().catch(() => ({}));
|
|
129
|
-
if (!started.ok)
|
|
129
|
+
if (!started.ok) {
|
|
130
|
+
if (started.status === 404 || started.status === 405) {
|
|
131
|
+
throw new Error('Browser login is not on this server yet. Use: sisu login --email <email> --password <password>');
|
|
132
|
+
}
|
|
133
|
+
if (started.status === 503) {
|
|
134
|
+
throw new Error('Sign-in is temporarily unavailable. Try again in a moment.');
|
|
135
|
+
}
|
|
130
136
|
throw new Error((0, http_1.errorDetail)(startBody, `device start failed (${started.status})`));
|
|
137
|
+
}
|
|
131
138
|
const deviceCode = String(startBody.device_code || '');
|
|
132
139
|
const userCode = String(startBody.user_code || '');
|
|
133
140
|
const rawVerify = String(startBody.verification_uri_complete || startBody.verification_uri || '');
|
package/dist/logo.js
CHANGED
|
@@ -14,10 +14,7 @@ function sisuMobiusArt(columns = 80, phase = 0, color = false) {
|
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
function sisuWordmark() {
|
|
17
|
-
return [
|
|
18
|
-
' 思 溯',
|
|
19
|
-
' S I S U',
|
|
20
|
-
].join('\n');
|
|
17
|
+
return ['思溯', 'SISU'].join('\n');
|
|
21
18
|
}
|
|
22
19
|
function sisuBanner(columns = 80, phase = 0, color = false) {
|
|
23
20
|
return [
|
package/dist/mobius.js
CHANGED
|
@@ -232,9 +232,9 @@ function renderMobiusFrame(options = {}) {
|
|
|
232
232
|
return ch;
|
|
233
233
|
let [r, g, b] = mobiusRgb(cell.t);
|
|
234
234
|
const lift = 0.18 + cell.shade * 0.95 + cell.spec * 0.7;
|
|
235
|
-
r = clamp(Math.round(r * lift), 0, 255);
|
|
236
|
-
g = clamp(Math.round(g * lift), 0, 255);
|
|
237
|
-
b = clamp(Math.round(b * lift), 0, 255);
|
|
235
|
+
r = clamp(Number.isFinite(r * lift) ? Math.round(r * lift) : 180, 0, 255);
|
|
236
|
+
g = clamp(Number.isFinite(g * lift) ? Math.round(g * lift) : 180, 0, 255);
|
|
237
|
+
b = clamp(Number.isFinite(b * lift) ? Math.round(b * lift) : 180, 0, 255);
|
|
238
238
|
return `${ansiRgb(r, g, b)}${ch}${RESET}`;
|
|
239
239
|
}).join('')).join('\n');
|
|
240
240
|
}
|
package/dist/pager/app.js
CHANGED
|
@@ -12,12 +12,18 @@ const ALT_ENTER = '\x1b[?1049h\x1b[?25l';
|
|
|
12
12
|
const ALT_LEAVE = '\x1b[?1049l\x1b[?25h';
|
|
13
13
|
const CLEAR_HOME = '\x1b[2J\x1b[H';
|
|
14
14
|
function formatChromeStatus(email, quota, conversationId) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
const who = (email || '').trim();
|
|
16
|
+
if (!who) {
|
|
17
|
+
const conv = (conversationId || '').trim();
|
|
18
|
+
return conv && conv !== 'new' ? `sisu · not signed in · ${conv}` : 'sisu · not signed in';
|
|
19
|
+
}
|
|
20
|
+
const parts = [who];
|
|
21
|
+
const quotaText = (quota || '').trim();
|
|
22
|
+
if (quotaText && quotaText !== 'quota unavailable')
|
|
23
|
+
parts.push(quotaText);
|
|
24
|
+
const conv = (conversationId || '').trim();
|
|
25
|
+
if (conv && conv !== 'new')
|
|
26
|
+
parts.push(conv);
|
|
21
27
|
return parts.join(' · ');
|
|
22
28
|
}
|
|
23
29
|
/** Short chrome fragment: `quota unlimited`, first `quota N pts`, or `quota unavailable`. */
|
|
@@ -73,9 +79,10 @@ async function runPager(io, transport, options = {}) {
|
|
|
73
79
|
const rows = options.rows ?? io.rows;
|
|
74
80
|
let theme = options.theme ?? 'dark';
|
|
75
81
|
let quotaLine = 'quota unavailable';
|
|
82
|
+
let chromeEmail = (options.email || '').trim();
|
|
76
83
|
const withChrome = (next) => ({
|
|
77
84
|
...next,
|
|
78
|
-
statusLine: formatChromeStatus(
|
|
85
|
+
statusLine: formatChromeStatus(chromeEmail, quotaLine, next.conversationId),
|
|
79
86
|
});
|
|
80
87
|
const refreshQuota = async () => {
|
|
81
88
|
if (!options.quota)
|
|
@@ -185,6 +192,35 @@ async function runPager(io, transport, options = {}) {
|
|
|
185
192
|
finish(0);
|
|
186
193
|
return;
|
|
187
194
|
}
|
|
195
|
+
if (head === '/login') {
|
|
196
|
+
if (!options.login) {
|
|
197
|
+
state = pushEntry(state, 'status', 'not wired');
|
|
198
|
+
paint();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
state = pushEntry(state, 'status', 'Opening browser to sign in…');
|
|
202
|
+
paint();
|
|
203
|
+
try {
|
|
204
|
+
const email = await options.login((line) => {
|
|
205
|
+
if (!running)
|
|
206
|
+
return;
|
|
207
|
+
state = pushEntry(state, 'status', line);
|
|
208
|
+
paint();
|
|
209
|
+
});
|
|
210
|
+
if (!running)
|
|
211
|
+
return;
|
|
212
|
+
chromeEmail = email;
|
|
213
|
+
state = withChrome(state);
|
|
214
|
+
state = pushEntry(state, 'status', `logged in as ${email}`);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
if (!running)
|
|
218
|
+
return;
|
|
219
|
+
state = pushEntry(state, 'status', err instanceof Error ? err.message : String(err));
|
|
220
|
+
}
|
|
221
|
+
paint();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
188
224
|
if (head === '/new') {
|
|
189
225
|
pickMode = false;
|
|
190
226
|
pickableIds.clear();
|
|
@@ -289,6 +325,12 @@ async function runPager(io, transport, options = {}) {
|
|
|
289
325
|
paint();
|
|
290
326
|
};
|
|
291
327
|
const sendTurn = async (prompt) => {
|
|
328
|
+
if (!chromeEmail && options.login) {
|
|
329
|
+
state = pushEntry(state, 'status', 'Not logged in. Type /login to sign in.');
|
|
330
|
+
state = clearDraft(state);
|
|
331
|
+
paint();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
292
334
|
state = pushEntry(state, 'user', prompt);
|
|
293
335
|
state = (0, model_1.startAssistant)(state);
|
|
294
336
|
state = clearDraft(state);
|
package/dist/pager/model.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.insertToolBeforeLiveAssistant = insertToolBeforeLiveAssistant;
|
|
|
8
8
|
exports.appendText = appendText;
|
|
9
9
|
exports.applyKey = applyKey;
|
|
10
10
|
exports.SLASH_COMMANDS = [
|
|
11
|
+
{ name: '/login', hint: 'Sign in with the browser' },
|
|
11
12
|
{ name: '/new', hint: 'Start a new conversation (alias: /clear)' },
|
|
12
13
|
{ name: '/resume', hint: 'Resume a conversation (alias: /history)' },
|
|
13
14
|
{ name: '/status', hint: 'Show session status' },
|
package/dist/pager/render.js
CHANGED
|
@@ -88,6 +88,25 @@ function windowAroundSelected(lines, entryOf, selected, budget, lastEntry) {
|
|
|
88
88
|
const windowStart = Math.max(0, Math.min(start, end + 1 - budget));
|
|
89
89
|
return lines.slice(windowStart, windowStart + budget);
|
|
90
90
|
}
|
|
91
|
+
function isIdleWelcome(state) {
|
|
92
|
+
return !state.conversationId && state.entries.length === 0;
|
|
93
|
+
}
|
|
94
|
+
function welcomeLines(guest) {
|
|
95
|
+
if (guest) {
|
|
96
|
+
return [
|
|
97
|
+
'SISU',
|
|
98
|
+
'',
|
|
99
|
+
'Sign in to start a conversation.',
|
|
100
|
+
'/login open the browser',
|
|
101
|
+
'/help commands',
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
return [
|
|
105
|
+
'SISU',
|
|
106
|
+
'',
|
|
107
|
+
'Ask anything, or type /help.',
|
|
108
|
+
];
|
|
109
|
+
}
|
|
91
110
|
function slashMenuLines(state) {
|
|
92
111
|
if (!state.slashOpen)
|
|
93
112
|
return [];
|
|
@@ -116,13 +135,17 @@ function renderPager(state, cols, rows, theme = 'dark') {
|
|
|
116
135
|
const bodyBudget = Math.max(0, height - chrome);
|
|
117
136
|
const slash = slashMenuLines(state);
|
|
118
137
|
const slashTake = Math.min(slash.length, bodyBudget);
|
|
119
|
-
const
|
|
138
|
+
const guest = !(state.statusLine || '').trim() || (state.statusLine || '').includes('not signed in');
|
|
139
|
+
const welcome = isIdleWelcome(state) ? welcomeLines(guest).map((line) => (line ? ` ${line}` : '')) : [];
|
|
140
|
+
const welcomeTake = Math.min(welcome.length, Math.max(0, bodyBudget - slashTake));
|
|
141
|
+
const scrollBudget = bodyBudget - slashTake - welcomeTake;
|
|
120
142
|
const laid = layoutScrollback(state, width);
|
|
121
143
|
const lastEntry = Math.max(0, state.entries.length - 1);
|
|
122
144
|
const visibleScroll = windowAroundSelected(laid.lines, laid.entryOf, state.selected, scrollBudget, lastEntry);
|
|
123
145
|
const body = [];
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
body.push(...welcome.slice(0, welcomeTake));
|
|
147
|
+
// Top-pad remaining scrollback so live status sits just above slash/prompt.
|
|
148
|
+
while (body.length + visibleScroll.length < welcomeTake + scrollBudget) {
|
|
126
149
|
body.push('');
|
|
127
150
|
}
|
|
128
151
|
body.push(...visibleScroll);
|
package/dist/tui.js
CHANGED
|
@@ -149,6 +149,7 @@ async function promptLogin(io, login) {
|
|
|
149
149
|
}
|
|
150
150
|
function tuiHelp() {
|
|
151
151
|
return [
|
|
152
|
+
'/login sign in with the browser',
|
|
152
153
|
'/status account and quota',
|
|
153
154
|
'/ls local workspace files',
|
|
154
155
|
'/history saved cloud conversations',
|
|
@@ -169,34 +170,30 @@ async function runTui(io, deps = {}) {
|
|
|
169
170
|
const openThread = deps.openThread ?? commands_1.openConversationCommand;
|
|
170
171
|
const training = deps.training ?? commands_1.setTrainingCommand;
|
|
171
172
|
const auth = deps.auth ?? store_1.readAuth;
|
|
173
|
+
const webLogin = deps.webLogin ?? commands_1.webLoginCommand;
|
|
172
174
|
const columns = deps.columns ?? process.stdout.columns ?? 80;
|
|
173
175
|
const animate = deps.animate ?? shouldAnimateSplash();
|
|
174
|
-
const color = deps.color ?? Boolean(process.stdout.isTTY);
|
|
175
176
|
try {
|
|
177
|
+
io.write(`\n${(0, logo_1.sisuWordmark)()}\n\n`);
|
|
176
178
|
if (animate) {
|
|
177
|
-
await
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
io.write(`${(0, logo_1.sisuBanner)(columns, 0.35, color)}\n`);
|
|
181
|
-
}
|
|
182
|
-
let account = auth();
|
|
183
|
-
if (!account) {
|
|
184
|
-
const login = deps.login ?? commands_1.loginCommand;
|
|
185
|
-
const result = await promptLogin(io, login);
|
|
186
|
-
if (result !== 'ok')
|
|
187
|
-
return 2;
|
|
188
|
-
account = auth();
|
|
189
|
-
if (!account) {
|
|
190
|
-
io.write('login did not persist auth\n');
|
|
191
|
-
return 2;
|
|
192
|
-
}
|
|
179
|
+
await (deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(80);
|
|
193
180
|
}
|
|
181
|
+
const account = auth();
|
|
182
|
+
const startWebLogin = async (notify) => {
|
|
183
|
+
return webLogin({
|
|
184
|
+
onStart: (info) => {
|
|
185
|
+
notify(`Open ${info.verification_uri_complete}`);
|
|
186
|
+
notify(`Confirm code ${info.user_code}`);
|
|
187
|
+
},
|
|
188
|
+
}, http);
|
|
189
|
+
};
|
|
194
190
|
if (deps.pager || shouldUsePager(deps)) {
|
|
195
191
|
io.close?.();
|
|
196
192
|
const transport = (0, transport_1.createFastApiTransport)(http);
|
|
197
193
|
return await (deps.pager ?? app_1.runPager)((0, stdio_1.stdioPagerIo)(), transport, {
|
|
198
194
|
columns,
|
|
199
|
-
email: account
|
|
195
|
+
email: account?.email,
|
|
196
|
+
login: startWebLogin,
|
|
200
197
|
quota: async () => (0, commands_1.formatQuota)(await (0, commands_1.fetchBalance)(http)),
|
|
201
198
|
status: () => status(http),
|
|
202
199
|
ls: () => {
|
|
@@ -211,12 +208,24 @@ async function runTui(io, deps = {}) {
|
|
|
211
208
|
});
|
|
212
209
|
}
|
|
213
210
|
io.write(`${await status(http)}\n`);
|
|
211
|
+
if (!account)
|
|
212
|
+
io.write('Not logged in. Type /login to sign in with your browser.\n');
|
|
214
213
|
io.write(`${tuiHelp()}\n\n`);
|
|
215
214
|
let newConversation = false;
|
|
216
215
|
while (true) {
|
|
217
216
|
const raw = (await io.question('› ')).trim();
|
|
218
217
|
if (!raw)
|
|
219
218
|
continue;
|
|
219
|
+
if (raw === '/login') {
|
|
220
|
+
try {
|
|
221
|
+
const email = await startWebLogin((line) => io.write(`${line}\n`));
|
|
222
|
+
io.write(`logged in as ${email}\n`);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
io.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
220
229
|
if (raw === '/quit' || raw === '/exit') {
|
|
221
230
|
io.write('bye\n');
|
|
222
231
|
return 0;
|