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.
Files changed (65) hide show
  1. package/dist/core/responseVerifier.d.ts +79 -0
  2. package/dist/core/responseVerifier.d.ts.map +1 -0
  3. package/dist/core/responseVerifier.js +443 -0
  4. package/dist/core/responseVerifier.js.map +1 -0
  5. package/dist/shell/interactiveShell.d.ts +5 -0
  6. package/dist/shell/interactiveShell.d.ts.map +1 -1
  7. package/dist/shell/interactiveShell.js +45 -14
  8. package/dist/shell/interactiveShell.js.map +1 -1
  9. package/dist/ui/ShellUIAdapter.d.ts +3 -0
  10. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  11. package/dist/ui/ShellUIAdapter.js +4 -10
  12. package/dist/ui/ShellUIAdapter.js.map +1 -1
  13. package/dist/ui/display.d.ts +15 -0
  14. package/dist/ui/display.d.ts.map +1 -1
  15. package/dist/ui/display.js +57 -0
  16. package/dist/ui/display.js.map +1 -1
  17. package/dist/ui/persistentPrompt.d.ts +4 -0
  18. package/dist/ui/persistentPrompt.d.ts.map +1 -1
  19. package/dist/ui/persistentPrompt.js +10 -11
  20. package/dist/ui/persistentPrompt.js.map +1 -1
  21. package/package.json +1 -1
  22. package/dist/bin/core/agent.js +0 -362
  23. package/dist/bin/core/agentProfileManifest.js +0 -187
  24. package/dist/bin/core/agentProfiles.js +0 -34
  25. package/dist/bin/core/agentRulebook.js +0 -135
  26. package/dist/bin/core/agentSchemaLoader.js +0 -233
  27. package/dist/bin/core/contextManager.js +0 -412
  28. package/dist/bin/core/contextWindow.js +0 -122
  29. package/dist/bin/core/customCommands.js +0 -80
  30. package/dist/bin/core/errors/apiKeyErrors.js +0 -114
  31. package/dist/bin/core/errors/errorTypes.js +0 -340
  32. package/dist/bin/core/errors/safetyValidator.js +0 -304
  33. package/dist/bin/core/errors.js +0 -32
  34. package/dist/bin/core/modelDiscovery.js +0 -755
  35. package/dist/bin/core/preferences.js +0 -224
  36. package/dist/bin/core/schemaValidator.js +0 -92
  37. package/dist/bin/core/secretStore.js +0 -199
  38. package/dist/bin/core/sessionStore.js +0 -187
  39. package/dist/bin/core/toolRuntime.js +0 -290
  40. package/dist/bin/core/types.js +0 -1
  41. package/dist/bin/shell/bracketedPasteManager.js +0 -350
  42. package/dist/bin/shell/fileChangeTracker.js +0 -65
  43. package/dist/bin/shell/interactiveShell.js +0 -2908
  44. package/dist/bin/shell/liveStatus.js +0 -78
  45. package/dist/bin/shell/shellApp.js +0 -290
  46. package/dist/bin/shell/systemPrompt.js +0 -60
  47. package/dist/bin/shell/updateManager.js +0 -108
  48. package/dist/bin/ui/ShellUIAdapter.js +0 -459
  49. package/dist/bin/ui/UnifiedUIController.js +0 -183
  50. package/dist/bin/ui/animation/AnimationScheduler.js +0 -430
  51. package/dist/bin/ui/codeHighlighter.js +0 -854
  52. package/dist/bin/ui/designSystem.js +0 -121
  53. package/dist/bin/ui/display.js +0 -1222
  54. package/dist/bin/ui/interrupts/InterruptManager.js +0 -437
  55. package/dist/bin/ui/layout.js +0 -139
  56. package/dist/bin/ui/orchestration/StatusOrchestrator.js +0 -403
  57. package/dist/bin/ui/outputMode.js +0 -38
  58. package/dist/bin/ui/persistentPrompt.js +0 -183
  59. package/dist/bin/ui/richText.js +0 -338
  60. package/dist/bin/ui/shortcutsHelp.js +0 -87
  61. package/dist/bin/ui/telemetry/UITelemetry.js +0 -443
  62. package/dist/bin/ui/textHighlighter.js +0 -210
  63. package/dist/bin/ui/theme.js +0 -116
  64. package/dist/bin/ui/toolDisplay.js +0 -423
  65. package/dist/bin/ui/toolDisplayAdapter.js +0 -357
