@jun133/kitty 0.0.10 → 0.0.12

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.
@@ -1,530 +0,0 @@
1
- // src/shell/tui/theme.ts
2
- var TUI_COLORS = {
3
- background: "#080808",
4
- panel: "#191919",
5
- panelStrong: "#222222",
6
- border: "#303030",
7
- muted: "#8d8d8d",
8
- text: "#e5e7eb",
9
- user: "#d6a84f",
10
- assistant: "#e5e7eb",
11
- reasoning: "#a8a8a8",
12
- thought: "#c99a3f",
13
- system: "#cbd5e1",
14
- success: "#34d399",
15
- warning: "#d6a84f",
16
- error: "#f87171"
17
- };
18
-
19
- // src/shell/tui/transcriptLayout.ts
20
- import wrapAnsi from "wrap-ansi";
21
- import stringWidth from "string-width";
22
-
23
- // src/shell/tui/markdown.ts
24
- import { marked } from "marked";
25
- function renderMarkdownLines(markdown) {
26
- const tokens = marked.lexer(markdown, {
27
- gfm: true
28
- });
29
- const lines = [];
30
- for (const token of tokens) {
31
- appendToken(lines, token);
32
- }
33
- return trimOuterBlankLines(lines);
34
- }
35
- function appendToken(lines, token) {
36
- switch (token.type) {
37
- case "heading":
38
- push(lines, "heading", inlineText(token.text));
39
- pushBlank(lines);
40
- return;
41
- case "paragraph":
42
- push(lines, "text", inlineText(token.text));
43
- pushBlank(lines);
44
- return;
45
- case "list":
46
- appendList(lines, token);
47
- pushBlank(lines);
48
- return;
49
- case "code":
50
- appendCode(lines, token);
51
- pushBlank(lines);
52
- return;
53
- case "blockquote":
54
- appendBlockquote(lines, token);
55
- pushBlank(lines);
56
- return;
57
- case "hr":
58
- push(lines, "rule", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
59
- pushBlank(lines);
60
- return;
61
- case "space":
62
- pushBlank(lines);
63
- return;
64
- case "table":
65
- appendTable(lines, token);
66
- pushBlank(lines);
67
- return;
68
- default:
69
- if (typeof token.raw === "string" && token.raw.trim()) {
70
- push(lines, "text", stripMarkdownInline(token.raw));
71
- pushBlank(lines);
72
- }
73
- }
74
- }
75
- function appendList(lines, token) {
76
- const start = typeof token.start === "number" ? token.start : Number.parseInt(String(token.start ?? 1), 10);
77
- const orderedStart = Number.isFinite(start) ? start : 1;
78
- token.items.forEach((item, index) => {
79
- const marker = token.ordered ? `${orderedStart + index}.` : "\u2022";
80
- const text = inlineText(item.text);
81
- const itemLines = text.split("\n");
82
- push(lines, "list", `${marker} ${itemLines[0] ?? ""}`);
83
- for (const line of itemLines.slice(1)) {
84
- push(lines, "list", ` ${line}`);
85
- }
86
- });
87
- }
88
- function appendCode(lines, token) {
89
- for (const line of token.text.split(/\r?\n/)) {
90
- push(lines, "code", line);
91
- }
92
- }
93
- function appendBlockquote(lines, token) {
94
- const nested = renderMarkdownLines(token.text);
95
- for (const line of nested) {
96
- push(lines, "quote", line.text ? `\u2502 ${line.text}` : "\u2502");
97
- }
98
- }
99
- function appendTable(lines, token) {
100
- const header = token.header.map((cell) => inlineText(cell.text));
101
- const rows = token.rows.map((row) => row.map((cell) => inlineText(cell.text)));
102
- const widths = header.map((cell, index) => Math.max(
103
- cell.length,
104
- ...rows.map((row) => row[index]?.length ?? 0)
105
- ));
106
- push(lines, "table", joinTableRow(header, widths));
107
- push(lines, "table", widths.map((width) => "\u2500".repeat(Math.max(3, width))).join("\u2500\u253C\u2500"));
108
- for (const row of token.rows) {
109
- push(lines, "table", joinTableRow(row.map((cell) => inlineText(cell.text)), widths));
110
- }
111
- }
112
- function joinTableRow(cells, widths) {
113
- return cells.map((cell, index) => cell.padEnd(widths[index] ?? cell.length)).join(" \u2502 ");
114
- }
115
- function inlineText(text) {
116
- return stripMarkdownInline(text).replace(/\s+\n/g, "\n").trimEnd();
117
- }
118
- function stripMarkdownInline(text) {
119
- return text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/_([^_]+)_/g, "$1").replace(/~~([^~]+)~~/g, "$1").replace(/<br\s*\/?>/gi, "\n").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
120
- }
121
- function push(lines, kind, text) {
122
- lines.push({ kind, text });
123
- }
124
- function pushBlank(lines) {
125
- push(lines, "text", "");
126
- }
127
- function trimOuterBlankLines(lines) {
128
- let start = 0;
129
- let end = lines.length;
130
- while (start < end && lines[start]?.text === "") {
131
- start += 1;
132
- }
133
- while (end > start && lines[end - 1]?.text === "") {
134
- end -= 1;
135
- }
136
- return lines.slice(start, end);
137
- }
138
-
139
- // src/shell/tui/transcriptLayout.ts
140
- var TRANSCRIPT_OUTER_PADDING_X = 3;
141
- var MIN_BODY_WIDTH = 8;
142
- var REASONING_PREFIX = "Thinking: ";
143
- function renderTranscriptLineViews(entries, viewportWidth, theme) {
144
- return entries.flatMap((entry) => renderEntryLineViews(entry, viewportWidth, theme));
145
- }
146
- function measureTranscriptRows(entries, viewportWidth, theme) {
147
- return renderTranscriptLineViews(entries, viewportWidth, theme).length;
148
- }
149
- function renderEntryLineViews(entry, viewportWidth, theme) {
150
- const frame = readRoleFrame(entry.role, viewportWidth);
151
- const style = readRoleStyle(entry.role, theme);
152
- const sourceRows = readEntryDisplayRows(entry);
153
- const contentRows = wrapEntryRows(entry, sourceRows, frame.bodyWidth);
154
- const rows = contentRows.length > 0 ? contentRows : [{ markdownKind: void 0, prefix: "", text: "" }];
155
- const entryId = entry.id;
156
- return [
157
- {
158
- id: `${entryId}-spacer`,
159
- entryId,
160
- role: entry.role,
161
- kind: "spacer",
162
- text: "",
163
- prefix: "",
164
- markdownKind: void 0,
165
- isFirstContentLine: false,
166
- frame,
167
- style
168
- },
169
- ...rows.map((row, index) => ({
170
- id: `${entryId}-line-${index + 1}`,
171
- entryId,
172
- role: entry.role,
173
- kind: "content",
174
- text: row.text,
175
- prefix: row.prefix,
176
- markdownKind: row.markdownKind,
177
- isFirstContentLine: index === 0,
178
- frame,
179
- style: applyMarkdownStyle(style, row.markdownKind, theme)
180
- }))
181
- ];
182
- }
183
- function readRoleFrame(role, viewportWidth) {
184
- const frameWidth = Math.max(1, viewportWidth - TRANSCRIPT_OUTER_PADDING_X * 2);
185
- const base = readRoleFrameBase(role);
186
- const bodyWidth = Math.max(
187
- MIN_BODY_WIDTH,
188
- frameWidth - base.marginLeft - base.paddingLeft - base.paddingRight - stringWidth(base.gutter) - base.gap
189
- );
190
- return {
191
- ...base,
192
- bodyWidth
193
- };
194
- }
195
- function readRoleFrameBase(role) {
196
- switch (role) {
197
- case "user":
198
- return {
199
- gap: 2,
200
- gutter: "\u2503",
201
- marginLeft: 1,
202
- paddingLeft: 1,
203
- paddingRight: 1
204
- };
205
- case "reasoning":
206
- return {
207
- gap: 2,
208
- gutter: "\u2503",
209
- marginLeft: 1,
210
- paddingLeft: 1,
211
- paddingRight: 1
212
- };
213
- case "system":
214
- return {
215
- gap: 2,
216
- gutter: "\u2502",
217
- marginLeft: 2,
218
- paddingLeft: 1,
219
- paddingRight: 1
220
- };
221
- case "assistant":
222
- return {
223
- gap: 2,
224
- gutter: " ",
225
- marginLeft: 2,
226
- paddingLeft: 1,
227
- paddingRight: 1
228
- };
229
- }
230
- }
231
- function readRoleStyle(role, theme) {
232
- switch (role) {
233
- case "user":
234
- return {
235
- accent: theme.user,
236
- background: theme.panelStrong,
237
- text: theme.text,
238
- bold: true,
239
- dim: false,
240
- italicPrefix: false
241
- };
242
- case "reasoning":
243
- return {
244
- accent: theme.border,
245
- background: void 0,
246
- text: theme.reasoning,
247
- bold: false,
248
- dim: true,
249
- italicPrefix: true
250
- };
251
- case "system":
252
- return {
253
- accent: theme.border,
254
- background: theme.panel,
255
- text: theme.system,
256
- bold: false,
257
- dim: false,
258
- italicPrefix: false
259
- };
260
- case "assistant":
261
- return {
262
- accent: theme.background,
263
- background: void 0,
264
- text: theme.assistant,
265
- bold: false,
266
- dim: false,
267
- italicPrefix: false
268
- };
269
- }
270
- }
271
- function readEntryDisplayRows(entry) {
272
- if (entry.role === "assistant" || entry.role === "reasoning") {
273
- const markdownRows = renderMarkdownLines(entry.text);
274
- return markdownRows.length > 0 ? markdownRows.map((row) => ({ markdownKind: row.kind, text: row.text })) : [{ markdownKind: void 0, text: "" }];
275
- }
276
- return entry.text.split(/\r?\n/).map((text) => ({ markdownKind: void 0, text }));
277
- }
278
- function wrapEntryRows(entry, sourceRows, bodyWidth) {
279
- if (entry.role !== "reasoning") {
280
- return sourceRows.flatMap((line) => wrapText(line.text, bodyWidth).map((text) => ({
281
- markdownKind: line.markdownKind,
282
- prefix: "",
283
- text
284
- })));
285
- }
286
- const rows = [];
287
- const firstBodyWidth = Math.max(1, bodyWidth - stringWidth(REASONING_PREFIX));
288
- sourceRows.forEach((line, sourceIndex) => {
289
- const width = sourceIndex === 0 ? firstBodyWidth : bodyWidth;
290
- const wrapped = wrapText(line.text, width);
291
- wrapped.forEach((text, wrappedIndex) => {
292
- rows.push({
293
- markdownKind: line.markdownKind,
294
- prefix: sourceIndex === 0 && wrappedIndex === 0 ? REASONING_PREFIX : "",
295
- text
296
- });
297
- });
298
- });
299
- return rows;
300
- }
301
- function wrapText(text, width) {
302
- const rows = [];
303
- for (const line of text.split(/\r?\n/)) {
304
- const wrapped = wrapAnsi(line, width, { hard: true, trim: false });
305
- rows.push(...wrapped.split(/\r?\n/));
306
- }
307
- return rows.length > 0 ? rows : [""];
308
- }
309
- function applyMarkdownStyle(base, kind, theme) {
310
- switch (kind) {
311
- case "heading":
312
- return {
313
- ...base,
314
- text: theme.user,
315
- bold: true
316
- };
317
- case "code":
318
- return {
319
- ...base,
320
- background: theme.panel,
321
- text: theme.system
322
- };
323
- case "quote":
324
- return {
325
- ...base,
326
- text: theme.reasoning,
327
- dim: true
328
- };
329
- case "rule":
330
- case "table":
331
- return {
332
- ...base,
333
- text: theme.muted
334
- };
335
- case "list":
336
- case "text":
337
- case void 0:
338
- return base;
339
- }
340
- }
341
-
342
- // src/shell/tui/store.ts
343
- var DEFAULT_DOCK = {
344
- work: {
345
- active: false,
346
- label: "\u7A7A\u95F2",
347
- detail: "\u6CA1\u6709\u540E\u53F0\u4EFB\u52A1\u6216\u5B50\u4EE3\u7406\u6B63\u5728\u6267\u884C"
348
- },
349
- background: "\u7A7A\u95F2",
350
- subagent: "\u7A7A\u95F2",
351
- context: "0 chars (0%)"
352
- };
353
- function createInitialTuiState(session) {
354
- return {
355
- transcript: session ? session.messages.flatMap(toTranscriptEntry) : [],
356
- dock: {
357
- ...DEFAULT_DOCK,
358
- context: formatContextBudget(session)
359
- },
360
- scroll: {
361
- offset: 0,
362
- stickToBottom: true,
363
- newContentPending: false
364
- },
365
- composer: {
366
- promptLabel: "> ",
367
- visibleRows: 1
368
- }
369
- };
370
- }
371
- function appendTranscriptEntry(state, entry, viewport) {
372
- const next = {
373
- ...state,
374
- transcript: [...state.transcript, { ...entry, id: createEntryId(state.transcript.length) }]
375
- };
376
- return applyContentChange(next, viewport);
377
- }
378
- function appendTranscriptText(state, role, text, viewport) {
379
- const last = state.transcript[state.transcript.length - 1];
380
- if (last && last.role === role && (role === "assistant" || role === "reasoning")) {
381
- const transcript = state.transcript.slice(0, -1);
382
- const next = {
383
- ...state,
384
- transcript: [...transcript, { ...last, text: `${last.text}${text}` }]
385
- };
386
- return applyContentChange(next, viewport);
387
- }
388
- return appendTranscriptEntry(state, { role, text }, viewport);
389
- }
390
- function updateRuntimeDock(state, dock) {
391
- return {
392
- ...state,
393
- dock: {
394
- ...state.dock,
395
- ...dock,
396
- work: {
397
- ...state.dock.work,
398
- ...dock.work
399
- }
400
- }
401
- };
402
- }
403
- function updateComposerState(state, composer) {
404
- return {
405
- ...state,
406
- composer: {
407
- ...state.composer,
408
- ...composer
409
- }
410
- };
411
- }
412
- function scrollTuiTranscript(state, viewport, delta) {
413
- const maxOffset = getMaxScrollOffset(state, viewport);
414
- const offset = clamp(state.scroll.offset + delta, 0, maxOffset);
415
- return {
416
- ...state,
417
- scroll: {
418
- offset,
419
- stickToBottom: offset >= maxOffset,
420
- newContentPending: offset >= maxOffset ? false : state.scroll.newContentPending
421
- }
422
- };
423
- }
424
- function scrollTuiTranscriptToTop(state) {
425
- return {
426
- ...state,
427
- scroll: {
428
- offset: 0,
429
- stickToBottom: false,
430
- newContentPending: state.scroll.newContentPending
431
- }
432
- };
433
- }
434
- function scrollTuiTranscriptToBottom(state, viewport) {
435
- return {
436
- ...state,
437
- scroll: {
438
- offset: getMaxScrollOffset(state, viewport),
439
- stickToBottom: true,
440
- newContentPending: false
441
- }
442
- };
443
- }
444
- function applyViewportResize(state, viewport) {
445
- if (state.scroll.stickToBottom) {
446
- return scrollTuiTranscriptToBottom(state, viewport);
447
- }
448
- return {
449
- ...state,
450
- scroll: {
451
- ...state.scroll,
452
- offset: clamp(state.scroll.offset, 0, getMaxScrollOffset(state, viewport))
453
- }
454
- };
455
- }
456
- function getMaxScrollOffset(state, viewport) {
457
- return Math.max(0, measureTranscriptRows2(state.transcript, viewport.width) - viewport.height);
458
- }
459
- function measureTranscriptRows2(entries, width) {
460
- return measureTranscriptRows(entries, width, TUI_COLORS);
461
- }
462
- function renderTranscriptLineViews2(entries, width) {
463
- return renderTranscriptLineViews(entries, width, TUI_COLORS);
464
- }
465
- function formatContextBudget(session) {
466
- const budget = session?.contextBudget;
467
- if (!budget) {
468
- return "0 chars (0%)";
469
- }
470
- const percent = Math.round(budget.usageRatio * 100);
471
- return `${budget.estimatedChars}/${budget.limitChars} chars (${percent}%)`;
472
- }
473
- function applyContentChange(state, viewport) {
474
- if (state.scroll.stickToBottom) {
475
- return scrollTuiTranscriptToBottom(state, viewport);
476
- }
477
- return {
478
- ...state,
479
- scroll: {
480
- ...state.scroll,
481
- newContentPending: true
482
- }
483
- };
484
- }
485
- function toTranscriptEntry(message, index) {
486
- if (message.source === "internal" || !message.content?.trim()) {
487
- return [];
488
- }
489
- if (message.role === "user") {
490
- return [{ id: createEntryId(index), role: "user", text: message.content }];
491
- }
492
- if (message.role === "assistant") {
493
- return [{ id: createEntryId(index), role: "assistant", text: message.content }];
494
- }
495
- return [];
496
- }
497
- function parseSubmittedInputEcho(text) {
498
- const lines = text.split(/\r?\n/);
499
- if (lines.length === 0 || !lines[0]?.startsWith("> ")) {
500
- return void 0;
501
- }
502
- const parsed = lines.map((line, index) => {
503
- const prefix = index === 0 ? "> " : "\u2026 ";
504
- return line.startsWith(prefix) ? line.slice(prefix.length) : line;
505
- }).join("\n");
506
- return parsed.trim() ? parsed : void 0;
507
- }
508
- function createEntryId(index) {
509
- return `entry-${index + 1}`;
510
- }
511
- function clamp(value, min, max) {
512
- return Math.max(min, Math.min(max, value));
513
- }
514
-
515
- export {
516
- TUI_COLORS,
517
- TRANSCRIPT_OUTER_PADDING_X,
518
- createInitialTuiState,
519
- appendTranscriptEntry,
520
- appendTranscriptText,
521
- updateRuntimeDock,
522
- updateComposerState,
523
- scrollTuiTranscript,
524
- scrollTuiTranscriptToTop,
525
- scrollTuiTranscriptToBottom,
526
- applyViewportResize,
527
- renderTranscriptLineViews2 as renderTranscriptLineViews,
528
- formatContextBudget,
529
- parseSubmittedInputEcho
530
- };