@oh-my-pi/pi-coding-agent 9.6.0 → 9.6.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/CHANGELOG.md +17 -0
- package/docs/theme.md +9 -12
- package/package.json +7 -7
- package/src/exa/render.ts +12 -12
- package/src/ipy/gateway-coordinator.ts +1 -1
- package/src/ipy/kernel.ts +5 -0
- package/src/ipy/runtime.ts +2 -6
- package/src/lsp/render.ts +16 -30
- package/src/modes/components/bash-execution.ts +2 -4
- package/src/modes/components/custom-message.ts +1 -1
- package/src/modes/components/footer.ts +1 -1
- package/src/modes/components/history-search.ts +3 -2
- package/src/modes/components/hook-message.ts +1 -1
- package/src/modes/components/python-execution.ts +2 -4
- package/src/modes/components/read-tool-group.ts +1 -1
- package/src/modes/components/session-selector.ts +7 -11
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/tool-execution.ts +3 -3
- package/src/modes/components/ttsr-notification.ts +1 -1
- package/src/modes/components/welcome.ts +2 -2
- package/src/modes/controllers/event-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/theme/theme.ts +0 -11
- package/src/patch/shared.ts +7 -10
- package/src/task/executor.ts +1 -1
- package/src/task/render.ts +31 -33
- package/src/tools/bash.ts +3 -3
- package/src/tools/calculator.ts +3 -3
- package/src/tools/fetch.ts +4 -6
- package/src/tools/python.ts +22 -31
- package/src/tools/read.ts +1 -1
- package/src/tools/render-utils.ts +12 -21
- package/src/tools/review.ts +1 -7
- package/src/tools/ssh.ts +6 -8
- package/src/tools/write.ts +5 -5
- package/src/tui/code-cell.ts +2 -2
- package/src/tui/output-block.ts +2 -2
- package/src/tui/tree-list.ts +1 -3
- package/src/tui/utils.ts +3 -5
- package/src/web/search/render.ts +14 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [9.6.2] - 2026-02-01
|
|
6
|
+
### Changed
|
|
7
|
+
|
|
8
|
+
- Replaced hardcoded ellipsis strings with Unicode ellipsis character (…) throughout rendering code
|
|
9
|
+
- Removed `format.ellipsis` symbol from theme configuration; ellipsis now uses literal Unicode character
|
|
10
|
+
- Updated `truncate()` function to `truncateToWidth()` with simplified API accepting default ellipsis parameter
|
|
11
|
+
- Simplified `formatMoreItems()` function signature by removing theme parameter dependency
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- Removed `format.ellipsis` symbol key from theme symbol maps (Unicode, Nerd, and ASCII presets)
|
|
16
|
+
- Removed `ellipsis` property from `SymbolTheme` type
|
|
17
|
+
|
|
18
|
+
## [9.6.1] - 2026-02-01
|
|
19
|
+
|
|
4
20
|
### Fixed
|
|
5
21
|
|
|
22
|
+
- Fixed output handling to prioritize text/markdown over text/plain when both are available, ensuring Markdown content is displayed correctly
|
|
6
23
|
- Fixed bash command normalization to preserve newlines in heredocs and multiline commands
|
|
7
24
|
|
|
8
25
|
## [9.6.0] - 2026-02-01
|
package/docs/theme.md
CHANGED
|
@@ -157,14 +157,14 @@ Example:
|
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
159
|
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
"symbols": {
|
|
161
|
+
"preset": "ascii",
|
|
162
|
+
"overrides": {
|
|
163
|
+
"icon.model": "[M]",
|
|
164
|
+
"sep.powerlineLeft": ">",
|
|
165
|
+
"sep.powerlineRight": "<"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
168
|
}
|
|
169
169
|
```
|
|
170
170
|
|
|
@@ -179,7 +179,7 @@ Symbol keys by category:
|
|
|
179
179
|
- Icons: `icon.model`, `icon.folder`, `icon.file`, `icon.git`, `icon.branch`, `icon.tokens`, `icon.context`, `icon.cost`, `icon.time`, `icon.pi`, `icon.agents`, `icon.cache`, `icon.input`, `icon.output`, `icon.host`, `icon.session`, `icon.package`, `icon.warning`, `icon.rewind`, `icon.auto`, `icon.extensionSkill`, `icon.extensionTool`, `icon.extensionSlashCommand`, `icon.extensionMcp`, `icon.extensionRule`, `icon.extensionHook`, `icon.extensionPrompt`, `icon.extensionContextFile`, `icon.extensionInstruction`
|
|
180
180
|
- Thinking: `thinking.minimal`, `thinking.low`, `thinking.medium`, `thinking.high`, `thinking.xhigh`
|
|
181
181
|
- Checkboxes: `checkbox.checked`, `checkbox.unchecked`
|
|
182
|
-
- Formatting: `format.
|
|
182
|
+
- Formatting: `format.bullet`, `format.dash`
|
|
183
183
|
- Markdown: `md.quoteBorder`, `md.hrChar`, `md.bullet`
|
|
184
184
|
|
|
185
185
|
### Color Values
|
|
@@ -620,7 +620,6 @@ const userMsg = theme.bg("userMessageBg", theme.fg("userMessageText", "Hello"));
|
|
|
620
620
|
**Color resolution:**
|
|
621
621
|
|
|
622
622
|
1. **Detect terminal capabilities:**
|
|
623
|
-
|
|
624
623
|
- Check `$COLORTERM` env var (`truecolor` or `24bit` → truecolor support)
|
|
625
624
|
- Check `$TERM` env var (`*-256color` → 256-color support)
|
|
626
625
|
- Fallback to 256-color mode if detection fails
|
|
@@ -644,13 +643,11 @@ const userMsg = theme.bg("userMessageBg", theme.fg("userMessageText", "Hello"));
|
|
|
644
643
|
4. **Convert colors to ANSI codes based on terminal capability:**
|
|
645
644
|
|
|
646
645
|
**Truecolor mode (24-bit):**
|
|
647
|
-
|
|
648
646
|
- Hex (`"#ff0000"`) → `\x1b[38;2;255;0;0m`
|
|
649
647
|
- 256-color (`42`) → `\x1b[38;5;42m` (keep as-is)
|
|
650
648
|
- Empty string (`""`) → `\x1b[39m`
|
|
651
649
|
|
|
652
650
|
**256-color mode:**
|
|
653
|
-
|
|
654
651
|
- Hex (`"#ff0000"`) → convert to nearest RGB cube color → `\x1b[38;5;196m`
|
|
655
652
|
- 256-color (`42`) → `\x1b[38;5;42m` (keep as-is)
|
|
656
653
|
- Empty string (`""`) → `\x1b[39m`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.3",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -79,12 +79,12 @@
|
|
|
79
79
|
"test": "bun test"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@oh-my-pi/omp-stats": "9.6.
|
|
83
|
-
"@oh-my-pi/pi-agent-core": "9.6.
|
|
84
|
-
"@oh-my-pi/pi-ai": "9.6.
|
|
85
|
-
"@oh-my-pi/pi-natives": "9.6.
|
|
86
|
-
"@oh-my-pi/pi-tui": "9.6.
|
|
87
|
-
"@oh-my-pi/pi-utils": "9.6.
|
|
82
|
+
"@oh-my-pi/omp-stats": "9.6.3",
|
|
83
|
+
"@oh-my-pi/pi-agent-core": "9.6.3",
|
|
84
|
+
"@oh-my-pi/pi-ai": "9.6.3",
|
|
85
|
+
"@oh-my-pi/pi-natives": "9.6.3",
|
|
86
|
+
"@oh-my-pi/pi-tui": "9.6.3",
|
|
87
|
+
"@oh-my-pi/pi-utils": "9.6.3",
|
|
88
88
|
"@openai/agents": "^0.4.4",
|
|
89
89
|
"@sinclair/typebox": "^0.34.48",
|
|
90
90
|
"ajv": "^8.17.1",
|
package/src/exa/render.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
getPreviewLines,
|
|
18
18
|
PREVIEW_LIMITS,
|
|
19
19
|
TRUNCATE_LENGTHS,
|
|
20
|
-
|
|
20
|
+
truncateToWidth,
|
|
21
21
|
} from "../tools/render-utils";
|
|
22
22
|
import type { ExaRenderDetails } from "./types";
|
|
23
23
|
|
|
@@ -72,14 +72,14 @@ export function renderExaResult(
|
|
|
72
72
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
73
73
|
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg(
|
|
74
74
|
"toolOutput",
|
|
75
|
-
|
|
75
|
+
truncateToWidth(displayLines[i], COLLAPSED_PREVIEW_LINE_LEN),
|
|
76
76
|
)}`;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (remaining > 0) {
|
|
80
80
|
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
|
81
81
|
"muted",
|
|
82
|
-
formatMoreItems(remaining, "line"
|
|
82
|
+
formatMoreItems(remaining, "line"),
|
|
83
83
|
)}`;
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -119,17 +119,17 @@ export function renderExaResult(
|
|
|
119
119
|
const first = results[0];
|
|
120
120
|
const previewText = first.text ?? first.title ?? "";
|
|
121
121
|
const previewLines = previewText
|
|
122
|
-
? getPreviewLines(previewText, COLLAPSED_PREVIEW_LINES, COLLAPSED_PREVIEW_LINE_LEN
|
|
122
|
+
? getPreviewLines(previewText, COLLAPSED_PREVIEW_LINES, COLLAPSED_PREVIEW_LINE_LEN)
|
|
123
123
|
: [];
|
|
124
124
|
const safePreviewLines = previewLines.length > 0 ? previewLines : ["No preview text"];
|
|
125
125
|
const totalLines = previewText.split("\n").filter(l => l.trim()).length;
|
|
126
126
|
const remainingLines = Math.max(0, totalLines - previewLines.length);
|
|
127
127
|
const extraItems: string[] = [];
|
|
128
128
|
if (remainingLines > 0) {
|
|
129
|
-
extraItems.push(formatMoreItems(remainingLines, "line"
|
|
129
|
+
extraItems.push(formatMoreItems(remainingLines, "line"));
|
|
130
130
|
}
|
|
131
131
|
if (resultCount > 1) {
|
|
132
|
-
extraItems.push(formatMoreItems(resultCount - 1, "result"
|
|
132
|
+
extraItems.push(formatMoreItems(resultCount - 1, "result"));
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
for (let i = 0; i < safePreviewLines.length; i++) {
|
|
@@ -160,7 +160,7 @@ export function renderExaResult(
|
|
|
160
160
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
161
161
|
const cont = isLast ? " " : uiTheme.tree.vertical;
|
|
162
162
|
|
|
163
|
-
const title =
|
|
163
|
+
const title = truncateToWidth(res.title ?? "Untitled", MAX_TITLE_LEN);
|
|
164
164
|
const domain = res.url ? getDomain(res.url) : "";
|
|
165
165
|
const domainPart = domain ? uiTheme.fg("dim", ` (${domain})`) : "";
|
|
166
166
|
|
|
@@ -193,13 +193,13 @@ export function renderExaResult(
|
|
|
193
193
|
for (const line of displayLines) {
|
|
194
194
|
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
195
195
|
"toolOutput",
|
|
196
|
-
|
|
196
|
+
truncateToWidth(line.trim(), EXPANDED_TEXT_LINE_LEN),
|
|
197
197
|
)}`;
|
|
198
198
|
}
|
|
199
199
|
if (textLines.length > EXPANDED_TEXT_LINES) {
|
|
200
200
|
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
201
201
|
"muted",
|
|
202
|
-
formatMoreItems(textLines.length - EXPANDED_TEXT_LINES, "line"
|
|
202
|
+
formatMoreItems(textLines.length - EXPANDED_TEXT_LINES, "line"),
|
|
203
203
|
)}`;
|
|
204
204
|
}
|
|
205
205
|
}
|
|
@@ -214,13 +214,13 @@ export function renderExaResult(
|
|
|
214
214
|
const h = res.highlights[j];
|
|
215
215
|
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
216
216
|
"muted",
|
|
217
|
-
`${uiTheme.format.dash} ${
|
|
217
|
+
`${uiTheme.format.dash} ${truncateToWidth(h, MAX_HIGHLIGHT_LEN)}`,
|
|
218
218
|
)}`;
|
|
219
219
|
}
|
|
220
220
|
if (res.highlights.length > maxHighlights) {
|
|
221
221
|
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
222
222
|
"muted",
|
|
223
|
-
formatMoreItems(res.highlights.length - maxHighlights, "highlight"
|
|
223
|
+
formatMoreItems(res.highlights.length - maxHighlights, "highlight"),
|
|
224
224
|
)}`;
|
|
225
225
|
}
|
|
226
226
|
}
|
|
@@ -232,7 +232,7 @@ export function renderExaResult(
|
|
|
232
232
|
/** Render Exa call (query/args preview) */
|
|
233
233
|
export function renderExaCall(args: Record<string, unknown>, toolName: string, uiTheme: Theme): Component {
|
|
234
234
|
const toolLabel = toolName || "Exa Search";
|
|
235
|
-
const query = typeof args.query === "string" ?
|
|
235
|
+
const query = typeof args.query === "string" ? truncateToWidth(args.query, 80) : "?";
|
|
236
236
|
const numResults = typeof args.num_results === "number" ? args.num_results : undefined;
|
|
237
237
|
|
|
238
238
|
let text = `${uiTheme.fg("toolTitle", toolLabel)} ${uiTheme.fg("accent", query)}`;
|
package/src/ipy/kernel.ts
CHANGED
|
@@ -984,6 +984,11 @@ export class PythonKernel {
|
|
|
984
984
|
outputs.push({ type: "json", data: data["application/json"] });
|
|
985
985
|
}
|
|
986
986
|
|
|
987
|
+
// Check text/markdown before text/plain since Markdown objects provide both
|
|
988
|
+
// (text/plain is just the repr)
|
|
989
|
+
if (typeof data["text/markdown"] === "string") {
|
|
990
|
+
return { text: normalizeDisplayText(String(data["text/markdown"])), outputs };
|
|
991
|
+
}
|
|
987
992
|
if (typeof data["text/plain"] === "string") {
|
|
988
993
|
return { text: normalizeDisplayText(String(data["text/plain"])), outputs };
|
|
989
994
|
}
|
package/src/ipy/runtime.ts
CHANGED
|
@@ -104,8 +104,6 @@ function resolvePathKey(env: Record<string, string | undefined>): string {
|
|
|
104
104
|
export interface PythonRuntime {
|
|
105
105
|
/** Path to python executable */
|
|
106
106
|
pythonPath: string;
|
|
107
|
-
/** Path to windowless python executable (pythonw.exe on Windows, same as pythonPath otherwise) */
|
|
108
|
-
pythonwPath: string;
|
|
109
107
|
/** Filtered environment variables */
|
|
110
108
|
env: Record<string, string | undefined>;
|
|
111
109
|
/** Path to virtual environment, if detected */
|
|
@@ -177,8 +175,7 @@ export function resolvePythonRuntime(cwd: string, baseEnv: Record<string, string
|
|
|
177
175
|
const currentPath = env[pathKey];
|
|
178
176
|
env[pathKey] = currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir;
|
|
179
177
|
return {
|
|
180
|
-
pythonPath: pythonCandidate,
|
|
181
|
-
pythonwPath: resolveWindowlessPython(pythonCandidate),
|
|
178
|
+
pythonPath: resolveWindowlessPython(pythonCandidate),
|
|
182
179
|
env,
|
|
183
180
|
venvPath,
|
|
184
181
|
};
|
|
@@ -190,8 +187,7 @@ export function resolvePythonRuntime(cwd: string, baseEnv: Record<string, string
|
|
|
190
187
|
throw new Error("Python executable not found on PATH");
|
|
191
188
|
}
|
|
192
189
|
return {
|
|
193
|
-
pythonPath,
|
|
194
|
-
pythonwPath: resolveWindowlessPython(pythonPath),
|
|
190
|
+
pythonPath: resolveWindowlessPython(pythonPath),
|
|
195
191
|
env,
|
|
196
192
|
venvPath: null,
|
|
197
193
|
};
|
package/src/lsp/render.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
formatStatusIcon,
|
|
18
18
|
shortenPath,
|
|
19
19
|
TRUNCATE_LENGTHS,
|
|
20
|
-
|
|
20
|
+
truncateToWidth,
|
|
21
21
|
} from "../tools/render-utils";
|
|
22
22
|
import { renderOutputBlock, renderStatusLine } from "../tui";
|
|
23
23
|
import type { LspParams, LspToolDetails } from "./types";
|
|
@@ -32,10 +32,8 @@ import type { LspParams, LspToolDetails } from "./types";
|
|
|
32
32
|
*/
|
|
33
33
|
export function renderCall(args: LspParams, theme: Theme): Text {
|
|
34
34
|
const actionLabel = (args.action ?? "request").replace(/_/g, " ");
|
|
35
|
-
const queryPreview = args.query ?
|
|
36
|
-
const replacementPreview = args.replacement
|
|
37
|
-
? truncate(args.replacement, TRUNCATE_LENGTHS.SHORT, theme.format.ellipsis)
|
|
38
|
-
: undefined;
|
|
35
|
+
const queryPreview = args.query ? truncateToWidth(args.query, TRUNCATE_LENGTHS.SHORT) : undefined;
|
|
36
|
+
const replacementPreview = args.replacement ? truncateToWidth(args.replacement, TRUNCATE_LENGTHS.SHORT) : undefined;
|
|
39
37
|
|
|
40
38
|
let target: string | undefined;
|
|
41
39
|
let hasFileTarget = false;
|
|
@@ -265,7 +263,7 @@ function renderHover(
|
|
|
265
263
|
|
|
266
264
|
let output = `${icon}${langLabel}${expandHint}`;
|
|
267
265
|
if (beforeCode) {
|
|
268
|
-
const preview =
|
|
266
|
+
const preview = truncateToWidth(beforeCode, TRUNCATE_LENGTHS.TITLE);
|
|
269
267
|
output += `\n ${theme.fg("dim", theme.tree.branch)} ${theme.fg("muted", preview)}`;
|
|
270
268
|
}
|
|
271
269
|
const h = theme.boxSharp.horizontal;
|
|
@@ -274,14 +272,11 @@ function renderHover(
|
|
|
274
272
|
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${firstCodeLine}`;
|
|
275
273
|
|
|
276
274
|
if (codeLines.length > 1) {
|
|
277
|
-
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${theme.fg(
|
|
278
|
-
"muted",
|
|
279
|
-
`${theme.format.ellipsis} ${codeLines.length - 1} more lines`,
|
|
280
|
-
)}`;
|
|
275
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${theme.fg("muted", `… ${codeLines.length - 1} more lines`)}`;
|
|
281
276
|
}
|
|
282
277
|
|
|
283
278
|
if (afterCode) {
|
|
284
|
-
const docPreview =
|
|
279
|
+
const docPreview = truncateToWidth(afterCode, TRUNCATE_LENGTHS.TITLE);
|
|
285
280
|
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", docPreview)}`;
|
|
286
281
|
} else {
|
|
287
282
|
output += `\n ${theme.fg("mdCodeBlockBorder", bottom)}`;
|
|
@@ -377,7 +372,7 @@ function renderDiagnostics(
|
|
|
377
372
|
if (item.message) {
|
|
378
373
|
output += `\n ${theme.fg("dim", detailPrefix)}${theme.fg(
|
|
379
374
|
"muted",
|
|
380
|
-
|
|
375
|
+
truncateToWidth(item.message, TRUNCATE_LENGTHS.LINE),
|
|
381
376
|
)}`;
|
|
382
377
|
}
|
|
383
378
|
}
|
|
@@ -402,15 +397,12 @@ function renderDiagnostics(
|
|
|
402
397
|
const severityColor = severityToColor(item.severity);
|
|
403
398
|
const location = formatDiagnosticLocation(item.file, item.line, item.col, theme);
|
|
404
399
|
const message = item.message
|
|
405
|
-
? ` ${theme.fg("muted",
|
|
400
|
+
? ` ${theme.fg("muted", truncateToWidth(item.message, TRUNCATE_LENGTHS.CONTENT))}`
|
|
406
401
|
: "";
|
|
407
402
|
output += `\n ${theme.fg("dim", branch)} ${theme.fg(severityColor, location)}${message}`;
|
|
408
403
|
}
|
|
409
404
|
if (remaining > 0) {
|
|
410
|
-
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
411
|
-
"muted",
|
|
412
|
-
`${theme.format.ellipsis} ${remaining} more`,
|
|
413
|
-
)}`;
|
|
405
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", `… ${remaining} more`)}`;
|
|
414
406
|
}
|
|
415
407
|
|
|
416
408
|
return output.split("\n");
|
|
@@ -473,14 +465,14 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
473
465
|
const context = `at ${file}:${line}:${col}`;
|
|
474
466
|
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locCont)}${theme.fg(
|
|
475
467
|
"muted",
|
|
476
|
-
|
|
468
|
+
truncateToWidth(context, TRUNCATE_LENGTHS.LINE),
|
|
477
469
|
)}`;
|
|
478
470
|
}
|
|
479
471
|
}
|
|
480
472
|
if (locs.length > maxLocsPerFile) {
|
|
481
473
|
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
482
474
|
"muted",
|
|
483
|
-
|
|
475
|
+
`… ${locs.length - maxLocsPerFile} more`,
|
|
484
476
|
)}`;
|
|
485
477
|
}
|
|
486
478
|
}
|
|
@@ -489,7 +481,7 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
489
481
|
if (files.length > maxFiles) {
|
|
490
482
|
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
491
483
|
"muted",
|
|
492
|
-
formatMoreItems(files.length - maxFiles, "file"
|
|
484
|
+
formatMoreItems(files.length - maxFiles, "file"),
|
|
493
485
|
)}`;
|
|
494
486
|
}
|
|
495
487
|
|
|
@@ -596,10 +588,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
596
588
|
)}`;
|
|
597
589
|
}
|
|
598
590
|
if (topLevelCount > 3) {
|
|
599
|
-
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
600
|
-
"muted",
|
|
601
|
-
`${theme.format.ellipsis} ${topLevelCount - 3} more`,
|
|
602
|
-
)}`;
|
|
591
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", `… ${topLevelCount - 3} more`)}`;
|
|
603
592
|
}
|
|
604
593
|
|
|
605
594
|
return output.split("\n");
|
|
@@ -635,10 +624,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
635
624
|
|
|
636
625
|
const firstLine = lines[0] || "No output";
|
|
637
626
|
const expandHint = formatExpandHint(theme, expanded, lines.length > 1);
|
|
638
|
-
let output = `${icon} ${theme.fg(
|
|
639
|
-
"dim",
|
|
640
|
-
truncate(firstLine, TRUNCATE_LENGTHS.TITLE, theme.format.ellipsis),
|
|
641
|
-
)}${expandHint}`;
|
|
627
|
+
let output = `${icon} ${theme.fg("dim", truncateToWidth(firstLine, TRUNCATE_LENGTHS.TITLE))}${expandHint}`;
|
|
642
628
|
|
|
643
629
|
if (lines.length > 1) {
|
|
644
630
|
const previewLines = lines.slice(1, 4);
|
|
@@ -647,13 +633,13 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
647
633
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
648
634
|
output += `\n ${theme.fg("dim", branch)} ${theme.fg(
|
|
649
635
|
"dim",
|
|
650
|
-
|
|
636
|
+
truncateToWidth(previewLines[i].trim(), TRUNCATE_LENGTHS.CONTENT),
|
|
651
637
|
)}`;
|
|
652
638
|
}
|
|
653
639
|
if (lines.length > 4) {
|
|
654
640
|
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
655
641
|
"muted",
|
|
656
|
-
formatMoreItems(lines.length - 4, "line"
|
|
642
|
+
formatMoreItems(lines.length - 4, "line"),
|
|
657
643
|
)}`;
|
|
658
644
|
}
|
|
659
645
|
}
|
|
@@ -48,7 +48,7 @@ export class BashExecutionComponent extends Container {
|
|
|
48
48
|
ui,
|
|
49
49
|
spinner => theme.fg(colorKey, spinner),
|
|
50
50
|
text => theme.fg("muted", text),
|
|
51
|
-
`Running
|
|
51
|
+
`Running… (esc to cancel)`,
|
|
52
52
|
getSymbolTheme().spinnerFrames,
|
|
53
53
|
);
|
|
54
54
|
this.contentContainer.addChild(this.loader);
|
|
@@ -150,9 +150,7 @@ export class BashExecutionComponent extends Container {
|
|
|
150
150
|
|
|
151
151
|
// Show how many lines are hidden (collapsed preview)
|
|
152
152
|
if (hiddenLineCount > 0) {
|
|
153
|
-
statusParts.push(
|
|
154
|
-
theme.fg("dim", `${theme.format.ellipsis} ${hiddenLineCount} more lines (ctrl+o to expand)`),
|
|
155
|
-
);
|
|
153
|
+
statusParts.push(theme.fg("dim", `… ${hiddenLineCount} more lines (ctrl+o to expand)`));
|
|
156
154
|
}
|
|
157
155
|
|
|
158
156
|
if (this.status === "cancelled") {
|
|
@@ -87,7 +87,7 @@ export class CustomMessageComponent extends Container {
|
|
|
87
87
|
if (!this._expanded) {
|
|
88
88
|
const lines = text.split("\n");
|
|
89
89
|
if (lines.length > 5) {
|
|
90
|
-
text = `${lines.slice(0, 5).join("\n")}\n
|
|
90
|
+
text = `${lines.slice(0, 5).join("\n")}\n…`;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -317,7 +317,7 @@ export class FooterComponent implements Component {
|
|
|
317
317
|
.map(([, text]) => sanitizeStatusText(text));
|
|
318
318
|
const statusLine = sortedStatuses.join(" ");
|
|
319
319
|
// Truncate to terminal width with dim ellipsis for consistency with footer style
|
|
320
|
-
lines.push(truncateToWidth(statusLine, width
|
|
320
|
+
lines.push(truncateToWidth(statusLine, width));
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
return lines;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Component,
|
|
3
3
|
Container,
|
|
4
|
+
Ellipsis,
|
|
4
5
|
Input,
|
|
5
6
|
matchesKey,
|
|
6
7
|
padding,
|
|
@@ -55,13 +56,13 @@ class HistoryResultsList implements Component {
|
|
|
55
56
|
const maxWidth = width - cursorWidth;
|
|
56
57
|
|
|
57
58
|
const normalized = entry.prompt.replace(/\s+/g, " ").trim();
|
|
58
|
-
const truncated = truncateToWidth(normalized, maxWidth
|
|
59
|
+
const truncated = truncateToWidth(normalized, maxWidth);
|
|
59
60
|
lines.push(cursor + (isSelected ? theme.bold(truncated) : truncated));
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
if (startIndex > 0 || endIndex < this.results.length) {
|
|
63
64
|
const scrollText = ` (${this.selectedIndex + 1}/${this.results.length})`;
|
|
64
|
-
lines.push(theme.fg("muted", truncateToWidth(scrollText, width,
|
|
65
|
+
lines.push(theme.fg("muted", truncateToWidth(scrollText, width, Ellipsis.Omit)));
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
return lines;
|
|
@@ -88,7 +88,7 @@ export class HookMessageComponent extends Container {
|
|
|
88
88
|
if (!this._expanded) {
|
|
89
89
|
const lines = text.split("\n");
|
|
90
90
|
if (lines.length > 5) {
|
|
91
|
-
text = `${lines.slice(0, 5).join("\n")}\n
|
|
91
|
+
text = `${lines.slice(0, 5).join("\n")}\n…`;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -51,7 +51,7 @@ export class PythonExecutionComponent extends Container {
|
|
|
51
51
|
ui,
|
|
52
52
|
spinner => theme.fg(colorKey, spinner),
|
|
53
53
|
text => theme.fg("muted", text),
|
|
54
|
-
`Running
|
|
54
|
+
`Running… (esc to cancel)`,
|
|
55
55
|
getSymbolTheme().spinnerFrames,
|
|
56
56
|
);
|
|
57
57
|
this.contentContainer.addChild(this.loader);
|
|
@@ -136,9 +136,7 @@ export class PythonExecutionComponent extends Container {
|
|
|
136
136
|
const statusParts: string[] = [];
|
|
137
137
|
|
|
138
138
|
if (hiddenLineCount > 0) {
|
|
139
|
-
statusParts.push(
|
|
140
|
-
theme.fg("dim", `${theme.format.ellipsis} ${hiddenLineCount} more lines (ctrl+o to expand)`),
|
|
141
|
-
);
|
|
139
|
+
statusParts.push(theme.fg("dim", `… ${hiddenLineCount} more lines (ctrl+o to expand)`));
|
|
142
140
|
}
|
|
143
141
|
|
|
144
142
|
if (this.status === "cancelled") {
|
|
@@ -105,7 +105,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
105
105
|
|
|
106
106
|
private formatPath(entry: ReadEntry): string {
|
|
107
107
|
const filePath = shortenPath(entry.path);
|
|
108
|
-
let pathDisplay = filePath ? theme.fg("accent", filePath) : theme.fg("toolOutput",
|
|
108
|
+
let pathDisplay = filePath ? theme.fg("accent", filePath) : theme.fg("toolOutput", "…");
|
|
109
109
|
if (entry.offset !== undefined || entry.limit !== undefined) {
|
|
110
110
|
const startLine = entry.offset ?? 1;
|
|
111
111
|
const endLine = entry.limit !== undefined ? startLine + entry.limit - 1 : "";
|
|
@@ -74,15 +74,11 @@ class SessionList implements Component {
|
|
|
74
74
|
if (this.filteredSessions.length === 0) {
|
|
75
75
|
if (this.showCwd) {
|
|
76
76
|
// "All" scope - no sessions anywhere that match filter
|
|
77
|
-
lines.push(truncateToWidth(theme.fg("muted", " No sessions found"), width
|
|
77
|
+
lines.push(truncateToWidth(theme.fg("muted", " No sessions found"), width));
|
|
78
78
|
} else {
|
|
79
79
|
// "Current folder" scope - hint to try "all"
|
|
80
80
|
lines.push(
|
|
81
|
-
truncateToWidth(
|
|
82
|
-
theme.fg("muted", " No sessions in current folder. Press Tab to view all."),
|
|
83
|
-
width,
|
|
84
|
-
theme.format.ellipsis,
|
|
85
|
-
),
|
|
81
|
+
truncateToWidth(theme.fg("muted", " No sessions in current folder. Press Tab to view all."), width),
|
|
86
82
|
);
|
|
87
83
|
}
|
|
88
84
|
return lines;
|
|
@@ -128,16 +124,16 @@ class SessionList implements Component {
|
|
|
128
124
|
|
|
129
125
|
if (session.title) {
|
|
130
126
|
// Has title: show title on first line, dimmed first message on second line
|
|
131
|
-
const truncatedTitle = truncateToWidth(session.title, maxWidth
|
|
127
|
+
const truncatedTitle = truncateToWidth(session.title, maxWidth);
|
|
132
128
|
const titleLine = cursor + (isSelected ? theme.bold(truncatedTitle) : truncatedTitle);
|
|
133
129
|
lines.push(titleLine);
|
|
134
130
|
|
|
135
131
|
// Second line: dimmed first message preview
|
|
136
|
-
const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth
|
|
132
|
+
const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth);
|
|
137
133
|
lines.push(` ${theme.fg("dim", truncatedPreview)}`);
|
|
138
134
|
} else {
|
|
139
135
|
// No title: show first message as main line
|
|
140
|
-
const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth
|
|
136
|
+
const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth);
|
|
141
137
|
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
|
142
138
|
lines.push(messageLine);
|
|
143
139
|
}
|
|
@@ -146,7 +142,7 @@ class SessionList implements Component {
|
|
|
146
142
|
const modified = formatDate(session.modified);
|
|
147
143
|
const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
|
|
148
144
|
const metadata = ` ${modified} ${theme.sep.dot} ${msgCount}`;
|
|
149
|
-
const metadataLine = theme.fg("dim", truncateToWidth(metadata, width
|
|
145
|
+
const metadataLine = theme.fg("dim", truncateToWidth(metadata, width));
|
|
150
146
|
|
|
151
147
|
lines.push(metadataLine);
|
|
152
148
|
lines.push(""); // Blank line between sessions
|
|
@@ -155,7 +151,7 @@ class SessionList implements Component {
|
|
|
155
151
|
// Add scroll indicator if needed
|
|
156
152
|
if (startIndex > 0 || endIndex < this.filteredSessions.length) {
|
|
157
153
|
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;
|
|
158
|
-
const scrollInfo = theme.fg("muted", truncateToWidth(scrollText, width
|
|
154
|
+
const scrollInfo = theme.fg("muted", truncateToWidth(scrollText, width));
|
|
159
155
|
lines.push(scrollInfo);
|
|
160
156
|
}
|
|
161
157
|
|
|
@@ -102,7 +102,7 @@ const pathSegment: StatusLineSegment = {
|
|
|
102
102
|
|
|
103
103
|
const maxLen = opts.maxLength ?? 40;
|
|
104
104
|
if (pwd.length > maxLen) {
|
|
105
|
-
const ellipsis =
|
|
105
|
+
const ellipsis = "…";
|
|
106
106
|
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
107
107
|
pwd = `${ellipsis}${pwd.slice(-sliceLen)}`;
|
|
108
108
|
}
|
|
@@ -423,6 +423,6 @@ export class StatusLineComponent implements Component {
|
|
|
423
423
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
424
424
|
.map(([, text]) => sanitizeStatusText(text));
|
|
425
425
|
const hookLine = sortedStatuses.join(" ");
|
|
426
|
-
return [truncateToWidth(hookLine, width
|
|
426
|
+
return [truncateToWidth(hookLine, width)];
|
|
427
427
|
}
|
|
428
428
|
}
|
|
@@ -54,7 +54,7 @@ function formatCompactValue(value: unknown, maxLength: number): string {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (rendered.length > maxLength) {
|
|
57
|
-
rendered = `${rendered.slice(0, maxLength - 1)}
|
|
57
|
+
rendered = `${rendered.slice(0, maxLength - 1)}…`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return rendered;
|
|
@@ -630,7 +630,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
630
630
|
text += `\n${theme.fg("dim", "(none)")}`;
|
|
631
631
|
}
|
|
632
632
|
if (argsPreview.remaining > 0) {
|
|
633
|
-
text += theme.fg("dim", `\n
|
|
633
|
+
text += theme.fg("dim", `\n… (${argsPreview.remaining} more args) (ctrl+o to expand)`);
|
|
634
634
|
}
|
|
635
635
|
|
|
636
636
|
const output = this.getTextOutput().trim();
|
|
@@ -643,7 +643,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
643
643
|
text += ` ${theme.fg("dim", `(${lines.length} lines)`)}`;
|
|
644
644
|
text += `\n${displayLines.map(line => theme.fg("toolOutput", line)).join("\n")}`;
|
|
645
645
|
if (remaining > 0) {
|
|
646
|
-
text += theme.fg("dim", `\n
|
|
646
|
+
text += theme.fg("dim", `\n… (${remaining} earlier lines) (ctrl+o to expand)`);
|
|
647
647
|
}
|
|
648
648
|
} else {
|
|
649
649
|
text += ` ${theme.fg("dim", "(empty)")}`;
|
|
@@ -59,7 +59,7 @@ export class TtsrNotificationComponent extends Container {
|
|
|
59
59
|
// Truncate to first 2 lines
|
|
60
60
|
const lines = displayText.split("\n");
|
|
61
61
|
if (lines.length > 2) {
|
|
62
|
-
displayText = `${lines.slice(0, 2).join("\n")}
|
|
62
|
+
displayText = `${lines.slice(0, 2).join("\n")}…`;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -165,7 +165,7 @@ export class WelcomeComponent implements Component {
|
|
|
165
165
|
private centerText(text: string, width: number): string {
|
|
166
166
|
const visLen = visibleWidth(text);
|
|
167
167
|
if (visLen >= width) {
|
|
168
|
-
return truncateToWidth(text, width
|
|
168
|
+
return truncateToWidth(text, width);
|
|
169
169
|
}
|
|
170
170
|
const leftPad = Math.floor((width - visLen) / 2);
|
|
171
171
|
const rightPad = width - visLen - leftPad;
|
|
@@ -206,7 +206,7 @@ export class WelcomeComponent implements Component {
|
|
|
206
206
|
private fitToWidth(str: string, width: number): string {
|
|
207
207
|
const visLen = visibleWidth(str);
|
|
208
208
|
if (visLen > width) {
|
|
209
|
-
const ellipsis =
|
|
209
|
+
const ellipsis = "…";
|
|
210
210
|
const ellipsisWidth = visibleWidth(ellipsis);
|
|
211
211
|
const maxWidth = Math.max(0, width - ellipsisWidth);
|
|
212
212
|
let truncated = "";
|