@stevezhou/sisu 0.1.3 → 0.1.5

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/logo.js CHANGED
@@ -1,10 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sisuTreeLines = exports.sisuTreeArt = exports.sisuMarkLines = exports.sisuMarkArt = void 0;
3
4
  exports.sisuMobiusArt = sisuMobiusArt;
4
5
  exports.sisuWordmark = sisuWordmark;
6
+ exports.displayWidth = displayWidth;
7
+ exports.sisuSplash = sisuSplash;
8
+ exports.sisuWelcomeCopy = sisuWelcomeCopy;
5
9
  exports.sisuBanner = sisuBanner;
6
10
  exports.stripAnsi = stripAnsi;
11
+ var mark_1 = require("./mark");
12
+ Object.defineProperty(exports, "sisuMarkArt", { enumerable: true, get: function () { return mark_1.sisuMarkArt; } });
13
+ Object.defineProperty(exports, "sisuMarkLines", { enumerable: true, get: function () { return mark_1.sisuMarkLines; } });
14
+ var tree_1 = require("./tree");
15
+ Object.defineProperty(exports, "sisuTreeArt", { enumerable: true, get: function () { return tree_1.sisuTreeArt; } });
16
+ Object.defineProperty(exports, "sisuTreeLines", { enumerable: true, get: function () { return tree_1.sisuTreeLines; } });
17
+ const mark_2 = require("./mark");
7
18
  const mobius_1 = require("./mobius");
