@stevezhou/sisu 0.1.1 → 0.1.3
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/pager/app.js +12 -9
- package/dist/pager/render.js +74 -39
- package/dist/pager/theme.js +53 -5
- 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/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`. */
|
|
@@ -89,9 +95,6 @@ async function runPager(io, transport, options = {}) {
|
|
|
89
95
|
}
|
|
90
96
|
};
|
|
91
97
|
let state = withChrome((0, model_1.createPagerState)());
|
|
92
|
-
if (!chromeEmail && options.login) {
|
|
93
|
-
state = pushEntry(state, 'status', 'Not logged in. Type /login to sign in with your browser.');
|
|
94
|
-
}
|
|
95
98
|
let rest = '';
|
|
96
99
|
let newConversation = false;
|
|
97
100
|
let pickMode = false;
|
package/dist/pager/render.js
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripAnsi = void 0;
|
|
3
4
|
exports.renderPager = renderPager;
|
|
4
5
|
const model_1 = require("./model");
|
|
5
6
|
const theme_1 = require("./theme");
|
|
7
|
+
Object.defineProperty(exports, "stripAnsi", { enumerable: true, get: function () { return theme_1.stripAnsi; } });
|
|
6
8
|
const PROMPT_PREFIX = '› ';
|
|
7
9
|
const PROMPT_BOX_ROWS = 2;
|
|
8
10
|
const STATUS_ROWS = 1;
|
|
9
11
|
const MARK_WIDTH = 2;
|
|
10
|
-
/** Pad (and clip reserved chrome) to exactly `cols` cells. */
|
|
11
|
-
function pad(line, cols) {
|
|
12
|
-
const width = Math.max(0, cols);
|
|
13
|
-
if (width === 0)
|
|
14
|
-
return '';
|
|
15
|
-
const plain = (0, theme_1.stripAnsi)(line);
|
|
16
|
-
if (plain.length === width)
|
|
17
|
-
return plain;
|
|
18
|
-
if (plain.length > width)
|
|
19
|
-
return plain.slice(0, width);
|
|
20
|
-
return plain.padEnd(width, ' ');
|
|
21
|
-
}
|
|
22
12
|
function wrapPlain(text, width) {
|
|
23
13
|
const max = Math.max(1, width);
|
|
24
14
|
if (!text)
|
|
@@ -40,6 +30,24 @@ function lineCount(text) {
|
|
|
40
30
|
return 0;
|
|
41
31
|
return text.split('\n').length;
|
|
42
32
|
}
|
|
33
|
+
function paintKind(kind, body, theme) {
|
|
34
|
+
if (!body)
|
|
35
|
+
return body;
|
|
36
|
+
if (kind === 'user')
|
|
37
|
+
return theme.user(body);
|
|
38
|
+
if (kind === 'tool')
|
|
39
|
+
return theme.tool(body);
|
|
40
|
+
if (kind === 'status')
|
|
41
|
+
return theme.dim(body);
|
|
42
|
+
return theme.text(body);
|
|
43
|
+
}
|
|
44
|
+
function entryPrefix(kind) {
|
|
45
|
+
if (kind === 'user')
|
|
46
|
+
return 'you ';
|
|
47
|
+
if (kind === 'tool')
|
|
48
|
+
return 'tool ';
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
43
51
|
function entryBodyLines(entry, wrapWidth) {
|
|
44
52
|
if (entry.folded) {
|
|
45
53
|
const n = lineCount(entry.text);
|
|
@@ -48,18 +56,19 @@ function entryBodyLines(entry, wrapWidth) {
|
|
|
48
56
|
}
|
|
49
57
|
if (!entry.text)
|
|
50
58
|
return [''];
|
|
51
|
-
return wrapPlain(entry.text
|
|
59
|
+
return wrapPlain(`${entryPrefix(entry.kind)}${entry.text}`, wrapWidth);
|
|
52
60
|
}
|
|
53
|
-
function layoutScrollback(state, cols) {
|
|
61
|
+
function layoutScrollback(state, cols, theme) {
|
|
54
62
|
const wrapWidth = Math.max(1, cols - MARK_WIDTH);
|
|
55
63
|
const lines = [];
|
|
56
64
|
const entryOf = [];
|
|
57
65
|
for (let i = 0; i < state.entries.length; i += 1) {
|
|
58
66
|
const entry = state.entries[i];
|
|
59
67
|
const body = entryBodyLines(entry, wrapWidth);
|
|
60
|
-
const
|
|
68
|
+
const selected = i === state.selected && state.entries.length > 0;
|
|
61
69
|
for (let j = 0; j < body.length; j += 1) {
|
|
62
|
-
|
|
70
|
+
const mark = selected && j === 0 ? theme.accent('▸ ') : ' ';
|
|
71
|
+
lines.push(`${mark}${paintKind(entry.kind, body[j], theme)}`);
|
|
63
72
|
entryOf.push(i);
|
|
64
73
|
}
|
|
65
74
|
}
|
|
@@ -88,24 +97,48 @@ function windowAroundSelected(lines, entryOf, selected, budget, lastEntry) {
|
|
|
88
97
|
const windowStart = Math.max(0, Math.min(start, end + 1 - budget));
|
|
89
98
|
return lines.slice(windowStart, windowStart + budget);
|
|
90
99
|
}
|
|
91
|
-
function
|
|
100
|
+
function isIdleWelcome(state) {
|
|
101
|
+
return !state.conversationId && state.entries.length === 0;
|
|
102
|
+
}
|
|
103
|
+
function center(text, width) {
|
|
104
|
+
const padLeft = Math.max(0, Math.floor((width - text.length) / 2));
|
|
105
|
+
return `${' '.repeat(padLeft)}${text}`;
|
|
106
|
+
}
|
|
107
|
+
function welcomeLines(guest, width, theme) {
|
|
108
|
+
const title = theme.accent(center('SISU', width));
|
|
109
|
+
if (guest) {
|
|
110
|
+
return [
|
|
111
|
+
title,
|
|
112
|
+
'',
|
|
113
|
+
theme.text(center('Sign in to start a conversation.', width)),
|
|
114
|
+
'',
|
|
115
|
+
theme.dim(center('/login browser', width)),
|
|
116
|
+
theme.dim(center('/help commands', width)),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
return [
|
|
120
|
+
title,
|
|
121
|
+
'',
|
|
122
|
+
theme.dim(center('Ask anything, or type /help.', width)),
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
function slashMenuLines(state, theme) {
|
|
92
126
|
if (!state.slashOpen)
|
|
93
127
|
return [];
|
|
94
128
|
const items = (0, model_1.filterSlash)(state.draft);
|
|
95
129
|
return items.map((item, index) => {
|
|
96
|
-
const
|
|
97
|
-
|
|
130
|
+
const active = index === state.slashIndex;
|
|
131
|
+
const mark = active ? theme.accent('› ') : ' ';
|
|
132
|
+
const name = active ? theme.accent(item.name) : theme.text(item.name);
|
|
133
|
+
return `${mark}${name} ${theme.dim(item.hint)}`;
|
|
98
134
|
});
|
|
99
135
|
}
|
|
100
136
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* frame itself is plain so `line.length === cols` for the tty writer.
|
|
104
|
-
* Deterministic: no clock, no I/O.
|
|
137
|
+
* Fixed-grid frame: always `rows` lines, each `cols` visible cells.
|
|
138
|
+
* Colors are 24-bit SGR; measure width with stripAnsi / visibleWidth.
|
|
105
139
|
*/
|
|
106
|
-
function renderPager(state, cols, rows,
|
|
107
|
-
|
|
108
|
-
(0, theme_1.getTheme)(theme);
|
|
140
|
+
function renderPager(state, cols, rows, themeName = 'dark') {
|
|
141
|
+
const theme = (0, theme_1.getTheme)(themeName);
|
|
109
142
|
const height = Math.max(0, rows);
|
|
110
143
|
const width = Math.max(0, cols);
|
|
111
144
|
if (height === 0)
|
|
@@ -114,34 +147,36 @@ function renderPager(state, cols, rows, theme = 'dark') {
|
|
|
114
147
|
const statusRows = height > promptRows ? Math.min(STATUS_ROWS, height - promptRows) : 0;
|
|
115
148
|
const chrome = promptRows + statusRows;
|
|
116
149
|
const bodyBudget = Math.max(0, height - chrome);
|
|
117
|
-
const slash = slashMenuLines(state);
|
|
150
|
+
const slash = slashMenuLines(state, theme);
|
|
118
151
|
const slashTake = Math.min(slash.length, bodyBudget);
|
|
119
|
-
const
|
|
120
|
-
const
|
|
152
|
+
const guest = !(state.statusLine || '').trim() || (state.statusLine || '').includes('not signed in');
|
|
153
|
+
const welcome = isIdleWelcome(state) ? welcomeLines(guest, width, theme) : [];
|
|
154
|
+
const welcomeTake = Math.min(welcome.length, Math.max(0, bodyBudget - slashTake));
|
|
155
|
+
const scrollBudget = bodyBudget - slashTake - welcomeTake;
|
|
156
|
+
const laid = layoutScrollback(state, width, theme);
|
|
121
157
|
const lastEntry = Math.max(0, state.entries.length - 1);
|
|
122
158
|
const visibleScroll = windowAroundSelected(laid.lines, laid.entryOf, state.selected, scrollBudget, lastEntry);
|
|
123
159
|
const body = [];
|
|
124
|
-
|
|
125
|
-
while (body.length + visibleScroll.length < scrollBudget) {
|
|
160
|
+
body.push(...welcome.slice(0, welcomeTake));
|
|
161
|
+
while (body.length + visibleScroll.length < welcomeTake + scrollBudget) {
|
|
126
162
|
body.push('');
|
|
127
163
|
}
|
|
128
164
|
body.push(...visibleScroll);
|
|
129
165
|
body.push(...slash.slice(0, slashTake));
|
|
130
|
-
const lines = body.map((line) =>
|
|
166
|
+
const lines = body.map((line) => (0, theme_1.padVisible)(line, width));
|
|
131
167
|
if (statusRows > 0) {
|
|
132
|
-
lines.push(
|
|
168
|
+
lines.push((0, theme_1.padVisible)(theme.dim(state.statusLine ?? ''), width));
|
|
133
169
|
}
|
|
134
170
|
if (promptRows >= 2) {
|
|
135
|
-
|
|
136
|
-
lines.push(
|
|
137
|
-
lines.push(pad(`${PROMPT_PREFIX}${state.draft}`, width));
|
|
171
|
+
lines.push((0, theme_1.padVisible)(theme.border('─'.repeat(width)), width));
|
|
172
|
+
lines.push((0, theme_1.padVisible)(`${theme.accent(PROMPT_PREFIX)}${theme.text(state.draft)}`, width));
|
|
138
173
|
}
|
|
139
174
|
else if (promptRows === 1) {
|
|
140
|
-
lines.push(
|
|
175
|
+
lines.push((0, theme_1.padVisible)(`${theme.accent(PROMPT_PREFIX)}${theme.text(state.draft)}`, width));
|
|
141
176
|
}
|
|
142
177
|
while (lines.length < height)
|
|
143
|
-
lines.push(
|
|
178
|
+
lines.push((0, theme_1.padVisible)('', width));
|
|
144
179
|
if (lines.length > height)
|
|
145
180
|
lines.length = height;
|
|
146
|
-
return lines.map((line) =>
|
|
181
|
+
return lines.map((line) => (0, theme_1.padVisible)(line, width)).join('\n');
|
|
147
182
|
}
|
package/dist/pager/theme.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/** SiSu pager palette: blue → purple → gold
|
|
2
|
+
/** SiSu pager palette: blue → purple → gold. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.getTheme = getTheme;
|
|
5
5
|
exports.stripAnsi = stripAnsi;
|
|
6
|
+
exports.visibleWidth = visibleWidth;
|
|
7
|
+
exports.clipVisible = clipVisible;
|
|
8
|
+
exports.padVisible = padVisible;
|
|
6
9
|
const RESET = '\x1b[0m';
|
|
7
10
|
function ansiRgb(r, g, b) {
|
|
8
11
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
@@ -10,16 +13,17 @@ function ansiRgb(r, g, b) {
|
|
|
10
13
|
function paint(prefix) {
|
|
11
14
|
return (s) => (s ? `${prefix}${s}${RESET}` : s);
|
|
12
15
|
}
|
|
13
|
-
/** Brand anchors from mobiusRgb (blue, purple, gold). */
|
|
14
16
|
const BLUE = ansiRgb(37, 99, 235);
|
|
15
17
|
const PURPLE = ansiRgb(124, 58, 237);
|
|
16
18
|
const GOLD = ansiRgb(217, 119, 6);
|
|
17
19
|
const DARK = {
|
|
18
20
|
text: paint(ansiRgb(230, 232, 240)),
|
|
19
|
-
dim: paint(ansiRgb(
|
|
21
|
+
dim: paint(ansiRgb(118, 124, 148)),
|
|
20
22
|
accent: paint(GOLD),
|
|
21
23
|
error: paint(ansiRgb(239, 68, 68)),
|
|
22
|
-
border: paint(
|
|
24
|
+
border: paint(ansiRgb(72, 64, 96)),
|
|
25
|
+
user: paint(ansiRgb(156, 174, 214)),
|
|
26
|
+
tool: paint(PURPLE),
|
|
23
27
|
reset: RESET,
|
|
24
28
|
};
|
|
25
29
|
const LIGHT = {
|
|
@@ -28,12 +32,56 @@ const LIGHT = {
|
|
|
28
32
|
accent: paint(GOLD),
|
|
29
33
|
error: paint(ansiRgb(185, 28, 28)),
|
|
30
34
|
border: paint(BLUE),
|
|
35
|
+
user: paint(ansiRgb(37, 99, 180)),
|
|
36
|
+
tool: paint(PURPLE),
|
|
31
37
|
reset: RESET,
|
|
32
38
|
};
|
|
33
39
|
function getTheme(name = 'dark') {
|
|
34
40
|
return name === 'light' ? LIGHT : DARK;
|
|
35
41
|
}
|
|
36
|
-
/** Strip 24-bit / SGR sequences for visible-width measurement. */
|
|
37
42
|
function stripAnsi(s) {
|
|
38
43
|
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
39
44
|
}
|
|
45
|
+
function visibleWidth(line) {
|
|
46
|
+
return stripAnsi(line).length;
|
|
47
|
+
}
|
|
48
|
+
function clipVisible(line, width) {
|
|
49
|
+
if (width <= 0)
|
|
50
|
+
return '';
|
|
51
|
+
if (visibleWidth(line) <= width)
|
|
52
|
+
return line;
|
|
53
|
+
let seen = 0;
|
|
54
|
+
let out = '';
|
|
55
|
+
const re = /\x1b\[[0-9;]*m/g;
|
|
56
|
+
let last = 0;
|
|
57
|
+
let match = re.exec(line);
|
|
58
|
+
while (match) {
|
|
59
|
+
for (let i = last; i < match.index && seen < width; i += 1) {
|
|
60
|
+
out += line[i];
|
|
61
|
+
seen += 1;
|
|
62
|
+
}
|
|
63
|
+
if (seen >= width)
|
|
64
|
+
break;
|
|
65
|
+
out += match[0];
|
|
66
|
+
last = match.index + match[0].length;
|
|
67
|
+
match = re.exec(line);
|
|
68
|
+
}
|
|
69
|
+
for (let i = last; i < line.length && seen < width; i += 1) {
|
|
70
|
+
if (line[i] === '\x1b')
|
|
71
|
+
break;
|
|
72
|
+
out += line[i];
|
|
73
|
+
seen += 1;
|
|
74
|
+
}
|
|
75
|
+
return out + RESET;
|
|
76
|
+
}
|
|
77
|
+
function padVisible(line, cols) {
|
|
78
|
+
const width = Math.max(0, cols);
|
|
79
|
+
if (width === 0)
|
|
80
|
+
return '';
|
|
81
|
+
const vis = visibleWidth(line);
|
|
82
|
+
if (vis === width)
|
|
83
|
+
return line;
|
|
84
|
+
if (vis > width)
|
|
85
|
+
return clipVisible(line, width);
|
|
86
|
+
return `${line}${' '.repeat(width - vis)}`;
|
|
87
|
+
}
|