@letta-ai/letta-code 0.5.0 → 0.5.2
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/README.md +89 -2
- package/letta.js +3860 -1472
- package/package.json +1 -1
- package/vendor/ink-text-input/build/index.js +65 -18
package/package.json
CHANGED
|
@@ -2,9 +2,34 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { Text, useInput } from 'ink';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Determines if the input should be treated as a control sequence (not inserted as text).
|
|
7
|
+
* This centralizes escape sequence filtering to prevent garbage characters from being inserted.
|
|
8
|
+
*/
|
|
9
|
+
function isControlSequence(input, key) {
|
|
10
|
+
// Pasted content is handled separately
|
|
11
|
+
if (key?.isPasted) return true;
|
|
12
|
+
|
|
13
|
+
// Standard control keys (but NOT plain escape - apps may need it for shortcuts)
|
|
14
|
+
if (key.tab || (key.ctrl && input === 'c')) return true;
|
|
15
|
+
if (key.shift && key.tab) return true;
|
|
16
|
+
|
|
17
|
+
// Ctrl+W (delete word) - handled by parent component
|
|
18
|
+
if (key.ctrl && (input === 'w' || input === 'W')) return true;
|
|
19
|
+
|
|
20
|
+
// Option+Arrow escape sequences: Ink parses \x1bb as meta=true, input='b'
|
|
21
|
+
if (key.meta && (input === 'b' || input === 'B' || input === 'f' || input === 'F')) return true;
|
|
22
|
+
|
|
23
|
+
// Filter specific escape sequences that would insert garbage, but allow plain ESC through
|
|
24
|
+
// CSI sequences (ESC[...), Option+Delete (ESC + DEL), and other multi-char escape sequences
|
|
25
|
+
if (input && typeof input === 'string' && input.startsWith('\x1b') && input.length > 1) return true;
|
|
26
|
+
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
5
30
|
function TextInput({ value: originalValue, placeholder = '', focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit, externalCursorOffset, onCursorOffsetChange }) {
|
|
6
|
-
const [state, setState] = useState({ cursorOffset: (originalValue || '').length, cursorWidth: 0 });
|
|
7
|
-
const { cursorOffset, cursorWidth } = state;
|
|
31
|
+
const [state, setState] = useState({ cursorOffset: (originalValue || '').length, cursorWidth: 0, killBuffer: '' });
|
|
32
|
+
const { cursorOffset, cursorWidth, killBuffer } = state;
|
|
8
33
|
useEffect(() => {
|
|
9
34
|
setState(previousState => {
|
|
10
35
|
if (!focus || !showCursor) {
|
|
@@ -12,7 +37,7 @@ function TextInput({ value: originalValue, placeholder = '', focus = true, mask,
|
|
|
12
37
|
}
|
|
13
38
|
const newValue = originalValue || '';
|
|
14
39
|
if (previousState.cursorOffset > newValue.length - 1) {
|
|
15
|
-
return { cursorOffset: newValue.length, cursorWidth: 0 };
|
|
40
|
+
return { ...previousState, cursorOffset: newValue.length, cursorWidth: 0 };
|
|
16
41
|
}
|
|
17
42
|
return previousState;
|
|
18
43
|
});
|
|
@@ -21,7 +46,7 @@ function TextInput({ value: originalValue, placeholder = '', focus = true, mask,
|
|
|
21
46
|
if (typeof externalCursorOffset === 'number') {
|
|
22
47
|
const newValue = originalValue || '';
|
|
23
48
|
const clamped = Math.max(0, Math.min(externalCursorOffset, newValue.length));
|
|
24
|
-
setState(prev => ({ cursorOffset: clamped, cursorWidth: 0 }));
|
|
49
|
+
setState(prev => ({ ...prev, cursorOffset: clamped, cursorWidth: 0 }));
|
|
25
50
|
if (typeof onCursorOffsetChange === 'function') onCursorOffsetChange(clamped);
|
|
26
51
|
}
|
|
27
52
|
}, [externalCursorOffset, originalValue, onCursorOffsetChange]);
|
|
@@ -42,11 +67,8 @@ function TextInput({ value: originalValue, placeholder = '', focus = true, mask,
|
|
|
42
67
|
}
|
|
43
68
|
}
|
|
44
69
|
useInput((input, key) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
// Treat Escape as a control key (don't insert into value)
|
|
49
|
-
if (key.escape || (key.ctrl && input === 'c') || key.tab || (key.shift && key.tab)) {
|
|
70
|
+
// Filter control sequences (escape keys, Option+Arrow garbage, etc.)
|
|
71
|
+
if (isControlSequence(input, key)) {
|
|
50
72
|
return;
|
|
51
73
|
}
|
|
52
74
|
if (key.return) {
|
|
@@ -58,22 +80,25 @@ function TextInput({ value: originalValue, placeholder = '', focus = true, mask,
|
|
|
58
80
|
let nextCursorOffset = cursorOffset;
|
|
59
81
|
let nextValue = originalValue;
|
|
60
82
|
let nextCursorWidth = 0;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
let nextKillBuffer = killBuffer;
|
|
84
|
+
if (key.leftArrow || key.rightArrow) {
|
|
85
|
+
// Skip if meta is pressed - Option+Arrow is handled by parent for word navigation
|
|
86
|
+
if (key.meta) {
|
|
87
|
+
return;
|
|
64
88
|
}
|
|
65
|
-
}
|
|
66
|
-
else if (key.rightArrow) {
|
|
67
89
|
if (showCursor) {
|
|
68
|
-
nextCursorOffset
|
|
90
|
+
nextCursorOffset += key.leftArrow ? -1 : 1;
|
|
69
91
|
}
|
|
70
92
|
}
|
|
71
93
|
else if (key.upArrow || key.downArrow) {
|
|
72
|
-
//
|
|
73
|
-
// Parent will check cursor position to determine if at boundary
|
|
94
|
+
// Let parent decide (wrapped line navigation)
|
|
74
95
|
return;
|
|
75
96
|
}
|
|
76
97
|
else if (key.backspace || key.delete) {
|
|
98
|
+
// Skip if meta is pressed - Option+Delete is handled by parent for word deletion
|
|
99
|
+
if (key.meta) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
77
102
|
if (cursorOffset > 0) {
|
|
78
103
|
nextValue = originalValue.slice(0, cursorOffset - 1) + originalValue.slice(cursorOffset, originalValue.length);
|
|
79
104
|
nextCursorOffset--;
|
|
@@ -91,6 +116,28 @@ function TextInput({ value: originalValue, placeholder = '', focus = true, mask,
|
|
|
91
116
|
nextCursorOffset = originalValue.length;
|
|
92
117
|
}
|
|
93
118
|
}
|
|
119
|
+
else if (key.ctrl && input === 'k') {
|
|
120
|
+
// CTRL-K: kill from cursor to end of line
|
|
121
|
+
if (cursorOffset < originalValue.length) {
|
|
122
|
+
nextKillBuffer = originalValue.slice(cursorOffset);
|
|
123
|
+
nextValue = originalValue.slice(0, cursorOffset);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (key.ctrl && input === 'u') {
|
|
127
|
+
// CTRL-U: kill from beginning to cursor
|
|
128
|
+
if (cursorOffset > 0) {
|
|
129
|
+
nextKillBuffer = originalValue.slice(0, cursorOffset);
|
|
130
|
+
nextValue = originalValue.slice(cursorOffset);
|
|
131
|
+
nextCursorOffset = 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (key.ctrl && input === 'y') {
|
|
135
|
+
// CTRL-Y: yank (paste) from kill buffer
|
|
136
|
+
if (killBuffer) {
|
|
137
|
+
nextValue = originalValue.slice(0, cursorOffset) + killBuffer + originalValue.slice(cursorOffset);
|
|
138
|
+
nextCursorOffset = cursorOffset + killBuffer.length;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
94
141
|
else {
|
|
95
142
|
nextValue = originalValue.slice(0, cursorOffset) + input + originalValue.slice(cursorOffset, originalValue.length);
|
|
96
143
|
nextCursorOffset += input.length;
|
|
@@ -99,7 +146,7 @@ function TextInput({ value: originalValue, placeholder = '', focus = true, mask,
|
|
|
99
146
|
}
|
|
100
147
|
}
|
|
101
148
|
nextCursorOffset = Math.max(0, Math.min(nextCursorOffset, nextValue.length));
|
|
102
|
-
setState({ cursorOffset: nextCursorOffset, cursorWidth: nextCursorWidth });
|
|
149
|
+
setState(prev => ({ ...prev, cursorOffset: nextCursorOffset, cursorWidth: nextCursorWidth, killBuffer: nextKillBuffer }));
|
|
103
150
|
if (typeof onCursorOffsetChange === 'function') onCursorOffsetChange(nextCursorOffset);
|
|
104
151
|
if (nextValue !== originalValue) {
|
|
105
152
|
onChange(nextValue);
|