erosolar-cli 1.7.14 → 1.7.16
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/dist/core/responseVerifier.d.ts +79 -0
- package/dist/core/responseVerifier.d.ts.map +1 -0
- package/dist/core/responseVerifier.js +443 -0
- package/dist/core/responseVerifier.js.map +1 -0
- package/dist/shell/interactiveShell.d.ts +10 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +80 -0
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +3 -0
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +4 -10
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +4 -0
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +10 -11
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
- package/dist/bin/core/agent.js +0 -362
- package/dist/bin/core/agentProfileManifest.js +0 -187
- package/dist/bin/core/agentProfiles.js +0 -34
- package/dist/bin/core/agentRulebook.js +0 -135
- package/dist/bin/core/agentSchemaLoader.js +0 -233
- package/dist/bin/core/contextManager.js +0 -412
- package/dist/bin/core/contextWindow.js +0 -122
- package/dist/bin/core/customCommands.js +0 -80
- package/dist/bin/core/errors/apiKeyErrors.js +0 -114
- package/dist/bin/core/errors/errorTypes.js +0 -340
- package/dist/bin/core/errors/safetyValidator.js +0 -304
- package/dist/bin/core/errors.js +0 -32
- package/dist/bin/core/modelDiscovery.js +0 -755
- package/dist/bin/core/preferences.js +0 -224
- package/dist/bin/core/schemaValidator.js +0 -92
- package/dist/bin/core/secretStore.js +0 -199
- package/dist/bin/core/sessionStore.js +0 -187
- package/dist/bin/core/toolRuntime.js +0 -290
- package/dist/bin/core/types.js +0 -1
- package/dist/bin/shell/bracketedPasteManager.js +0 -350
- package/dist/bin/shell/fileChangeTracker.js +0 -65
- package/dist/bin/shell/interactiveShell.js +0 -2908
- package/dist/bin/shell/liveStatus.js +0 -78
- package/dist/bin/shell/shellApp.js +0 -290
- package/dist/bin/shell/systemPrompt.js +0 -60
- package/dist/bin/shell/updateManager.js +0 -108
- package/dist/bin/ui/ShellUIAdapter.js +0 -459
- package/dist/bin/ui/UnifiedUIController.js +0 -183
- package/dist/bin/ui/animation/AnimationScheduler.js +0 -430
- package/dist/bin/ui/codeHighlighter.js +0 -854
- package/dist/bin/ui/designSystem.js +0 -121
- package/dist/bin/ui/display.js +0 -1222
- package/dist/bin/ui/interrupts/InterruptManager.js +0 -437
- package/dist/bin/ui/layout.js +0 -139
- package/dist/bin/ui/orchestration/StatusOrchestrator.js +0 -403
- package/dist/bin/ui/outputMode.js +0 -38
- package/dist/bin/ui/persistentPrompt.js +0 -183
- package/dist/bin/ui/richText.js +0 -338
- package/dist/bin/ui/shortcutsHelp.js +0 -87
- package/dist/bin/ui/telemetry/UITelemetry.js +0 -443
- package/dist/bin/ui/textHighlighter.js +0 -210
- package/dist/bin/ui/theme.js +0 -116
- package/dist/bin/ui/toolDisplay.js +0 -423
- package/dist/bin/ui/toolDisplayAdapter.js +0 -357
package/dist/bin/ui/theme.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import gradientString from 'gradient-string';
|
|
3
|
-
/**
|
|
4
|
-
* Theme system matching the Erosolar CLI aesthetics
|
|
5
|
-
*/
|
|
6
|
-
export const theme = {
|
|
7
|
-
primary: chalk.hex('#6366F1'), // Indigo
|
|
8
|
-
secondary: chalk.hex('#8B5CF6'), // Purple
|
|
9
|
-
accent: chalk.hex('#EC4899'), // Pink
|
|
10
|
-
success: chalk.hex('#10B981'), // Green
|
|
11
|
-
warning: chalk.hex('#F59E0B'), // Amber
|
|
12
|
-
error: chalk.hex('#EF4444'), // Red
|
|
13
|
-
info: chalk.hex('#3B82F6'), // Blue
|
|
14
|
-
dim: chalk.dim,
|
|
15
|
-
bold: chalk.bold,
|
|
16
|
-
italic: chalk.italic,
|
|
17
|
-
underline: chalk.underline,
|
|
18
|
-
gradient: {
|
|
19
|
-
primary: gradientString(['#6366F1', '#8B5CF6', '#EC4899']),
|
|
20
|
-
cool: gradientString(['#3B82F6', '#6366F1', '#8B5CF6']),
|
|
21
|
-
warm: gradientString(['#F59E0B', '#EC4899', '#EF4444']),
|
|
22
|
-
success: gradientString(['#10B981', '#34D399']),
|
|
23
|
-
},
|
|
24
|
-
ui: {
|
|
25
|
-
border: chalk.hex('#4B5563'),
|
|
26
|
-
background: chalk.bgHex('#1F2937'),
|
|
27
|
-
userPromptBackground: chalk.bgHex('#4C1D95'),
|
|
28
|
-
muted: chalk.hex('#9CA3AF'),
|
|
29
|
-
text: chalk.hex('#F3F4F6'),
|
|
30
|
-
highlight: chalk.hex('#FCD34D').bold, // Important text
|
|
31
|
-
emphasis: chalk.hex('#F472B6').bold, // Emphasized text
|
|
32
|
-
code: chalk.hex('#A78BFA'), // Inline code
|
|
33
|
-
number: chalk.hex('#60A5FA'), // Numbers
|
|
34
|
-
string: chalk.hex('#34D399'), // Strings
|
|
35
|
-
keyword: chalk.hex('#F472B6'), // Keywords
|
|
36
|
-
operator: chalk.hex('#9CA3AF'), // Operators
|
|
37
|
-
},
|
|
38
|
-
metrics: {
|
|
39
|
-
elapsedLabel: chalk.hex('#FBBF24').bold,
|
|
40
|
-
elapsedValue: chalk.hex('#F472B6'),
|
|
41
|
-
},
|
|
42
|
-
fields: {
|
|
43
|
-
label: chalk.hex('#FCD34D').bold,
|
|
44
|
-
agent: chalk.hex('#F472B6'),
|
|
45
|
-
profile: chalk.hex('#C084FC'),
|
|
46
|
-
model: chalk.hex('#A855F7'),
|
|
47
|
-
workspace: chalk.hex('#38BDF8'),
|
|
48
|
-
},
|
|
49
|
-
link: {
|
|
50
|
-
label: chalk.hex('#F472B6').underline,
|
|
51
|
-
url: chalk.hex('#38BDF8'),
|
|
52
|
-
},
|
|
53
|
-
diff: {
|
|
54
|
-
header: chalk.hex('#FBBF24'),
|
|
55
|
-
hunk: chalk.hex('#60A5FA'),
|
|
56
|
-
added: chalk.hex('#10B981'),
|
|
57
|
-
removed: chalk.hex('#EF4444'),
|
|
58
|
-
meta: chalk.hex('#9CA3AF'),
|
|
59
|
-
},
|
|
60
|
-
user: chalk.hex('#3B82F6'),
|
|
61
|
-
assistant: chalk.hex('#8B5CF6'),
|
|
62
|
-
system: chalk.hex('#6B7280'),
|
|
63
|
-
tool: chalk.hex('#10B981'),
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Claude Code style icons
|
|
67
|
-
* Following the official Claude Code UI conventions:
|
|
68
|
-
* - ⏺ (action): Used for tool calls, actions, and thinking/reasoning
|
|
69
|
-
* - ⎿ (subaction): Used for results, details, and nested information
|
|
70
|
-
* - ─ (separator): Horizontal lines for dividing sections (not in this object)
|
|
71
|
-
* - > (user prompt): User input prefix (used in formatUserPrompt)
|
|
72
|
-
*/
|
|
73
|
-
export const icons = {
|
|
74
|
-
success: '✓',
|
|
75
|
-
error: '✗',
|
|
76
|
-
warning: '⚠',
|
|
77
|
-
info: 'ℹ',
|
|
78
|
-
arrow: '→',
|
|
79
|
-
bullet: '•',
|
|
80
|
-
thinking: '◐',
|
|
81
|
-
tool: '⚙',
|
|
82
|
-
user: '❯',
|
|
83
|
-
assistant: '◆',
|
|
84
|
-
loading: '⣾',
|
|
85
|
-
action: '⏺', // Claude Code: tool actions and thoughts
|
|
86
|
-
subaction: '⎿', // Claude Code: results and details
|
|
87
|
-
sparkle: '✨', // Erosolar branding
|
|
88
|
-
};
|
|
89
|
-
export function formatBanner(profileLabel, model) {
|
|
90
|
-
const name = profileLabel || 'Agent';
|
|
91
|
-
const title = theme.gradient.primary(name);
|
|
92
|
-
const subtitle = theme.ui.muted(`${model} • Interactive Shell`);
|
|
93
|
-
return `\n${title}\n${subtitle}\n`;
|
|
94
|
-
}
|
|
95
|
-
export function formatUserPrompt(_profile) {
|
|
96
|
-
// Simple, stable prompt - no complex animations
|
|
97
|
-
const glyph = theme.user('>');
|
|
98
|
-
return `${glyph} `;
|
|
99
|
-
}
|
|
100
|
-
export function formatToolCall(name, status) {
|
|
101
|
-
const statusIcon = status === 'running' ? icons.thinking :
|
|
102
|
-
status === 'success' ? icons.success : icons.error;
|
|
103
|
-
const statusColor = status === 'running' ? theme.info :
|
|
104
|
-
status === 'success' ? theme.success : theme.error;
|
|
105
|
-
return `${statusColor(statusIcon)} ${theme.tool(name)}`;
|
|
106
|
-
}
|
|
107
|
-
export function formatMessage(role, content) {
|
|
108
|
-
switch (role) {
|
|
109
|
-
case 'user':
|
|
110
|
-
return `${theme.user('You:')} ${content}`;
|
|
111
|
-
case 'assistant':
|
|
112
|
-
return `${theme.assistant('Assistant:')} ${content}`;
|
|
113
|
-
case 'system':
|
|
114
|
-
return theme.system(`[System] ${content}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool Display Formatter - Claude Code style
|
|
3
|
-
*
|
|
4
|
-
* Implements the clean, informative tool execution display that Claude Code uses:
|
|
5
|
-
* - Tool call indicators with inline args
|
|
6
|
-
* - Result summaries with status indicators
|
|
7
|
-
* - Expandable content with previews
|
|
8
|
-
* - Diff formatting with colors
|
|
9
|
-
*/
|
|
10
|
-
import { theme } from './theme.js';
|
|
11
|
-
/**
|
|
12
|
-
* Format tool call display (Claude Code style)
|
|
13
|
-
*
|
|
14
|
-
* Example output:
|
|
15
|
-
* ⏺ Read(src/core/agent.ts)
|
|
16
|
-
* ⏺ Search(pattern: "TODO|FIXME", output_mode: "content", head_limit: 15)
|
|
17
|
-
*/
|
|
18
|
-
export function formatToolCall(call, options = {}) {
|
|
19
|
-
const includePrefix = options.includePrefix ?? true;
|
|
20
|
-
const symbol = includePrefix ? `${theme.info('⏺')} ` : '';
|
|
21
|
-
const toolName = theme.tool(call.name);
|
|
22
|
-
// Format args inline (only show relevant ones)
|
|
23
|
-
const argsDisplay = formatInlineArgs(call.args);
|
|
24
|
-
return `${symbol}${toolName}${argsDisplay}`;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Format tool result display (Claude Code style)
|
|
28
|
-
*
|
|
29
|
-
* Example output:
|
|
30
|
-
* ⎿ Read 340 lines
|
|
31
|
-
* ⎿ Found 15 lines (ctrl+o to expand)
|
|
32
|
-
* ⎿ Completed
|
|
33
|
-
*/
|
|
34
|
-
export function formatToolResult(result, options = {}) {
|
|
35
|
-
const includePrefix = options.includePrefix ?? true;
|
|
36
|
-
const prefix = includePrefix
|
|
37
|
-
? result.status === 'error'
|
|
38
|
-
? `${theme.error('⎿')} `
|
|
39
|
-
: `${theme.success('⎿')} `
|
|
40
|
-
: '';
|
|
41
|
-
let output = `${prefix}${result.summary}`;
|
|
42
|
-
// Add expandable indicator if there's hidden content
|
|
43
|
-
if (result.totalLines && result.linesShown && result.totalLines > result.linesShown) {
|
|
44
|
-
const expandHint = theme.ui.muted('(ctrl+o to expand)');
|
|
45
|
-
output += ` ${expandHint}`;
|
|
46
|
-
}
|
|
47
|
-
return output;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Format inline args for tool call display
|
|
51
|
-
* Keeps it concise, shows only non-default values
|
|
52
|
-
* Prioritizes important args like paths, patterns, commands
|
|
53
|
-
*/
|
|
54
|
-
function formatInlineArgs(args) {
|
|
55
|
-
// Priority order for different argument types
|
|
56
|
-
const priorityArgs = [
|
|
57
|
-
'file_path', 'path', 'pattern', 'command', 'query', 'url',
|
|
58
|
-
'output_mode', 'glob', 'type', 'head_limit', 'offset',
|
|
59
|
-
];
|
|
60
|
-
// Special handling for specific args
|
|
61
|
-
const skipDefaults = new Set([
|
|
62
|
-
'dangerouslyDisableSandbox',
|
|
63
|
-
'run_in_background',
|
|
64
|
-
'description',
|
|
65
|
-
]);
|
|
66
|
-
const formattedArgs = [];
|
|
67
|
-
for (const [key, value] of Object.entries(args)) {
|
|
68
|
-
// Skip empty/null/undefined
|
|
69
|
-
if (value === null || value === undefined || value === '')
|
|
70
|
-
continue;
|
|
71
|
-
// Skip common defaults
|
|
72
|
-
if (skipDefaults.has(key))
|
|
73
|
-
continue;
|
|
74
|
-
if (key === 'path' && value === '.')
|
|
75
|
-
continue;
|
|
76
|
-
if (key === 'format' && value === 'plain')
|
|
77
|
-
continue;
|
|
78
|
-
if (key === 'output_mode' && value === 'files_with_matches')
|
|
79
|
-
continue;
|
|
80
|
-
// Determine priority
|
|
81
|
-
const priority = priorityArgs.indexOf(key);
|
|
82
|
-
const actualPriority = priority === -1 ? 999 : priority;
|
|
83
|
-
// Format value
|
|
84
|
-
let formatted;
|
|
85
|
-
if (typeof value === 'string') {
|
|
86
|
-
// Smart truncation for paths
|
|
87
|
-
if (key === 'file_path' || key === 'path') {
|
|
88
|
-
formatted = truncatePathForDisplay(value, 60);
|
|
89
|
-
}
|
|
90
|
-
else if (key === 'pattern' || key === 'query') {
|
|
91
|
-
// Show patterns/queries more prominently
|
|
92
|
-
if (value.length > 40) {
|
|
93
|
-
formatted = `"${value.slice(0, 37)}..."`;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
formatted = `"${value}"`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else if (key === 'command') {
|
|
100
|
-
// Truncate commands intelligently
|
|
101
|
-
if (value.length > 60) {
|
|
102
|
-
formatted = `"${value.slice(0, 57)}..."`;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
formatted = `"${value}"`;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
// Regular string truncation
|
|
110
|
-
if (value.length > 50) {
|
|
111
|
-
formatted = `"${value.slice(0, 47)}..."`;
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
formatted = `"${value}"`;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
else if (typeof value === 'boolean') {
|
|
119
|
-
// Only show boolean if true (false is usually default)
|
|
120
|
-
if (!value)
|
|
121
|
-
continue;
|
|
122
|
-
formatted = 'true';
|
|
123
|
-
}
|
|
124
|
-
else if (typeof value === 'number') {
|
|
125
|
-
formatted = String(value);
|
|
126
|
-
}
|
|
127
|
-
else if (Array.isArray(value)) {
|
|
128
|
-
// Show array length
|
|
129
|
-
formatted = `[${value.length} items]`;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
formatted = JSON.stringify(value);
|
|
133
|
-
}
|
|
134
|
-
formattedArgs.push({ key, value: formatted, priority: actualPriority });
|
|
135
|
-
}
|
|
136
|
-
// Sort by priority and limit to most important args
|
|
137
|
-
formattedArgs.sort((a, b) => a.priority - b.priority);
|
|
138
|
-
const displayArgs = formattedArgs.slice(0, 5); // Show max 5 args
|
|
139
|
-
if (displayArgs.length === 0) {
|
|
140
|
-
return '';
|
|
141
|
-
}
|
|
142
|
-
// Format as inline args
|
|
143
|
-
const argStrings = displayArgs.map(arg => {
|
|
144
|
-
// For primary args (path, pattern, command), show value directly
|
|
145
|
-
if (['file_path', 'path', 'pattern', 'command', 'query', 'url'].includes(arg.key)) {
|
|
146
|
-
return arg.value;
|
|
147
|
-
}
|
|
148
|
-
// For others, show key: value
|
|
149
|
-
return `${arg.key}: ${arg.value}`;
|
|
150
|
-
});
|
|
151
|
-
return `(${argStrings.join(', ')})`;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Truncate path intelligently for display
|
|
155
|
-
* Keeps filename and important parent dirs
|
|
156
|
-
*/
|
|
157
|
-
function truncatePathForDisplay(path, maxLength) {
|
|
158
|
-
if (path.length <= maxLength)
|
|
159
|
-
return path;
|
|
160
|
-
const parts = path.split('/').filter(p => p);
|
|
161
|
-
if (parts.length <= 2) {
|
|
162
|
-
// Short path, just truncate
|
|
163
|
-
return path.slice(0, maxLength - 3) + '...';
|
|
164
|
-
}
|
|
165
|
-
// Keep filename and truncate middle
|
|
166
|
-
const filename = parts[parts.length - 1];
|
|
167
|
-
if (!filename) {
|
|
168
|
-
return path.slice(0, maxLength - 3) + '...';
|
|
169
|
-
}
|
|
170
|
-
const firstPart = parts.slice(0, 2).join('/');
|
|
171
|
-
if (firstPart.length + filename.length + 4 <= maxLength) {
|
|
172
|
-
return `${firstPart}/.../${filename}`;
|
|
173
|
-
}
|
|
174
|
-
// Just show filename if still too long
|
|
175
|
-
if (filename.length <= maxLength - 3) {
|
|
176
|
-
return `.../${filename}`;
|
|
177
|
-
}
|
|
178
|
-
return filename.slice(0, maxLength - 3) + '...';
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Format expandable content preview
|
|
182
|
-
*
|
|
183
|
-
* Example:
|
|
184
|
-
* import { foo } from 'bar';
|
|
185
|
-
* … +312 lines (ctrl+o to expand)
|
|
186
|
-
*/
|
|
187
|
-
export function formatExpandablePreview(content, maxLines = 5) {
|
|
188
|
-
const lines = content.split('\n');
|
|
189
|
-
const totalLines = lines.length;
|
|
190
|
-
if (totalLines <= maxLines) {
|
|
191
|
-
return {
|
|
192
|
-
preview: content,
|
|
193
|
-
isExpanded: true,
|
|
194
|
-
totalLines,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
// Show first N lines
|
|
198
|
-
const previewLines = lines.slice(0, maxLines);
|
|
199
|
-
const remainingLines = totalLines - maxLines;
|
|
200
|
-
const ellipsis = theme.ui.muted(`… +${remainingLines} lines`);
|
|
201
|
-
const hint = theme.ui.muted('(ctrl+o to expand)');
|
|
202
|
-
previewLines.push(`${ellipsis} ${hint}`);
|
|
203
|
-
return {
|
|
204
|
-
preview: previewLines.join('\n'),
|
|
205
|
-
isExpanded: false,
|
|
206
|
-
totalLines,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Format diff output (Claude Code style)
|
|
211
|
-
*
|
|
212
|
-
* Example:
|
|
213
|
-
* Update(src/core/agent.ts)
|
|
214
|
-
* ⎿ Updated src/core/agent.ts with 2 additions and 1 removal
|
|
215
|
-
* 75 private async processConversation(): Promise<string> {
|
|
216
|
-
* 76 while (true) {
|
|
217
|
-
* 77 - this.pruneMessagesIfNeeded();
|
|
218
|
-
* 77 + await this.pruneMessagesIfNeeded();
|
|
219
|
-
* 78
|
|
220
|
-
*/
|
|
221
|
-
export function formatDiff(diff) {
|
|
222
|
-
const lines = [];
|
|
223
|
-
for (const line of diff) {
|
|
224
|
-
let formatted;
|
|
225
|
-
switch (line.type) {
|
|
226
|
-
case 'add':
|
|
227
|
-
// Green + with line number
|
|
228
|
-
const addNum = line.lineNumber ? theme.ui.muted(String(line.lineNumber).padStart(6)) : ' ';
|
|
229
|
-
const addSymbol = theme.diff.added('+');
|
|
230
|
-
const addContent = theme.diff.added(line.content);
|
|
231
|
-
formatted = `${addNum} ${addSymbol} ${addContent}`;
|
|
232
|
-
break;
|
|
233
|
-
case 'remove':
|
|
234
|
-
// Red - with line number
|
|
235
|
-
const remNum = line.lineNumber ? theme.ui.muted(String(line.lineNumber).padStart(6)) : ' ';
|
|
236
|
-
const remSymbol = theme.diff.removed('-');
|
|
237
|
-
const remContent = theme.diff.removed(line.content);
|
|
238
|
-
formatted = `${remNum} ${remSymbol} ${remContent}`;
|
|
239
|
-
break;
|
|
240
|
-
case 'context':
|
|
241
|
-
// Gray line number, regular content
|
|
242
|
-
const ctxNum = line.lineNumber ? theme.ui.muted(String(line.lineNumber).padStart(6)) : ' ';
|
|
243
|
-
formatted = `${ctxNum} ${line.content}`;
|
|
244
|
-
break;
|
|
245
|
-
case 'info':
|
|
246
|
-
// Special info line (file headers, etc.)
|
|
247
|
-
formatted = theme.diff.header(line.content);
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
lines.push(formatted);
|
|
251
|
-
}
|
|
252
|
-
return lines.join('\n');
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Format diff summary
|
|
256
|
-
* Example: "Updated src/core/agent.ts with 2 additions and 1 removal"
|
|
257
|
-
*/
|
|
258
|
-
export function formatDiffSummary(file, additions, removals) {
|
|
259
|
-
const parts = ['Updated', file, 'with'];
|
|
260
|
-
if (additions > 0) {
|
|
261
|
-
parts.push(theme.diff.added(`${additions} addition${additions === 1 ? '' : 's'}`));
|
|
262
|
-
}
|
|
263
|
-
if (removals > 0) {
|
|
264
|
-
if (additions > 0) {
|
|
265
|
-
parts.push('and');
|
|
266
|
-
}
|
|
267
|
-
parts.push(theme.diff.removed(`${removals} removal${removals === 1 ? '' : 's'}`));
|
|
268
|
-
}
|
|
269
|
-
return parts.join(' ');
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Format multiline content with indentation (Claude Code style)
|
|
273
|
-
*
|
|
274
|
-
* Adds proper indentation and line wrapping
|
|
275
|
-
*/
|
|
276
|
-
export function formatIndentedContent(content, indent = 4) {
|
|
277
|
-
const lines = content.split('\n');
|
|
278
|
-
const indentStr = ' '.repeat(indent);
|
|
279
|
-
return lines.map(line => `${indentStr}${line}`).join('\n');
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Format token usage indicator
|
|
283
|
-
* Example: "• Context 5% used (7.2k tokens)"
|
|
284
|
-
*/
|
|
285
|
-
export function formatTokenUsage(percentage, tokens) {
|
|
286
|
-
const bullet = theme.info('•');
|
|
287
|
-
let pct;
|
|
288
|
-
if (percentage >= 90) {
|
|
289
|
-
pct = theme.error(`${percentage}%`);
|
|
290
|
-
}
|
|
291
|
-
else if (percentage >= 70) {
|
|
292
|
-
pct = theme.warning(`${percentage}%`);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
pct = theme.success(`${percentage}%`);
|
|
296
|
-
}
|
|
297
|
-
const tokensStr = tokens ? ` (${formatTokenCount(tokens)} tokens)` : '';
|
|
298
|
-
return `${bullet} Context ${pct} used${tokensStr}`;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Format token count with k/M suffixes
|
|
302
|
-
*/
|
|
303
|
-
function formatTokenCount(tokens) {
|
|
304
|
-
if (tokens >= 1000000) {
|
|
305
|
-
return `${(tokens / 1000000).toFixed(1)}M`;
|
|
306
|
-
}
|
|
307
|
-
if (tokens >= 1000) {
|
|
308
|
-
return `${(tokens / 1000).toFixed(1)}k`;
|
|
309
|
-
}
|
|
310
|
-
return String(tokens);
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Format timing information
|
|
314
|
-
* Example: "(1m 43s)" or "(250ms)"
|
|
315
|
-
*/
|
|
316
|
-
export function formatDuration(ms) {
|
|
317
|
-
if (ms < 1000) {
|
|
318
|
-
return `${Math.round(ms)}ms`;
|
|
319
|
-
}
|
|
320
|
-
const seconds = Math.floor(ms / 1000);
|
|
321
|
-
const minutes = Math.floor(seconds / 60);
|
|
322
|
-
const remainingSeconds = seconds % 60;
|
|
323
|
-
if (minutes > 0) {
|
|
324
|
-
return `${minutes}m ${remainingSeconds}s`;
|
|
325
|
-
}
|
|
326
|
-
return `${seconds}s`;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Create a "thinking" indicator
|
|
330
|
-
* Example: "∴ Thinking…" or "∴ Thought for 2s (ctrl+o to show thinking)"
|
|
331
|
-
*/
|
|
332
|
-
export function formatThinking(durationMs, hasContent = false) {
|
|
333
|
-
const symbol = theme.info('∴');
|
|
334
|
-
if (durationMs === undefined) {
|
|
335
|
-
return `${symbol} Thinking…`;
|
|
336
|
-
}
|
|
337
|
-
const duration = formatDuration(durationMs);
|
|
338
|
-
const hint = hasContent ? theme.ui.muted('(ctrl+o to show thinking)') : '';
|
|
339
|
-
return `${symbol} Thought for ${duration}${hint ? ` ${hint}` : ''}`;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Format status line at bottom
|
|
343
|
-
* Example: "• Ready for prompts (250ms)"
|
|
344
|
-
*/
|
|
345
|
-
export function formatStatusLine(message, durationMs, tokenUsage) {
|
|
346
|
-
const parts = [];
|
|
347
|
-
// Add token usage first if available
|
|
348
|
-
if (tokenUsage) {
|
|
349
|
-
parts.push(formatTokenUsage(tokenUsage.percentage, tokenUsage.tokens));
|
|
350
|
-
}
|
|
351
|
-
// Add message with duration
|
|
352
|
-
const duration = durationMs ? ` (${formatDuration(durationMs)})` : '';
|
|
353
|
-
parts.push(message + duration);
|
|
354
|
-
return parts.join(' · ');
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Smart truncate for shell commands
|
|
358
|
-
* Preserves important parts like command name and key flags
|
|
359
|
-
*/
|
|
360
|
-
export function truncateCommand(command, maxLength = 60) {
|
|
361
|
-
if (command.length <= maxLength)
|
|
362
|
-
return command;
|
|
363
|
-
// Try to keep command name and first few args
|
|
364
|
-
const parts = command.split(/\s+/).filter(p => p);
|
|
365
|
-
if (parts.length === 0 || !parts[0]) {
|
|
366
|
-
return command.slice(0, maxLength - 3) + '...';
|
|
367
|
-
}
|
|
368
|
-
if (parts.length === 1) {
|
|
369
|
-
// Single long command/path
|
|
370
|
-
return command.slice(0, maxLength - 3) + '...';
|
|
371
|
-
}
|
|
372
|
-
// Keep first part (command) and truncate rest
|
|
373
|
-
const cmdName = parts[0];
|
|
374
|
-
const rest = parts.slice(1).join(' ');
|
|
375
|
-
if (cmdName.length + 7 > maxLength) {
|
|
376
|
-
return cmdName.slice(0, maxLength - 3) + '...';
|
|
377
|
-
}
|
|
378
|
-
const availableSpace = maxLength - cmdName.length - 4; // -4 for " ..."
|
|
379
|
-
if (rest.length <= availableSpace) {
|
|
380
|
-
return command;
|
|
381
|
-
}
|
|
382
|
-
return `${cmdName} ${rest.slice(0, availableSpace)}...`;
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Format file size in human-readable format
|
|
386
|
-
*/
|
|
387
|
-
export function formatFileSize(bytes) {
|
|
388
|
-
if (bytes < 1024)
|
|
389
|
-
return `${bytes}B`;
|
|
390
|
-
if (bytes < 1024 * 1024)
|
|
391
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
392
|
-
if (bytes < 1024 * 1024 * 1024)
|
|
393
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
394
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Format JSON output with syntax highlighting
|
|
398
|
-
*/
|
|
399
|
-
export function formatJSON(data, compact = false) {
|
|
400
|
-
const json = JSON.stringify(data, null, compact ? 0 : 2);
|
|
401
|
-
// Add basic syntax highlighting
|
|
402
|
-
return json
|
|
403
|
-
.replace(/"([^"]+)":/g, (_, key) => `${theme.info(`"${key}"`)}: `) // Keys in blue
|
|
404
|
-
.replace(/: "([^"]*)"/g, (_, value) => `: ${theme.success(`"${value}"`)}`) // String values in green
|
|
405
|
-
.replace(/: (\d+\.?\d*)/g, (_, num) => `: ${theme.warning(num)}`) // Numbers in amber
|
|
406
|
-
.replace(/: (true|false|null)/g, (_, bool) => `: ${theme.secondary(bool)}`); // Booleans in purple
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Format a list of items (Claude Code style)
|
|
410
|
-
* Example:
|
|
411
|
-
* • Item 1
|
|
412
|
-
* • Item 2
|
|
413
|
-
* • Item 3
|
|
414
|
-
*/
|
|
415
|
-
export function formatList(items, bullet = '•') {
|
|
416
|
-
return items.map(item => ` ${theme.info(bullet)} ${item}`).join('\n');
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Format a key-value pair (Claude Code style)
|
|
420
|
-
*/
|
|
421
|
-
export function formatKeyValue(key, value) {
|
|
422
|
-
return `${theme.ui.muted(key)}: ${value}`;
|
|
423
|
-
}
|