@@ -1,290 +0,0 @@
1
- import { ToolArgumentValidationError, validateToolArguments, } from './schemaValidator.js';
2
- // Idempotent tools that can be safely cached
3
- const CACHEABLE_TOOLS = new Set([
4
- 'Read',
5
- 'read_file',
6
- 'Glob',
7
- 'glob_search',
8
- 'Grep',
9
- 'grep_search',
10
- 'find_definition',
11
- 'analyze_code_quality',
12
- 'extract_exports',
13
- ]);
14
- export class ToolRuntime {
15
- constructor(baseTools = [], options = {}) {
16
- this.registry = new Map();
17
- this.registrationOrder = [];
18
- this.cache = new Map();
19
- this.observer = options.observer ?? null;
20
- this.contextManager = options.contextManager ?? null;
21
- this.enableCache = options.enableCache ?? true;
22
- this.cacheTTLMs = options.cacheTTLMs ?? 5 * 60 * 1000; // 5 minutes default
23
- if (baseTools.length) {
24
- this.registerSuite({
25
- id: 'runtime.core',
26
- description: 'Core runtime metadata tools',
27
- tools: baseTools,
28
- });
29
- }
30
- }
31
- registerSuite(suite) {
32
- if (!suite?.id?.trim()) {
33
- throw new Error('Tool suite id cannot be blank.');
34
- }
35
- this.unregisterSuite(suite.id);
36
- for (const definition of suite.tools ?? []) {
37
- this.addTool(definition, suite.id);
38
- }
39
- }
40
- unregisterSuite(id) {
41
- if (!id?.trim()) {
42
- return;
43
- }
44
- for (const [name, record] of this.registry.entries()) {
45
- if (record.suiteId === id) {
46
- this.registry.delete(name);
47
- this.removeFromOrder(name);
48
- }
49
- }
50
- }
51
- listProviderTools() {
52
- return this.registrationOrder
53
- .map((name) => this.registry.get(name))
54
- .filter((record) => Boolean(record))
55
- .map(({ definition }) => {
56
- const tool = {
57
- name: definition.name,
58
- description: definition.description,
59
- };
60
- if (definition.parameters) {
61
- tool.parameters = definition.parameters;
62
- }
63
- return tool;
64
- });
65
- }
66
- async execute(call) {
67
- const record = this.registry.get(call.name);
68
- if (!record) {
69
- const message = `Tool "${call.name}" is not available.`;
70
- this.observer?.onToolError?.(call, message);
71
- return message;
72
- }
73
- // Check if tool is cacheable
74
- const isCacheable = record.definition.cacheable ?? CACHEABLE_TOOLS.has(call.name);
75
- // Try to get from cache
76
- if (this.enableCache && isCacheable) {
77
- const cacheKey = this.getCacheKey(call);
78
- const cached = this.cache.get(cacheKey);
79
- if (cached && Date.now() - cached.timestamp < this.cacheTTLMs) {
80
- this.observer?.onCacheHit?.(call);
81
- this.observer?.onToolResult?.(call, cached.result);
82
- return cached.result;
83
- }
84
- }
85
- this.observer?.onToolStart?.(call);
86
- try {
87
- const args = normalizeToolArguments(call.arguments);
88
- validateToolArguments(record.definition.name, record.definition.parameters, args);
89
- const result = await record.definition.handler(args);
90
- let output = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
91
- // Truncate output if context manager is available
92
- if (this.contextManager) {
93
- const truncated = this.contextManager.truncateToolOutput(output, call.name);
94
- if (truncated.wasTruncated) {
95
- output = truncated.content;
96
- // Log truncation for debugging
97
- if (process.env['DEBUG_CONTEXT']) {
98
- console.warn(`[Context Manager] Truncated ${call.name} output: ${truncated.originalLength} -> ${truncated.truncatedLength} chars`);
99
- }
100
- }
101
- }
102
- // Cache the result if cacheable
103
- if (this.enableCache && isCacheable) {
104
- const cacheKey = this.getCacheKey(call);
105
- this.cache.set(cacheKey, {
106
- result: output,
107
- timestamp: Date.now(),
108
- });
109
- }
110
- this.observer?.onToolResult?.(call, output);
111
- return output;
112
- }
113
- catch (error) {
114
- let formatted;
115
- if (error instanceof ToolArgumentValidationError) {
116
- formatted = error.message;
117
- }
118
- else {
119
- const message = error instanceof Error ? error.message : String(error);
120
- formatted = `Failed to run "${call.name}": ${message}`;
121
- }
122
- this.observer?.onToolError?.(call, formatted);
123
- return formatted;
124
- }
125
- }
126
- getCacheKey(call) {
127
- return `${call.name}:${JSON.stringify(call.arguments)}`;
128
- }
129
- clearCache() {
130
- this.cache.clear();
131
- }
132
- getCacheStats() {
133
- let totalSize = 0;
134
- for (const entry of this.cache.values()) {
135
- totalSize += entry.result.length;
136
- }
137
- return {
138
- size: totalSize,
139
- entries: this.cache.size,
140
- };
141
- }
142
- addTool(definition, suiteId) {
143
- if (!definition?.name?.trim()) {
144
- throw new Error(`Tool names cannot be blank (suite "${suiteId}").`);
145
- }
146
- if (this.registry.has(definition.name)) {
147
- const owner = this.registry.get(definition.name)?.suiteId ?? 'unknown';
148
- throw new Error(`Tool "${definition.name}" already registered by suite "${owner}".`);
149
- }
150
- this.registry.set(definition.name, {
151
- suiteId,
152
- definition,
153
- });
154
- this.registrationOrder.push(definition.name);
155
- }
156
- removeFromOrder(name) {
157
- const index = this.registrationOrder.indexOf(name);
158
- if (index >= 0) {
159
- this.registrationOrder.splice(index, 1);
160
- }
161
- }
162
- }
163
- export function createDefaultToolRuntime(context, toolSuites = [], options = {}) {
164
- const runtime = new ToolRuntime([
165
- buildContextSnapshotTool(context.workspaceContext),
166
- buildCapabilitiesTool(context),
167
- buildProfileInspectorTool(context),
168
- ], options);
169
- for (const suite of toolSuites) {
170
- runtime.registerSuite(suite);
171
- }
172
- return runtime;
173
- }
174
- function buildContextSnapshotTool(workspaceContext) {
175
- return {
176
- name: 'context_snapshot',
177
- description: 'Returns a summary of the repository context. NOTE: Full context is already in your system prompt - only use this if you need to refresh or verify the context.',
178
- parameters: {
179
- type: 'object',
180
- properties: {
181
- format: {
182
- type: 'string',
183
- description: 'Use "plain" for raw text or "markdown" for a fenced block.',
184
- enum: ['plain', 'markdown'],
185
- },
186
- },
187
- },
188
- handler: (args) => {
189
- if (!workspaceContext?.trim()) {
190
- return 'Workspace context is unavailable.';
191
- }
192
- // CRITICAL: Return only summary to prevent context duplication
193
- // Full context is already in system prompt
194
- const lines = workspaceContext.trim().split('\n');
195
- const totalLines = lines.length;
196
- // Return just first 20 lines + summary
197
- const preview = lines.slice(0, 20).join('\n');
198
- const summary = [
199
- preview,
200
- '',
201
- `[Workspace context: ${totalLines} total lines]`,
202
- `[Full context already available in system prompt - use Read/Glob tools for specific files]`
203
- ].join('\n');
204
- const format = args['format'] === 'markdown' ? 'markdown' : 'plain';
205
- if (format === 'markdown') {
206
- return ['```text', summary, '```'].join('\n');
207
- }
208
- return summary;
209
- },
210
- };
211
- }
212
- function buildCapabilitiesTool(context) {
213
- return {
214
- name: 'capabilities_overview',
215
- description: 'Summarizes the agent runtime capabilities including available tools and features.',
216
- parameters: {
217
- type: 'object',
218
- properties: {
219
- audience: {
220
- type: 'string',
221
- enum: ['developer', 'model'],
222
- description: 'Tailors the tone of the description.',
223
- },
224
- },
225
- },
226
- handler: (args) => {
227
- const audience = args['audience'];
228
- const adjective = audience === 'developer' ? 'Operator facing' : 'Model facing';
229
- return [
230
- `${adjective} capabilities summary:`,
231
- '- Full file system access (read, write, list, search).',
232
- '- Bash command execution for running scripts and tools.',
233
- '- Advanced code search and pattern matching.',
234
- '- Deterministic workspace context snapshot appended to the system prompt.',
235
- '- Tool invocations are logged in realtime for transparency.',
236
- `- Active provider: ${context.provider} (${context.model}).`,
237
- ].join('\n');
238
- },
239
- };
240
- }
241
- function buildProfileInspectorTool(context) {
242
- return {
243
- name: 'profile_details',
244
- description: 'Returns the configuration of the active CLI profile.',
245
- parameters: {
246
- type: 'object',
247
- properties: {
248
- includeWorkspaceContext: {
249
- type: 'boolean',
250
- description: 'Set true to append the workspace context snapshot if available.',
251
- },
252
- },
253
- additionalProperties: false,
254
- },
255
- handler: (args) => {
256
- const payload = {
257
- profile: context.profileName,
258
- provider: context.provider,
259
- model: context.model,
260
- workspaceContext: args['includeWorkspaceContext'] ? context.workspaceContext ?? null : null,
261
- };
262
- return JSON.stringify(payload, null, 2);
263
- },
264
- };
265
- }
266
- function normalizeToolArguments(value) {
267
- if (value instanceof Map) {
268
- return Object.fromEntries(value.entries());
269
- }
270
- if (isRecord(value)) {
271
- return value;
272
- }
273
- if (typeof value === 'string') {
274
- const trimmed = value.trim();
275
- if (!trimmed) {
276
- return {};
277
- }
278
- try {
279
- const parsed = JSON.parse(trimmed);
280
- return isRecord(parsed) ? parsed : {};
281
- }
282
- catch {
283
- return {};
284
- }
285
- }
286
- return {};
287
- }
288
- function isRecord(value) {
289
- return typeof value === 'object' && value !== null && !Array.isArray(value);
290
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,350 +0,0 @@
1
- /**
2
- * Bracketed paste manager for handling terminal paste operations.
3
- *
4
- * This class handles the bracketed paste mode in terminals, which allows
5
- * pasted text to be distinguished from typed input. This is particularly
6
- * useful for handling multi-line pastes and preventing unwanted execution
7
- * of commands during paste operations.
8
- */
9
- export class BracketedPasteManager {
10
- constructor(enabled) {
11
- this.inPaste = false;
12
- this.pasteBuffer = [];
13
- /** Tracks accumulated multi-line input even without bracketed paste support */
14
- this.multiLineBuffer = [];
15
- this.multiLineMode = false;
16
- this.lastInputTime = 0;
17
- /** Time threshold (ms) to consider rapid input as paste */
18
- this.PASTE_THRESHOLD_MS = 50;
19
- /** Raw data buffer for capturing complete bracketed paste content */
20
- this.rawPasteBuffer = '';
21
- /** Callback for when raw paste is complete */
22
- this.onRawPasteComplete = null;
23
- /** Whether we're currently capturing raw data */
24
- this.capturingRaw = false;
25
- this.enabled = enabled;
26
- }
27
- /**
28
- * Process raw data from stdin before readline handles it.
29
- * This intercepts bracketed paste content and captures it in full.
30
- * Returns true if the data was consumed (should not be passed to readline).
31
- */
32
- processRawData(data) {
33
- if (!this.enabled) {
34
- return { consumed: false };
35
- }
36
- const startIdx = data.indexOf(BracketedPasteManager.START_MARKER);
37
- const endIdx = data.indexOf(BracketedPasteManager.END_MARKER);
38
- // Case 1: Not in paste mode and no start marker - pass through
39
- if (!this.capturingRaw && startIdx === -1) {
40
- return { consumed: false };
41
- }
42
- // Case 2: Found start marker - begin capturing
43
- if (!this.capturingRaw && startIdx !== -1) {
44
- this.capturingRaw = true;
45
- const beforeStart = data.substring(0, startIdx);
46
- const afterStart = data.substring(startIdx + BracketedPasteManager.START_MARKER.length);
47
- // Check if end marker is in same chunk
48
- const endInAfter = afterStart.indexOf(BracketedPasteManager.END_MARKER);
49
- if (endInAfter !== -1) {
50
- // Complete paste in single chunk
51
- const pasteContent = afterStart.substring(0, endInAfter);
52
- const afterEnd = afterStart.substring(endInAfter + BracketedPasteManager.END_MARKER.length);
53
- this.capturingRaw = false;
54
- this.rawPasteBuffer = '';
55
- // Emit the complete paste
56
- if (this.onRawPasteComplete) {
57
- this.onRawPasteComplete(pasteContent);
58
- }
59
- // Return any content before/after markers to be passed through
60
- const passThrough = beforeStart + afterEnd;
61
- return { consumed: true, passThrough: passThrough || undefined };
62
- }
63
- // Start marker found but no end marker yet
64
- this.rawPasteBuffer = afterStart;
65
- // Pass through anything before the start marker
66
- return { consumed: true, passThrough: beforeStart || undefined };
67
- }
68
- // Case 3: Currently capturing and found end marker
69
- if (this.capturingRaw && endIdx !== -1) {
70
- const beforeEnd = data.substring(0, endIdx);
71
- const afterEnd = data.substring(endIdx + BracketedPasteManager.END_MARKER.length);
72
- const pasteContent = this.rawPasteBuffer + beforeEnd;
73
- this.capturingRaw = false;
74
- this.rawPasteBuffer = '';
75
- // Emit the complete paste
76
- if (this.onRawPasteComplete) {
77
- this.onRawPasteComplete(pasteContent);
78
- }
79
- // Pass through anything after end marker
80
- return { consumed: true, passThrough: afterEnd || undefined };
81
- }
82
- // Case 4: Currently capturing, no end marker - accumulate
83
- if (this.capturingRaw) {
84
- this.rawPasteBuffer += data;
85
- return { consumed: true };
86
- }
87
- return { consumed: false };
88
- }
89
- /**
90
- * Set callback for when a complete paste is captured via raw data processing
91
- */
92
- setRawPasteCallback(callback) {
93
- this.onRawPasteComplete = callback;
94
- }
95
- /**
96
- * Check if currently capturing raw paste data
97
- */
98
- isCapturingRaw() {
99
- return this.capturingRaw;
100
- }
101
- /**
102
- * Get current raw buffer size (for display)
103
- */
104
- getRawBufferLineCount() {
105
- if (!this.rawPasteBuffer)
106
- return 0;
107
- return this.rawPasteBuffer.split('\n').length;
108
- }
109
- /**
110
- * Get preview of raw buffer content
111
- */
112
- getRawBufferPreview() {
113
- if (!this.rawPasteBuffer)
114
- return '';
115
- const lines = this.rawPasteBuffer.split('\n');
116
- return this.getPreview(lines);
117
- }
118
- /**
119
- * Process input text, handling bracketed paste sequences
120
- */
121
- process(input) {
122
- if (!this.enabled) {
123
- return this.processWithoutBracketed(input);
124
- }
125
- const startMarker = '\u001b[200~';
126
- const endMarker = '\u001b[201~';
127
- // If we're already in a paste, look for end marker
128
- if (this.inPaste) {
129
- const endIndex = input.indexOf(endMarker);
130
- if (endIndex !== -1) {
131
- // End of paste found
132
- const beforeEnd = input.substring(0, endIndex);
133
- const afterEnd = input.substring(endIndex + endMarker.length);
134
- this.pasteBuffer.push(beforeEnd);
135
- const result = this.pasteBuffer.join('\n');
136
- const lineCount = this.pasteBuffer.length;
137
- const preview = this.getPreview(this.pasteBuffer);
138
- // Reset state
139
- this.inPaste = false;
140
- this.pasteBuffer = [];
141
- // If there's text after the end marker, process it normally
142
- if (afterEnd) {
143
- const afterResult = this.process(afterEnd);
144
- if (afterResult.handled && afterResult.result !== undefined) {
145
- return {
146
- handled: true,
147
- result: result + afterResult.result,
148
- lineCount,
149
- preview,
150
- };
151
- }
152
- return {
153
- handled: true,
154
- result: result + afterEnd,
155
- lineCount,
156
- preview,
157
- };
158
- }
159
- return {
160
- handled: true,
161
- result,
162
- lineCount,
163
- preview,
164
- };
165
- }
166
- else {
167
- // Still in paste, no end marker found
168
- this.pasteBuffer.push(input);
169
- return {
170
- handled: true,
171
- isPending: true,
172
- lineCount: this.pasteBuffer.length,
173
- preview: this.getPreview(this.pasteBuffer),
174
- };
175
- }
176
- }
177
- // Not in paste, look for start marker
178
- const startIndex = input.indexOf(startMarker);
179
- if (startIndex !== -1) {
180
- // Start of paste found
181
- const beforeStart = input.substring(0, startIndex);
182
- const afterStart = input.substring(startIndex + startMarker.length);
183
- // Start paste mode
184
- this.inPaste = true;
185
- this.pasteBuffer = [];
186
- // Process the text after the start marker
187
- if (afterStart) {
188
- const afterResult = this.process(afterStart);
189
- if (afterResult.handled && afterResult.result !== undefined) {
190
- return {
191
- handled: true,
192
- result: beforeStart + afterResult.result,
193
- lineCount: afterResult.lineCount,
194
- preview: afterResult.preview,
195
- };
196
- }
197
- // Still pending
198
- return {
199
- handled: true,
200
- result: beforeStart,
201
- isPending: this.inPaste,
202
- lineCount: this.pasteBuffer.length,
203
- preview: this.getPreview(this.pasteBuffer),
204
- };
205
- }
206
- return {
207
- handled: true,
208
- result: beforeStart,
209
- isPending: true,
210
- lineCount: 0,
211
- };
212
- }
213
- // No paste markers found
214
- return { handled: false };
215
- }
216
- /**
217
- * Process input when bracketed paste is not enabled
218
- * Uses timing heuristics to detect rapid multi-line input (likely paste)
219
- */
220
- processWithoutBracketed(input) {
221
- const now = Date.now();
222
- const timeSinceLastInput = now - this.lastInputTime;
223
- this.lastInputTime = now;
224
- // Check if this looks like part of a rapid paste
225
- if (timeSinceLastInput < this.PASTE_THRESHOLD_MS && this.multiLineBuffer.length > 0) {
226
- // Continue accumulating
227
- this.multiLineBuffer.push(input);
228
- this.multiLineMode = true;
229
- return {
230
- handled: true,
231
- isPending: true,
232
- lineCount: this.multiLineBuffer.length,
233
- preview: this.getPreview(this.multiLineBuffer),
234
- };
235
- }
236
- // If we were in multi-line mode and there's a pause, finalize
237
- if (this.multiLineMode && this.multiLineBuffer.length > 0) {
238
- this.multiLineBuffer.push(input);
239
- const result = this.multiLineBuffer.join('\n');
240
- const lineCount = this.multiLineBuffer.length;
241
- const preview = this.getPreview(this.multiLineBuffer);
242
- this.multiLineBuffer = [];
243
- this.multiLineMode = false;
244
- return {
245
- handled: true,
246
- result,
247
- lineCount,
248
- preview,
249
- };
250
- }
251
- // Check for embedded newlines in a single input (indicates paste)
252
- if (input.includes('\n')) {
253
- const lines = input.split('\n');
254
- return {
255
- handled: true,
256
- result: input,
257
- lineCount: lines.length,
258
- preview: this.getPreview(lines),
259
- };
260
- }
261
- // Start tracking for potential multi-line paste
262
- this.multiLineBuffer = [input];
263
- return { handled: false };
264
- }
265
- /**
266
- * Generate a preview string for multi-line content
267
- */
268
- getPreview(lines) {
269
- if (lines.length === 0) {
270
- return '';
271
- }
272
- const firstLine = lines[0] || '';
273
- const truncatedFirst = firstLine.length > 50
274
- ? firstLine.slice(0, 47) + '...'
275
- : firstLine;
276
- if (lines.length === 1) {
277
- return truncatedFirst;
278
- }
279
- return `${truncatedFirst} [+${lines.length - 1} more lines]`;
280
- }
281
- /**
282
- * Get current multi-line state for display purposes
283
- */
284
- getMultiLineState() {
285
- const buffer = this.inPaste ? this.pasteBuffer :
286
- this.multiLineMode ? this.multiLineBuffer : null;
287
- if (!buffer || buffer.length === 0) {
288
- return null;
289
- }
290
- const firstLine = buffer[0] || '';
291
- const lastLine = buffer[buffer.length - 1] || '';
292
- return {
293
- content: buffer.join('\n'),
294
- lineCount: buffer.length,
295
- firstLinePreview: firstLine.length > 40 ? firstLine.slice(0, 37) + '...' : firstLine,
296
- lastLinePreview: lastLine.length > 40 ? lastLine.slice(0, 37) + '...' : lastLine,
297
- };
298
- }
299
- /**
300
- * Format a multi-line paste as a collapsed block for display
301
- */
302
- static formatCollapsedBlock(content, maxPreviewLength = 60) {
303
- const lines = content.split('\n');
304
- if (lines.length <= 1) {
305
- return content;
306
- }
307
- const firstLine = (lines[0] || '').trim();
308
- const truncatedFirst = firstLine.length > maxPreviewLength
309
- ? firstLine.slice(0, maxPreviewLength - 3) + '...'
310
- : firstLine;
311
- return `📋 ${truncatedFirst} [${lines.length} lines]`;
312
- }
313
- /**
314
- * Check if currently processing a paste
315
- */
316
- isInPaste() {
317
- return this.inPaste || this.multiLineMode;
318
- }
319
- /**
320
- * Get the current paste buffer
321
- */
322
- getPasteBuffer() {
323
- return this.inPaste ? [...this.pasteBuffer] : [...this.multiLineBuffer];
324
- }
325
- /**
326
- * Finalize any pending multi-line input
327
- */
328
- finalize() {
329
- if (this.multiLineMode && this.multiLineBuffer.length > 1) {
330
- const result = this.multiLineBuffer.join('\n');
331
- this.multiLineBuffer = [];
332
- this.multiLineMode = false;
333
- return result;
334
- }
335
- return null;
336
- }
337
- /**
338
- * Reset the paste state
339
- */
340
- reset() {
341
- this.inPaste = false;
342
- this.pasteBuffer = [];
343
- this.multiLineMode = false;
344
- this.multiLineBuffer = [];
345
- this.rawPasteBuffer = '';
346
- this.capturingRaw = false;
347
- }
348
- }
349
- BracketedPasteManager.START_MARKER = '\u001b[200~';
350
- BracketedPasteManager.END_MARKER = '\u001b[201~';