erosolar-cli 1.7.13 → 1.7.15
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 +5 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +45 -14
- 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/display.d.ts +15 -0
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +57 -0
- package/dist/ui/display.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
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StatusOrchestrator - Unified status management system
|
|
3
|
-
* Coordinates between tool execution, live status tracking, and overlay display
|
|
4
|
-
*/
|
|
5
|
-
import { EventEmitter } from 'events';
|
|
6
|
-
export class StatusOrchestrator extends EventEmitter {
|
|
7
|
-
constructor() {
|
|
8
|
-
super();
|
|
9
|
-
this.statusListeners = new Set();
|
|
10
|
-
this.context = {
|
|
11
|
-
base: null,
|
|
12
|
-
overrides: new Map(),
|
|
13
|
-
tools: new Map(),
|
|
14
|
-
animations: new Map(),
|
|
15
|
-
interrupts: {
|
|
16
|
-
pending: [],
|
|
17
|
-
active: null,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
// Default priority resolver
|
|
21
|
-
this.priorityResolver = (a, b) => {
|
|
22
|
-
const tonePriority = {
|
|
23
|
-
danger: 4,
|
|
24
|
-
warning: 3,
|
|
25
|
-
success: 2,
|
|
26
|
-
info: 1,
|
|
27
|
-
};
|
|
28
|
-
const aPriority = tonePriority[a.tone || 'info'];
|
|
29
|
-
const bPriority = tonePriority[b.tone || 'info'];
|
|
30
|
-
return bPriority - aPriority;
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Set base status
|
|
35
|
-
*/
|
|
36
|
-
setBaseStatus(status) {
|
|
37
|
-
this.context.base = status;
|
|
38
|
-
this.emitEvent({
|
|
39
|
-
type: 'status.base.changed',
|
|
40
|
-
timestamp: Date.now(),
|
|
41
|
-
data: { status },
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Push status override
|
|
46
|
-
*/
|
|
47
|
-
pushOverride(id, status) {
|
|
48
|
-
this.context.overrides.set(id, status);
|
|
49
|
-
this.emitEvent({
|
|
50
|
-
type: 'status.override.pushed',
|
|
51
|
-
timestamp: Date.now(),
|
|
52
|
-
data: { id, status },
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Clear status override
|
|
57
|
-
*/
|
|
58
|
-
clearOverride(id) {
|
|
59
|
-
const removed = this.context.overrides.delete(id);
|
|
60
|
-
if (removed) {
|
|
61
|
-
this.emitEvent({
|
|
62
|
-
type: 'status.override.cleared',
|
|
63
|
-
timestamp: Date.now(),
|
|
64
|
-
data: { id },
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Handle tool lifecycle events
|
|
70
|
-
*/
|
|
71
|
-
onToolStart(toolCall) {
|
|
72
|
-
const toolStatus = {
|
|
73
|
-
toolId: toolCall.id,
|
|
74
|
-
tool: toolCall.name,
|
|
75
|
-
status: 'starting',
|
|
76
|
-
description: this.describeToolOperation(toolCall),
|
|
77
|
-
startedAt: Date.now(),
|
|
78
|
-
updatedAt: Date.now(),
|
|
79
|
-
tone: this.getToolTone(toolCall.name),
|
|
80
|
-
};
|
|
81
|
-
this.context.tools.set(toolCall.id, toolStatus);
|
|
82
|
-
this.emitEvent({
|
|
83
|
-
type: 'tool.start',
|
|
84
|
-
timestamp: Date.now(),
|
|
85
|
-
data: { toolCall, toolStatus },
|
|
86
|
-
});
|
|
87
|
-
// Update to running status after a brief delay
|
|
88
|
-
setTimeout(() => {
|
|
89
|
-
const status = this.context.tools.get(toolCall.id);
|
|
90
|
-
if (status && status.status === 'starting') {
|
|
91
|
-
status.status = 'running';
|
|
92
|
-
status.updatedAt = Date.now();
|
|
93
|
-
this.emitEvent({
|
|
94
|
-
type: 'tool.progress',
|
|
95
|
-
timestamp: Date.now(),
|
|
96
|
-
data: { toolCall, toolStatus: status },
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}, 100);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Update tool progress
|
|
103
|
-
*/
|
|
104
|
-
onToolProgress(toolId, progress) {
|
|
105
|
-
const toolStatus = this.context.tools.get(toolId);
|
|
106
|
-
if (!toolStatus)
|
|
107
|
-
return;
|
|
108
|
-
toolStatus.progress = {
|
|
109
|
-
current: progress.current,
|
|
110
|
-
total: progress.total,
|
|
111
|
-
percentage: Math.round((progress.current / progress.total) * 100),
|
|
112
|
-
};
|
|
113
|
-
if (progress.message) {
|
|
114
|
-
toolStatus.detail = progress.message;
|
|
115
|
-
}
|
|
116
|
-
toolStatus.updatedAt = Date.now();
|
|
117
|
-
this.emitEvent({
|
|
118
|
-
type: 'tool.progress',
|
|
119
|
-
timestamp: Date.now(),
|
|
120
|
-
data: { toolId, toolStatus, progress },
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Handle tool completion
|
|
125
|
-
*/
|
|
126
|
-
onToolComplete(toolId, result) {
|
|
127
|
-
const toolStatus = this.context.tools.get(toolId);
|
|
128
|
-
if (!toolStatus)
|
|
129
|
-
return;
|
|
130
|
-
toolStatus.status = 'completed';
|
|
131
|
-
toolStatus.updatedAt = Date.now();
|
|
132
|
-
this.emitEvent({
|
|
133
|
-
type: 'tool.complete',
|
|
134
|
-
timestamp: Date.now(),
|
|
135
|
-
data: { toolId, toolStatus, result },
|
|
136
|
-
});
|
|
137
|
-
// Remove from active tools after animation
|
|
138
|
-
setTimeout(() => {
|
|
139
|
-
this.context.tools.delete(toolId);
|
|
140
|
-
}, 1000);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Handle tool error
|
|
144
|
-
*/
|
|
145
|
-
onToolError(toolId, error) {
|
|
146
|
-
const toolStatus = this.context.tools.get(toolId);
|
|
147
|
-
if (!toolStatus)
|
|
148
|
-
return;
|
|
149
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
150
|
-
toolStatus.status = 'error';
|
|
151
|
-
toolStatus.tone = 'danger';
|
|
152
|
-
toolStatus.detail = errorMessage || 'An error occurred';
|
|
153
|
-
toolStatus.updatedAt = Date.now();
|
|
154
|
-
this.emitEvent({
|
|
155
|
-
type: 'tool.error',
|
|
156
|
-
timestamp: Date.now(),
|
|
157
|
-
data: { toolId, toolStatus, error },
|
|
158
|
-
});
|
|
159
|
-
// Remove from active tools after delay
|
|
160
|
-
setTimeout(() => {
|
|
161
|
-
this.context.tools.delete(toolId);
|
|
162
|
-
}, 2000);
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Queue an interrupt
|
|
166
|
-
*/
|
|
167
|
-
queueInterrupt(interrupt) {
|
|
168
|
-
const id = `interrupt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
169
|
-
const fullInterrupt = {
|
|
170
|
-
...interrupt,
|
|
171
|
-
id,
|
|
172
|
-
timestamp: Date.now(),
|
|
173
|
-
};
|
|
174
|
-
// Insert based on priority
|
|
175
|
-
const index = this.context.interrupts.pending.findIndex((i) => i.priority < interrupt.priority);
|
|
176
|
-
if (index === -1) {
|
|
177
|
-
this.context.interrupts.pending.push(fullInterrupt);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
this.context.interrupts.pending.splice(index, 0, fullInterrupt);
|
|
181
|
-
}
|
|
182
|
-
this.emitEvent({
|
|
183
|
-
type: 'interrupt.received',
|
|
184
|
-
timestamp: Date.now(),
|
|
185
|
-
data: { interrupt: fullInterrupt },
|
|
186
|
-
});
|
|
187
|
-
// Process interrupt queue
|
|
188
|
-
this.processInterrupts();
|
|
189
|
-
return id;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Process pending interrupts
|
|
193
|
-
*/
|
|
194
|
-
processInterrupts() {
|
|
195
|
-
if (this.context.interrupts.active || this.context.interrupts.pending.length === 0) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const interrupt = this.context.interrupts.pending.shift();
|
|
199
|
-
if (!interrupt)
|
|
200
|
-
return;
|
|
201
|
-
this.context.interrupts.active = interrupt;
|
|
202
|
-
// Auto-clear interrupt after 3 seconds if not cleared manually
|
|
203
|
-
setTimeout(() => {
|
|
204
|
-
if (this.context.interrupts.active?.id === interrupt.id) {
|
|
205
|
-
this.clearInterrupt(interrupt.id);
|
|
206
|
-
}
|
|
207
|
-
}, 3000);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Clear an active interrupt
|
|
211
|
-
*/
|
|
212
|
-
clearInterrupt(id) {
|
|
213
|
-
if (this.context.interrupts.active?.id === id) {
|
|
214
|
-
this.context.interrupts.active = null;
|
|
215
|
-
this.processInterrupts(); // Process next in queue
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Get the current aggregated status
|
|
220
|
-
*/
|
|
221
|
-
getCurrentStatus() {
|
|
222
|
-
// Priority order:
|
|
223
|
-
// 1. Active interrupt
|
|
224
|
-
// 2. Active tools
|
|
225
|
-
// 3. Overrides
|
|
226
|
-
// 4. Base status
|
|
227
|
-
if (this.context.interrupts.active) {
|
|
228
|
-
return {
|
|
229
|
-
text: this.context.interrupts.active.message,
|
|
230
|
-
tone: 'warning',
|
|
231
|
-
startedAt: this.context.interrupts.active.timestamp,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
// Get most important tool status
|
|
235
|
-
if (this.context.tools.size > 0) {
|
|
236
|
-
const toolStatuses = Array.from(this.context.tools.values());
|
|
237
|
-
const activeTools = toolStatuses.filter((t) => t.status === 'running');
|
|
238
|
-
if (activeTools.length > 0) {
|
|
239
|
-
const mostRecent = activeTools.sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
240
|
-
if (mostRecent) {
|
|
241
|
-
return {
|
|
242
|
-
text: mostRecent.description,
|
|
243
|
-
detail: mostRecent.detail,
|
|
244
|
-
tone: mostRecent.tone,
|
|
245
|
-
startedAt: mostRecent.startedAt,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// Get highest priority override
|
|
251
|
-
if (this.context.overrides.size > 0) {
|
|
252
|
-
const overrides = Array.from(this.context.overrides.values());
|
|
253
|
-
const sorted = overrides.sort(this.priorityResolver);
|
|
254
|
-
return sorted[0] || null;
|
|
255
|
-
}
|
|
256
|
-
return this.context.base;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Register animation
|
|
260
|
-
*/
|
|
261
|
-
registerAnimation(id, type, data) {
|
|
262
|
-
this.context.animations.set(id, {
|
|
263
|
-
id,
|
|
264
|
-
type,
|
|
265
|
-
frame: 0,
|
|
266
|
-
startedAt: Date.now(),
|
|
267
|
-
data,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Update animation frame
|
|
272
|
-
*/
|
|
273
|
-
updateAnimationFrame(id) {
|
|
274
|
-
const animation = this.context.animations.get(id);
|
|
275
|
-
if (animation) {
|
|
276
|
-
animation.frame++;
|
|
277
|
-
this.emitEvent({
|
|
278
|
-
type: 'animation.frame',
|
|
279
|
-
timestamp: Date.now(),
|
|
280
|
-
data: { animation },
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Clear animation
|
|
286
|
-
*/
|
|
287
|
-
clearAnimation(id) {
|
|
288
|
-
this.context.animations.delete(id);
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Subscribe to status events
|
|
292
|
-
*/
|
|
293
|
-
subscribe(listener) {
|
|
294
|
-
this.statusListeners.add(listener);
|
|
295
|
-
return () => this.statusListeners.delete(listener);
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Emit status event
|
|
299
|
-
*/
|
|
300
|
-
emitEvent(event) {
|
|
301
|
-
this.emit(event.type, event);
|
|
302
|
-
this.statusListeners.forEach((listener) => listener(event));
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Get tool operation description
|
|
306
|
-
*/
|
|
307
|
-
describeToolOperation(toolCall) {
|
|
308
|
-
const params = toolCall.arguments;
|
|
309
|
-
switch (toolCall.name) {
|
|
310
|
-
case 'read_file':
|
|
311
|
-
return `Reading ${this.truncatePath(params.path)}`;
|
|
312
|
-
case 'write_file':
|
|
313
|
-
return `Writing ${this.truncatePath(params.path)}`;
|
|
314
|
-
case 'edit_file':
|
|
315
|
-
return `Editing ${this.truncatePath(params.path)}`;
|
|
316
|
-
case 'bash':
|
|
317
|
-
return `Running: ${this.truncateCommand(params.command)}`;
|
|
318
|
-
case 'search_files':
|
|
319
|
-
return `Searching for: ${this.truncateQuery(params.query)}`;
|
|
320
|
-
case 'list_directory':
|
|
321
|
-
return `Listing ${this.truncatePath(params.path || '.')}`;
|
|
322
|
-
default:
|
|
323
|
-
return `Running ${toolCall.name}`;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Get tone for tool type
|
|
328
|
-
*/
|
|
329
|
-
getToolTone(tool) {
|
|
330
|
-
const dangerousTools = ['bash', 'write_file', 'edit_file', 'delete_file'];
|
|
331
|
-
const warningTools = ['install', 'uninstall', 'update'];
|
|
332
|
-
if (dangerousTools.includes(tool)) {
|
|
333
|
-
return 'warning';
|
|
334
|
-
}
|
|
335
|
-
if (warningTools.includes(tool)) {
|
|
336
|
-
return 'warning';
|
|
337
|
-
}
|
|
338
|
-
return 'info';
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Helper to truncate paths
|
|
342
|
-
*/
|
|
343
|
-
truncatePath(path, maxLength = 40) {
|
|
344
|
-
if (!path)
|
|
345
|
-
return '';
|
|
346
|
-
if (path.length <= maxLength)
|
|
347
|
-
return path;
|
|
348
|
-
const parts = path.split('/');
|
|
349
|
-
if (parts.length <= 2) {
|
|
350
|
-
return '...' + path.slice(-(maxLength - 3));
|
|
351
|
-
}
|
|
352
|
-
// Keep first and last parts
|
|
353
|
-
const first = parts[0] || '';
|
|
354
|
-
const last = parts[parts.length - 1] || '';
|
|
355
|
-
const middle = '...';
|
|
356
|
-
const result = `${first}/${middle}/${last}`;
|
|
357
|
-
if (result.length > maxLength) {
|
|
358
|
-
return '...' + last.slice(-(maxLength - 3));
|
|
359
|
-
}
|
|
360
|
-
return result;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Helper to truncate commands
|
|
364
|
-
*/
|
|
365
|
-
truncateCommand(command, maxLength = 40) {
|
|
366
|
-
if (!command)
|
|
367
|
-
return '';
|
|
368
|
-
if (command.length <= maxLength)
|
|
369
|
-
return command;
|
|
370
|
-
return command.slice(0, maxLength - 3) + '...';
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Helper to truncate search queries
|
|
374
|
-
*/
|
|
375
|
-
truncateQuery(query, maxLength = 30) {
|
|
376
|
-
if (!query)
|
|
377
|
-
return '';
|
|
378
|
-
if (query.length <= maxLength)
|
|
379
|
-
return query;
|
|
380
|
-
return query.slice(0, maxLength - 3) + '...';
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Get full context for debugging
|
|
384
|
-
*/
|
|
385
|
-
getContext() {
|
|
386
|
-
return { ...this.context };
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Reset all status
|
|
390
|
-
*/
|
|
391
|
-
reset() {
|
|
392
|
-
this.context = {
|
|
393
|
-
base: null,
|
|
394
|
-
overrides: new Map(),
|
|
395
|
-
tools: new Map(),
|
|
396
|
-
animations: new Map(),
|
|
397
|
-
interrupts: {
|
|
398
|
-
pending: [],
|
|
399
|
-
active: null,
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Output mode configuration for terminal UI
|
|
3
|
-
*
|
|
4
|
-
* Controls whether to use decorative box-drawing characters or plain text.
|
|
5
|
-
* Plain mode outputs clipboard-friendly text without Unicode borders.
|
|
6
|
-
*
|
|
7
|
-
* Environment variable: EROSOLAR_PLAIN_OUTPUT=true
|
|
8
|
-
*/
|
|
9
|
-
let _plainMode = null;
|
|
10
|
-
/**
|
|
11
|
-
* Check if plain output mode is enabled.
|
|
12
|
-
* Plain mode outputs text without box-drawing characters for clean clipboard copying.
|
|
13
|
-
*/
|
|
14
|
-
export function isPlainOutputMode() {
|
|
15
|
-
if (_plainMode !== null) {
|
|
16
|
-
return _plainMode;
|
|
17
|
-
}
|
|
18
|
-
const envValue = process.env['EROSOLAR_PLAIN_OUTPUT'];
|
|
19
|
-
if (envValue !== undefined) {
|
|
20
|
-
_plainMode = ['true', '1', 'yes', 'on'].includes(envValue.toLowerCase());
|
|
21
|
-
return _plainMode;
|
|
22
|
-
}
|
|
23
|
-
// Default to plain output unless explicitly disabled
|
|
24
|
-
_plainMode = true;
|
|
25
|
-
return _plainMode;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Override the plain output mode setting (useful for testing)
|
|
29
|
-
*/
|
|
30
|
-
export function setPlainOutputMode(enabled) {
|
|
31
|
-
_plainMode = enabled;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Reset plain output mode to check environment variable again
|
|
35
|
-
*/
|
|
36
|
-
export function resetPlainOutputMode() {
|
|
37
|
-
_plainMode = null;
|
|
38
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PersistentPrompt - Minimal, stable chat input at bottom of terminal
|
|
3
|
-
*
|
|
4
|
-
* Design principles:
|
|
5
|
-
* - Simple: No complex cursor manipulation or multi-line rendering
|
|
6
|
-
* - Stable: Uses standard readline without fighting it
|
|
7
|
-
* - Reliable: Clear separation between output area and input area
|
|
8
|
-
*/
|
|
9
|
-
import * as readline from 'node:readline';
|
|
10
|
-
import { theme } from './theme.js';
|
|
11
|
-
/**
|
|
12
|
-
* Minimal prompt manager that doesn't fight with readline
|
|
13
|
-
*/
|
|
14
|
-
export class PersistentPrompt {
|
|
15
|
-
constructor(writeStream, promptText = '> ') {
|
|
16
|
-
this.isEnabled = true;
|
|
17
|
-
this.writeStream = writeStream;
|
|
18
|
-
this.promptText = promptText;
|
|
19
|
-
this.statusBarState = {};
|
|
20
|
-
this.promptState = {
|
|
21
|
-
userInput: '',
|
|
22
|
-
cursorPosition: 0,
|
|
23
|
-
isVisible: true,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Update the prompt text
|
|
28
|
-
*/
|
|
29
|
-
setPromptText(text) {
|
|
30
|
-
this.promptText = text;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Get the formatted prompt string
|
|
34
|
-
*/
|
|
35
|
-
getPrompt() {
|
|
36
|
-
return this.promptText;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Update user input state (for tracking only - readline handles display)
|
|
40
|
-
*/
|
|
41
|
-
updateInput(input, cursorPos) {
|
|
42
|
-
this.promptState.userInput = input;
|
|
43
|
-
this.promptState.cursorPosition = cursorPos;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Update status bar information
|
|
47
|
-
*/
|
|
48
|
-
updateStatusBar(state) {
|
|
49
|
-
this.statusBarState = { ...this.statusBarState, ...state };
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Show a status line above the prompt (call before readline.prompt())
|
|
53
|
-
*/
|
|
54
|
-
showStatus() {
|
|
55
|
-
if (!this.isEnabled)
|
|
56
|
-
return;
|
|
57
|
-
const status = this.buildStatusLine();
|
|
58
|
-
if (status) {
|
|
59
|
-
this.writeStream.write('\n' + status + '\n');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Show the prompt
|
|
64
|
-
*/
|
|
65
|
-
show() {
|
|
66
|
-
this.promptState.isVisible = true;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Hide the prompt
|
|
70
|
-
*/
|
|
71
|
-
hide() {
|
|
72
|
-
this.promptState.isVisible = false;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Enable or disable
|
|
76
|
-
*/
|
|
77
|
-
setEnabled(enabled) {
|
|
78
|
-
this.isEnabled = enabled;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Clear - no-op for minimal implementation
|
|
82
|
-
*/
|
|
83
|
-
clear() {
|
|
84
|
-
// Intentionally minimal - let readline handle clearing
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Build status line string
|
|
88
|
-
*/
|
|
89
|
-
buildStatusLine() {
|
|
90
|
-
const parts = [];
|
|
91
|
-
if (this.statusBarState.fileChanges) {
|
|
92
|
-
parts.push(this.statusBarState.fileChanges);
|
|
93
|
-
}
|
|
94
|
-
if (typeof this.statusBarState.contextUsage === 'number') {
|
|
95
|
-
const remaining = Math.max(0, 100 - this.statusBarState.contextUsage);
|
|
96
|
-
parts.push(`${remaining}% context`);
|
|
97
|
-
}
|
|
98
|
-
if (this.statusBarState.message?.trim()) {
|
|
99
|
-
parts.push(this.statusBarState.message.trim());
|
|
100
|
-
}
|
|
101
|
-
if (!parts.length) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
return theme.ui.muted(parts.join(' • '));
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Handle terminal resize - no-op for minimal implementation
|
|
108
|
-
*/
|
|
109
|
-
handleResize() {
|
|
110
|
-
// Readline handles resize
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Dispose
|
|
114
|
-
*/
|
|
115
|
-
dispose() {
|
|
116
|
-
// Nothing to clean up in minimal implementation
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Simple input box that wraps readline with stable behavior
|
|
121
|
-
*/
|
|
122
|
-
export class SimpleInputBox {
|
|
123
|
-
constructor(promptText = '> ') {
|
|
124
|
-
this.rl = null;
|
|
125
|
-
this.statusText = '';
|
|
126
|
-
this.promptText = promptText;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Initialize readline interface
|
|
130
|
-
*/
|
|
131
|
-
init(input, output) {
|
|
132
|
-
this.rl = readline.createInterface({
|
|
133
|
-
input,
|
|
134
|
-
output,
|
|
135
|
-
prompt: this.promptText,
|
|
136
|
-
terminal: true,
|
|
137
|
-
});
|
|
138
|
-
return this.rl;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Set prompt text
|
|
142
|
-
*/
|
|
143
|
-
setPrompt(text) {
|
|
144
|
-
this.promptText = text;
|
|
145
|
-
if (this.rl) {
|
|
146
|
-
this.rl.setPrompt(text);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Set status text (shown above prompt on next prompt())
|
|
151
|
-
*/
|
|
152
|
-
setStatus(text) {
|
|
153
|
-
this.statusText = text;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Show prompt with optional status line
|
|
157
|
-
*/
|
|
158
|
-
prompt(preserveCursor) {
|
|
159
|
-
if (!this.rl)
|
|
160
|
-
return;
|
|
161
|
-
// Show status line if set
|
|
162
|
-
if (this.statusText) {
|
|
163
|
-
process.stdout.write('\n' + theme.ui.muted(this.statusText) + '\n');
|
|
164
|
-
this.statusText = '';
|
|
165
|
-
}
|
|
166
|
-
this.rl.prompt(preserveCursor);
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Get readline interface
|
|
170
|
-
*/
|
|
171
|
-
getInterface() {
|
|
172
|
-
return this.rl;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Close and cleanup
|
|
176
|
-
*/
|
|
177
|
-
close() {
|
|
178
|
-
if (this.rl) {
|
|
179
|
-
this.rl.close();
|
|
180
|
-
this.rl = null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|