@mcp-use/cli 1.0.0 → 1.0.1

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 (54) hide show
  1. package/dist/InputPrompt.d.ts +13 -0
  2. package/dist/InputPrompt.js +188 -0
  3. package/dist/MultilineInput.d.ts +13 -0
  4. package/dist/MultilineInput.js +154 -0
  5. package/dist/MultilineTextInput.d.ts +11 -0
  6. package/dist/MultilineTextInput.js +97 -0
  7. package/dist/PasteAwareInput.d.ts +13 -0
  8. package/dist/PasteAwareInput.js +183 -0
  9. package/dist/SimpleMultilineInput.d.ts +11 -0
  10. package/dist/SimpleMultilineInput.js +125 -0
  11. package/dist/app.d.ts +1 -5
  12. package/dist/app.js +291 -186
  13. package/dist/cli.js +2 -5
  14. package/dist/commands.d.ts +15 -30
  15. package/dist/commands.js +308 -568
  16. package/dist/components/AsciiLogo.d.ts +2 -0
  17. package/dist/components/AsciiLogo.js +7 -0
  18. package/dist/components/Footer.d.ts +5 -0
  19. package/dist/components/Footer.js +19 -0
  20. package/dist/components/InputPrompt.d.ts +13 -0
  21. package/dist/components/InputPrompt.js +188 -0
  22. package/dist/components/Messages.d.ts +21 -0
  23. package/dist/components/Messages.js +80 -0
  24. package/dist/components/ServerStatus.d.ts +7 -0
  25. package/dist/components/ServerStatus.js +36 -0
  26. package/dist/components/Spinner.d.ts +16 -0
  27. package/dist/components/Spinner.js +63 -0
  28. package/dist/components/ToolStatus.d.ts +8 -0
  29. package/dist/components/ToolStatus.js +33 -0
  30. package/dist/components/textInput.d.ts +1 -0
  31. package/dist/components/textInput.js +1 -0
  32. package/dist/logger.d.ts +10 -0
  33. package/dist/logger.js +48 -0
  34. package/dist/mcp-service.d.ts +5 -4
  35. package/dist/mcp-service.js +98 -207
  36. package/dist/services/agent-service.d.ts +56 -0
  37. package/dist/services/agent-service.js +203 -0
  38. package/dist/services/cli-service.d.ts +132 -0
  39. package/dist/services/cli-service.js +591 -0
  40. package/dist/services/index.d.ts +4 -0
  41. package/dist/services/index.js +4 -0
  42. package/dist/services/llm-service.d.ts +174 -0
  43. package/dist/services/llm-service.js +567 -0
  44. package/dist/services/mcp-config-service.d.ts +69 -0
  45. package/dist/services/mcp-config-service.js +426 -0
  46. package/dist/services/mcp-service.d.ts +1 -0
  47. package/dist/services/mcp-service.js +1 -0
  48. package/dist/services/utility-service.d.ts +47 -0
  49. package/dist/services/utility-service.js +208 -0
  50. package/dist/storage.js +4 -4
  51. package/dist/types.d.ts +30 -0
  52. package/dist/types.js +1 -0
  53. package/package.json +22 -8
  54. package/readme.md +68 -39
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface InputPromptProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSubmit: (value: string) => void;
6
+ onHistoryUp?: () => void;
7
+ onHistoryDown?: () => void;
8
+ placeholder?: string;
9
+ mask?: string;
10
+ focus?: boolean;
11
+ }
12
+ export declare const InputPrompt: React.FC<InputPromptProps>;
13
+ export {};
@@ -0,0 +1,188 @@
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ export const InputPrompt = ({ value, onChange, onSubmit, onHistoryUp, onHistoryDown, placeholder = 'Type your message...', mask, focus = true, }) => {
4
+ const [cursorPosition, setCursorPosition] = useState(value.length);
5
+ const [isMultiline, setIsMultiline] = useState(false);
6
+ const pasteBufferRef = useRef('');
7
+ const pasteTimeoutRef = useRef(null);
8
+ const lastInputTimeRef = useRef(Date.now());
9
+ // Track if we should allow multiline
10
+ useEffect(() => {
11
+ setIsMultiline(value.includes('\n'));
12
+ }, [value]);
13
+ // Update cursor position when value changes externally
14
+ useEffect(() => {
15
+ setCursorPosition(value.length);
16
+ }, [value]);
17
+ const handleSubmit = useCallback(() => {
18
+ const trimmedValue = value.trim();
19
+ if (trimmedValue) {
20
+ onSubmit(trimmedValue);
21
+ onChange('');
22
+ setCursorPosition(0);
23
+ setIsMultiline(false);
24
+ }
25
+ }, [value, onSubmit, onChange]);
26
+ const processPasteBuffer = useCallback(() => {
27
+ if (pasteBufferRef.current) {
28
+ const beforeCursor = value.slice(0, cursorPosition);
29
+ const afterCursor = value.slice(cursorPosition);
30
+ // Process the paste buffer to handle carriage returns
31
+ let processedPaste = pasteBufferRef.current
32
+ .replace(/\r\n/g, '\n')
33
+ .replace(/\r/g, '\n');
34
+ const newValue = beforeCursor + processedPaste + afterCursor;
35
+ onChange(newValue);
36
+ setCursorPosition(cursorPosition + processedPaste.length);
37
+ if (processedPaste.includes('\n')) {
38
+ setIsMultiline(true);
39
+ }
40
+ pasteBufferRef.current = '';
41
+ }
42
+ }, [value, cursorPosition, onChange]);
43
+ useInput((input, key) => {
44
+ if (!focus)
45
+ return;
46
+ const now = Date.now();
47
+ const timeSinceLastInput = now - lastInputTimeRef.current;
48
+ lastInputTimeRef.current = now;
49
+ // Detect paste by checking if we're getting rapid inputs or multi-character input
50
+ const isProbablyPaste = input &&
51
+ (timeSinceLastInput < 50 || input.length > 1) &&
52
+ !key.ctrl &&
53
+ !key.meta;
54
+ if (isProbablyPaste) {
55
+ // Accumulate paste buffer
56
+ pasteBufferRef.current += input;
57
+ // Clear existing timeout
58
+ if (pasteTimeoutRef.current) {
59
+ clearTimeout(pasteTimeoutRef.current);
60
+ }
61
+ // Set new timeout to process paste
62
+ pasteTimeoutRef.current = setTimeout(() => {
63
+ processPasteBuffer();
64
+ pasteTimeoutRef.current = null;
65
+ }, 100);
66
+ return;
67
+ }
68
+ // Process any pending paste buffer first
69
+ if (pasteBufferRef.current) {
70
+ processPasteBuffer();
71
+ }
72
+ // Submit on Enter (without modifiers)
73
+ if (key.return && !key.ctrl && !key.meta && !key.shift) {
74
+ handleSubmit();
75
+ return;
76
+ }
77
+ // New line on Ctrl+Enter, Meta+Enter, or Shift+Enter
78
+ if (key.return && (key.ctrl || key.meta || key.shift)) {
79
+ const beforeCursor = value.slice(0, cursorPosition);
80
+ const afterCursor = value.slice(cursorPosition);
81
+ onChange(beforeCursor + '\n' + afterCursor);
82
+ setCursorPosition(cursorPosition + 1);
83
+ setIsMultiline(true);
84
+ return;
85
+ }
86
+ // History navigation with up/down arrows (only in single-line mode)
87
+ if (!isMultiline) {
88
+ if (key.upArrow && onHistoryUp) {
89
+ onHistoryUp();
90
+ return;
91
+ }
92
+ if (key.downArrow && onHistoryDown) {
93
+ onHistoryDown();
94
+ return;
95
+ }
96
+ }
97
+ // Backspace
98
+ if (key.backspace || key.delete) {
99
+ if (cursorPosition > 0) {
100
+ const beforeCursor = value.slice(0, cursorPosition - 1);
101
+ const afterCursor = value.slice(cursorPosition);
102
+ onChange(beforeCursor + afterCursor);
103
+ setCursorPosition(cursorPosition - 1);
104
+ }
105
+ return;
106
+ }
107
+ // Navigation keys
108
+ if (key.leftArrow) {
109
+ setCursorPosition(Math.max(0, cursorPosition - 1));
110
+ return;
111
+ }
112
+ if (key.rightArrow) {
113
+ setCursorPosition(Math.min(value.length, cursorPosition + 1));
114
+ return;
115
+ }
116
+ // Home/End keys
117
+ if (key.ctrl && input === 'a') {
118
+ setCursorPosition(0);
119
+ return;
120
+ }
121
+ if (key.ctrl && input === 'e') {
122
+ setCursorPosition(value.length);
123
+ return;
124
+ }
125
+ // Regular character input (not paste)
126
+ if (input && !key.ctrl && !key.meta) {
127
+ const beforeCursor = value.slice(0, cursorPosition);
128
+ const afterCursor = value.slice(cursorPosition);
129
+ // Process input to handle carriage returns
130
+ const processedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
131
+ const newValue = beforeCursor + processedInput + afterCursor;
132
+ onChange(newValue);
133
+ setCursorPosition(cursorPosition + processedInput.length);
134
+ if (processedInput.includes('\n')) {
135
+ setIsMultiline(true);
136
+ }
137
+ }
138
+ });
139
+ // Render the input
140
+ const renderContent = () => {
141
+ if (!value && placeholder && !focus) {
142
+ return React.createElement(Text, { dimColor: true }, placeholder);
143
+ }
144
+ let displayValue = mask ? value.replace(/./g, mask) : value;
145
+ if (!isMultiline) {
146
+ // Single line rendering with cursor
147
+ const beforeCursor = displayValue.slice(0, cursorPosition);
148
+ const atCursor = displayValue[cursorPosition] || ' ';
149
+ const afterCursor = displayValue.slice(cursorPosition + 1);
150
+ return (React.createElement(Text, null,
151
+ beforeCursor,
152
+ focus && React.createElement(Text, { inverse: true }, atCursor),
153
+ afterCursor));
154
+ }
155
+ // Multiline rendering
156
+ const lines = displayValue.split('\n');
157
+ let pos = 0;
158
+ let cursorRow = 0;
159
+ let cursorCol = 0;
160
+ for (let row = 0; row < lines.length; row++) {
161
+ const lineLength = lines[row]?.length || 0;
162
+ if (pos + lineLength >= cursorPosition) {
163
+ cursorRow = row;
164
+ cursorCol = cursorPosition - pos;
165
+ break;
166
+ }
167
+ pos += lineLength + 1; // +1 for newline
168
+ }
169
+ return lines.map((line, row) => {
170
+ if (row === cursorRow && focus) {
171
+ const before = line.slice(0, cursorCol);
172
+ const at = line[cursorCol] || ' ';
173
+ const after = line.slice(cursorCol + 1);
174
+ return (React.createElement(Box, { key: row },
175
+ React.createElement(Text, null,
176
+ before,
177
+ React.createElement(Text, { inverse: true }, at),
178
+ after)));
179
+ }
180
+ return (React.createElement(Box, { key: row },
181
+ React.createElement(Text, null, line || ' ')));
182
+ });
183
+ };
184
+ return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
185
+ renderContent(),
186
+ isMultiline && focus && (React.createElement(Box, { marginTop: 1 },
187
+ React.createElement(Text, { dimColor: true, italic: true }, "Enter to submit \u2022 Ctrl/Shift+Enter for new line")))));
188
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface MultilineInputProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSubmit: (value: string) => void;
6
+ onHistoryUp?: () => void;
7
+ onHistoryDown?: () => void;
8
+ placeholder?: string;
9
+ mask?: string;
10
+ focus?: boolean;
11
+ }
12
+ export declare const MultilineInput: React.FC<MultilineInputProps>;
13
+ export {};
@@ -0,0 +1,154 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ export const MultilineInput = ({ value, onChange, onSubmit, onHistoryUp, onHistoryDown, placeholder = 'Type your message...', mask, focus = true }) => {
4
+ const [cursorPosition, setCursorPosition] = useState(value.length);
5
+ const [isMultiline, setIsMultiline] = useState(false);
6
+ // Track if we should allow multiline
7
+ useEffect(() => {
8
+ setIsMultiline(value.includes('\n'));
9
+ }, [value]);
10
+ // Update cursor position when value changes externally (e.g., from history)
11
+ useEffect(() => {
12
+ setCursorPosition(value.length);
13
+ }, [value]);
14
+ const handleSubmit = useCallback(() => {
15
+ const trimmedValue = value.trim();
16
+ if (trimmedValue) {
17
+ onSubmit(trimmedValue);
18
+ onChange('');
19
+ setCursorPosition(0);
20
+ setIsMultiline(false);
21
+ }
22
+ }, [value, onSubmit, onChange]);
23
+ useInput((input, key) => {
24
+ if (!focus)
25
+ return;
26
+ // Submit on Enter (without modifiers)
27
+ if (key.return && !key.ctrl && !key.meta && !key.shift) {
28
+ handleSubmit();
29
+ return;
30
+ }
31
+ // New line on Ctrl+Enter, Meta+Enter, or Shift+Enter
32
+ if (key.return && (key.ctrl || key.meta || key.shift)) {
33
+ const beforeCursor = value.slice(0, cursorPosition);
34
+ const afterCursor = value.slice(cursorPosition);
35
+ onChange(beforeCursor + '\n' + afterCursor);
36
+ setCursorPosition(cursorPosition + 1);
37
+ setIsMultiline(true);
38
+ return;
39
+ }
40
+ // History navigation with up/down arrows (only in single-line mode)
41
+ if (!isMultiline) {
42
+ if (key.upArrow && onHistoryUp) {
43
+ onHistoryUp();
44
+ return;
45
+ }
46
+ if (key.downArrow && onHistoryDown) {
47
+ onHistoryDown();
48
+ return;
49
+ }
50
+ }
51
+ // Backspace
52
+ if (key.backspace || key.delete) {
53
+ if (cursorPosition > 0) {
54
+ const beforeCursor = value.slice(0, cursorPosition - 1);
55
+ const afterCursor = value.slice(cursorPosition);
56
+ onChange(beforeCursor + afterCursor);
57
+ setCursorPosition(cursorPosition - 1);
58
+ }
59
+ return;
60
+ }
61
+ // Navigation keys
62
+ if (key.leftArrow) {
63
+ setCursorPosition(Math.max(0, cursorPosition - 1));
64
+ return;
65
+ }
66
+ if (key.rightArrow) {
67
+ setCursorPosition(Math.min(value.length, cursorPosition + 1));
68
+ return;
69
+ }
70
+ // Home/End keys
71
+ if (key.ctrl && input === 'a') {
72
+ setCursorPosition(0);
73
+ return;
74
+ }
75
+ if (key.ctrl && input === 'e') {
76
+ setCursorPosition(value.length);
77
+ return;
78
+ }
79
+ // Regular character input (including paste)
80
+ if (input && !key.ctrl && !key.meta) {
81
+ // Handle potential paste operation (multi-character input)
82
+ const beforeCursor = value.slice(0, cursorPosition);
83
+ const afterCursor = value.slice(cursorPosition);
84
+ // Process the input character by character to handle special characters
85
+ let processedInput = '';
86
+ for (let i = 0; i < input.length; i++) {
87
+ const char = input[i];
88
+ // Convert carriage returns to newlines
89
+ if (char === '\r') {
90
+ processedInput += '\n';
91
+ }
92
+ else {
93
+ processedInput += char;
94
+ }
95
+ }
96
+ const newValue = beforeCursor + processedInput + afterCursor;
97
+ onChange(newValue);
98
+ setCursorPosition(cursorPosition + processedInput.length);
99
+ // Check if the input contains newlines (e.g., from paste)
100
+ if (processedInput.includes('\n')) {
101
+ setIsMultiline(true);
102
+ }
103
+ }
104
+ });
105
+ // Render the input
106
+ const renderContent = () => {
107
+ if (!value && placeholder && !focus) {
108
+ return React.createElement(Text, { dimColor: true }, placeholder);
109
+ }
110
+ let displayValue = mask ? value.replace(/./g, mask) : value;
111
+ if (!isMultiline) {
112
+ // Single line rendering with cursor
113
+ const beforeCursor = displayValue.slice(0, cursorPosition);
114
+ const atCursor = displayValue[cursorPosition] || ' ';
115
+ const afterCursor = displayValue.slice(cursorPosition + 1);
116
+ return (React.createElement(Text, null,
117
+ beforeCursor,
118
+ focus && React.createElement(Text, { inverse: true }, atCursor),
119
+ afterCursor));
120
+ }
121
+ // Multiline rendering
122
+ const lines = displayValue.split('\n');
123
+ let pos = 0;
124
+ let cursorRow = 0;
125
+ let cursorCol = 0;
126
+ for (let row = 0; row < lines.length; row++) {
127
+ const lineLength = lines[row]?.length || 0;
128
+ if (pos + lineLength >= cursorPosition) {
129
+ cursorRow = row;
130
+ cursorCol = cursorPosition - pos;
131
+ break;
132
+ }
133
+ pos += lineLength + 1; // +1 for newline
134
+ }
135
+ return lines.map((line, row) => {
136
+ if (row === cursorRow && focus) {
137
+ const before = line.slice(0, cursorCol);
138
+ const at = line[cursorCol] || ' ';
139
+ const after = line.slice(cursorCol + 1);
140
+ return (React.createElement(Box, { key: row },
141
+ React.createElement(Text, null,
142
+ before,
143
+ React.createElement(Text, { inverse: true }, at),
144
+ after)));
145
+ }
146
+ return React.createElement(Box, { key: row },
147
+ React.createElement(Text, null, line || ' '));
148
+ });
149
+ };
150
+ return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
151
+ renderContent(),
152
+ isMultiline && focus && (React.createElement(Box, { marginTop: 1 },
153
+ React.createElement(Text, { dimColor: true, italic: true }, "Enter to submit \u2022 Ctrl/Shift+Enter for new line")))));
154
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface MultilineTextInputProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSubmit: (value: string) => void;
6
+ placeholder?: string;
7
+ mask?: string;
8
+ focus?: boolean;
9
+ }
10
+ export declare const MultilineTextInput: React.FC<MultilineTextInputProps>;
11
+ export {};
@@ -0,0 +1,97 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ export const MultilineTextInput = ({ value, onChange, onSubmit, placeholder = '', mask, focus = true }) => {
4
+ const [cursorPosition, setCursorPosition] = useState(value.length);
5
+ useEffect(() => {
6
+ setCursorPosition(value.length);
7
+ }, [value]);
8
+ useInput((input, key) => {
9
+ if (!focus)
10
+ return;
11
+ if (key.return && !key.shift) {
12
+ // Submit on Enter (without Shift)
13
+ onSubmit(value);
14
+ return;
15
+ }
16
+ if (key.return && key.shift) {
17
+ // New line on Shift+Enter
18
+ const beforeCursor = value.slice(0, cursorPosition);
19
+ const afterCursor = value.slice(cursorPosition);
20
+ const newValue = beforeCursor + '\n' + afterCursor;
21
+ onChange(newValue);
22
+ setCursorPosition(cursorPosition + 1);
23
+ return;
24
+ }
25
+ if (key.backspace || key.delete) {
26
+ if (cursorPosition > 0) {
27
+ const beforeCursor = value.slice(0, cursorPosition - 1);
28
+ const afterCursor = value.slice(cursorPosition);
29
+ onChange(beforeCursor + afterCursor);
30
+ setCursorPosition(Math.max(0, cursorPosition - 1));
31
+ }
32
+ return;
33
+ }
34
+ if (key.leftArrow) {
35
+ setCursorPosition(Math.max(0, cursorPosition - 1));
36
+ return;
37
+ }
38
+ if (key.rightArrow) {
39
+ setCursorPosition(Math.min(value.length, cursorPosition + 1));
40
+ return;
41
+ }
42
+ // Handle paste (Ctrl+V or Cmd+V)
43
+ if ((key.ctrl || key.meta) && input === 'v') {
44
+ // Unfortunately, Ink doesn't provide clipboard content directly
45
+ // This is a limitation of terminal-based input
46
+ return;
47
+ }
48
+ // Regular character input (including multi-character pastes)
49
+ if (input && !key.ctrl && !key.meta) {
50
+ const beforeCursor = value.slice(0, cursorPosition);
51
+ const afterCursor = value.slice(cursorPosition);
52
+ const newValue = beforeCursor + input + afterCursor;
53
+ onChange(newValue);
54
+ setCursorPosition(cursorPosition + input.length);
55
+ }
56
+ });
57
+ // Render the input with cursor
58
+ const renderContent = () => {
59
+ let displayValue = value;
60
+ if (mask) {
61
+ displayValue = value.replace(/./g, mask);
62
+ }
63
+ if (!displayValue && placeholder && !focus) {
64
+ return React.createElement(Text, { dimColor: true }, placeholder);
65
+ }
66
+ // Add cursor to the display value
67
+ const beforeCursor = displayValue.slice(0, cursorPosition);
68
+ const atCursor = displayValue[cursorPosition] || ' ';
69
+ const afterCursor = displayValue.slice(cursorPosition + 1);
70
+ // Split by newlines and render each line
71
+ const beforeLines = beforeCursor.split('\n');
72
+ const afterLines = (atCursor + afterCursor).split('\n');
73
+ return (React.createElement(React.Fragment, null,
74
+ beforeLines.map((line, index) => {
75
+ const isLastBeforeLine = index === beforeLines.length - 1;
76
+ if (isLastBeforeLine && afterLines.length > 0) {
77
+ // This line contains the cursor
78
+ const firstAfterLine = afterLines[0] || '';
79
+ const cursorChar = firstAfterLine[0] || ' ';
80
+ const restOfLine = firstAfterLine.slice(1);
81
+ return (React.createElement(Box, { key: `line-${index}` },
82
+ React.createElement(Text, null,
83
+ line,
84
+ React.createElement(Text, { inverse: true }, cursorChar),
85
+ restOfLine)));
86
+ }
87
+ return React.createElement(Box, { key: `line-${index}` },
88
+ React.createElement(Text, null, line));
89
+ }),
90
+ afterLines.slice(1).map((line, index) => (React.createElement(Box, { key: `after-line-${index}` },
91
+ React.createElement(Text, null, line))))));
92
+ };
93
+ return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
94
+ renderContent(),
95
+ focus && value.includes('\n') && (React.createElement(Box, { marginTop: 1 },
96
+ React.createElement(Text, { dimColor: true, italic: true }, "(Shift+Enter for new line, Enter to submit)")))));
97
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface PasteAwareInputProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSubmit: (value: string) => void;
6
+ onHistoryUp?: () => void;
7
+ onHistoryDown?: () => void;
8
+ placeholder?: string;
9
+ mask?: string;
10
+ focus?: boolean;
11
+ }
12
+ export declare const PasteAwareInput: React.FC<PasteAwareInputProps>;
13
+ export {};
@@ -0,0 +1,183 @@
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ export const PasteAwareInput = ({ value, onChange, onSubmit, onHistoryUp, onHistoryDown, placeholder = 'Type your message...', mask, focus = true }) => {
4
+ const [cursorPosition, setCursorPosition] = useState(value.length);
5
+ const [isMultiline, setIsMultiline] = useState(false);
6
+ const pasteBufferRef = useRef('');
7
+ const pasteTimeoutRef = useRef(null);
8
+ const lastInputTimeRef = useRef(Date.now());
9
+ // Track if we should allow multiline
10
+ useEffect(() => {
11
+ setIsMultiline(value.includes('\n'));
12
+ }, [value]);
13
+ // Update cursor position when value changes externally
14
+ useEffect(() => {
15
+ setCursorPosition(value.length);
16
+ }, [value]);
17
+ const handleSubmit = useCallback(() => {
18
+ const trimmedValue = value.trim();
19
+ if (trimmedValue) {
20
+ onSubmit(trimmedValue);
21
+ onChange('');
22
+ setCursorPosition(0);
23
+ setIsMultiline(false);
24
+ }
25
+ }, [value, onSubmit, onChange]);
26
+ const processPasteBuffer = useCallback(() => {
27
+ if (pasteBufferRef.current) {
28
+ const beforeCursor = value.slice(0, cursorPosition);
29
+ const afterCursor = value.slice(cursorPosition);
30
+ // Process the paste buffer to handle carriage returns
31
+ let processedPaste = pasteBufferRef.current.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
32
+ const newValue = beforeCursor + processedPaste + afterCursor;
33
+ onChange(newValue);
34
+ setCursorPosition(cursorPosition + processedPaste.length);
35
+ if (processedPaste.includes('\n')) {
36
+ setIsMultiline(true);
37
+ }
38
+ pasteBufferRef.current = '';
39
+ }
40
+ }, [value, cursorPosition, onChange]);
41
+ useInput((input, key) => {
42
+ if (!focus)
43
+ return;
44
+ const now = Date.now();
45
+ const timeSinceLastInput = now - lastInputTimeRef.current;
46
+ lastInputTimeRef.current = now;
47
+ // Detect paste by checking if we're getting rapid inputs or multi-character input
48
+ const isProbablyPaste = input && (timeSinceLastInput < 50 || input.length > 1) && !key.ctrl && !key.meta;
49
+ if (isProbablyPaste) {
50
+ // Accumulate paste buffer
51
+ pasteBufferRef.current += input;
52
+ // Clear existing timeout
53
+ if (pasteTimeoutRef.current) {
54
+ clearTimeout(pasteTimeoutRef.current);
55
+ }
56
+ // Set new timeout to process paste
57
+ pasteTimeoutRef.current = setTimeout(() => {
58
+ processPasteBuffer();
59
+ pasteTimeoutRef.current = null;
60
+ }, 100);
61
+ return;
62
+ }
63
+ // Process any pending paste buffer first
64
+ if (pasteBufferRef.current) {
65
+ processPasteBuffer();
66
+ }
67
+ // Submit on Enter (without modifiers)
68
+ if (key.return && !key.ctrl && !key.meta && !key.shift) {
69
+ handleSubmit();
70
+ return;
71
+ }
72
+ // New line on Ctrl+Enter, Meta+Enter, or Shift+Enter
73
+ if (key.return && (key.ctrl || key.meta || key.shift)) {
74
+ const beforeCursor = value.slice(0, cursorPosition);
75
+ const afterCursor = value.slice(cursorPosition);
76
+ onChange(beforeCursor + '\n' + afterCursor);
77
+ setCursorPosition(cursorPosition + 1);
78
+ setIsMultiline(true);
79
+ return;
80
+ }
81
+ // History navigation with up/down arrows (only in single-line mode)
82
+ if (!isMultiline) {
83
+ if (key.upArrow && onHistoryUp) {
84
+ onHistoryUp();
85
+ return;
86
+ }
87
+ if (key.downArrow && onHistoryDown) {
88
+ onHistoryDown();
89
+ return;
90
+ }
91
+ }
92
+ // Backspace
93
+ if (key.backspace || key.delete) {
94
+ if (cursorPosition > 0) {
95
+ const beforeCursor = value.slice(0, cursorPosition - 1);
96
+ const afterCursor = value.slice(cursorPosition);
97
+ onChange(beforeCursor + afterCursor);
98
+ setCursorPosition(cursorPosition - 1);
99
+ }
100
+ return;
101
+ }
102
+ // Navigation keys
103
+ if (key.leftArrow) {
104
+ setCursorPosition(Math.max(0, cursorPosition - 1));
105
+ return;
106
+ }
107
+ if (key.rightArrow) {
108
+ setCursorPosition(Math.min(value.length, cursorPosition + 1));
109
+ return;
110
+ }
111
+ // Home/End keys
112
+ if (key.ctrl && input === 'a') {
113
+ setCursorPosition(0);
114
+ return;
115
+ }
116
+ if (key.ctrl && input === 'e') {
117
+ setCursorPosition(value.length);
118
+ return;
119
+ }
120
+ // Regular character input (not paste)
121
+ if (input && !key.ctrl && !key.meta) {
122
+ const beforeCursor = value.slice(0, cursorPosition);
123
+ const afterCursor = value.slice(cursorPosition);
124
+ // Process input to handle carriage returns
125
+ const processedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
126
+ const newValue = beforeCursor + processedInput + afterCursor;
127
+ onChange(newValue);
128
+ setCursorPosition(cursorPosition + processedInput.length);
129
+ if (processedInput.includes('\n')) {
130
+ setIsMultiline(true);
131
+ }
132
+ }
133
+ });
134
+ // Render the input
135
+ const renderContent = () => {
136
+ if (!value && placeholder && !focus) {
137
+ return React.createElement(Text, { dimColor: true }, placeholder);
138
+ }
139
+ let displayValue = mask ? value.replace(/./g, mask) : value;
140
+ if (!isMultiline) {
141
+ // Single line rendering with cursor
142
+ const beforeCursor = displayValue.slice(0, cursorPosition);
143
+ const atCursor = displayValue[cursorPosition] || ' ';
144
+ const afterCursor = displayValue.slice(cursorPosition + 1);
145
+ return (React.createElement(Text, null,
146
+ beforeCursor,
147
+ focus && React.createElement(Text, { inverse: true }, atCursor),
148
+ afterCursor));
149
+ }
150
+ // Multiline rendering
151
+ const lines = displayValue.split('\n');
152
+ let pos = 0;
153
+ let cursorRow = 0;
154
+ let cursorCol = 0;
155
+ for (let row = 0; row < lines.length; row++) {
156
+ const lineLength = lines[row]?.length || 0;
157
+ if (pos + lineLength >= cursorPosition) {
158
+ cursorRow = row;
159
+ cursorCol = cursorPosition - pos;
160
+ break;
161
+ }
162
+ pos += lineLength + 1; // +1 for newline
163
+ }
164
+ return lines.map((line, row) => {
165
+ if (row === cursorRow && focus) {
166
+ const before = line.slice(0, cursorCol);
167
+ const at = line[cursorCol] || ' ';
168
+ const after = line.slice(cursorCol + 1);
169
+ return (React.createElement(Box, { key: row },
170
+ React.createElement(Text, null,
171
+ before,
172
+ React.createElement(Text, { inverse: true }, at),
173
+ after)));
174
+ }
175
+ return React.createElement(Box, { key: row },
176
+ React.createElement(Text, null, line || ' '));
177
+ });
178
+ };
179
+ return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
180
+ renderContent(),
181
+ isMultiline && focus && (React.createElement(Box, { marginTop: 1 },
182
+ React.createElement(Text, { dimColor: true, italic: true }, "Enter to submit \u2022 Ctrl/Shift+Enter for new line")))));
183
+ };