@oh-my-pi/pi-coding-agent 3.15.1 → 3.20.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/CHANGELOG.md +51 -1
- package/docs/extensions.md +1055 -0
- package/docs/rpc.md +69 -13
- package/docs/session-tree-plan.md +1 -1
- package/examples/extensions/README.md +141 -0
- package/examples/extensions/api-demo.ts +87 -0
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/extensions/hello.ts +33 -0
- package/examples/extensions/pirate.ts +44 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/05-tools.ts +7 -3
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/06-hooks.ts +14 -13
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/08-slash-commands.ts +17 -12
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-full-control.ts +6 -6
- package/package.json +11 -7
- package/src/capability/extension-module.ts +34 -0
- package/src/cli/args.ts +22 -7
- package/src/cli/file-processor.ts +38 -67
- package/src/cli/list-models.ts +1 -1
- package/src/config.ts +25 -14
- package/src/core/agent-session.ts +505 -242
- package/src/core/auth-storage.ts +33 -21
- package/src/core/compaction/branch-summarization.ts +4 -4
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +430 -0
- package/src/core/custom-commands/loader.ts +9 -0
- package/src/core/custom-tools/wrapper.ts +5 -0
- package/src/core/event-bus.ts +59 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/extensions/index.ts +100 -0
- package/src/core/extensions/loader.ts +501 -0
- package/src/core/extensions/runner.ts +477 -0
- package/src/core/extensions/types.ts +712 -0
- package/src/core/extensions/wrapper.ts +147 -0
- package/src/core/hooks/types.ts +2 -2
- package/src/core/index.ts +10 -21
- package/src/core/keybindings.ts +199 -0
- package/src/core/messages.ts +26 -7
- package/src/core/model-registry.ts +123 -46
- package/src/core/model-resolver.ts +7 -5
- package/src/core/prompt-templates.ts +242 -0
- package/src/core/sdk.ts +378 -295
- package/src/core/session-manager.ts +72 -58
- package/src/core/settings-manager.ts +118 -22
- package/src/core/system-prompt.ts +24 -1
- package/src/core/terminal-notify.ts +37 -0
- package/src/core/tools/context.ts +4 -4
- package/src/core/tools/exa/mcp-client.ts +5 -4
- package/src/core/tools/exa/render.ts +176 -131
- package/src/core/tools/gemini-image.ts +361 -0
- package/src/core/tools/git.ts +216 -0
- package/src/core/tools/index.ts +28 -15
- package/src/core/tools/lsp/config.ts +5 -4
- package/src/core/tools/lsp/index.ts +17 -12
- package/src/core/tools/lsp/render.ts +39 -47
- package/src/core/tools/read.ts +66 -29
- package/src/core/tools/render-utils.ts +268 -0
- package/src/core/tools/renderers.ts +243 -225
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +66 -58
- package/src/core/tools/task/index.ts +29 -10
- package/src/core/tools/task/model-resolver.ts +8 -13
- package/src/core/tools/task/omp-command.ts +24 -0
- package/src/core/tools/task/render.ts +35 -60
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/web-fetch.ts +29 -28
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/core/tools/web-search/providers/exa.ts +6 -5
- package/src/core/tools/web-search/render.ts +66 -111
- package/src/core/voice-controller.ts +135 -0
- package/src/core/voice-supervisor.ts +1003 -0
- package/src/core/voice.ts +308 -0
- package/src/discovery/builtin.ts +75 -1
- package/src/discovery/claude.ts +47 -1
- package/src/discovery/codex.ts +54 -2
- package/src/discovery/gemini.ts +55 -2
- package/src/discovery/helpers.ts +100 -1
- package/src/discovery/index.ts +2 -0
- package/src/index.ts +14 -9
- package/src/lib/worktree/collapse.ts +179 -0
- package/src/lib/worktree/constants.ts +14 -0
- package/src/lib/worktree/errors.ts +23 -0
- package/src/lib/worktree/git.ts +110 -0
- package/src/lib/worktree/index.ts +23 -0
- package/src/lib/worktree/operations.ts +216 -0
- package/src/lib/worktree/session.ts +114 -0
- package/src/lib/worktree/stats.ts +67 -0
- package/src/main.ts +61 -37
- package/src/migrations.ts +37 -7
- package/src/modes/interactive/components/bash-execution.ts +6 -4
- package/src/modes/interactive/components/custom-editor.ts +55 -0
- package/src/modes/interactive/components/custom-message.ts +95 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/extensions/types.ts +1 -0
- package/src/modes/interactive/components/footer.ts +324 -0
- package/src/modes/interactive/components/hook-selector.ts +3 -3
- package/src/modes/interactive/components/model-selector.ts +7 -6
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/settings-defs.ts +55 -6
- package/src/modes/interactive/components/status-line.ts +45 -37
- package/src/modes/interactive/components/tool-execution.ts +95 -23
- package/src/modes/interactive/interactive-mode.ts +643 -113
- package/src/modes/interactive/theme/defaults/index.ts +16 -16
- package/src/modes/print-mode.ts +14 -72
- package/src/modes/rpc/rpc-client.ts +23 -9
- package/src/modes/rpc/rpc-mode.ts +137 -125
- package/src/modes/rpc/rpc-types.ts +46 -24
- package/src/prompts/task.md +1 -0
- package/src/prompts/tools/gemini-image.md +4 -0
- package/src/prompts/tools/git.md +9 -0
- package/src/prompts/voice-summary.md +12 -0
- package/src/utils/image-convert.ts +26 -0
- package/src/utils/image-resize.ts +215 -0
- package/src/utils/shell-snapshot.ts +22 -20
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import type
|
|
9
|
+
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
10
10
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
11
11
|
import type { AskToolDetails } from "./ask";
|
|
12
12
|
import type { FindToolDetails } from "./find";
|
|
@@ -16,6 +16,15 @@ import { renderCall as renderLspCall, renderResult as renderLspResult } from "./
|
|
|
16
16
|
import type { LspToolDetails } from "./lsp/types";
|
|
17
17
|
import type { NotebookToolDetails } from "./notebook";
|
|
18
18
|
import type { OutputToolDetails } from "./output";
|
|
19
|
+
import {
|
|
20
|
+
formatBytes,
|
|
21
|
+
formatCount,
|
|
22
|
+
formatExpandHint,
|
|
23
|
+
formatMoreItems,
|
|
24
|
+
PREVIEW_LIMITS,
|
|
25
|
+
TRUNCATE_LENGTHS,
|
|
26
|
+
truncate,
|
|
27
|
+
} from "./render-utils";
|
|
19
28
|
import { renderCall as renderTaskCall, renderResult as renderTaskResult } from "./task/render";
|
|
20
29
|
import type { TaskToolDetails } from "./task/types";
|
|
21
30
|
import { renderWebFetchCall, renderWebFetchResult, type WebFetchToolDetails } from "./web-fetch";
|
|
@@ -32,6 +41,34 @@ interface ToolRenderer<TArgs = any, TDetails = any> {
|
|
|
32
41
|
): Component;
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
45
|
+
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
46
|
+
|
|
47
|
+
function formatMeta(meta: string[], theme: Theme): string {
|
|
48
|
+
return meta.length > 0 ? ` ${theme.fg("muted", meta.join(theme.sep.dot))}` : "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatScope(scopePath: string | undefined, theme: Theme): string {
|
|
52
|
+
return scopePath ? ` ${theme.fg("muted", `in ${scopePath}`)}` : "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatTruncationSuffix(truncated: boolean, theme: Theme): string {
|
|
56
|
+
return truncated ? theme.fg("warning", " (truncated)") : "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function renderErrorMessage(_toolLabel: string, message: string, theme: Theme): Text {
|
|
60
|
+
const clean = message.replace(/^Error:\s*/, "").trim();
|
|
61
|
+
return new Text(
|
|
62
|
+
`${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${clean || "Unknown error"}`)}`,
|
|
63
|
+
0,
|
|
64
|
+
0,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderEmptyMessage(_toolLabel: string, message: string, theme: Theme): Text {
|
|
69
|
+
return new Text(`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", message)}`, 0, 0);
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
// ============================================================================
|
|
36
73
|
// Grep Renderer
|
|
37
74
|
// ============================================================================
|
|
@@ -52,58 +89,55 @@ interface GrepArgs {
|
|
|
52
89
|
|
|
53
90
|
const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
|
|
54
91
|
renderCall(args, theme) {
|
|
55
|
-
|
|
56
|
-
text
|
|
92
|
+
const label = theme.fg("toolTitle", theme.bold("Grep"));
|
|
93
|
+
let text = `${label} ${theme.fg("accent", args.pattern || "?")}`;
|
|
57
94
|
|
|
58
95
|
const meta: string[] = [];
|
|
59
|
-
if (args.path) meta.push(args.path);
|
|
96
|
+
if (args.path) meta.push(`in ${args.path}`);
|
|
60
97
|
if (args.glob) meta.push(`glob:${args.glob}`);
|
|
61
98
|
if (args.type) meta.push(`type:${args.type}`);
|
|
62
|
-
if (args.outputMode && args.outputMode !== "files_with_matches") meta.push(args.outputMode);
|
|
99
|
+
if (args.outputMode && args.outputMode !== "files_with_matches") meta.push(`mode:${args.outputMode}`);
|
|
63
100
|
if (args.caseSensitive) {
|
|
64
|
-
meta.push("
|
|
101
|
+
meta.push("case:sensitive");
|
|
65
102
|
} else if (args.ignoreCase) {
|
|
66
|
-
meta.push("
|
|
103
|
+
meta.push("case:insensitive");
|
|
67
104
|
}
|
|
105
|
+
if (args.literal) meta.push("literal");
|
|
68
106
|
if (args.multiline) meta.push("multiline");
|
|
107
|
+
if (args.context !== undefined) meta.push(`context:${args.context}`);
|
|
108
|
+
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
69
109
|
|
|
70
|
-
|
|
71
|
-
text += ` ${theme.fg("muted", meta.join(" "))}`;
|
|
72
|
-
}
|
|
110
|
+
text += formatMeta(meta, theme);
|
|
73
111
|
|
|
74
112
|
return new Text(text, 0, 0);
|
|
75
113
|
},
|
|
76
114
|
|
|
77
115
|
renderResult(result, { expanded }, theme) {
|
|
116
|
+
const label = "Grep";
|
|
78
117
|
const details = result.details;
|
|
79
118
|
|
|
80
|
-
// Error case
|
|
81
119
|
if (details?.error) {
|
|
82
|
-
return
|
|
120
|
+
return renderErrorMessage(label, details.error, theme);
|
|
83
121
|
}
|
|
84
122
|
|
|
85
|
-
// Check for detailed rendering data - fall back to structured output if not available
|
|
86
123
|
const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
|
|
87
124
|
|
|
88
125
|
if (!hasDetailedData) {
|
|
89
126
|
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
90
127
|
if (!textContent || textContent === "No matches found") {
|
|
91
|
-
return
|
|
92
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No matches found")}`,
|
|
93
|
-
0,
|
|
94
|
-
0,
|
|
95
|
-
);
|
|
128
|
+
return renderEmptyMessage(label, "No matches found", theme);
|
|
96
129
|
}
|
|
97
130
|
|
|
98
131
|
const lines = textContent.split("\n").filter((line) => line.trim() !== "");
|
|
99
|
-
const maxLines = expanded ? lines.length :
|
|
132
|
+
const maxLines = expanded ? lines.length : Math.min(lines.length, COLLAPSED_TEXT_LIMIT);
|
|
100
133
|
const displayLines = lines.slice(0, maxLines);
|
|
101
134
|
const remaining = lines.length - maxLines;
|
|
135
|
+
const hasMore = remaining > 0;
|
|
102
136
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)}`;
|
|
137
|
+
const icon = theme.styledSymbol("status.success", "success");
|
|
138
|
+
const summary = formatCount("item", lines.length);
|
|
139
|
+
const expandHint = formatExpandHint(expanded, hasMore, theme);
|
|
140
|
+
let text = `${icon} ${theme.fg("dim", summary)}${expandHint}`;
|
|
107
141
|
|
|
108
142
|
for (let i = 0; i < displayLines.length; i++) {
|
|
109
143
|
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
@@ -114,9 +148,10 @@ const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
|
|
|
114
148
|
if (remaining > 0) {
|
|
115
149
|
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
116
150
|
"muted",
|
|
117
|
-
|
|
151
|
+
formatMoreItems(remaining, "item", theme),
|
|
118
152
|
)}`;
|
|
119
153
|
}
|
|
154
|
+
|
|
120
155
|
return new Text(text, 0, 0);
|
|
121
156
|
}
|
|
122
157
|
|
|
@@ -126,33 +161,29 @@ const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
|
|
|
126
161
|
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
127
162
|
const files = details?.files ?? [];
|
|
128
163
|
|
|
129
|
-
// No matches
|
|
130
164
|
if (matchCount === 0) {
|
|
131
|
-
return
|
|
132
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No matches found")}`,
|
|
133
|
-
0,
|
|
134
|
-
0,
|
|
135
|
-
);
|
|
165
|
+
return renderEmptyMessage(label, "No matches found", theme);
|
|
136
166
|
}
|
|
137
167
|
|
|
138
|
-
// Build summary
|
|
139
168
|
const icon = theme.styledSymbol("status.success", "success");
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
summary = `${matchCount} match${matchCount !== 1 ? "es" : ""} in ${fileCount} file${fileCount !== 1 ? "s" : ""}`;
|
|
147
|
-
}
|
|
169
|
+
const summaryParts =
|
|
170
|
+
mode === "files_with_matches"
|
|
171
|
+
? [formatCount("file", fileCount)]
|
|
172
|
+
: [formatCount("match", matchCount), formatCount("file", fileCount)];
|
|
173
|
+
const summaryText = summaryParts.join(theme.sep.dot);
|
|
174
|
+
const scopeLabel = formatScope(details?.scopePath, theme);
|
|
148
175
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
176
|
+
const fileEntries: Array<{ path: string; count?: number }> = details?.fileMatches?.length
|
|
177
|
+
? details.fileMatches.map((entry) => ({ path: entry.path, count: entry.count }))
|
|
178
|
+
: files.map((path) => ({ path }));
|
|
179
|
+
const maxFiles = expanded ? fileEntries.length : Math.min(fileEntries.length, COLLAPSED_LIST_LIMIT);
|
|
180
|
+
const hasMoreFiles = fileEntries.length > maxFiles;
|
|
181
|
+
const expandHint = formatExpandHint(expanded, hasMoreFiles, theme);
|
|
152
182
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
let text = `${icon} ${theme.fg("dim", summaryText)}${formatTruncationSuffix(
|
|
184
|
+
truncated,
|
|
185
|
+
theme,
|
|
186
|
+
)}${scopeLabel}${expandHint}`;
|
|
156
187
|
|
|
157
188
|
const truncationReasons: string[] = [];
|
|
158
189
|
if (details?.matchLimitReached) {
|
|
@@ -168,33 +199,36 @@ const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
|
|
|
168
199
|
truncationReasons.push("line length");
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
const
|
|
172
|
-
? details.fileMatches.map((entry) => ({ path: entry.path, count: entry.count }))
|
|
173
|
-
: files.map((path) => ({ path }));
|
|
202
|
+
const hasTruncation = truncationReasons.length > 0;
|
|
174
203
|
|
|
175
|
-
// Show file tree if we have files
|
|
176
204
|
if (fileEntries.length > 0) {
|
|
177
|
-
const maxFiles = expanded ? fileEntries.length : Math.min(fileEntries.length, 8);
|
|
178
205
|
for (let i = 0; i < maxFiles; i++) {
|
|
179
206
|
const entry = fileEntries[i];
|
|
180
|
-
const isLast = i === maxFiles - 1 &&
|
|
207
|
+
const isLast = i === maxFiles - 1 && !hasMoreFiles && !hasTruncation;
|
|
181
208
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
209
|
+
const isDir = entry.path.endsWith("/");
|
|
210
|
+
const entryPath = isDir ? entry.path.slice(0, -1) : entry.path;
|
|
211
|
+
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
212
|
+
const entryIcon = isDir
|
|
213
|
+
? theme.fg("accent", theme.icon.folder)
|
|
214
|
+
: theme.fg("muted", theme.getLangIcon(lang));
|
|
182
215
|
const countLabel =
|
|
183
216
|
entry.count !== undefined
|
|
184
217
|
? ` ${theme.fg("dim", `(${entry.count} match${entry.count !== 1 ? "es" : ""})`)}`
|
|
185
218
|
: "";
|
|
186
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", entry.path)}${countLabel}`;
|
|
219
|
+
text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg("accent", entry.path)}${countLabel}`;
|
|
187
220
|
}
|
|
188
221
|
|
|
189
|
-
if (
|
|
190
|
-
|
|
222
|
+
if (hasMoreFiles) {
|
|
223
|
+
const moreFilesBranch = hasTruncation ? theme.tree.branch : theme.tree.last;
|
|
224
|
+
text += `\n ${theme.fg("dim", moreFilesBranch)} ${theme.fg(
|
|
191
225
|
"muted",
|
|
192
|
-
|
|
226
|
+
formatMoreItems(fileEntries.length - maxFiles, "file", theme),
|
|
193
227
|
)}`;
|
|
194
228
|
}
|
|
195
229
|
}
|
|
196
230
|
|
|
197
|
-
if (
|
|
231
|
+
if (hasTruncation) {
|
|
198
232
|
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
199
233
|
"warning",
|
|
200
234
|
`truncated: ${truncationReasons.join(", ")}`,
|
|
@@ -220,54 +254,48 @@ interface FindArgs {
|
|
|
220
254
|
|
|
221
255
|
const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
|
|
222
256
|
renderCall(args, theme) {
|
|
223
|
-
|
|
224
|
-
text
|
|
257
|
+
const label = theme.fg("toolTitle", theme.bold("Find"));
|
|
258
|
+
let text = `${label} ${theme.fg("accent", args.pattern || "*")}`;
|
|
225
259
|
|
|
226
260
|
const meta: string[] = [];
|
|
227
|
-
if (args.path) meta.push(args.path);
|
|
261
|
+
if (args.path) meta.push(`in ${args.path}`);
|
|
228
262
|
if (args.type && args.type !== "all") meta.push(`type:${args.type}`);
|
|
229
|
-
if (args.hidden) meta.push("
|
|
263
|
+
if (args.hidden) meta.push("hidden");
|
|
264
|
+
if (args.sortByMtime) meta.push("sort:mtime");
|
|
265
|
+
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
230
266
|
|
|
231
|
-
|
|
232
|
-
text += ` ${theme.fg("muted", meta.join(" "))}`;
|
|
233
|
-
}
|
|
267
|
+
text += formatMeta(meta, theme);
|
|
234
268
|
|
|
235
269
|
return new Text(text, 0, 0);
|
|
236
270
|
},
|
|
237
271
|
|
|
238
272
|
renderResult(result, { expanded }, theme) {
|
|
273
|
+
const label = "Find";
|
|
239
274
|
const details = result.details;
|
|
240
275
|
|
|
241
|
-
// Error case
|
|
242
276
|
if (details?.error) {
|
|
243
|
-
return
|
|
277
|
+
return renderErrorMessage(label, details.error, theme);
|
|
244
278
|
}
|
|
245
279
|
|
|
246
|
-
// Check for detailed rendering data - fall back to parsing raw output if not available
|
|
247
280
|
const hasDetailedData = details?.fileCount !== undefined;
|
|
248
|
-
|
|
249
|
-
// Get text content for fallback or to extract file list
|
|
250
281
|
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
251
282
|
|
|
252
283
|
if (!hasDetailedData) {
|
|
253
284
|
if (!textContent || textContent.includes("No files matching") || textContent.trim() === "") {
|
|
254
|
-
return
|
|
255
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No files found")}`,
|
|
256
|
-
0,
|
|
257
|
-
0,
|
|
258
|
-
);
|
|
285
|
+
return renderEmptyMessage(label, "No files found", theme);
|
|
259
286
|
}
|
|
260
287
|
|
|
261
|
-
// Parse the raw output as file list
|
|
262
288
|
const lines = textContent.split("\n").filter((l) => l.trim());
|
|
263
|
-
const maxLines = expanded ? lines.length : Math.min(lines.length,
|
|
289
|
+
const maxLines = expanded ? lines.length : Math.min(lines.length, COLLAPSED_LIST_LIMIT);
|
|
264
290
|
const displayLines = lines.slice(0, maxLines);
|
|
265
291
|
const remaining = lines.length - maxLines;
|
|
292
|
+
const hasMore = remaining > 0;
|
|
293
|
+
|
|
294
|
+
const icon = theme.styledSymbol("status.success", "success");
|
|
295
|
+
const summary = formatCount("file", lines.length);
|
|
296
|
+
const expandHint = formatExpandHint(expanded, hasMore, theme);
|
|
297
|
+
let text = `${icon} ${theme.fg("dim", summary)}${expandHint}`;
|
|
266
298
|
|
|
267
|
-
let text = `${theme.styledSymbol("status.success", "success")} ${theme.fg("toolTitle", "find")} ${theme.fg(
|
|
268
|
-
"dim",
|
|
269
|
-
`${lines.length} file${lines.length !== 1 ? "s" : ""}`,
|
|
270
|
-
)}`;
|
|
271
299
|
for (let i = 0; i < displayLines.length; i++) {
|
|
272
300
|
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
273
301
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
@@ -276,7 +304,7 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
|
|
|
276
304
|
if (remaining > 0) {
|
|
277
305
|
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
278
306
|
"muted",
|
|
279
|
-
|
|
307
|
+
formatMoreItems(remaining, "file", theme),
|
|
280
308
|
)}`;
|
|
281
309
|
}
|
|
282
310
|
return new Text(text, 0, 0);
|
|
@@ -286,26 +314,21 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
|
|
|
286
314
|
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
287
315
|
const files = details?.files ?? [];
|
|
288
316
|
|
|
289
|
-
// No matches
|
|
290
317
|
if (fileCount === 0) {
|
|
291
|
-
return
|
|
292
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No files found")}`,
|
|
293
|
-
0,
|
|
294
|
-
0,
|
|
295
|
-
);
|
|
318
|
+
return renderEmptyMessage(label, "No files found", theme);
|
|
296
319
|
}
|
|
297
320
|
|
|
298
|
-
// Build summary
|
|
299
321
|
const icon = theme.styledSymbol("status.success", "success");
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
322
|
+
const summaryText = formatCount("file", fileCount);
|
|
323
|
+
const scopeLabel = formatScope(details?.scopePath, theme);
|
|
324
|
+
const maxFiles = expanded ? files.length : Math.min(files.length, COLLAPSED_LIST_LIMIT);
|
|
325
|
+
const hasMoreFiles = files.length > maxFiles;
|
|
326
|
+
const expandHint = formatExpandHint(expanded, hasMoreFiles, theme);
|
|
305
327
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
328
|
+
let text = `${icon} ${theme.fg("dim", summaryText)}${formatTruncationSuffix(
|
|
329
|
+
truncated,
|
|
330
|
+
theme,
|
|
331
|
+
)}${scopeLabel}${expandHint}`;
|
|
309
332
|
|
|
310
333
|
const truncationReasons: string[] = [];
|
|
311
334
|
if (details?.resultLimitReached) {
|
|
@@ -315,24 +338,32 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
|
|
|
315
338
|
truncationReasons.push("size limit");
|
|
316
339
|
}
|
|
317
340
|
|
|
318
|
-
|
|
341
|
+
const hasTruncation = truncationReasons.length > 0;
|
|
342
|
+
|
|
319
343
|
if (files.length > 0) {
|
|
320
|
-
const maxFiles = expanded ? files.length : Math.min(files.length, 8);
|
|
321
344
|
for (let i = 0; i < maxFiles; i++) {
|
|
322
|
-
const isLast = i === maxFiles - 1 &&
|
|
345
|
+
const isLast = i === maxFiles - 1 && !hasMoreFiles && !hasTruncation;
|
|
323
346
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
324
|
-
|
|
347
|
+
const entry = files[i];
|
|
348
|
+
const isDir = entry.endsWith("/");
|
|
349
|
+
const entryPath = isDir ? entry.slice(0, -1) : entry;
|
|
350
|
+
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
351
|
+
const entryIcon = isDir
|
|
352
|
+
? theme.fg("accent", theme.icon.folder)
|
|
353
|
+
: theme.fg("muted", theme.getLangIcon(lang));
|
|
354
|
+
text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg("accent", entry)}`;
|
|
325
355
|
}
|
|
326
356
|
|
|
327
|
-
if (
|
|
328
|
-
|
|
357
|
+
if (hasMoreFiles) {
|
|
358
|
+
const moreFilesBranch = hasTruncation ? theme.tree.branch : theme.tree.last;
|
|
359
|
+
text += `\n ${theme.fg("dim", moreFilesBranch)} ${theme.fg(
|
|
329
360
|
"muted",
|
|
330
|
-
|
|
361
|
+
formatMoreItems(files.length - maxFiles, "file", theme),
|
|
331
362
|
)}`;
|
|
332
363
|
}
|
|
333
364
|
}
|
|
334
365
|
|
|
335
|
-
if (
|
|
366
|
+
if (hasTruncation) {
|
|
336
367
|
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
337
368
|
"warning",
|
|
338
369
|
`truncated: ${truncationReasons.join(", ")}`,
|
|
@@ -365,7 +396,7 @@ function renderCellPreview(lines: string[], expanded: boolean, theme: Theme): st
|
|
|
365
396
|
return `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", "(empty cell)")}`;
|
|
366
397
|
}
|
|
367
398
|
|
|
368
|
-
const maxLines = expanded ? normalized.length : Math.min(normalized.length,
|
|
399
|
+
const maxLines = expanded ? normalized.length : Math.min(normalized.length, COLLAPSED_TEXT_LIMIT);
|
|
369
400
|
let text = "";
|
|
370
401
|
|
|
371
402
|
for (let i = 0; i < maxLines; i++) {
|
|
@@ -377,10 +408,7 @@ function renderCellPreview(lines: string[], expanded: boolean, theme: Theme): st
|
|
|
377
408
|
|
|
378
409
|
const remaining = normalized.length - maxLines;
|
|
379
410
|
if (remaining > 0) {
|
|
380
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
381
|
-
"muted",
|
|
382
|
-
`${theme.format.ellipsis} ${remaining} more lines`,
|
|
383
|
-
)}`;
|
|
411
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, "line", theme))}`;
|
|
384
412
|
}
|
|
385
413
|
|
|
386
414
|
return text;
|
|
@@ -388,28 +416,26 @@ function renderCellPreview(lines: string[], expanded: boolean, theme: Theme): st
|
|
|
388
416
|
|
|
389
417
|
const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
|
|
390
418
|
renderCall(args, theme) {
|
|
391
|
-
|
|
392
|
-
text
|
|
419
|
+
const label = theme.fg("toolTitle", theme.bold("Notebook"));
|
|
420
|
+
let text = `${label} ${theme.fg("accent", args.action || "?")}`;
|
|
393
421
|
|
|
394
422
|
const meta: string[] = [];
|
|
395
|
-
meta.push(args.notebookPath || "?");
|
|
423
|
+
meta.push(`in ${args.notebookPath || "?"}`);
|
|
396
424
|
if (args.cellNumber !== undefined) meta.push(`cell:${args.cellNumber}`);
|
|
397
|
-
if (args.cellType) meta.push(args.cellType);
|
|
425
|
+
if (args.cellType) meta.push(`type:${args.cellType}`);
|
|
398
426
|
|
|
399
|
-
|
|
400
|
-
text += ` ${theme.fg("muted", meta.join(" "))}`;
|
|
401
|
-
}
|
|
427
|
+
text += formatMeta(meta, theme);
|
|
402
428
|
|
|
403
429
|
return new Text(text, 0, 0);
|
|
404
430
|
},
|
|
405
431
|
|
|
406
432
|
renderResult(result, { expanded }, theme) {
|
|
433
|
+
const label = "Notebook";
|
|
407
434
|
const details = result.details;
|
|
408
435
|
|
|
409
|
-
// Error case - check for error in content
|
|
410
436
|
const content = result.content?.[0];
|
|
411
437
|
if (content?.type === "text" && content.text?.startsWith("Error:")) {
|
|
412
|
-
return
|
|
438
|
+
return renderErrorMessage(label, content.text, theme);
|
|
413
439
|
}
|
|
414
440
|
|
|
415
441
|
const action = details?.action ?? "edit";
|
|
@@ -418,33 +444,18 @@ const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
|
|
|
418
444
|
const totalCells = details?.totalCells;
|
|
419
445
|
const cellSource = details?.cellSource;
|
|
420
446
|
const lineCount = cellSource?.length;
|
|
421
|
-
const canExpand = cellSource !== undefined && cellSource.length >
|
|
447
|
+
const canExpand = cellSource !== undefined && cellSource.length > COLLAPSED_TEXT_LIMIT;
|
|
422
448
|
|
|
423
|
-
// Build summary
|
|
424
449
|
const icon = theme.styledSymbol("status.success", "success");
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
case "delete":
|
|
432
|
-
summary = `Deleted cell at index ${cellIndex}`;
|
|
433
|
-
break;
|
|
434
|
-
default:
|
|
435
|
-
summary = `Edited ${cellType || "cell"} at index ${cellIndex}`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (lineCount !== undefined) {
|
|
439
|
-
summary += ` (${lineCount} line${lineCount !== 1 ? "s" : ""})`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (totalCells !== undefined) {
|
|
443
|
-
summary += ` (${totalCells} total)`;
|
|
444
|
-
}
|
|
450
|
+
const actionLabel = action === "insert" ? "Inserted" : action === "delete" ? "Deleted" : "Edited";
|
|
451
|
+
const cellLabel = cellType || "cell";
|
|
452
|
+
const summaryParts = [`${actionLabel} ${cellLabel} at index ${cellIndex ?? "?"}`];
|
|
453
|
+
if (lineCount !== undefined) summaryParts.push(formatCount("line", lineCount));
|
|
454
|
+
if (totalCells !== undefined) summaryParts.push(`${totalCells} total`);
|
|
455
|
+
const summaryText = summaryParts.join(theme.sep.dot);
|
|
445
456
|
|
|
446
|
-
const expandHint =
|
|
447
|
-
let text = `${icon} ${theme.fg("
|
|
457
|
+
const expandHint = formatExpandHint(expanded, canExpand, theme);
|
|
458
|
+
let text = `${icon} ${theme.fg("dim", summaryText)}${expandHint}`;
|
|
448
459
|
|
|
449
460
|
if (cellSource) {
|
|
450
461
|
text += renderCellPreview(cellSource, expanded, theme);
|
|
@@ -467,15 +478,26 @@ interface AskArgs {
|
|
|
467
478
|
const askRenderer: ToolRenderer<AskArgs, AskToolDetails> = {
|
|
468
479
|
renderCall(args, theme) {
|
|
469
480
|
if (!args.question) {
|
|
470
|
-
return
|
|
481
|
+
return renderErrorMessage("Ask", "No question provided", theme);
|
|
471
482
|
}
|
|
472
483
|
|
|
473
|
-
const
|
|
474
|
-
let text =
|
|
484
|
+
const label = theme.fg("toolTitle", theme.bold("Ask"));
|
|
485
|
+
let text = `${label} ${theme.fg("accent", args.question)}`;
|
|
486
|
+
|
|
487
|
+
const meta: string[] = [];
|
|
488
|
+
if (args.multi) meta.push("multi");
|
|
489
|
+
if (args.options?.length) meta.push(`options:${args.options.length}`);
|
|
490
|
+
text += formatMeta(meta, theme);
|
|
475
491
|
|
|
476
492
|
if (args.options?.length) {
|
|
477
|
-
for (
|
|
478
|
-
|
|
493
|
+
for (let i = 0; i < args.options.length; i++) {
|
|
494
|
+
const opt = args.options[i];
|
|
495
|
+
const isLast = i === args.options.length - 1;
|
|
496
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
497
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg(
|
|
498
|
+
"dim",
|
|
499
|
+
theme.checkbox.unchecked,
|
|
500
|
+
)} ${theme.fg("muted", opt.label)}`;
|
|
479
501
|
}
|
|
480
502
|
}
|
|
481
503
|
|
|
@@ -494,7 +516,7 @@ const askRenderer: ToolRenderer<AskArgs, AskToolDetails> = {
|
|
|
494
516
|
? theme.styledSymbol("status.success", "success")
|
|
495
517
|
: theme.styledSymbol("status.warning", "warning");
|
|
496
518
|
|
|
497
|
-
let text = `${statusIcon} ${theme.fg("
|
|
519
|
+
let text = `${statusIcon} ${theme.fg("accent", details.question)}`;
|
|
498
520
|
|
|
499
521
|
if (details.customInput) {
|
|
500
522
|
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.styledSymbol(
|
|
@@ -552,23 +574,10 @@ interface OutputArgs {
|
|
|
552
574
|
format?: "raw" | "json" | "stripped";
|
|
553
575
|
}
|
|
554
576
|
|
|
555
|
-
/** Format byte count for display */
|
|
556
|
-
function formatBytes(bytes: number): string {
|
|
557
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
558
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
559
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function truncateLine(text: string, maxLen: number, ellipsis: string): string {
|
|
563
|
-
if (text.length <= maxLen) return text;
|
|
564
|
-
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
565
|
-
return `${text.slice(0, sliceLen)}${ellipsis}`;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
577
|
type OutputEntry = OutputToolDetails["outputs"][number];
|
|
569
578
|
|
|
570
579
|
function formatOutputMeta(entry: OutputEntry, theme: Theme): string {
|
|
571
|
-
const metaParts = [
|
|
580
|
+
const metaParts = [formatCount("line", entry.lineCount), formatBytes(entry.charCount)];
|
|
572
581
|
if (entry.provenance) {
|
|
573
582
|
metaParts.push(`agent ${entry.provenance.agent}(${entry.provenance.index})`);
|
|
574
583
|
}
|
|
@@ -578,48 +587,56 @@ function formatOutputMeta(entry: OutputEntry, theme: Theme): string {
|
|
|
578
587
|
const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
|
|
579
588
|
renderCall(args, theme) {
|
|
580
589
|
const ids = args.ids?.join(", ") ?? "?";
|
|
581
|
-
const label = theme.fg("toolTitle", theme.bold("
|
|
582
|
-
|
|
583
|
-
|
|
590
|
+
const label = theme.fg("toolTitle", theme.bold("Output"));
|
|
591
|
+
let text = `${label} ${theme.fg("accent", ids)}`;
|
|
592
|
+
|
|
593
|
+
const meta: string[] = [];
|
|
594
|
+
if (args.format && args.format !== "raw") meta.push(`format:${args.format}`);
|
|
595
|
+
text += formatMeta(meta, theme);
|
|
596
|
+
|
|
597
|
+
return new Text(text, 0, 0);
|
|
584
598
|
},
|
|
585
599
|
|
|
586
600
|
renderResult(result, { expanded }, theme) {
|
|
601
|
+
const label = "Output";
|
|
587
602
|
const details = result.details;
|
|
588
603
|
|
|
589
|
-
// Error case: some IDs not found
|
|
590
604
|
if (details?.notFound?.length) {
|
|
591
|
-
|
|
605
|
+
const icon = theme.styledSymbol("status.error", "error");
|
|
606
|
+
let text = `${icon} ${theme.fg("error", `Error: Not found: ${details.notFound.join(", ")}`)}`;
|
|
592
607
|
if (details.availableIds?.length) {
|
|
593
|
-
text += `\n${theme.fg("dim",
|
|
608
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
609
|
+
"muted",
|
|
610
|
+
`Available: ${details.availableIds.join(", ")}`,
|
|
611
|
+
)}`;
|
|
594
612
|
} else {
|
|
595
|
-
text += `\n${theme.fg("dim",
|
|
613
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
614
|
+
"muted",
|
|
615
|
+
"No outputs available in current session",
|
|
616
|
+
)}`;
|
|
596
617
|
}
|
|
597
618
|
return new Text(text, 0, 0);
|
|
598
619
|
}
|
|
599
620
|
|
|
600
621
|
const outputs = details?.outputs ?? [];
|
|
601
622
|
|
|
602
|
-
// No session case
|
|
603
623
|
if (outputs.length === 0) {
|
|
604
624
|
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
605
|
-
return
|
|
606
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", textContent || "No outputs")}`,
|
|
607
|
-
0,
|
|
608
|
-
0,
|
|
609
|
-
);
|
|
625
|
+
return renderEmptyMessage(label, textContent || "No outputs", theme);
|
|
610
626
|
}
|
|
611
627
|
|
|
612
|
-
// Success: summary + tree display
|
|
613
|
-
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
614
628
|
const icon = theme.styledSymbol("status.success", "success");
|
|
615
|
-
const summary = `read ${
|
|
616
|
-
let text = `${icon} ${theme.fg("toolTitle", "output")} ${theme.fg("dim", summary)}${expandHint}`;
|
|
617
|
-
|
|
629
|
+
const summary = `read ${formatCount("output", outputs.length)}`;
|
|
618
630
|
const previewLimit = expanded ? 3 : 1;
|
|
619
631
|
const maxOutputs = expanded ? outputs.length : Math.min(outputs.length, 5);
|
|
632
|
+
const hasMoreOutputs = outputs.length > maxOutputs;
|
|
633
|
+
const hasMorePreview = outputs.some((o) => (o.previewLines?.length ?? 0) > previewLimit);
|
|
634
|
+
const expandHint = formatExpandHint(expanded, hasMoreOutputs || hasMorePreview, theme);
|
|
635
|
+
let text = `${icon} ${theme.fg("dim", summary)}${expandHint}`;
|
|
636
|
+
|
|
620
637
|
for (let i = 0; i < maxOutputs; i++) {
|
|
621
638
|
const o = outputs[i];
|
|
622
|
-
const isLast = i === maxOutputs - 1 &&
|
|
639
|
+
const isLast = i === maxOutputs - 1 && !hasMoreOutputs;
|
|
623
640
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
624
641
|
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", o.id)} ${formatOutputMeta(o, theme)}`;
|
|
625
642
|
|
|
@@ -628,19 +645,19 @@ const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
|
|
|
628
645
|
if (shownPreview.length > 0) {
|
|
629
646
|
const childPrefix = isLast ? " " : ` ${theme.fg("dim", theme.tree.vertical)} `;
|
|
630
647
|
for (const line of shownPreview) {
|
|
631
|
-
const previewText =
|
|
632
|
-
text += `\n${childPrefix}${theme.fg("dim", theme.tree.hook)} ${theme.fg(
|
|
633
|
-
"
|
|
634
|
-
|
|
635
|
-
)}`;
|
|
648
|
+
const previewText = truncate(line, TRUNCATE_LENGTHS.CONTENT, theme.format.ellipsis);
|
|
649
|
+
text += `\n${childPrefix}${theme.fg("dim", theme.tree.hook)} ${theme.fg(
|
|
650
|
+
"muted",
|
|
651
|
+
"preview:",
|
|
652
|
+
)} ${theme.fg("toolOutput", previewText)}`;
|
|
636
653
|
}
|
|
637
654
|
}
|
|
638
655
|
}
|
|
639
656
|
|
|
640
|
-
if (
|
|
657
|
+
if (hasMoreOutputs) {
|
|
641
658
|
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
642
659
|
"muted",
|
|
643
|
-
|
|
660
|
+
formatMoreItems(outputs.length - maxOutputs, "output", theme),
|
|
644
661
|
)}`;
|
|
645
662
|
}
|
|
646
663
|
|
|
@@ -668,27 +685,26 @@ interface LsArgs {
|
|
|
668
685
|
|
|
669
686
|
const lsRenderer: ToolRenderer<LsArgs, LsToolDetails> = {
|
|
670
687
|
renderCall(args, theme) {
|
|
671
|
-
|
|
672
|
-
text
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
688
|
+
const label = theme.fg("toolTitle", theme.bold("Ls"));
|
|
689
|
+
let text = `${label} ${theme.fg("accent", args.path || ".")}`;
|
|
690
|
+
|
|
691
|
+
const meta: string[] = [];
|
|
692
|
+
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
693
|
+
text += formatMeta(meta, theme);
|
|
694
|
+
|
|
676
695
|
return new Text(text, 0, 0);
|
|
677
696
|
},
|
|
678
697
|
|
|
679
698
|
renderResult(result, { expanded }, theme) {
|
|
699
|
+
const label = "Ls";
|
|
680
700
|
const details = result.details;
|
|
681
|
-
const textContent = result.content?.find((c
|
|
701
|
+
const textContent = result.content?.find((c) => c.type === "text")?.text ?? "";
|
|
682
702
|
|
|
683
703
|
if (
|
|
684
704
|
(!textContent || textContent.trim() === "" || textContent.trim() === "(empty directory)") &&
|
|
685
705
|
(!details?.entries || details.entries.length === 0)
|
|
686
706
|
) {
|
|
687
|
-
return
|
|
688
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "Empty directory")}`,
|
|
689
|
-
0,
|
|
690
|
-
0,
|
|
691
|
-
);
|
|
707
|
+
return renderEmptyMessage(label, "Empty directory", theme);
|
|
692
708
|
}
|
|
693
709
|
|
|
694
710
|
let entries: string[] = details?.entries ? [...details.entries] : [];
|
|
@@ -698,11 +714,7 @@ const lsRenderer: ToolRenderer<LsArgs, LsToolDetails> = {
|
|
|
698
714
|
}
|
|
699
715
|
|
|
700
716
|
if (entries.length === 0) {
|
|
701
|
-
return
|
|
702
|
-
`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "Empty directory")}`,
|
|
703
|
-
0,
|
|
704
|
-
0,
|
|
705
|
-
);
|
|
717
|
+
return renderEmptyMessage(label, "Empty directory", theme);
|
|
706
718
|
}
|
|
707
719
|
|
|
708
720
|
let dirCount = details?.dirCount;
|
|
@@ -724,41 +736,47 @@ const lsRenderer: ToolRenderer<LsArgs, LsToolDetails> = {
|
|
|
724
736
|
? theme.styledSymbol("status.warning", "warning")
|
|
725
737
|
: theme.styledSymbol("status.success", "success");
|
|
726
738
|
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
|
|
739
|
+
const summaryText = [formatCount("dir", dirCount ?? 0), formatCount("file", fileCount ?? 0)].join(theme.sep.dot);
|
|
740
|
+
const maxEntries = expanded ? entries.length : Math.min(entries.length, COLLAPSED_LIST_LIMIT);
|
|
741
|
+
const hasMoreEntries = entries.length > maxEntries;
|
|
742
|
+
const expandHint = formatExpandHint(expanded, hasMoreEntries, theme);
|
|
730
743
|
|
|
731
|
-
|
|
732
|
-
const reasonParts: string[] = [];
|
|
733
|
-
if (details?.entryLimitReached) {
|
|
734
|
-
reasonParts.push(`entry limit ${details.entryLimitReached}`);
|
|
735
|
-
}
|
|
736
|
-
if (details?.truncation?.truncated) {
|
|
737
|
-
reasonParts.push(`output cap ${formatBytes(details.truncation.maxBytes)}`);
|
|
738
|
-
}
|
|
739
|
-
const reasonText = reasonParts.length > 0 ? `truncated: ${reasonParts.join(", ")}` : "truncated";
|
|
740
|
-
text += ` ${theme.fg("warning", `(${reasonText})`)}`;
|
|
741
|
-
}
|
|
744
|
+
let text = `${icon} ${theme.fg("dim", summaryText)}${formatTruncationSuffix(truncated, theme)}${expandHint}`;
|
|
742
745
|
|
|
743
|
-
|
|
744
|
-
|
|
746
|
+
const truncationReasons: string[] = [];
|
|
747
|
+
if (details?.entryLimitReached) {
|
|
748
|
+
truncationReasons.push(`entry limit ${details.entryLimitReached}`);
|
|
745
749
|
}
|
|
750
|
+
if (details?.truncation?.truncated) {
|
|
751
|
+
truncationReasons.push(`output cap ${formatBytes(details.truncation.maxBytes)}`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const hasTruncation = truncationReasons.length > 0;
|
|
746
755
|
|
|
747
|
-
const maxEntries = expanded ? entries.length : Math.min(entries.length, 12);
|
|
748
756
|
for (let i = 0; i < maxEntries; i++) {
|
|
749
757
|
const entry = entries[i];
|
|
750
|
-
const isLast = i === maxEntries - 1 &&
|
|
758
|
+
const isLast = i === maxEntries - 1 && !hasMoreEntries && !hasTruncation;
|
|
751
759
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
752
760
|
const isDir = entry.endsWith("/");
|
|
753
|
-
const
|
|
761
|
+
const entryPath = isDir ? entry.slice(0, -1) : entry;
|
|
762
|
+
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
763
|
+
const entryIcon = isDir ? theme.fg("accent", theme.icon.folder) : theme.fg("muted", theme.getLangIcon(lang));
|
|
754
764
|
const entryColor = isDir ? "accent" : "toolOutput";
|
|
755
765
|
text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg(entryColor, entry)}`;
|
|
756
766
|
}
|
|
757
767
|
|
|
758
|
-
if (
|
|
759
|
-
|
|
768
|
+
if (hasMoreEntries) {
|
|
769
|
+
const moreEntriesBranch = hasTruncation ? theme.tree.branch : theme.tree.last;
|
|
770
|
+
text += `\n ${theme.fg("dim", moreEntriesBranch)} ${theme.fg(
|
|
760
771
|
"muted",
|
|
761
|
-
|
|
772
|
+
formatMoreItems(entries.length - maxEntries, "entry", theme),
|
|
773
|
+
)}`;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (hasTruncation) {
|
|
777
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
778
|
+
"warning",
|
|
779
|
+
`truncated: ${truncationReasons.join(", ")}`,
|
|
762
780
|
)}`;
|
|
763
781
|
}
|
|
764
782
|
|