19
+ const tree_2 = require("./tree");
20
+ const RESET = '\x1b[0m';
21
+ const TERRACOTTA = '\x1b[38;2;184;90;58m';
22
+ const INK = '\x1b[38;2;220;214;204m';
23
+ const INK_DIM = '\x1b[38;2;140;132;120m';
24
+ const HEADLINE = '思有所溯';
25
+ const TAGLINE = '为科研而生的工作台——记录退到思考之后,不打扰任何一念。';
26
+ const COLOPHON = [
27
+ '对话、文献与数据,落笔成档',
28
+ '每个结论,都能沿枝回到它的根',
29
+ '众人的知识彼此嫁接,长成一片林',
30
+ ];
31
+ const NUMERALS = ['一', '二', '三'];
8
32
  function sisuMobiusArt(columns = 80, phase = 0, color = false) {
9
33
  return (0, mobius_1.renderMobiusFrame)({
10
34
  cols: (0, mobius_1.mobiusFrameWidth)(columns),
@@ -16,14 +40,133 @@ function sisuMobiusArt(columns = 80, phase = 0, color = false) {
16
40
  function sisuWordmark() {
17
41
  return ['思溯', 'SISU'].join('\n');
18
42
  }
19
- function sisuBanner(columns = 80, phase = 0, color = false) {
20
- return [
21
- '',
22
- sisuMobiusArt(columns, phase, color),
23
- '',
24
- sisuWordmark(),
43
+ /** Terminal cells, not JS string length CJK and ∞ must sit on the grid. */
44
+ function displayWidth(text) {
45
+ let width = 0;
46
+ for (const ch of stripAnsi(text)) {
47
+ const code = ch.codePointAt(0) ?? 0;
48
+ width += isWide(code) ? 2 : 1;
49
+ }
50
+ return width;
51
+ }
52
+ function isWide(code) {
53
+ return ((code >= 0x1100 && code <= 0x115f) ||
54
+ (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
55
+ (code >= 0xac00 && code <= 0xd7a3) ||
56
+ (code >= 0xf900 && code <= 0xfaff) ||
57
+ (code >= 0xfe10 && code <= 0xfe19) ||
58
+ (code >= 0xfe30 && code <= 0xfe6f) ||
59
+ (code >= 0xff00 && code <= 0xff60) ||
60
+ (code >= 0xffe0 && code <= 0xffe6));
61
+ }
62
+ function paint(text, prefix, color) {
63
+ if (!color || !text)
64
+ return text;
65
+ return `${prefix}${text}${RESET}`;
66
+ }
67
+ function infinityMark(color) {
68
+ if (!color)
69
+ return '∞';
70
+ const [r, g, b] = (0, mark_2.markRgb)(0, 2);
71
+ return `\x1b[38;2;${r};${g};${b}m∞${RESET}`;
72
+ }
73
+ function lockup(color) {
74
+ const inf = infinityMark(color);
75
+ const name = paint('思溯', INK, color);
76
+ const latin = paint('SiSu', INK_DIM, color);
77
+ return [`${inf} ${name}`, ` ${latin}`];
78
+ }
79
+ function colophon(columns, color) {
80
+ const lines = [];
81
+ for (let i = 0; i < COLOPHON.length; i += 1) {
82
+ const num = paint(NUMERALS[i], TERRACOTTA, color);
83
+ const body = paint(COLOPHON[i], INK_DIM, color);
84
+ const line = `${num} ${body}`;
85
+ if (displayWidth(line) + 2 <= columns)
86
+ lines.push(line);
87
+ }
88
+ return lines;
89
+ }
90
+ function padLeft(text, n) {
91
+ return `${' '.repeat(Math.max(0, n))}${text}`;
92
+ }
93
+ function center(text, width) {
94
+ const vis = displayWidth(text);
95
+ const pad = Math.max(0, Math.floor((width - vis) / 2));
96
+ return padLeft(text, pad);
97
+ }
98
+ function copyColumn(columns, color, compact) {
99
+ const indent = 2;
100
+ const lines = [
101
+ ...lockup(color).map((line) => padLeft(line, indent)),
25
102
  '',
26
- ].join('\n');
103
+ padLeft(paint(HEADLINE, INK, color), indent),
104
+ ];
105
+ if (!compact && displayWidth(TAGLINE) + indent <= columns) {
106
+ lines.push(padLeft(paint(TAGLINE, INK_DIM, color), indent));
107
+ }
108
+ if (!compact) {
109
+ const notes = colophon(columns, color);
110
+ if (notes.length) {
111
+ lines.push('');
112
+ for (const note of notes)
113
+ lines.push(padLeft(note, indent));
114
+ }
115
+ }
116
+ return lines;
117
+ }
118
+ function treeColumn(columns, color) {
119
+ const raw = (0, tree_2.sisuTreeLines)(columns);
120
+ const painted = (0, tree_2.sisuTreeArt)(columns, color).split('\n');
121
+ const treeWidth = raw.reduce((max, line) => Math.max(max, line.length), 0);
122
+ const pad = Math.max(0, Math.floor((columns - treeWidth) / 2));
123
+ return painted.map((line, i) => {
124
+ const vis = raw[i]?.length ?? displayWidth(line);
125
+ return padLeft(`${line}${' '.repeat(Math.max(0, treeWidth - vis))}`, pad);
126
+ });
127
+ }
128
+ /**
129
+ * Login-page splash: lockup + 思有所溯 + 题跋 over the 溯源之树.
130
+ * Side-by-side on a wide terminal; stacked when the sheet is narrow.
131
+ */
132
+ function sisuSplash(columns = 80, color = true) {
133
+ const width = Math.max(20, Math.floor(columns));
134
+ const compact = width < 52;
135
+ const copy = copyColumn(width, color, compact);
136
+ const tree = treeColumn(width, color);
137
+ if (width >= 100) {
138
+ const gap = 3;
139
+ const leftW = Math.max(...copy.map((line) => displayWidth(line)));
140
+ const rightW = width - leftW - gap;
141
+ const right = treeColumn(rightW, color);
142
+ const rows = Math.max(copy.length, right.length);
143
+ const merged = [''];
144
+ for (let i = 0; i < rows; i += 1) {
145
+ const left = copy[i] ?? '';
146
+ const pad = Math.max(0, leftW + gap - displayWidth(left));
147
+ merged.push(`${left}${' '.repeat(pad)}${right[i] ?? ''}`);
148
+ }
149
+ merged.push('');
150
+ return merged.join('\n');
151
+ }
152
+ return ['', ...copy, '', ...tree, ''].join('\n');
153
+ }
154
+ function sisuWelcomeCopy(guest, color = true) {
155
+ const lines = [
156
+ ...lockup(color),
157
+ paint(HEADLINE, INK, color),
158
+ ];
159
+ if (guest) {
160
+ lines.push(paint('Sign in to start a conversation.', INK_DIM, color));
161
+ lines.push(paint('/login browser /help commands', INK_DIM, color));
162
+ }
163
+ else {
164
+ lines.push(paint('Ask anything, or type /help.', INK_DIM, color));
165
+ }
166
+ return lines;
167
+ }
168
+ function sisuBanner(columns = 80, _phase = 0, color = false) {
169
+ return sisuSplash(columns, color);
27
170
  }
28
171
  function stripAnsi(text) {
29
172
  return text.replace(/\x1b\[[0-9;]*m/g, '');
package/dist/main.js CHANGED
@@ -28,7 +28,7 @@ Usage:
28
28
  sisu history
29
29
  sisu thread <conversation-id>
30
30
  sisu training --on|--off
31
- sisu interactive TUI (Möbius splash)
31
+ sisu interactive TUI (welcome tree)
32
32
  sisu help
33
33
 
34
34
  Auth and workspaces live in $SISU_HOME (default ~/.sisu), shared with Desktop.
package/dist/mark.js ADDED
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /** Hand-drawn SiSu Möbius mark — the web ∞ ribbon, not a 3D rasterizer. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.markRgb = markRgb;
5
+ exports.sisuMarkLines = sisuMarkLines;
6
+ exports.sisuMarkArt = sisuMarkArt;
7
+ exports.sisuMarkWidth = sisuMarkWidth;
8
+ exports.sisuMarkHeight = sisuMarkHeight;
9
+ const RESET = '\x1b[0m';
10
+ const MARK_WIDE = [
11
+ ' ▄▄████▄▄ ▄▄████▄▄ ',
12
+ ' ▄██▀ ▀██▄ ▄██▀ ▀██▄ ',
13
+ ' ██ ▀█▄ ▄█▀ ██ ',
14
+ ' ██ ▀████▀ ██ ',
15
+ ' ██ ▄████▄ ██ ',
16
+ ' ██ ▄█▀ ▀█▄ ██ ',
17
+ ' ▀██▄ ▄██▀ ▀██▄ ▄██▀ ',
18
+ ' ▀▀████▀▀ ▀▀████▀▀ ',
19
+ ];
20
+ const MARK_NARROW = [
21
+ ' ▄██▄ ▄██▄ ',
22
+ ' █▀ ▀█▄ ▄█▀ ▀█ ',
23
+ ' █ ▀██▀ █ ',
24
+ ' █ ▄██▄ █ ',
25
+ ' █▄ ▄█▀ ▀█▄ ▄█ ',
26
+ ' ▀██▀ ▀██▀ ',
27
+ ];
28
+ function lerp(a, b, t) {
29
+ return a + (b - a) * t;
30
+ }
31
+ /** Brand gradient along the ribbon: blue → purple → gold. */
32
+ function markRgb(x, width) {
33
+ const t = width <= 1 ? 0 : Math.max(0, Math.min(1, x / (width - 1)));
34
+ if (t < 0.5) {
35
+ const k = t / 0.5;
36
+ return [
37
+ Math.round(lerp(37, 124, k)),
38
+ Math.round(lerp(99, 58, k)),
39
+ Math.round(lerp(235, 237, k)),
40
+ ];
41
+ }
42
+ const k = (t - 0.5) / 0.5;
43
+ return [
44
+ Math.round(lerp(124, 217, k)),
45
+ Math.round(lerp(58, 119, k)),
46
+ Math.round(lerp(237, 6, k)),
47
+ ];
48
+ }
49
+ function paintLine(line) {
50
+ const width = line.length;
51
+ let out = '';
52
+ for (let i = 0; i < line.length; i += 1) {
53
+ const ch = line[i];
54
+ if (ch === ' ') {
55
+ out += ' ';
56
+ continue;
57
+ }
58
+ const [r, g, b] = markRgb(i, width);
59
+ out += `\x1b[38;2;${r};${g};${b}m${ch}`;
60
+ }
61
+ return `${out}${RESET}`;
62
+ }
63
+ function sisuMarkLines(columns = 80) {
64
+ return columns >= 48 ? MARK_WIDE : MARK_NARROW;
65
+ }
66
+ function sisuMarkArt(columns = 80, color = true) {
67
+ const lines = sisuMarkLines(columns);
68
+ const painted = color ? lines.map(paintLine) : lines;
69
+ return painted.join('\n');
70
+ }
71
+ function sisuMarkWidth(columns = 80) {
72
+ return sisuMarkLines(columns).reduce((max, line) => Math.max(max, line.length), 0);
73
+ }
74
+ function sisuMarkHeight(columns = 80) {
75
+ return sisuMarkLines(columns).length;
76
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stripAnsi = void 0;
4
4
  exports.renderPager = renderPager;
5
+ const logo_1 = require("../logo");
5
6
  const model_1 = require("./model");
6
7
  const theme_1 = require("./theme");
7
8
  Object.defineProperty(exports, "stripAnsi", { enumerable: true, get: function () { return theme_1.stripAnsi; } });
@@ -100,27 +101,22 @@ function windowAroundSelected(lines, entryOf, selected, budget, lastEntry) {
100
101
  function isIdleWelcome(state) {
101
102
  return !state.conversationId && state.entries.length === 0;
102
103
  }
103
- function center(text, width) {
104
- const padLeft = Math.max(0, Math.floor((width - text.length) / 2));
104
+ function center(text, width, vis = (0, logo_1.displayWidth)(text)) {
105
+ const padLeft = Math.max(0, Math.floor((width - vis) / 2));
105
106
  return `${' '.repeat(padLeft)}${text}`;
106
107
  }
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
- ];
108
+ function welcomeLines(guest, width, height, _theme) {
109
+ const copy = (0, logo_1.sisuWelcomeCopy)(guest, true).map((line) => center(line, width));
110
+ const treeCols = height >= 18 && width >= 48 ? Math.min(width, 72) : width >= 36 ? 44 : 28;
111
+ const raw = (0, logo_1.sisuTreeLines)(treeCols);
112
+ const painted = (0, logo_1.sisuTreeArt)(treeCols, true).split('\n');
113
+ const treeWidth = raw.reduce((max, line) => Math.max(max, line.length), 0);
114
+ const treePad = Math.max(0, Math.floor((width - treeWidth) / 2));
115
+ const tree = painted.map((line, i) => {
116
+ const vis = raw[i]?.length ?? (0, logo_1.displayWidth)(line);
117
+ return `${' '.repeat(treePad)}${line}${' '.repeat(Math.max(0, treeWidth - vis))}`;
118
+ });
119
+ return [...copy, '', ...tree];
124
120
  }
125
121
  function slashMenuLines(state, theme) {
126
122
  if (!state.slashOpen)
@@ -150,7 +146,7 @@ function renderPager(state, cols, rows, themeName = 'dark') {
150
146
  const slash = slashMenuLines(state, theme);
151
147
  const slashTake = Math.min(slash.length, bodyBudget);
152
148
  const guest = !(state.statusLine || '').trim() || (state.statusLine || '').includes('not signed in');
153
- const welcome = isIdleWelcome(state) ? welcomeLines(guest, width, theme) : [];
149
+ const welcome = isIdleWelcome(state) ? welcomeLines(guest, width, height, theme) : [];
154
150
  const welcomeTake = Math.min(welcome.length, Math.max(0, bodyBudget - slashTake));
155
151
  const scrollBudget = bodyBudget - slashTake - welcomeTake;
156
152
  const laid = layoutScrollback(state, width, theme);
package/dist/tree.js ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /** 溯源之树 — hand-inked ASCII of the login welcome painting. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.sisuTreeLines = sisuTreeLines;
5
+ exports.sisuTreeArt = sisuTreeArt;
6
+ exports.sisuTreeWidth = sisuTreeWidth;
7
+ exports.sisuTreeHeight = sisuTreeHeight;
8
+ const RESET = '\x1b[0m';
9
+ const INK = [196, 186, 170];
10
+ const INK_DIM = [130, 120, 108];
11
+ const FRUIT = [184, 90, 58];
12
+ const HILL = [90, 82, 70];
13
+ /**
14
+ * Desktop login tree. Trunk sits near center; crown opens into an airy
15
+ * fan with terracotta tips — the same 图景 as BrandCanvas, not a Möbius.
16
+ */
17
+ const TREE_WIDE = [
18
+ ' • • • • • • •',
19
+ ' • • • • • • • • • •',
20
+ ' • • • • • • • • • • • •',
21
+ ' • • ╲ • ╲ • ╱ • ╲ • • •',
22
+ ' • ╲ ╱ ╲ ╲ ╱ ╲ ╱ ╲ ╲ ╲ ╱',
23
+ ' • ╲ ╱ ╲ ╲ ╱ ╲ ╱ ╲ ╲ ╲╱',
24
+ ' ╲ ╲╱ ╲ ╲╱ ╲ ╱ ╲ ╲ ╱',
25
+ ' ╲ ╱ ╲ ╱ Y ╲ ╲ ╱',
26
+ ' ╲ ╱ ╲ ╱ ╱ ╲ ╲╱',
27
+ ' ╲ ╱ Y ╱ ╲ ╱',
28
+ ' ╲ ╱ ╱ ╲ ╱ ╲╱',
29
+ ' Y ╱ ╲ ╱ ╱',
30
+ ' │ ╱ ╲ ╱',
31
+ ' │ ╱ Y',
32
+ ' │ ╱',
33
+ ' │',
34
+ '▁▁▁▂▂▁▁▁▁▁▁▁▁▁│▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁',
35
+ ];
36
+ const TREE_MID = [
37
+ ' • • • • • • •',
38
+ ' • • • • • • • • •',
39
+ ' • • ╲ • ╲ • ╱ • ╲ • •',
40
+ ' • ╲ ╱ ╲ ╲ ╱ ╲ ╱ ╲ ╲ ╱',
41
+ ' ╲ ╲ ╱ ╲ ╲ ╱ ╲ ╱ ╲ Y',
42
+ ' ╲ Y ╲ Y ╲╱ ╲ ╱',
43
+ ' ╲ ╱ ╲ ╱ ╱ Y',
44
+ ' ╲╱ Y ╱ ╱',
45
+ ' │ ╱ ╲ ╱',
46
+ ' │ ╱ ╲ ╱',
47
+ ' │ ╱ ╲╱',
48
+ ' │',
49
+ '▁▁▂▂▁▁▁▁▁▁▁│▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁',
50
+ ];
51
+ const TREE_NARROW = [
52
+ ' • • • • •',
53
+ ' • ╲ • ╱ • ╲ •',
54
+ ' ╲ ╱ ╲ ╱ ╲ ╱ ╲╱',
55
+ ' ╲ ╱ Y ╲╱ ╱',
56
+ ' Y ╱ ╲ ╱',
57
+ ' │ ╱ ╲╱',
58
+ ' │',
59
+ '▁▁▁▁▁▁▁▁│▁▁▁▁▁▁▁▁▁▁▁▁▁▁',
60
+ ];
61
+ function lerp(a, b, t) {
62
+ return a + (b - a) * t;
63
+ }
64
+ function paintRgb(ch, rgb) {
65
+ return `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${ch}`;
66
+ }
67
+ function inkAt(x, width) {
68
+ const t = width <= 1 ? 0 : x / (width - 1);
69
+ return [
70
+ Math.round(lerp(INK[0], INK_DIM[0], t * 0.45)),
71
+ Math.round(lerp(INK[1], INK_DIM[1], t * 0.45)),
72
+ Math.round(lerp(INK[2], INK_DIM[2], t * 0.45)),
73
+ ];
74
+ }
75
+ function paintLine(line, color) {
76
+ if (!color)
77
+ return line;
78
+ let out = '';
79
+ for (let i = 0; i < line.length; i += 1) {
80
+ const ch = line[i];
81
+ if (ch === ' ') {
82
+ out += ' ';
83
+ continue;
84
+ }
85
+ if (ch === '•') {
86
+ out += paintRgb(ch, FRUIT);
87
+ continue;
88
+ }
89
+ if (ch === '▁' || ch === '▂') {
90
+ out += paintRgb(ch, HILL);
91
+ continue;
92
+ }
93
+ out += paintRgb(ch, inkAt(i, line.length));
94
+ }
95
+ return `${out}${RESET}`;
96
+ }
97
+ function padBlock(lines) {
98
+ const width = lines.reduce((max, line) => Math.max(max, line.length), 0);
99
+ return lines.map((line) => line.padEnd(width, ' '));
100
+ }
101
+ function sisuTreeLines(columns = 80) {
102
+ if (columns >= 68)
103
+ return padBlock(TREE_WIDE);
104
+ if (columns >= 44)
105
+ return padBlock(TREE_MID);
106
+ return padBlock(TREE_NARROW);
107
+ }
108
+ function sisuTreeArt(columns = 80, color = true) {
109
+ return sisuTreeLines(columns).map((line) => paintLine(line, color)).join('\n');
110
+ }
111
+ function sisuTreeWidth(columns = 80) {
112
+ return sisuTreeLines(columns).reduce((max, line) => Math.max(max, line.length), 0);
113
+ }
114
+ function sisuTreeHeight(columns = 80) {
115
+ return sisuTreeLines(columns).length;
116
+ }
package/dist/tui.js CHANGED
@@ -174,7 +174,7 @@ async function runTui(io, deps = {}) {
174
174
  const columns = deps.columns ?? process.stdout.columns ?? 80;
175
175
  const animate = deps.animate ?? shouldAnimateSplash();
176
176
  try {
177
- io.write(`\n${(0, logo_1.sisuWordmark)()}\n\n`);
177
+ io.write(`${(0, logo_1.sisuSplash)(columns, true)}\n`);
178
178
  if (animate) {
179
179
  await (deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(80);
180
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stevezhou/sisu",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "SiSu CLI — one login, cloud quota, local workspace",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://www.sisu.chat",