termux-dev 1.6.0 → 1.8.0
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/README.md +214 -166
- package/assets/banner.svg +23 -28
- package/assets/preview.png +0 -0
- package/assets/preview.svg +92 -10
- package/dist/cli/banner.js +1 -1
- package/dist/cli/clipboard-write.js +55 -0
- package/dist/cli/clipboard.js +182 -106
- package/dist/cli/colors.js +67 -0
- package/dist/cli/confirm.js +50 -0
- package/dist/cli/doctor.js +4 -4
- package/dist/cli/effort.js +80 -0
- package/dist/cli/export.js +3 -3
- package/dist/cli/files.js +19 -5
- package/dist/cli/headless.js +23 -11
- package/dist/cli/index.js +1377 -480
- package/dist/cli/keys.js +69 -0
- package/dist/cli/liveblock.js +45 -0
- package/dist/cli/log.js +19 -0
- package/dist/cli/markdown.js +158 -36
- package/dist/cli/mcp-panel.js +432 -0
- package/dist/cli/permission.js +84 -0
- package/dist/cli/picker.js +135 -0
- package/dist/cli/prompt.js +256 -56
- package/dist/cli/resume.js +15 -0
- package/dist/cli/rewind.js +51 -0
- package/dist/cli/runtime_errors.js +6 -0
- package/dist/cli/server.js +85 -24
- package/dist/cli/settings.js +307 -0
- package/dist/cli/statusline.js +1 -1
- package/dist/cli/theme.js +126 -108
- package/dist/cli/transcript.js +114 -0
- package/dist/cli/typeahead.js +371 -0
- package/dist/cli/ui.js +1021 -0
- package/dist/cli/updater.js +23 -21
- package/dist/cli/usage-panel.js +242 -0
- package/dist/cli/width.js +178 -0
- package/dist/core/capabilities.js +433 -0
- package/dist/core/catalog-util.js +27 -0
- package/dist/core/checkpoint.js +90 -0
- package/dist/core/context-stats.js +29 -0
- package/dist/core/cost.js +191 -0
- package/dist/core/formats.js +199 -0
- package/dist/core/history.js +51 -1
- package/dist/core/hooks.js +79 -0
- package/dist/core/i18n.js +42 -0
- package/dist/core/local-config.js +69 -0
- package/dist/core/locales/de.js +210 -0
- package/dist/core/locales/en.js +221 -0
- package/dist/core/locales/es.js +210 -0
- package/dist/core/locales/fr.js +210 -0
- package/dist/core/locales/ja.js +210 -0
- package/dist/core/locales/pt.js +210 -0
- package/dist/core/locales/ru.js +210 -0
- package/dist/core/locales/uk.js +210 -0
- package/dist/core/locales/zh.js +210 -0
- package/dist/core/loop.js +125 -49
- package/dist/core/memory.js +39 -12
- package/dist/core/models.js +33 -20
- package/dist/core/network.js +12 -11
- package/dist/core/notify.js +54 -0
- package/dist/core/planfile.js +43 -0
- package/dist/core/price-catalog.json +1 -0
- package/dist/core/prices.js +249 -0
- package/dist/core/reasoning-catalog.json +1 -0
- package/dist/core/reasoning.js +393 -0
- package/dist/core/redact.js +41 -0
- package/dist/core/secure-file.js +22 -0
- package/dist/core/session.js +16 -5
- package/dist/core/snapshot.js +14 -2
- package/dist/core/title.js +61 -0
- package/dist/core/types.js +1 -1
- package/dist/core/usage.js +43 -7
- package/dist/core/vision-catalog.json +1 -0
- package/dist/core/vision.js +123 -0
- package/dist/mcp/client.js +36 -5
- package/dist/mcp/http-client.js +174 -0
- package/dist/mcp/manager.js +330 -93
- package/dist/permissions/guard.js +385 -117
- package/dist/prompts/builder.js +46 -38
- package/dist/providers/anthropic.js +517 -0
- package/dist/providers/index.js +18 -1
- package/dist/providers/openai.js +138 -125
- package/dist/providers/shared.js +116 -0
- package/dist/providers/toolguard.js +101 -0
- package/dist/tools/bash.js +141 -51
- package/dist/tools/diagnostics.js +76 -48
- package/dist/tools/fs.js +77 -5
- package/dist/tools/index.js +2 -1
- package/dist/tools/mcp.js +72 -0
- package/dist/tools/packages.js +16 -1
- package/dist/tools/plan.js +8 -8
- package/dist/tools/questions.js +50 -54
- package/dist/tools/server.js +6 -0
- package/dist/tools/todo.js +20 -18
- package/dist/tools/web.js +143 -8
- package/package.json +1 -1
package/dist/cli/theme.js
CHANGED
|
@@ -1,113 +1,110 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
name: 'Cyan Cyber',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
id: '
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
desc: '
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
name: '
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
id: '
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
2
|
+
import { bgHex, fgHex } from './colors.js';
|
|
3
|
+
const SPECS = [
|
|
4
|
+
{
|
|
5
|
+
id: 'cyan', name: 'Cyan Cyber', desc: 'Electric neon cyan & obsidian (Default)', aliases: ['blue', 'neon', 'cyber'],
|
|
6
|
+
colors: { accent: '#00f2fe', plan: '#b794f6', ok: '#5fd787', warn: '#ffb454', err: '#ff6b6b', yolo: '#ff8a5c', text: '#dadada', muted: '#bcbcbc', faint: '#8a8a8a', rule: '#767676', shade: '#262626' },
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: 'purple', name: 'Synthwave Purple', desc: 'Vibrant neon magenta & violet retro', aliases: ['magenta', 'violet', 'pink', 'synthwave'],
|
|
10
|
+
colors: { accent: '#d946ef', plan: '#22d3ee', ok: '#86efac', warn: '#fbbf24', err: '#fb7185', yolo: '#fb923c', text: '#e6e0f0', muted: '#b9b0c9', faint: '#8f869f', rule: '#6d6580', shade: '#221a2e' },
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'matrix', name: 'Matrix Hacker', desc: 'Classic bright phosphor green terminal', aliases: ['green', 'hacker', 'terminal'],
|
|
14
|
+
colors: { accent: '#22c55e', plan: '#a3e635', ok: '#4ade80', warn: '#facc15', err: '#f87171', yolo: '#fb923c', text: '#d4f5dc', muted: '#a7c9b0', faint: '#7fa38a', rule: '#5f7f6a', shade: '#10241a' },
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'amber', name: 'Solar Amber', desc: 'Warm vintage CRT amber gold', aliases: ['yellow', 'gold', 'solar', 'orange'],
|
|
18
|
+
colors: { accent: '#f59e0b', plan: '#fb7185', ok: '#a3e635', warn: '#fde047', err: '#f87171', yolo: '#ef6a3a', text: '#f1e6d0', muted: '#c9b896', faint: '#9f8f70', rule: '#7a6c52', shade: '#2a2012' },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'crimson', name: 'Ruby Crimson', desc: 'Aggressive cyberpunk scarlet red', aliases: ['red', 'ruby', 'cyberpunk'],
|
|
22
|
+
colors: { accent: '#ef4444', plan: '#f472b6', ok: '#86efac', warn: '#fbbf24', err: '#fca5a5', yolo: '#f59e0b', text: '#f0dede', muted: '#c9b0b0', faint: '#9d8787', rule: '#7a6666', shade: '#2c1618' },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'monochrome', name: 'Pure Monochrome', desc: 'Crisp minimal pure white & gray', aliases: ['white', 'mono', 'gray', 'grey'],
|
|
26
|
+
colors: { accent: '#f5f5f5', plan: '#a8a8a8', ok: '#bcd9bc', warn: '#e6e6e6', err: '#ff8f8f', yolo: '#ffffff', text: '#e4e4e4', muted: '#b8b8b8', faint: '#8c8c8c', rule: '#6e6e6e', shade: '#262626' },
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'nord', name: 'Nord Frost', desc: 'Arctic frost blues, calm and muted', aliases: ['nordic', 'frost', 'arctic'],
|
|
30
|
+
colors: { accent: '#88c0d0', plan: '#b48ead', ok: '#a3be8c', warn: '#ebcb8b', err: '#e5808b', yolo: '#d08770', text: '#eceff4', muted: '#b6bfd0', faint: '#8791a6', rule: '#616b80', shade: '#2e3440' },
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'dracula', name: 'Dracula', desc: 'Dark purple with vivid pink and green', aliases: ['drac', 'vampire'],
|
|
34
|
+
colors: { accent: '#bd93f9', plan: '#ff79c6', ok: '#50fa7b', warn: '#f1fa8c', err: '#ff6e6e', yolo: '#ffb86c', text: '#f8f8f2', muted: '#c3c7dc', faint: '#8e94b5', rule: '#626a8c', shade: '#282a36' },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'tokyonight', name: 'Tokyo Night', desc: 'Neon-lit Tokyo night, indigo and blue', aliases: ['tokyo', 'night', 'tokyo-night'],
|
|
38
|
+
colors: { accent: '#7aa2f7', plan: '#bb9af7', ok: '#9ece6a', warn: '#e0af68', err: '#f7768e', yolo: '#ff9e64', text: '#c0caf5', muted: '#a9b1d6', faint: '#7f87b0', rule: '#565f89', shade: '#1a1b26' },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'gruvbox', name: 'Gruvbox', desc: 'Retro warm earth tones, yellow and orange', aliases: ['gruv', 'retro'],
|
|
42
|
+
colors: { accent: '#fabd2f', plan: '#d3869b', ok: '#b8bb26', warn: '#fe8019', err: '#fb4934', yolo: '#e8742a', text: '#ebdbb2', muted: '#bdae93', faint: '#98897a', rule: '#7c6f64', shade: '#32302f' },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'catppuccin', name: 'Catppuccin Mocha', desc: 'Soothing pastel mocha, mauve accents', aliases: ['mocha', 'cat', 'catppuccin-mocha'],
|
|
46
|
+
colors: { accent: '#cba6f7', plan: '#89b4fa', ok: '#a6e3a1', warn: '#f9e2af', err: '#f38ba8', yolo: '#fab387', text: '#cdd6f4', muted: '#bac2de', faint: '#9399b2', rule: '#6c7086', shade: '#1e1e2e' },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'solarized', name: 'Solarized Dark', desc: 'Low-glare teal and olive classic', aliases: ['solarized-dark', 'sol'],
|
|
50
|
+
colors: { accent: '#2aa198', plan: '#8f94dc', ok: '#9bb000', warn: '#d9a21b', err: '#ee6a67', yolo: '#ea7b4b', text: '#bcc8c8', muted: '#9eb0b2', faint: '#7f9aa0', rule: '#586e75', shade: '#0b2a33' },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'rosepine', name: 'Rose Pine', desc: 'Soft rose and gold on dusky violet', aliases: ['rose', 'pine', 'rose-pine'],
|
|
54
|
+
colors: { accent: '#ebbcba', plan: '#c4a7e7', ok: '#9ccfd8', warn: '#f6c177', err: '#eb6f92', yolo: '#ea9a97', text: '#e0def4', muted: '#c1bed6', faint: '#908caa', rule: '#6e6a86', shade: '#1f1d2e' },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'monokai', name: 'Monokai', desc: 'Bold lime and magenta editor classic', aliases: ['mono-kai'],
|
|
58
|
+
colors: { accent: '#a6e22e', plan: '#ae81ff', ok: '#a6e22e', warn: '#e6db74', err: '#f92672', yolo: '#fd971f', text: '#f8f8f2', muted: '#cfcfc2', faint: '#9a9a8e', rule: '#75715e', shade: '#272822' },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'github', name: 'GitHub Dark', desc: 'Clean blue and green, easy on the eyes', aliases: ['gh', 'git'],
|
|
62
|
+
colors: { accent: '#58a6ff', plan: '#bc8cff', ok: '#3fb950', warn: '#d29922', err: '#f85149', yolo: '#e3742f', text: '#e6edf3', muted: '#b1bac4', faint: '#8b949e', rule: '#6e7681', shade: '#161b22' },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'ocean', name: 'Deep Ocean', desc: 'Deep sea cyan and indigo', aliases: ['sea', 'deepsea'],
|
|
66
|
+
colors: { accent: '#38bdf8', plan: '#818cf8', ok: '#34d399', warn: '#fbbf24', err: '#fb7185', yolo: '#fb923c', text: '#dbeafe', muted: '#a9c3dd', faint: '#7f9dba', rule: '#55708c', shade: '#0c1a2b' },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'sunset', name: 'Sunset Glow', desc: 'Coral and violet dusk gradient', aliases: ['dusk', 'coral'],
|
|
70
|
+
colors: { accent: '#ff7e67', plan: '#c084fc', ok: '#86efac', warn: '#fcd34d', err: '#f87171', yolo: '#fb923c', text: '#f5e6e0', muted: '#d1b8b0', faint: '#a69089', rule: '#806560', shade: '#2b1621' },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'forest', name: 'Forest Moss', desc: 'Mossy greens and woodland earth', aliases: ['moss', 'woods'],
|
|
74
|
+
colors: { accent: '#8fbc6a', plan: '#d4a373', ok: '#a3d977', warn: '#e9c46a', err: '#ef8a72', yolo: '#f4a261', text: '#e5e8d8', muted: '#b9c0a6', faint: '#8f987b', rule: '#66705a', shade: '#1c2418' },
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const bold = (s) => (pc.isColorSupported ? `\x1b[1m${s}\x1b[22m` : s);
|
|
78
|
+
function buildTheme(spec, options = {}) {
|
|
79
|
+
const c = spec.colors;
|
|
80
|
+
const tokens = {};
|
|
81
|
+
for (const key of Object.keys(c)) {
|
|
82
|
+
tokens[key] = key === 'shade' ? bgHex(c[key], options) : fgHex(c[key], options);
|
|
80
83
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'cyberpunk': 'crimson',
|
|
106
|
-
'white': 'monochrome',
|
|
107
|
-
'mono': 'monochrome',
|
|
108
|
-
'gray': 'monochrome',
|
|
109
|
-
'grey': 'monochrome'
|
|
110
|
-
};
|
|
84
|
+
const black = fgHex('#000000', options);
|
|
85
|
+
return {
|
|
86
|
+
id: spec.id,
|
|
87
|
+
name: spec.name,
|
|
88
|
+
desc: spec.desc,
|
|
89
|
+
symbol: '●',
|
|
90
|
+
hex: c.accent,
|
|
91
|
+
colors: c,
|
|
92
|
+
tokens,
|
|
93
|
+
colorFn: tokens.accent,
|
|
94
|
+
boldFn: (s) => bold(tokens.accent(s)),
|
|
95
|
+
accentFn: tokens.plan,
|
|
96
|
+
badgeFn: (s) => bgHex(c.accent, options)(black(s)),
|
|
97
|
+
diffAddBg: (s) => bgHex(c.ok, options)(black(s)),
|
|
98
|
+
diffRemoveBg: (s) => bgHex(c.err, options)(black(s)),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export const THEMES = Object.fromEntries(SPECS.map((spec) => [spec.id, buildTheme(spec)]));
|
|
102
|
+
const THEME_ALIASES = {};
|
|
103
|
+
SPECS.forEach((spec, index) => {
|
|
104
|
+
THEME_ALIASES[String(index + 1)] = spec.id; // "1" ... "18"
|
|
105
|
+
for (const alias of spec.aliases ?? [])
|
|
106
|
+
THEME_ALIASES[alias] = spec.id;
|
|
107
|
+
});
|
|
111
108
|
let activeThemeId = 'cyan';
|
|
112
109
|
export function getTheme(themeId) {
|
|
113
110
|
const id = (themeId || activeThemeId).toLowerCase().trim();
|
|
@@ -138,3 +135,24 @@ export function getCurrentTheme() {
|
|
|
138
135
|
export function listThemes() {
|
|
139
136
|
return Object.values(THEMES);
|
|
140
137
|
}
|
|
138
|
+
/** Four dots showing the palette at a glance: accent, plan, ok, warn. */
|
|
139
|
+
export function renderSwatch(theme) {
|
|
140
|
+
return [theme.tokens.accent, theme.tokens.plan, theme.tokens.ok, theme.tokens.warn].map((paint) => paint(theme.symbol)).join(' ');
|
|
141
|
+
}
|
|
142
|
+
/** A small sample screen painted with the theme, shown while choosing one. */
|
|
143
|
+
export function renderThemePreview(theme) {
|
|
144
|
+
const k = theme.tokens;
|
|
145
|
+
const modes = [k.accent(`${theme.symbol} agent`), k.plan('◆ plan'), k.yolo('▲ yolo')].join(' ');
|
|
146
|
+
const states = [k.ok('✓ done'), k.warn('! careful'), k.err('✗ failed')].join(' ');
|
|
147
|
+
const text = [k.text('text'), k.muted('muted'), k.faint('faint'), k.rule('─────')].join(' ');
|
|
148
|
+
const message = k.shade(` ${k.accent('❯')} ${k.text('explain this project')} `);
|
|
149
|
+
return [modes, states, text, message].join('\n');
|
|
150
|
+
}
|
|
151
|
+
/** Builds a theme with explicit colour options (tests, forced 256-colour output). */
|
|
152
|
+
export function buildThemeFor(id, options) {
|
|
153
|
+
const spec = SPECS.find((s) => s.id === id);
|
|
154
|
+
if (!spec)
|
|
155
|
+
throw new Error(`Unknown theme: ${id}`);
|
|
156
|
+
return buildTheme(spec, options);
|
|
157
|
+
}
|
|
158
|
+
export const THEME_IDS = SPECS.map((s) => s.id);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getActionDescription } from '../core/loop.js';
|
|
2
|
+
import { formatDiffBox } from './diff.js';
|
|
3
|
+
import { renderMarkdown } from './markdown.js';
|
|
4
|
+
import { palette, renderPlanCard, renderResultLine, renderTaskCard, renderToolLine, renderUserEcho, summarizeToolResult } from './ui.js';
|
|
5
|
+
import { displayWidth, truncateWidth, wrapText } from './width.js';
|
|
6
|
+
import { t } from '../core/i18n.js';
|
|
7
|
+
const MAX_ECHO_LINES = 300;
|
|
8
|
+
/** The rows under a tool line: a plan card, a diff, or the output (one row folded, all of it unfolded). */
|
|
9
|
+
export function toolResultRows(name, result, opts) {
|
|
10
|
+
const { cols } = opts;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(result);
|
|
13
|
+
if (parsed && typeof parsed === 'object') {
|
|
14
|
+
if (parsed.type === 'todo_list' && Array.isArray(parsed.tasks) && parsed.tasks.length > 0)
|
|
15
|
+
return renderTaskCard(parsed.tasks, cols);
|
|
16
|
+
if (parsed.type === 'plan_ready' && typeof parsed.summary === 'string')
|
|
17
|
+
return renderPlanCard({ summary: parsed.summary, files: parsed.files, steps: parsed.steps }, cols);
|
|
18
|
+
if (parsed.action === 'edit' && parsed.diffLines) {
|
|
19
|
+
const box = formatDiffBox(parsed.path, parsed.diffLines, Math.min(cols - 4, 80), undefined, !!opts.diffColors);
|
|
20
|
+
const rows = box.split('\n').map((l) => ' ' + l);
|
|
21
|
+
if (parsed.summary)
|
|
22
|
+
rows.push(renderResultLine(parsed.summary, 'ok', cols));
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
|
25
|
+
if (parsed.summary)
|
|
26
|
+
return [renderResultLine(String(parsed.summary), 'ok', cols)];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
return summarizeToolResult(name, result, cols, opts.expanded);
|
|
31
|
+
}
|
|
32
|
+
export function assistantRows(text) {
|
|
33
|
+
const rows = renderMarkdown(text).replace(/\s+$/, '').split('\n');
|
|
34
|
+
return rows.map((row, i) => (i === 0 ? ' ' + palette.accent('●') + ' ' : ' ') + row);
|
|
35
|
+
}
|
|
36
|
+
/** ` ∴ thought for 0.3s · 112 tok (ctrl+p to expand)`, the hint dropped and the rest cut when the screen is narrow. */
|
|
37
|
+
export function collapsedThoughtLine(summary, hasText, cols) {
|
|
38
|
+
const room = Math.max(10, cols - 1 - 4);
|
|
39
|
+
const hint = hasText ? ` (${t('view.expandThought')})` : '';
|
|
40
|
+
const text = displayWidth(summary + hint) <= room ? summary + hint : truncateWidth(summary, room);
|
|
41
|
+
return palette.muted(' ∴ ') + palette.faint(text);
|
|
42
|
+
}
|
|
43
|
+
function thoughtRows(item, opts) {
|
|
44
|
+
const summary = `${t('status.thoughtFor', { sec: item.sec })}${item.stats}`;
|
|
45
|
+
if (!opts.thinkingExpanded || !item.text.trim()) {
|
|
46
|
+
return [collapsedThoughtLine(summary, !!item.text.trim(), opts.cols)];
|
|
47
|
+
}
|
|
48
|
+
const bar = palette.faint(' │ ');
|
|
49
|
+
const body = item.text.trim().split('\n').flatMap((line) => (line.trim() === '' ? [''] : wrapText(line, Math.max(20, opts.cols - 8))));
|
|
50
|
+
return [palette.muted(` ∴ ${t('status.thinkingLine')}`), ...body.map((l) => bar + palette.muted(l)), palette.faint(` ╰ ${summary}`)];
|
|
51
|
+
}
|
|
52
|
+
/** One item as terminal rows (without the blank row that separates it from its neighbour). */
|
|
53
|
+
export function renderItem(item, opts) {
|
|
54
|
+
switch (item.kind) {
|
|
55
|
+
case 'user': {
|
|
56
|
+
const lines = item.text.replace(/\t/g, ' ').split('\n');
|
|
57
|
+
const text = lines.length > MAX_ECHO_LINES ? [...lines.slice(0, MAX_ECHO_LINES), `… +${lines.length - MAX_ECHO_LINES} lines`].join('\n') : item.text;
|
|
58
|
+
return renderUserEcho(text, opts.cols);
|
|
59
|
+
}
|
|
60
|
+
case 'thought':
|
|
61
|
+
return thoughtRows(item, opts);
|
|
62
|
+
case 'assistant':
|
|
63
|
+
return assistantRows(item.text);
|
|
64
|
+
case 'tool': {
|
|
65
|
+
const rows = renderToolLine(item.desc, opts.cols, opts.outputExpanded).split('\n');
|
|
66
|
+
if (item.result !== undefined)
|
|
67
|
+
rows.push(...toolResultRows(item.name, item.result, { cols: opts.cols, expanded: opts.outputExpanded, diffColors: opts.diffColors }));
|
|
68
|
+
return rows;
|
|
69
|
+
}
|
|
70
|
+
default:
|
|
71
|
+
return item.rows;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Whether a blank row goes between two neighbours: everywhere, except between two tool calls and after the user's own padded echo. */
|
|
75
|
+
export function gapBetween(prev, next) {
|
|
76
|
+
if (!prev)
|
|
77
|
+
return false;
|
|
78
|
+
if (prev.kind === 'user' || next.kind === 'user')
|
|
79
|
+
return false;
|
|
80
|
+
if (prev.kind === 'tool' && next.kind === 'tool')
|
|
81
|
+
return false;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
export function renderTranscript(items, opts) {
|
|
85
|
+
const out = [];
|
|
86
|
+
items.forEach((item, i) => {
|
|
87
|
+
if (gapBetween(items[i - 1], item))
|
|
88
|
+
out.push('');
|
|
89
|
+
out.push(...renderItem(item, opts));
|
|
90
|
+
});
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
/** A saved conversation as items (a resumed session has no reasoning texts: they were never stored). */
|
|
94
|
+
export function messagesToItems(messages) {
|
|
95
|
+
const results = new Map();
|
|
96
|
+
for (const m of messages)
|
|
97
|
+
if (m.role === 'tool' && m.tool_call_id)
|
|
98
|
+
results.set(m.tool_call_id, m);
|
|
99
|
+
const items = [];
|
|
100
|
+
for (const m of messages) {
|
|
101
|
+
if (m.role === 'user')
|
|
102
|
+
items.push({ kind: 'user', text: m.content.split('\n\n--- Attached Files ---')[0] });
|
|
103
|
+
else if (m.role === 'assistant') {
|
|
104
|
+
if (m.reasoning && m.reasoning.text.trim())
|
|
105
|
+
items.push({ kind: 'thought', text: m.reasoning.text, sec: m.reasoning.sec, stats: m.reasoning.stats });
|
|
106
|
+
if (m.content && m.content.trim())
|
|
107
|
+
items.push({ kind: 'assistant', text: m.content });
|
|
108
|
+
for (const call of m.tool_calls ?? []) {
|
|
109
|
+
items.push({ kind: 'tool', name: call.name, desc: getActionDescription(call.name, call.arguments ?? {}), result: results.get(call.id)?.content });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return items;
|
|
114
|
+
}
|