erosolar-cli 1.7.402 → 1.7.403

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.
@@ -57,10 +57,6 @@ export declare class ShellUIAdapter {
57
57
  * Uses compact rendering for same-line displays when appropriate
58
58
  */
59
59
  createToolObserver(): ToolRuntimeObserver;
60
- /**
61
- * Check if a tool should use compact (single-line) display
62
- */
63
- private isCompactTool;
64
60
  /**
65
61
  * Normalize preflight warnings for consistent display.
66
62
  */
@@ -82,10 +78,6 @@ export declare class ShellUIAdapter {
82
78
  * Includes file paths for file operations so users can see which files were accessed
83
79
  */
84
80
  private extractResultSummary;
85
- /**
86
- * Display compact single-line result
87
- */
88
- private displayCompactResult;
89
81
  /**
90
82
  * Determine if a tool should show initial ⏺ display
91
83
  * Returns true for tools with streaming/progressive output
@@ -175,20 +167,34 @@ export declare class ShellUIAdapter {
175
167
  */
176
168
  private parseFileChange;
177
169
  /**
178
- * Display tool result summary using Claude Code style.
179
- * Format: ⎿ Summary text (ctrl+o to expand)
180
- * Routes through display module to work with scroll regions
170
+ * Display tool result summary in a compact, badge-driven line with an optional preview block.
171
+ * Routes through display module to work with scroll regions while keeping output minimal.
181
172
  */
182
173
  private displayToolResultSummary;
183
174
  /**
184
175
  * Display full tool output directly under the summary block.
185
- * Keeps the raw tool content visible instead of only showing compact badges.
176
+ * Now uses the same compact preview system to keep verbose tools readable.
186
177
  */
187
178
  private displayFullToolOutput;
188
179
  /**
189
180
  * Format tool result summary in Claude Code style
190
181
  * Returns compact summary like "Found 4 files (ctrl+o to expand)" or "Read 50 lines"
191
182
  */
183
+ private formatCompactToolLine;
184
+ /**
185
+ * Build compact summary + detail parts for a tool result line.
186
+ * Keeps the main text meaningful (path/query) and pushes counts into detail.
187
+ */
188
+ private buildToolSummaryParts;
189
+ /**
190
+ * Render a compact preview of tool output (first N lines) with truncation hints.
191
+ */
192
+ private formatToolPreview;
193
+ private buildPreviewLines;
194
+ private getPreviewLineLimit;
195
+ private truncatePreviewLine;
196
+ private countOutputLines;
197
+ private parseTestCounts;
192
198
  private formatClaudeCodeResultSummary;
193
199
  /**
194
200
  * Format agent/task result summary showing nested tool uses
@@ -1 +1 @@
1
- {"version":3,"file":"ShellUIAdapter.d.ts","sourceRoot":"","sources":["../../src/ui/ShellUIAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ShellUIAdapter.d.ts","sourceRoot":"","sources":["../../src/ui/ShellUIAdapter.ts"],"names":[],"mappings":"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"}
@@ -167,17 +167,10 @@ export class ShellUIAdapter {
167
167
  this.fileChangeCallback(fileChange.path, fileChange.type, fileChange.additions, fileChange.removals);
168
168
  }
169
169
  }
170
- // Use compact display for quick single-result tools
171
- if (this.compactDisplayMode && this.isCompactTool(call.name)) {
172
- this.displayCompactResult(call, output, true);
173
- }
174
- else {
175
- // Claude Code style: Display tool result with ⎿ prefix
176
- this.displayToolResultSummary(call, output, true);
177
- }
170
+ // Render concise tool result + preview instead of verbose blocks
171
+ this.displayToolResultSummary(call, output, true);
178
172
  // Surface any captured preflight warnings in a structured block
179
173
  this.flushToolWarnings(call.id, call.name);
180
- this.displayFullToolOutput(call, output, true);
181
174
  // Clear status bar after showing result
182
175
  if (this._toolStatusCallback) {
183
176
  const completed = this.getToolActionDescription(call);
@@ -199,16 +192,9 @@ export class ShellUIAdapter {
199
192
  this.pendingOperations.delete(call.id);
200
193
  this.completedOperations.push(op);
201
194
  }
202
- // Use compact display for errors too
203
- if (this.compactDisplayMode && this.isCompactTool(call.name)) {
204
- this.displayCompactResult(call, message, false);
205
- }
206
- else {
207
- // Claude Code style: Display error with ⎿ prefix (error color)
208
- this.displayToolResultSummary(call, message, false);
209
- }
195
+ // Render concise error summary + preview instead of verbose blocks
196
+ this.displayToolResultSummary(call, message, false);
210
197
  this.flushToolWarnings(call.id, call.name);
211
- this.displayFullToolOutput(call, message, false);
212
198
  // Clear status bar after showing error
213
199
  if (this._toolStatusCallback) {
214
200
  const failed = this.getToolActionDescription(call);
@@ -251,18 +237,6 @@ export class ShellUIAdapter {
251
237
  },
252
238
  };
253
239
  }
254
- /**
255
- * Check if a tool should use compact (single-line) display
256
- */
257
- isCompactTool(toolName) {
258
- const compactTools = new Set([
259
- 'Read', 'read_file',
260
- 'Glob', 'glob',
261
- 'TodoWrite', 'todo_write',
262
- 'WebFetch', 'web_fetch',
263
- ]);
264
- return compactTools.has(toolName);
265
- }
266
240
  /**
267
241
  * Normalize preflight warnings for consistent display.
268
242
  */
@@ -395,21 +369,6 @@ export class ShellUIAdapter {
395
369
  return 'done';
396
370
  }
397
371
  }
398
- /**
399
- * Display compact single-line result
400
- */
401
- displayCompactResult(call, output, success) {
402
- const op = {
403
- id: call.id,
404
- name: call.name,
405
- status: success ? 'success' : 'error',
406
- summary: this.extractResultSummary(call, output),
407
- startedAt: Date.now(),
408
- };
409
- const badge = compactRenderer.formatToolBadge(op);
410
- // Add newline after result for visual separation
411
- this.display.stream(`${badge}\n\n`);
412
- }
413
372
  /**
414
373
  * Determine if a tool should show initial ⏺ display
415
374
  * Returns true for tools with streaming/progressive output
@@ -901,47 +860,282 @@ export class ShellUIAdapter {
901
860
  };
902
861
  }
903
862
  /**
904
- * Display tool result summary using Claude Code style.
905
- * Format: ⎿ Summary text (ctrl+o to expand)
906
- * Routes through display module to work with scroll regions
863
+ * Display tool result summary in a compact, badge-driven line with an optional preview block.
864
+ * Routes through display module to work with scroll regions while keeping output minimal.
907
865
  */
908
866
  displayToolResultSummary(call, output, success) {
909
- const args = call.arguments || {};
910
- // Get compact summary for the result
911
- const summary = this.formatClaudeCodeResultSummary(call.name, args, output, success);
912
- if (summary) {
913
- // Claude Code style: ⎿ Summary (indented under tool call)
914
- const prefix = success
915
- ? theme.success(icons.subaction)
916
- : theme.error(icons.subaction);
917
- // Add newline after result for visual separation
918
- this.display.stream(`\n ${prefix} ${summary}\n\n`);
867
+ const summaryLine = this.formatCompactToolLine(call, output, success);
868
+ const preview = this.formatToolPreview(call, output, success);
869
+ if (!summaryLine && !preview) {
870
+ return;
919
871
  }
872
+ const blocks = [];
873
+ if (summaryLine)
874
+ blocks.push(summaryLine);
875
+ if (preview)
876
+ blocks.push(preview);
877
+ // Add newline for separation while keeping output compact
878
+ this.display.stream(`\n${blocks.join('\n')}\n\n`);
920
879
  }
921
880
  /**
922
881
  * Display full tool output directly under the summary block.
923
- * Keeps the raw tool content visible instead of only showing compact badges.
882
+ * Now uses the same compact preview system to keep verbose tools readable.
924
883
  */
925
884
  displayFullToolOutput(call, output, success) {
926
885
  const normalized = typeof output === 'string' ? output.trimEnd() : '';
927
886
  if (!normalized) {
928
887
  return;
929
888
  }
930
- const icon = success ? theme.success(icons.subaction) : theme.error(icons.subaction);
931
- const summary = this.getToolSummary(call);
932
- const label = summary ? `${theme.tool(call.name)} ${theme.ui.muted(summary)}` : theme.tool(call.name);
933
- const header = `${icon} ${label}`;
934
- const body = normalized
935
- .split('\n')
936
- .map(line => ` ${line}`)
937
- .join('\n');
938
- // Use display.stream() so scroll regions and streaming writes stay coordinated
939
- this.display.stream(`\n${header}\n${body}\n\n`);
889
+ // Use the same preview builder to keep verbose output compact
890
+ const preview = this.formatToolPreview(call, normalized, success, true);
891
+ const summary = this.formatCompactToolLine(call, normalized, success);
892
+ const parts = [];
893
+ if (summary)
894
+ parts.push(summary);
895
+ if (preview)
896
+ parts.push(preview);
897
+ if (parts.length) {
898
+ this.display.stream(`\n${parts.join('\n')}\n\n`);
899
+ }
940
900
  }
941
901
  /**
942
902
  * Format tool result summary in Claude Code style
943
903
  * Returns compact summary like "Found 4 files (ctrl+o to expand)" or "Read 50 lines"
944
904
  */
905
+ formatCompactToolLine(call, output, success) {
906
+ const { main, detail } = this.buildToolSummaryParts(call, output, success);
907
+ const summary = main || this.getToolSummary(call) || call.name;
908
+ return compactRenderer.formatInlineResult(call.name, success ? 'success' : 'error', summary, detail);
909
+ }
910
+ /**
911
+ * Build compact summary + detail parts for a tool result line.
912
+ * Keeps the main text meaningful (path/query) and pushes counts into detail.
913
+ */
914
+ buildToolSummaryParts(call, output, success) {
915
+ const args = call.arguments || {};
916
+ const lineCount = this.countOutputLines(output);
917
+ switch (call.name) {
918
+ case 'Read':
919
+ case 'read_file': {
920
+ const path = this.extractPath(args, ['file_path', 'path']);
921
+ return {
922
+ main: path ? theme.info(this.truncatePath(path, 60)) : 'Read',
923
+ detail: `${lineCount} line${lineCount === 1 ? '' : 's'}`,
924
+ };
925
+ }
926
+ case 'Write':
927
+ case 'write_file': {
928
+ const path = this.extractPath(args, ['file_path', 'path']);
929
+ const content = args['content'] || output;
930
+ const lines = this.countOutputLines(content);
931
+ return {
932
+ main: path ? theme.info(this.truncatePath(path, 60)) : 'Write',
933
+ detail: `${lines} line${lines === 1 ? '' : 's'}`,
934
+ };
935
+ }
936
+ case 'Edit':
937
+ case 'edit_file':
938
+ case 'NotebookEdit': {
939
+ const change = this.parseFileChange(call, output);
940
+ const path = change?.path || this.extractPath(args, ['file_path', 'path', 'notebook_path']);
941
+ const detailParts = [];
942
+ if (change) {
943
+ detailParts.push(`+${change.additions}/-${change.removals}`);
944
+ }
945
+ else if (lineCount > 0) {
946
+ detailParts.push(`${lineCount} line${lineCount === 1 ? '' : 's'}`);
947
+ }
948
+ const action = call.name === 'NotebookEdit' ? 'Notebook' : 'Edit';
949
+ return {
950
+ main: path ? `${action} ${theme.info(this.truncatePath(path, 60))}` : action,
951
+ detail: detailParts.join(' · ') || undefined,
952
+ };
953
+ }
954
+ case 'Grep':
955
+ case 'grep': {
956
+ const pattern = args['pattern'] || '';
957
+ const path = this.extractPath(args, ['path', 'directory']);
958
+ const matches = output.trim() ? output.trim().split('\n').filter(l => l.trim()).length : 0;
959
+ const detailParts = [`${matches} match${matches === 1 ? '' : 'es'}`];
960
+ if (path && path !== '.') {
961
+ detailParts.push(`in ${this.truncatePath(path, 40)}`);
962
+ }
963
+ return {
964
+ main: pattern ? `"${pattern.length > 40 ? `${pattern.slice(0, 37)}...` : pattern}"` : 'Grep',
965
+ detail: detailParts.join(' · '),
966
+ };
967
+ }
968
+ case 'Glob':
969
+ case 'glob': {
970
+ const pattern = args['pattern'] || '*';
971
+ const files = output.trim() ? output.trim().split('\n').filter(f => f.trim()).length : 0;
972
+ const path = this.extractPath(args, ['path', 'directory']);
973
+ const detailParts = [`${files} file${files === 1 ? '' : 's'}`];
974
+ if (path && path !== '.') {
975
+ detailParts.push(`in ${this.truncatePath(path, 40)}`);
976
+ }
977
+ return {
978
+ main: pattern ? theme.info(pattern) : 'Glob',
979
+ detail: detailParts.join(' · '),
980
+ };
981
+ }
982
+ case 'Bash':
983
+ case 'bash': {
984
+ const command = args['command'] || '';
985
+ const shortCmd = command.split('\n')[0]?.trim() || command;
986
+ const main = shortCmd
987
+ ? `${theme.ui.muted('$')} ${shortCmd.length > 70 ? `${shortCmd.slice(0, 67)}...` : shortCmd}`
988
+ : 'Shell';
989
+ const detail = lineCount === 0 ? 'no output' : `${lineCount} line${lineCount === 1 ? '' : 's'}`;
990
+ return { main, detail };
991
+ }
992
+ case 'WebFetch':
993
+ case 'web_fetch': {
994
+ const url = args['url'] || '';
995
+ let host = url;
996
+ try {
997
+ host = new URL(url).hostname;
998
+ }
999
+ catch {
1000
+ // Keep original
1001
+ }
1002
+ const sizeStr = output.length > 1024 ? `${(output.length / 1024).toFixed(1)}KB` : `${output.length}B`;
1003
+ return { main: host ? `Fetch ${theme.info(host)}` : 'Fetch', detail: sizeStr };
1004
+ }
1005
+ case 'WebSearch':
1006
+ case 'web_search': {
1007
+ const query = args['query'] || '';
1008
+ const queryDisplay = query.length > 50 ? `${query.slice(0, 47)}...` : query;
1009
+ const resultMatches = output.match(/^\d+\./gm);
1010
+ const count = resultMatches ? resultMatches.length : this.countOutputLines(output);
1011
+ return { main: queryDisplay ? `Search "${queryDisplay}"` : 'Search', detail: `${count} result${count === 1 ? '' : 's'}` };
1012
+ }
1013
+ case 'Task':
1014
+ case 'task': {
1015
+ const desc = args['description'] || '';
1016
+ const trimmed = desc.length > 60 ? `${desc.slice(0, 57)}...` : desc;
1017
+ return { main: trimmed || 'Task', detail: success ? 'done' : 'incomplete' };
1018
+ }
1019
+ case 'TodoWrite':
1020
+ case 'todo_write': {
1021
+ const todos = args['todos'];
1022
+ const total = todos?.length || 0;
1023
+ return { main: 'Todos', detail: total ? `${total} item${total === 1 ? '' : 's'}` : undefined };
1024
+ }
1025
+ case 'run_tests': {
1026
+ const pattern = (args['pattern'] || args['filter']);
1027
+ const counts = this.parseTestCounts(output);
1028
+ const detailParts = [];
1029
+ if (counts.passed > 0)
1030
+ detailParts.push(`${counts.passed} pass`);
1031
+ if (counts.failed > 0)
1032
+ detailParts.push(`${counts.failed} fail`);
1033
+ if (counts.skipped > 0)
1034
+ detailParts.push(`${counts.skipped} skip`);
1035
+ if (detailParts.length === 0 && lineCount > 0) {
1036
+ detailParts.push(`${lineCount} line${lineCount === 1 ? '' : 's'}`);
1037
+ }
1038
+ return {
1039
+ main: pattern ? `Tests "${pattern}"` : 'Tests',
1040
+ detail: detailParts.join(' · ') || (success ? 'all passed' : 'failed'),
1041
+ };
1042
+ }
1043
+ case 'run_build': {
1044
+ const target = (args['target'] || args['script']);
1045
+ const errors = (output.match(/error/gi) || []).length;
1046
+ const detail = errors > 0
1047
+ ? `${errors} error${errors === 1 ? '' : 's'}`
1048
+ : `${lineCount} line${lineCount === 1 ? '' : 's'}`;
1049
+ return { main: target ? `Build ${target}` : 'Build', detail };
1050
+ }
1051
+ default: {
1052
+ const summary = this.extractResultSummary(call, output);
1053
+ if (summary) {
1054
+ return { main: summary };
1055
+ }
1056
+ const claudeSummary = this.formatClaudeCodeResultSummary(call.name, args, output, success);
1057
+ if (claudeSummary) {
1058
+ return { main: claudeSummary };
1059
+ }
1060
+ return { main: call.name, detail: lineCount ? `${lineCount} line${lineCount === 1 ? '' : 's'}` : undefined };
1061
+ }
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Render a compact preview of tool output (first N lines) with truncation hints.
1066
+ */
1067
+ formatToolPreview(call, output, success, force = false) {
1068
+ const previewLines = this.buildPreviewLines(call, output, success, force);
1069
+ if (!previewLines.length) {
1070
+ return null;
1071
+ }
1072
+ const gutter = theme.ui.muted('│');
1073
+ return previewLines.map(line => ` ${gutter} ${line}`).join('\n');
1074
+ }
1075
+ buildPreviewLines(call, output, success, force) {
1076
+ const normalized = (output || '').trim();
1077
+ const allLines = normalized ? normalized.split('\n').filter(l => l.trim()) : [];
1078
+ const limit = this.getPreviewLineLimit(call.name, success);
1079
+ if (limit <= 0 && !force) {
1080
+ return [];
1081
+ }
1082
+ const maxLines = limit > 0 ? limit : force ? Math.min(1, allLines.length) : 0;
1083
+ if (maxLines === 0 || allLines.length === 0) {
1084
+ return [];
1085
+ }
1086
+ const preview = allLines.slice(0, maxLines).map(line => this.truncatePreviewLine(line));
1087
+ const remaining = allLines.length - maxLines;
1088
+ if (remaining > 0) {
1089
+ preview.push(theme.ui.muted(`… +${remaining} more`));
1090
+ }
1091
+ return preview;
1092
+ }
1093
+ getPreviewLineLimit(toolName, success) {
1094
+ switch (toolName) {
1095
+ case 'Read':
1096
+ case 'read_file':
1097
+ return success ? 0 : 1;
1098
+ case 'Glob':
1099
+ case 'glob':
1100
+ return 3;
1101
+ case 'Grep':
1102
+ case 'grep':
1103
+ return 4;
1104
+ case 'Bash':
1105
+ case 'bash':
1106
+ return success ? 3 : 4;
1107
+ case 'run_tests':
1108
+ return success ? 2 : 4;
1109
+ case 'Edit':
1110
+ case 'edit_file':
1111
+ case 'NotebookEdit':
1112
+ return success ? 2 : 4;
1113
+ default:
1114
+ return success ? 2 : 3;
1115
+ }
1116
+ }
1117
+ truncatePreviewLine(line, maxLen = 100) {
1118
+ const trimmed = line.trim();
1119
+ if (trimmed.length <= maxLen) {
1120
+ return theme.dim(trimmed);
1121
+ }
1122
+ return theme.dim(`${trimmed.slice(0, maxLen - 3)}...`);
1123
+ }
1124
+ countOutputLines(output) {
1125
+ if (!output || !output.trim())
1126
+ return 0;
1127
+ return output.trimEnd().split('\n').length;
1128
+ }
1129
+ parseTestCounts(output) {
1130
+ const passMatch = output.match(/(\d+)\s*(?:passing|passed|✓)/i);
1131
+ const failMatch = output.match(/(\d+)\s*(?:failing|failed|✗)/i);
1132
+ const skipMatch = output.match(/(\d+)\s*(?:skipped|pending)/i);
1133
+ return {
1134
+ passed: passMatch ? parseInt(passMatch[1] || '0', 10) : 0,
1135
+ failed: failMatch ? parseInt(failMatch[1] || '0', 10) : 0,
1136
+ skipped: skipMatch ? parseInt(skipMatch[1] || '0', 10) : 0,
1137
+ };
1138
+ }
945
1139
  formatClaudeCodeResultSummary(toolName, args, output, success) {
946
1140
  const expandHint = theme.ui.muted('(ctrl+o to expand)');
947
1141
  if (!success) {