@plusonelabs/cue 0.0.37 → 0.0.39
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/backup-input/Cursor.backup.ts +261 -0
- package/backup-input/input-component.backup.tsx +121 -0
- package/backup-input/prompt-input.backup.tsx +80 -0
- package/backup-input/useTextInput.backup.ts +632 -0
- package/cue-v0.0.39.tar.gz +0 -0
- package/dist/cli.mjs +293 -287
- package/package.json +1 -1
- package/cue-v0.0.37.tar.gz +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles multi-line text navigation and editing
|
|
3
|
+
*/
|
|
4
|
+
export class Cursor {
|
|
5
|
+
readonly offset: number;
|
|
6
|
+
readonly text: string;
|
|
7
|
+
|
|
8
|
+
constructor(text: string, offset: number = 0) {
|
|
9
|
+
this.text = text;
|
|
10
|
+
this.offset = Math.max(0, Math.min(text.length, offset));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static fromText(text: string, _columns: number, offset: number = 0): Cursor {
|
|
14
|
+
return new Cursor(text, offset);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Navigation methods
|
|
18
|
+
left(): Cursor {
|
|
19
|
+
return new Cursor(this.text, this.offset - 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
right(): Cursor {
|
|
23
|
+
return new Cursor(this.text, this.offset + 1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
up(): Cursor {
|
|
27
|
+
const lines = this.text.split('\n');
|
|
28
|
+
let currentLine = 0;
|
|
29
|
+
let currentCol = 0;
|
|
30
|
+
let charCount = 0;
|
|
31
|
+
|
|
32
|
+
// Find current line and column
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const lineLength = lines[i].length;
|
|
35
|
+
if (charCount + lineLength >= this.offset) {
|
|
36
|
+
currentLine = i;
|
|
37
|
+
currentCol = this.offset - charCount;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
charCount += lineLength + 1; // +1 for newline
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (currentLine === 0) {
|
|
44
|
+
return new Cursor(this.text, 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Move to previous line, maintaining column position if possible
|
|
48
|
+
const prevLine = lines[currentLine - 1];
|
|
49
|
+
const newCol = Math.min(currentCol, prevLine.length);
|
|
50
|
+
|
|
51
|
+
let newOffset = 0;
|
|
52
|
+
for (let i = 0; i < currentLine - 1; i++) {
|
|
53
|
+
newOffset += lines[i].length + 1;
|
|
54
|
+
}
|
|
55
|
+
newOffset += newCol;
|
|
56
|
+
|
|
57
|
+
return new Cursor(this.text, newOffset);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
down(): Cursor {
|
|
61
|
+
const lines = this.text.split('\n');
|
|
62
|
+
let currentLine = 0;
|
|
63
|
+
let currentCol = 0;
|
|
64
|
+
let charCount = 0;
|
|
65
|
+
|
|
66
|
+
// Find current line and column
|
|
67
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68
|
+
const lineLength = lines[i].length;
|
|
69
|
+
if (charCount + lineLength >= this.offset) {
|
|
70
|
+
currentLine = i;
|
|
71
|
+
currentCol = this.offset - charCount;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
charCount += lineLength + 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (currentLine >= lines.length - 1) {
|
|
78
|
+
return new Cursor(this.text, this.text.length);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Move to next line, maintaining column position if possible
|
|
82
|
+
const nextLine = lines[currentLine + 1];
|
|
83
|
+
const newCol = Math.min(currentCol, nextLine.length);
|
|
84
|
+
|
|
85
|
+
let newOffset = 0;
|
|
86
|
+
for (let i = 0; i <= currentLine; i++) {
|
|
87
|
+
newOffset += lines[i].length + 1;
|
|
88
|
+
}
|
|
89
|
+
newOffset += newCol;
|
|
90
|
+
|
|
91
|
+
return new Cursor(this.text, Math.min(newOffset, this.text.length));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
startOfLine(): Cursor {
|
|
95
|
+
const lines = this.text.split('\n');
|
|
96
|
+
let charCount = 0;
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const lineLength = lines[i].length;
|
|
100
|
+
if (charCount + lineLength >= this.offset) {
|
|
101
|
+
return new Cursor(this.text, charCount);
|
|
102
|
+
}
|
|
103
|
+
charCount += lineLength + 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Cursor(this.text, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
endOfLine(): Cursor {
|
|
110
|
+
const lines = this.text.split('\n');
|
|
111
|
+
let charCount = 0;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < lines.length; i++) {
|
|
114
|
+
const lineLength = lines[i].length;
|
|
115
|
+
if (charCount + lineLength >= this.offset) {
|
|
116
|
+
return new Cursor(this.text, charCount + lineLength);
|
|
117
|
+
}
|
|
118
|
+
charCount += lineLength + 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return new Cursor(this.text, this.text.length);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
prevWord(): Cursor {
|
|
125
|
+
let pos = this.offset;
|
|
126
|
+
|
|
127
|
+
// Skip current word if we're in the middle of one
|
|
128
|
+
while (pos > 0 && /\w/.test(this.text[pos - 1])) {
|
|
129
|
+
pos--;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Skip whitespace
|
|
133
|
+
while (pos > 0 && /\s/.test(this.text[pos - 1])) {
|
|
134
|
+
pos--;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Skip to beginning of previous word
|
|
138
|
+
while (pos > 0 && /\w/.test(this.text[pos - 1])) {
|
|
139
|
+
pos--;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return new Cursor(this.text, pos);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
nextWord(): Cursor {
|
|
146
|
+
let pos = this.offset;
|
|
147
|
+
|
|
148
|
+
// Skip current word
|
|
149
|
+
while (pos < this.text.length && /\w/.test(this.text[pos])) {
|
|
150
|
+
pos++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Skip whitespace
|
|
154
|
+
while (pos < this.text.length && /\s/.test(this.text[pos])) {
|
|
155
|
+
pos++;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return new Cursor(this.text, pos);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Editing methods
|
|
162
|
+
insert(str: string): Cursor {
|
|
163
|
+
// Convert \r to \n for consistent line handling
|
|
164
|
+
const normalizedStr = str.replace(/\r/g, '\n');
|
|
165
|
+
const newText =
|
|
166
|
+
this.text.slice(0, this.offset) +
|
|
167
|
+
normalizedStr +
|
|
168
|
+
this.text.slice(this.offset);
|
|
169
|
+
return new Cursor(newText, this.offset + normalizedStr.length);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
backspace(): Cursor {
|
|
173
|
+
if (this.offset === 0) {
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
const newText =
|
|
177
|
+
this.text.slice(0, this.offset - 1) + this.text.slice(this.offset);
|
|
178
|
+
return new Cursor(newText, this.offset - 1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
del(): Cursor {
|
|
182
|
+
if (this.offset >= this.text.length) {
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
const newText =
|
|
186
|
+
this.text.slice(0, this.offset) + this.text.slice(this.offset + 1);
|
|
187
|
+
return new Cursor(newText, this.offset);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
deleteToLineStart(): Cursor {
|
|
191
|
+
const lineStart = this.startOfLine();
|
|
192
|
+
const newText =
|
|
193
|
+
this.text.slice(0, lineStart.offset) + this.text.slice(this.offset);
|
|
194
|
+
return new Cursor(newText, lineStart.offset);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
deleteToLineEnd(): Cursor {
|
|
198
|
+
const lineEnd = this.endOfLine();
|
|
199
|
+
const newText =
|
|
200
|
+
this.text.slice(0, this.offset) + this.text.slice(lineEnd.offset);
|
|
201
|
+
return new Cursor(newText, this.offset);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
deleteWordBefore(): Cursor {
|
|
205
|
+
const prevWord = this.prevWord();
|
|
206
|
+
const newText =
|
|
207
|
+
this.text.slice(0, prevWord.offset) + this.text.slice(this.offset);
|
|
208
|
+
return new Cursor(newText, prevWord.offset);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
deleteWordAfter(): Cursor {
|
|
212
|
+
const nextWord = this.nextWord();
|
|
213
|
+
const newText =
|
|
214
|
+
this.text.slice(0, this.offset) + this.text.slice(nextWord.offset);
|
|
215
|
+
return new Cursor(newText, this.offset);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Utility methods
|
|
219
|
+
equals(other: Cursor): boolean {
|
|
220
|
+
return this.offset === other.offset && this.text === other.text;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
render(
|
|
224
|
+
cursorChar: string,
|
|
225
|
+
mask?: string,
|
|
226
|
+
invert?: (text: string) => string
|
|
227
|
+
): string {
|
|
228
|
+
if (!invert) {
|
|
229
|
+
invert = (text: string) => text;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const lines = this.text.split('\n');
|
|
233
|
+
let currentLine = 0;
|
|
234
|
+
let currentCol = 0;
|
|
235
|
+
let charCount = 0;
|
|
236
|
+
|
|
237
|
+
// Find cursor position
|
|
238
|
+
for (let i = 0; i < lines.length; i++) {
|
|
239
|
+
const lineLength = lines[i].length;
|
|
240
|
+
if (charCount + lineLength >= this.offset) {
|
|
241
|
+
currentLine = i;
|
|
242
|
+
currentCol = this.offset - charCount;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
charCount += lineLength + 1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Render lines with cursor
|
|
249
|
+
return lines
|
|
250
|
+
.map((line, i) => {
|
|
251
|
+
if (i === currentLine) {
|
|
252
|
+
const beforeCursor = line.slice(0, currentCol);
|
|
253
|
+
const atCursor = line[currentCol] || cursorChar;
|
|
254
|
+
const afterCursor = line.slice(currentCol + 1);
|
|
255
|
+
return beforeCursor + invert(atCursor) + afterCursor;
|
|
256
|
+
}
|
|
257
|
+
return line;
|
|
258
|
+
})
|
|
259
|
+
.join('\n');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
|
|
4
|
+
interface InputComponentProps {
|
|
5
|
+
value: string;
|
|
6
|
+
suggestions: string[];
|
|
7
|
+
selectedSuggestion: number;
|
|
8
|
+
message: { show: boolean; text?: string };
|
|
9
|
+
commands: Array<{ name: string; description: string }>;
|
|
10
|
+
showWelcome: boolean;
|
|
11
|
+
showShortcuts: boolean;
|
|
12
|
+
cursor: number;
|
|
13
|
+
selection: { start: number; end: number } | null;
|
|
14
|
+
chatTarget?: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function InputComponent({
|
|
18
|
+
value,
|
|
19
|
+
suggestions,
|
|
20
|
+
selectedSuggestion,
|
|
21
|
+
message,
|
|
22
|
+
commands,
|
|
23
|
+
showWelcome,
|
|
24
|
+
showShortcuts,
|
|
25
|
+
cursor,
|
|
26
|
+
selection,
|
|
27
|
+
chatTarget,
|
|
28
|
+
}: InputComponentProps) {
|
|
29
|
+
return (
|
|
30
|
+
<Box flexDirection="column">
|
|
31
|
+
<Box borderStyle="round" borderColor="gray" paddingX={1}>
|
|
32
|
+
<Text>
|
|
33
|
+
<Text color="gray">
|
|
34
|
+
{chatTarget
|
|
35
|
+
? (() => {
|
|
36
|
+
// If chatTarget looks like a file path, don't truncate it
|
|
37
|
+
if (chatTarget.includes('/') || chatTarget.includes('\\')) {
|
|
38
|
+
// For file paths, show the filename or last part
|
|
39
|
+
const parts = chatTarget.split(/[/\\]/);
|
|
40
|
+
const filename = parts[parts.length - 1];
|
|
41
|
+
return `[chat ${filename}] > `;
|
|
42
|
+
}
|
|
43
|
+
// For regular client IDs, use the original truncation logic
|
|
44
|
+
return `[chat ${chatTarget.length <= 16 ? chatTarget : `${chatTarget.substring(0, 8)}...${chatTarget.substring(chatTarget.length - 4)}`}] > `;
|
|
45
|
+
})()
|
|
46
|
+
: '> '}
|
|
47
|
+
</Text>
|
|
48
|
+
{showWelcome && !value ? (
|
|
49
|
+
<Text backgroundColor="white" color="black">
|
|
50
|
+
_
|
|
51
|
+
</Text>
|
|
52
|
+
) : selection ? (
|
|
53
|
+
<>
|
|
54
|
+
{(() => {
|
|
55
|
+
const start = Math.min(selection.start, selection.end);
|
|
56
|
+
const end = Math.max(selection.start, selection.end);
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
<Text color="white">{value.slice(0, start)}</Text>
|
|
60
|
+
<Text backgroundColor="blue" color="white">
|
|
61
|
+
{value.slice(start, end)}
|
|
62
|
+
</Text>
|
|
63
|
+
<Text color="white">{value.slice(end)}</Text>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
})()}
|
|
67
|
+
</>
|
|
68
|
+
) : (
|
|
69
|
+
<>
|
|
70
|
+
<Text color="white">{value.slice(0, cursor)}</Text>
|
|
71
|
+
<Text backgroundColor="white" color="black">
|
|
72
|
+
{cursor < value.length ? value[cursor] : '_'}
|
|
73
|
+
</Text>
|
|
74
|
+
<Text color="white">{value.slice(cursor + 1)}</Text>
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
</Text>
|
|
78
|
+
</Box>
|
|
79
|
+
|
|
80
|
+
{suggestions.length > 0 && (
|
|
81
|
+
<Box flexDirection="column" paddingX={1}>
|
|
82
|
+
{suggestions.map((suggestion, index) => {
|
|
83
|
+
const command = commands.find((cmd) => cmd.name === suggestion);
|
|
84
|
+
return (
|
|
85
|
+
<Text
|
|
86
|
+
key={suggestion}
|
|
87
|
+
color={index === selectedSuggestion ? 'blue' : 'gray'}
|
|
88
|
+
>
|
|
89
|
+
{index === selectedSuggestion ? '> ' : ' '}/{suggestion} -{' '}
|
|
90
|
+
{command?.description || ''}
|
|
91
|
+
</Text>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
</Box>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{showShortcuts && (
|
|
98
|
+
<Box flexDirection="column" paddingX={1}>
|
|
99
|
+
<Text color="gray"> ? - Show/hide this help</Text>
|
|
100
|
+
<Text color="gray"> / - Show commands</Text>
|
|
101
|
+
<Text color="gray"> Esc Esc - Clear input</Text>
|
|
102
|
+
<Text color="gray"> ↑/↓ - Navigate commands</Text>
|
|
103
|
+
<Text color="gray"> Tab/Enter - Select command</Text>
|
|
104
|
+
<Text color="gray"> Ctrl+C - Exit</Text>
|
|
105
|
+
</Box>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{message.show && message.text && (
|
|
109
|
+
<Box paddingX={1}>
|
|
110
|
+
<Text color="yellow">{message.text}</Text>
|
|
111
|
+
</Box>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{chatTarget && <Text color="blue">Chat mode - Esc to exit</Text>}
|
|
115
|
+
|
|
116
|
+
{showWelcome && !showShortcuts && suggestions.length === 0 && (
|
|
117
|
+
<Text color="gray">? for shortcuts</Text>
|
|
118
|
+
)}
|
|
119
|
+
</Box>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
|
|
4
|
+
interface PromptInputProps {
|
|
5
|
+
onSubmit: (input: string) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function PromptInput({
|
|
11
|
+
onSubmit,
|
|
12
|
+
disabled = false,
|
|
13
|
+
placeholder = '',
|
|
14
|
+
}: PromptInputProps) {
|
|
15
|
+
const [input, setInput] = useState('');
|
|
16
|
+
|
|
17
|
+
useInput((inputChar, key) => {
|
|
18
|
+
if (disabled) return;
|
|
19
|
+
|
|
20
|
+
// Handle Enter key - submit input
|
|
21
|
+
if (key.return) {
|
|
22
|
+
if (input.trim()) {
|
|
23
|
+
onSubmit(input.trim());
|
|
24
|
+
setInput('');
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle backspace/delete - multiple detection methods like anon-kode-main
|
|
30
|
+
if (
|
|
31
|
+
key.backspace ||
|
|
32
|
+
key.delete ||
|
|
33
|
+
inputChar === '\b' ||
|
|
34
|
+
inputChar === '\x7f' ||
|
|
35
|
+
inputChar === '\x08'
|
|
36
|
+
) {
|
|
37
|
+
setInput((prev) => prev.slice(0, -1));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle Ctrl+C - exit
|
|
42
|
+
if (key.ctrl && inputChar === 'c') {
|
|
43
|
+
process.exit(0);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Add regular characters - exclude special keys
|
|
48
|
+
if (
|
|
49
|
+
inputChar &&
|
|
50
|
+
inputChar.length === 1 &&
|
|
51
|
+
!key.ctrl &&
|
|
52
|
+
!key.escape &&
|
|
53
|
+
!key.tab &&
|
|
54
|
+
!key.return &&
|
|
55
|
+
!key.upArrow &&
|
|
56
|
+
!key.downArrow &&
|
|
57
|
+
!key.leftArrow &&
|
|
58
|
+
!key.rightArrow
|
|
59
|
+
) {
|
|
60
|
+
setInput((prev) => prev + inputChar);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const displayInput = input || placeholder;
|
|
65
|
+
const isPlaceholder = !input && placeholder;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Box>
|
|
69
|
+
<Text color="gray">> </Text>
|
|
70
|
+
<Text color={isPlaceholder ? 'gray' : 'white'}>
|
|
71
|
+
{displayInput}
|
|
72
|
+
{!disabled && !isPlaceholder && (
|
|
73
|
+
<Text backgroundColor="white" color="black">
|
|
74
|
+
_
|
|
75
|
+
</Text>
|
|
76
|
+
)}
|
|
77
|
+
</Text>
|
|
78
|
+
</Box>
|
|
79
|
+
);
|
|
80
|
+
}
|