@rubixkube/rubix 0.0.2 → 0.0.3
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/CHANGELOG.md +29 -0
- package/LICENSE +33 -0
- package/README.md +29 -12
- package/dist/cli.js +7 -0
- package/dist/commands/login.js +23 -2
- package/dist/commands/model.js +84 -0
- package/dist/core/device-auth.js +39 -1
- package/dist/core/rubix-api.js +175 -20
- package/dist/core/session-store.js +36 -0
- package/dist/core/settings.js +25 -0
- package/dist/core/update-check.js +51 -0
- package/dist/core/whats-new.js +56 -0
- package/dist/ui/App.js +305 -39
- package/dist/ui/components/BrandPanel.js +1 -1
- package/dist/ui/components/ChatTranscript.js +61 -8
- package/dist/ui/components/Composer.js +66 -7
- package/dist/ui/components/DashboardPanel.js +1 -1
- package/dist/ui/components/SplashScreen.js +2 -8
- package/dist/ui/hooks/useBracketedPaste.js +27 -0
- package/package.json +6 -4
- package/patches/ink-multiline-input+0.1.0.patch +246 -16
|
@@ -9,5 +9,5 @@ const LOGO_LINES = [
|
|
|
9
9
|
"\\_| \\_\\__,_|_.__/|_/_/\\_\\_| |_|\\__,_.__/ \\___|",
|
|
10
10
|
];
|
|
11
11
|
export function BrandPanel({ cwd, model, user }) {
|
|
12
|
-
return (_jsx(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 0, marginBottom: 1, children: _jsxs(Box, { flexDirection: "row", width: "100%", children: [_jsxs(Box, { flexDirection: "column", width: 58, children: [LOGO_LINES.map((line, index) => (_jsx(Text, { color: index % 2 === 0 ? "cyan" : "magenta", children: line }, line))), _jsx(Text, { bold: true, color: "yellow", children: "RubixKube Agent CLI" }), _jsxs(Text, { dimColor: true, children: [model, " • ", user ? `logged in as ${user}` : "not logged in"] }), _jsx(Text, { dimColor: true, children: cwd })] }), _jsx(Box, { marginX: 1, children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "Tips for getting started" }), _jsx(Text, { dimColor: true, children: "1. Use /login to authenticate first." }), _jsx(Text, { dimColor: true, children: "2. Press / to open command search." }), _jsx(Text, { dimColor: true, children: "3. Use @ to mention files and ! for shell mode." }), _jsx(Box, { marginTop: 1 }), _jsx(Text, { color: "yellow", bold: true, children: "Recent activity" }), _jsx(Text, { dimColor: true, children: "No recent activity" })] })] }) }));
|
|
12
|
+
return (_jsx(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 0, marginBottom: 1, children: _jsxs(Box, { flexDirection: "row", width: "100%", children: [_jsxs(Box, { flexDirection: "column", width: 58, children: [LOGO_LINES.map((line, index) => (_jsx(Text, { color: index % 2 === 0 ? "cyan" : "magenta", children: line }, line))), _jsx(Text, { bold: true, color: "yellow", children: "RubixKube Agent CLI" }), _jsxs(Text, { dimColor: true, children: [model, " • ", user ? `logged in as ${user}` : "not logged in"] }), _jsx(Text, { dimColor: true, children: cwd })] }), _jsx(Box, { marginX: 1, children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "Tips for getting started" }), _jsx(Text, { dimColor: true, children: "1. Use /login to authenticate first." }), _jsx(Text, { dimColor: true, children: "2. Press / to open command search." }), _jsx(Text, { dimColor: true, children: "3. Use @ to mention files and '! ' (exclamation + space) for shell mode." }), _jsx(Box, { marginTop: 1 }), _jsx(Text, { color: "yellow", bold: true, children: "Recent activity" }), _jsx(Text, { dimColor: true, children: "No recent activity" })] })] }) }));
|
|
13
13
|
}
|
|
@@ -107,6 +107,36 @@ function buildTimelineRows(workflow, fullContent) {
|
|
|
107
107
|
}
|
|
108
108
|
if (event.type === "function_response") {
|
|
109
109
|
const raw = event.content || `[${name || eventId || "tool"}]`;
|
|
110
|
+
// Intercept Web UI component structures specifically mapped from Web Console
|
|
111
|
+
if (name === "create_ui_component") {
|
|
112
|
+
try {
|
|
113
|
+
// Remove the outer "[create_ui_component]" wrapper if present from stringification
|
|
114
|
+
const cleaned = raw.startsWith("[create_ui_component]\n")
|
|
115
|
+
? raw.replace("[create_ui_component]\n", "")
|
|
116
|
+
: raw;
|
|
117
|
+
const parsed = JSON.parse(cleaned);
|
|
118
|
+
if (parsed.status !== "failed" && !parsed.error && (parsed.title || parsed.body)) {
|
|
119
|
+
rows.push({
|
|
120
|
+
key: `${event.id}-${index}`,
|
|
121
|
+
label: "ui component",
|
|
122
|
+
content: "",
|
|
123
|
+
color: RUBIX_THEME.colors.thought,
|
|
124
|
+
uiData: {
|
|
125
|
+
title: parsed.title,
|
|
126
|
+
description: parsed.description,
|
|
127
|
+
body: parsed.body,
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
// Drop the preceding function_call for create_ui_component so the terminal won't get messy
|
|
131
|
+
if (rows.length > 1 && rows[rows.length - 2].label === "tool call" && rows[rows.length - 2].content.includes("create_ui_component")) {
|
|
132
|
+
rows.splice(rows.length - 2, 1);
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// eslint-disable-next-line no-empty
|
|
137
|
+
}
|
|
138
|
+
catch { }
|
|
139
|
+
}
|
|
110
140
|
const responseContent = fullContent ? tryPrettyJson(raw) || raw : compact(raw, 88);
|
|
111
141
|
const isError = /error|failed|exception|denied|invalid/i.test(responseContent);
|
|
112
142
|
if (!fullContent && previous?.label === "tool result" && previous.content === responseContent)
|
|
@@ -123,12 +153,13 @@ function buildTimelineRows(workflow, fullContent) {
|
|
|
123
153
|
return rows;
|
|
124
154
|
}
|
|
125
155
|
function buildWorkflowStats(workflow) {
|
|
126
|
-
const toolCalls = workflow.filter((e) => e.type === "function_call").length;
|
|
156
|
+
const toolCalls = workflow.filter((e) => e.type === "function_call" && !e.content.includes("create_ui_component")).length;
|
|
157
|
+
const thoughtCount = workflow.filter((e) => e.type === "thought").length;
|
|
127
158
|
const withTs = workflow.filter((e) => e.ts != null && e.ts > 0);
|
|
128
159
|
const durationSec = withTs.length >= 2
|
|
129
160
|
? Math.round((Math.max(...withTs.map((e) => e.ts)) - Math.min(...withTs.map((e) => e.ts))) / 1000)
|
|
130
161
|
: null;
|
|
131
|
-
return { toolCalls, durationSec };
|
|
162
|
+
return { toolCalls, thoughtCount, durationSec };
|
|
132
163
|
}
|
|
133
164
|
export const ChatTranscript = React.memo(function ChatTranscript({ messages, workflowViewMode = "detailed", }) {
|
|
134
165
|
if (messages.length === 0) {
|
|
@@ -160,16 +191,38 @@ export const ChatTranscript = React.memo(function ChatTranscript({ messages, wor
|
|
|
160
191
|
const hasWorkflow = workflow.length > 0;
|
|
161
192
|
// Build mixed timeline: interleave workflow events with text response
|
|
162
193
|
const renderMixedView = () => {
|
|
194
|
+
const uiComponents = timelineRows.filter(r => r.uiData);
|
|
195
|
+
const standardRows = timelineRows.filter(r => !r.uiData);
|
|
163
196
|
// Show timeline during streaming if workflow exists, or after streaming in detailed mode
|
|
164
197
|
const shouldShowTimeline = (isStreaming && hasWorkflow) || (!isStreaming && isDetailed && hasWorkflow);
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
const hasCollapsibleItems = stats.toolCalls > 0 || stats.thoughtCount > 0;
|
|
199
|
+
const renderSummaryFooter = () => {
|
|
200
|
+
if (isStreaming || !hasCollapsibleItems)
|
|
201
|
+
return null;
|
|
202
|
+
const parts = [];
|
|
203
|
+
if (stats.toolCalls > 0)
|
|
204
|
+
parts.push(`${stats.toolCalls} tool call${stats.toolCalls !== 1 ? "s" : ""}`);
|
|
205
|
+
if (stats.thoughtCount > 0)
|
|
206
|
+
parts.push(`${stats.thoughtCount} thought${stats.thoughtCount !== 1 ? "s" : ""}`);
|
|
207
|
+
const summaryText = parts.join(", ");
|
|
208
|
+
const timeText = stats.durationSec != null ? ` · ${stats.durationSec}s` : "";
|
|
209
|
+
return (_jsxs(Text, { dimColor: true, children: [" ··· ", summaryText, timeText, " ", isDetailed ? "(Ctrl+O to collapse)" : "(Ctrl+O for timeline)"] }));
|
|
210
|
+
};
|
|
211
|
+
return (_jsxs(Box, { flexDirection: "column", children: [standardRows.length > 0 && (_jsx(Box, { flexDirection: "column", children: standardRows
|
|
212
|
+
.filter((row) => shouldShowTimeline || row.label === "thought" || row.label === "tool call" || row.label === "tool error")
|
|
213
|
+
.map((row) => {
|
|
167
214
|
const lines = row.content.split("\n");
|
|
215
|
+
const firstLine = lines[0] || "";
|
|
216
|
+
if (!shouldShowTimeline) {
|
|
217
|
+
const displayTitle = row.label === "tool call"
|
|
218
|
+
? `${row.label}: ${firstLine}()`
|
|
219
|
+
: row.label === "thought"
|
|
220
|
+
? `${row.label}: ${firstLine} ...`
|
|
221
|
+
: `${row.label}`;
|
|
222
|
+
return (_jsxs(Text, { dimColor: true, children: [" ", _jsxs(Text, { color: row.color, children: ["\u25CF ", displayTitle] })] }, `condensed-${row.key}`));
|
|
223
|
+
}
|
|
168
224
|
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { children: i === 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: row.color, children: ["\u25CF ", row.label, ": "] }), _jsx(Text, { italic: true, dimColor: true, children: line })] })) : row.label === "thought" ? (_jsxs(Text, { dimColor: true, children: [" ", line] })) : (_jsxs(Text, { dimColor: true, children: [" ", line] })) }, `${row.key}-${i}`))) }, row.key));
|
|
169
|
-
}),
|
|
170
|
-
}
|
|
171
|
-
// Minimal view: text + summary stat
|
|
172
|
-
return (_jsxs(Box, { flexDirection: "column", children: [hasNonEmptyContent ? (_jsx(_Fragment, { children: contentLines.map((line, index) => (_jsxs(Text, { children: [_jsx(Text, { color: RUBIX_THEME.colors.assistantText, children: index === 0 ? "● " : " " }), line.length > 0 ? line : " "] }, `${message.id}-${index}`))) })) : null, hasWorkflow ? (_jsxs(Text, { dimColor: true, children: [" ", stats.toolCalls > 0 && `${stats.toolCalls} tool call${stats.toolCalls !== 1 ? "s" : ""}`, stats.toolCalls > 0 && stats.durationSec != null && " · ", stats.durationSec != null && `${stats.durationSec}s`, hasWorkflow && " · Ctrl+O for timeline"] })) : null] }));
|
|
225
|
+
}) })), uiComponents.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: shouldShowTimeline && standardRows.length > 0 ? 1 : 0, children: uiComponents.map((row) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, marginLeft: 4, paddingX: 2, paddingY: 1, borderStyle: "round", borderColor: row.uiData.color || "gray", children: [_jsx(Text, { bold: true, children: row.uiData.title }), row.uiData.description ? _jsx(Text, { dimColor: true, children: row.uiData.description }) : null, row.uiData.body ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: row.uiData.body }) })) : null] }, `ui-${row.key}`))) })), hasNonEmptyContent && (_jsx(Box, { flexDirection: "column", children: contentLines.map((line, index) => (_jsxs(Text, { children: [_jsx(Text, { color: RUBIX_THEME.colors.assistantText, children: (index === 0 && (!shouldShowTimeline || standardRows.length === 0)) ? "● " : " " }), line.length > 0 ? line : " "] }, `${message.id}-text-${index}`))) })), renderSummaryFooter()] }));
|
|
173
226
|
};
|
|
174
227
|
return (_jsx(Box, { flexDirection: "column", marginBottom: 0, children: renderMixedView() }, message.id));
|
|
175
228
|
}
|
|
@@ -6,32 +6,91 @@ import { MultilineInput } from "ink-multiline-input";
|
|
|
6
6
|
import { RUBIX_THEME } from "../theme.js";
|
|
7
7
|
/** Ctrl+letter shortcuts used by App — don't insert into composer. */
|
|
8
8
|
const GLOBAL_CTRL_KEYS = ["c", "d", "l", "o", "x"];
|
|
9
|
-
export const Composer = React.memo(function Composer({ value, resetToken, disabled, shellMode = false, placeholder = "Ask anything, / for commands, @ for files, ! for shell", rightStatus = "", busy = false, suggestion = "", suggestions = [], captureArrowKeys = false, onChange, onSubmit, }) {
|
|
9
|
+
export const Composer = React.memo(function Composer({ value, resetToken, disabled, shellMode = false, placeholder = "Ask anything, / for commands, @ for files, '! ' for shell", rightStatus = "", busy = false, suggestion = "", suggestions = [], captureArrowKeys = false, onChange, onSubmit, }) {
|
|
10
10
|
const effectivePlaceholder = disabled ? "busy..." : placeholder;
|
|
11
11
|
const bashMode = shellMode;
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
12
|
+
// ── Fast-typing fix ──────────────────────────────────────────────────────
|
|
13
|
+
// `MultilineInput` reads `value` *inside* its input handler closure.
|
|
14
|
+
// When typing quickly, React hasn't re-rendered yet, so the next character
|
|
15
|
+
// is inserted into the previous (stale) value — dropping characters.
|
|
16
|
+
//
|
|
17
|
+
// We keep `latestValueRef` synchronously up-to-date on every onChange so
|
|
18
|
+
// the effective value fed to `MultilineInput` on re-render is always fresh.
|
|
19
|
+
const latestValueRef = React.useRef(value);
|
|
16
20
|
const lastResetTokenRef = React.useRef(resetToken);
|
|
17
|
-
const
|
|
21
|
+
const [bufferedValue, setBufferedValue] = useState(value);
|
|
22
|
+
// On external reset (e.g. submit) propagate down.
|
|
18
23
|
useEffect(() => {
|
|
19
24
|
if (resetToken !== lastResetTokenRef.current) {
|
|
20
25
|
lastResetTokenRef.current = resetToken;
|
|
26
|
+
latestValueRef.current = value;
|
|
21
27
|
setBufferedValue(value);
|
|
22
28
|
}
|
|
23
29
|
}, [value, resetToken]);
|
|
30
|
+
const effectiveValue = resetToken !== lastResetTokenRef.current ? value : bufferedValue;
|
|
24
31
|
const handleChange = (newValue) => {
|
|
32
|
+
latestValueRef.current = newValue;
|
|
25
33
|
setBufferedValue(newValue);
|
|
26
34
|
onChange(newValue);
|
|
27
35
|
};
|
|
36
|
+
// Ref that always holds the latest buffered value so the paste handler below
|
|
37
|
+
// can append to it without a stale closure.
|
|
38
|
+
const bufferedValueRef = React.useRef(bufferedValue);
|
|
39
|
+
bufferedValueRef.current = bufferedValue;
|
|
40
|
+
// Accumulates paste content while between [200~ and [201~ markers.
|
|
41
|
+
// null = not currently in a paste; string = accumulating paste.
|
|
42
|
+
const pasteBufferRef = React.useRef(null);
|
|
28
43
|
const useFilteredInput = useCallback((handler, isActive) => useInput((input, key) => {
|
|
29
44
|
if (key.ctrl && GLOBAL_CTRL_KEYS.includes((input ?? "").toLowerCase()))
|
|
30
45
|
return;
|
|
31
46
|
if (captureArrowKeys && (key.upArrow || key.downArrow))
|
|
32
47
|
return;
|
|
48
|
+
// ── Bracketed Paste Mode (primary path) ─────────────────────────────
|
|
49
|
+
// Terminal BPM wraps pastes in \x1b[200~...\x1b[201~.
|
|
50
|
+
// Ink's key parser consumes the leading \x1b and delivers the rest as
|
|
51
|
+
// bare input strings: "[200~" and "[201~".
|
|
52
|
+
if (input === "[200~") {
|
|
53
|
+
pasteBufferRef.current = ""; // start accumulating
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (input === "[201~") {
|
|
57
|
+
const pasted = pasteBufferRef.current;
|
|
58
|
+
pasteBufferRef.current = null;
|
|
59
|
+
if (pasted !== null && pasted.length > 0) {
|
|
60
|
+
const normalised = pasted.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
61
|
+
const next = bufferedValueRef.current + normalised;
|
|
62
|
+
setBufferedValue(next);
|
|
63
|
+
onChange(next);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// While accumulating a bracketed paste, absorb all input here so
|
|
68
|
+
// nothing reaches MultilineInput (which fires onSubmit on every \n).
|
|
69
|
+
if (pasteBufferRef.current !== null) {
|
|
70
|
+
if (key.return) {
|
|
71
|
+
pasteBufferRef.current += "\n";
|
|
72
|
+
}
|
|
73
|
+
else if (typeof input === "string" && input.length > 0) {
|
|
74
|
+
pasteBufferRef.current += input;
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// ────────────────────────────────────────────────────────────────────
|
|
79
|
+
// ── Fallback: heuristic for terminals without BPM ───────────────────
|
|
80
|
+
// A real Enter key arrives with key.return===true and input="" / "\r".
|
|
81
|
+
// A pasted chunk is a longer string that contains "\n".
|
|
82
|
+
if (typeof input === "string" && input.includes("\n") && input.length > 1) {
|
|
83
|
+
const normalised = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
84
|
+
const next = bufferedValueRef.current + normalised;
|
|
85
|
+
setBufferedValue(next);
|
|
86
|
+
onChange(next);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// ────────────────────────────────────────────────────────────────────
|
|
33
90
|
handler(input, key);
|
|
34
|
-
}, { isActive }),
|
|
91
|
+
}, { isActive }),
|
|
92
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
+
[captureArrowKeys, onChange]);
|
|
35
94
|
return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { borderStyle: "single", borderColor: bashMode ? RUBIX_THEME.colors.bash : RUBIX_THEME.colors.brand, paddingX: 1, flexDirection: "row", alignItems: "flex-start", children: [_jsx(Text, { color: bashMode ? RUBIX_THEME.colors.bashPrompt : RUBIX_THEME.colors.assistantText, children: bashMode ? "! " : "> " }), _jsx(MultilineInput, { value: effectiveValue, onChange: handleChange, onSubmit: onSubmit, placeholder: effectivePlaceholder, focus: !disabled, rows: 1, maxRows: 10, textStyle: { color: bashMode ? RUBIX_THEME.colors.bash : RUBIX_THEME.colors.assistantText }, keyBindings: {
|
|
36
95
|
submit: (key) => !!key.return && !key.shift && !key.meta && !key.alt,
|
|
37
96
|
newline: (key) => !!key.return && (!!key.shift || !!key.meta || !!key.alt),
|
|
@@ -55,7 +55,7 @@ const NAV_TIPS = [
|
|
|
55
55
|
{ cmd: "/cluster", desc: "switch cluster" },
|
|
56
56
|
{ cmd: "?", desc: "show shortcuts" },
|
|
57
57
|
];
|
|
58
|
-
export const DashboardPanel = React.memo(function DashboardPanel({ user, agentName, cwd, recentSessions, selectedCluster }) {
|
|
58
|
+
export const DashboardPanel = React.memo(function DashboardPanel({ user, agentName, cwd, recentSessions, selectedCluster, }) {
|
|
59
59
|
const greetingName = displayName(user);
|
|
60
60
|
const workspace = compactWorkspace(cwd);
|
|
61
61
|
const latest = recentSessions.slice(0, 2);
|
|
@@ -7,17 +7,11 @@ import { Select } from "@inkjs/ui";
|
|
|
7
7
|
import { RUBIX_THEME } from "../theme.js";
|
|
8
8
|
import { VERSION } from "../../version.js";
|
|
9
9
|
const GRAD_BLUE = ["#4ea8ff", "#7f88ff"];
|
|
10
|
-
const WHATS_NEW = [
|
|
11
|
-
"Multi-cluster support with /cluster",
|
|
12
|
-
"Session history with auto-resume",
|
|
13
|
-
"Streaming workflow event tracing",
|
|
14
|
-
"/console opens the web dashboard",
|
|
15
|
-
];
|
|
16
10
|
const SPLASH_OPTIONS = [
|
|
17
11
|
{ label: "Login to RubixKube", value: "login" },
|
|
18
12
|
{ label: "Exit", value: "exit" },
|
|
19
13
|
];
|
|
20
|
-
export const SplashScreen = React.memo(function SplashScreen({ agentName, onActionSelect, selectDisabled = false, }) {
|
|
14
|
+
export const SplashScreen = React.memo(function SplashScreen({ agentName, whatsNew, onActionSelect, selectDisabled = false, }) {
|
|
21
15
|
const handleChange = useCallback((value) => onActionSelect(value), [onActionSelect]);
|
|
22
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 2, paddingY: 1, children: [_jsx(Box, { flexDirection: "column", children: _jsx(Gradient, { colors: GRAD_BLUE, children: _jsx(BigText, { text: "RUBIXKUBE", font: "block" }) }) }), _jsx(Box, { flexDirection: "row", marginTop: 1, children: _jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { dimColor: true, children: agentName }), _jsx(Text, { color: RUBIX_THEME.colors.border, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: "Site Reliability Intelligence" })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: RUBIX_THEME.colors.border, dimColor: true, children: "─".repeat(64) }) }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", width: 38, children: [_jsxs(Text, { color: RUBIX_THEME.colors.brand, bold: true, children: ["What's New \u00B7 v", VERSION] }),
|
|
16
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 2, paddingY: 1, children: [_jsx(Box, { flexDirection: "column", children: _jsx(Gradient, { colors: GRAD_BLUE, children: _jsx(BigText, { text: "RUBIXKUBE", font: "block" }) }) }), _jsx(Box, { flexDirection: "row", marginTop: 1, children: _jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { dimColor: true, children: agentName }), _jsx(Text, { color: RUBIX_THEME.colors.border, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: "Site Reliability Intelligence" })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: RUBIX_THEME.colors.border, dimColor: true, children: "─".repeat(64) }) }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [whatsNew.length > 0 && (_jsxs(Box, { flexDirection: "column", width: 38, children: [_jsxs(Text, { color: RUBIX_THEME.colors.brand, bold: true, children: ["What's New \u00B7 v", VERSION] }), whatsNew.map((item) => (_jsxs(Text, { children: [_jsx(Text, { color: RUBIX_THEME.colors.brand, children: "\u203A" }), _jsxs(Text, { dimColor: true, children: [" ", item] })] }, item)))] })), _jsx(Box, { paddingX: 2, flexDirection: "column", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Text, { color: RUBIX_THEME.colors.border, children: "\u2502" }, i))) }), _jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsx(Text, { color: RUBIX_THEME.colors.brand, bold: true, children: "Setup" }), _jsx(Text, { dimColor: true, children: "1. Choose Login below or type /login." }), _jsx(Text, { dimColor: true, children: "2. Open the verification URL." }), _jsx(Text, { dimColor: true, children: "3. Enter the shown device code." }), _jsx(Box, { marginTop: 1 }), _jsx(Text, { color: RUBIX_THEME.colors.brand, bold: true, children: "Prompt" }), _jsx(Text, { dimColor: true, children: "/ commands \u00B7 @ files \u00B7 ! shell" })] })] }), _jsxs(Box, { marginTop: 2, flexDirection: "column", children: [_jsx(Text, { color: RUBIX_THEME.colors.brand, bold: true, children: "What would you like to do?" }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: SPLASH_OPTIONS, visibleOptionCount: 2, isDisabled: selectDisabled, onChange: handleChange }) })] })] }) }));
|
|
23
17
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* useBracketedPaste
|
|
4
|
+
*
|
|
5
|
+
* Enables / disables terminal Bracketed Paste Mode (BPM).
|
|
6
|
+
* When BPM is active the terminal wraps every CMD+V paste in
|
|
7
|
+
* the escape sequences \x1b[200~ ... \x1b[201~, which Composer
|
|
8
|
+
* detects inside `useFilteredInput` to accumulate the full paste
|
|
9
|
+
* block before delivering it to the input value.
|
|
10
|
+
*
|
|
11
|
+
* This hook only handles the terminal toggle — all accumulation
|
|
12
|
+
* logic lives in Composer.tsx so there is a single stdin consumer.
|
|
13
|
+
*/
|
|
14
|
+
export function useBracketedPaste() {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!process.stdout.isTTY)
|
|
17
|
+
return;
|
|
18
|
+
// Enable bracketed paste mode.
|
|
19
|
+
process.stdout.write("\x1b[?2004h");
|
|
20
|
+
return () => {
|
|
21
|
+
if (process.stdout.isTTY) {
|
|
22
|
+
// Disable bracketed paste mode on cleanup.
|
|
23
|
+
process.stdout.write("\x1b[?2004l");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubixkube/rubix",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Chat with your infrastructure from the terminal. RubixKube CLI for Site Reliability Intelligence—predict, prevent, and fix failures with AI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"patches",
|
|
12
|
-
"
|
|
12
|
+
"CHANGELOG.md",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
15
17
|
"postinstall": "patch-package",
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
"mttr",
|
|
34
36
|
"ai-agent"
|
|
35
37
|
],
|
|
36
|
-
"license": "
|
|
38
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
37
39
|
"repository": {
|
|
38
40
|
"type": "git",
|
|
39
41
|
"url": "https://github.com/rubixkube-io/rubix-cli"
|
|
@@ -65,4 +67,4 @@
|
|
|
65
67
|
"url": "https://github.com/rubixkube-io/rubix-cli/issues"
|
|
66
68
|
},
|
|
67
69
|
"author": "RubixKube <connect@rubixkube.ai>"
|
|
68
|
-
}
|
|
70
|
+
}
|
|
@@ -1,20 +1,211 @@
|
|
|
1
1
|
diff --git a/node_modules/ink-multiline-input/dist/index.js b/node_modules/ink-multiline-input/dist/index.js
|
|
2
|
-
index b1262e7..
|
|
2
|
+
index b1262e7..53a5c68 100644
|
|
3
3
|
--- a/node_modules/ink-multiline-input/dist/index.js
|
|
4
4
|
+++ b/node_modules/ink-multiline-input/dist/index.js
|
|
5
|
-
@@ -
|
|
6
|
-
|
|
5
|
+
@@ -1,5 +1,5 @@
|
|
6
|
+
// src/MultilineInput.tsx
|
|
7
|
+
-import { useState as useState2, useEffect as useEffect3, useMemo as useMemo2 } from "react";
|
|
8
|
+
+import { useState as useState2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
9
|
+
import { useInput } from "ink";
|
|
10
|
+
|
|
11
|
+
// src/ControlledMultilineInput.tsx
|
|
12
|
+
@@ -212,7 +212,8 @@ var ControlledMultilineInput = ({
|
|
13
|
+
flexDirection: "column",
|
|
14
|
+
flexGrow: 0,
|
|
15
|
+
flexShrink: 0,
|
|
16
|
+
- children: /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
|
|
17
|
+
+ children: /* @__PURE__ */ jsxs(Box2, {
|
|
18
|
+
+ flexDirection: "column", children: [
|
|
19
|
+
/* @__PURE__ */ jsxs(
|
|
20
|
+
Box2,
|
|
21
|
+
{
|
|
22
|
+
@@ -221,16 +222,23 @@ var ControlledMultilineInput = ({
|
|
23
|
+
flexShrink: 0,
|
|
24
|
+
flexDirection: "column",
|
|
25
|
+
children: [
|
|
26
|
+
- /* @__PURE__ */ jsx2(Box2, { marginTop: -scrollOffset, flexDirection: "column", children: /* @__PURE__ */ jsx2(MeasureBox, { onHeightChange: setContentHeight, children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
27
|
+
- preCursor?.map((segment, idx) => /* @__PURE__ */ jsx2(Text, { ...getStyle(segment.type), children: segment.value }, idx)),
|
|
28
|
+
- postCursor?.map((segment, idx) => /* @__PURE__ */ jsx2(Text, { ...getStyle(segment.type), children: segment.value }, idx))
|
|
29
|
+
- ] }) }) }),
|
|
30
|
+
+ /* @__PURE__ */ jsx2(Box2, {
|
|
31
|
+
+ marginTop: -scrollOffset, flexDirection: "column", children: /* @__PURE__ */ jsx2(MeasureBox, {
|
|
32
|
+
+ onHeightChange: setContentHeight, children: /* @__PURE__ */ jsxs(Text, {
|
|
33
|
+
+ children: [
|
|
34
|
+
+ preCursor?.map((segment, idx) => /* @__PURE__ */ jsx2(Text, { ...getStyle(segment.type), children: segment.value }, idx)),
|
|
35
|
+
+ postCursor?.map((segment, idx) => /* @__PURE__ */ jsx2(Text, { ...getStyle(segment.type), children: segment.value }, idx))
|
|
36
|
+
+ ]
|
|
37
|
+
+ })
|
|
38
|
+
+ })
|
|
39
|
+
+ }),
|
|
40
|
+
/* @__PURE__ */ jsx2(Spacer, {})
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
/* @__PURE__ */ jsx2(MeasureBox, { onHeightChange: setMarkerHeight, children: /* @__PURE__ */ jsx2(Text, { children: preCursor?.map((segment, idx) => /* @__PURE__ */ jsx2(Text, { ...getStyle(segment.type), children: segment.value }, idx)) }) })
|
|
45
|
+
- ] })
|
|
46
|
+
+ ]
|
|
47
|
+
+ })
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
@@ -250,21 +258,53 @@ var MultilineInput = ({
|
|
52
|
+
}) => {
|
|
53
|
+
const [cursorIndex, setCursorIndex] = useState2(value.length);
|
|
54
|
+
const [pasteLength, setPasteLength] = useState2(0);
|
|
55
|
+
+ // ── Fast-typing fix ─────────────────────────────────────────────────────
|
|
56
|
+
+ // Both `value` (prop) and `cursorIndex` (state) are captured by the
|
|
57
|
+
+ // useCustomInput closure at render time. When keystrokes arrive faster
|
|
58
|
+
+ // than React can re-render, the closure sees stale values and characters
|
|
59
|
+
+ // are inserted into an outdated string at wrong positions.
|
|
60
|
+
+ //
|
|
61
|
+
+ // Fix: mirror both in refs that we update **synchronously inside the
|
|
62
|
+
+ // handler** right after computing new values. The next keystroke reads
|
|
63
|
+
+ // the refs (always current) instead of the stale closure variables.
|
|
64
|
+
+ const valueRef = useRef2(value);
|
|
65
|
+
+ const cursorRef = useRef2(cursorIndex);
|
|
66
|
+
+ // Track the last value we sent to the parent via onChange so we can
|
|
67
|
+
+ // distinguish external resets (parent changed value independently)
|
|
68
|
+
+ // from our own in-flight updates that React hasn't applied yet.
|
|
69
|
+
+ const lastNotifiedRef = useRef2(value);
|
|
70
|
+
+ // Only sync from props when the parent has caught up or an external
|
|
71
|
+
+ // reset occurred. Without this guard, a render with a stale prop
|
|
72
|
+
+ // (from a previous keystroke's onChange that hasn't propagated yet)
|
|
73
|
+
+ // would overwrite the ref and undo the handler's synchronous updates.
|
|
74
|
+
+ if (value !== lastNotifiedRef.current) {
|
|
75
|
+
+ // External reset (e.g. submit cleared the value, or parent changed it)
|
|
76
|
+
+ valueRef.current = value;
|
|
77
|
+
+ cursorRef.current = cursorIndex;
|
|
78
|
+
+ lastNotifiedRef.current = value;
|
|
79
|
+
+ }
|
|
80
|
+
useEffect3(() => {
|
|
81
|
+
if (cursorIndex > value.length) {
|
|
82
|
+
setCursorIndex(value.length);
|
|
83
|
+
+ cursorRef.current = value.length;
|
|
84
|
+
}
|
|
85
|
+
}, [value, cursorIndex]);
|
|
86
|
+
useCustomInput((input, key) => {
|
|
87
|
+
+ // Read latest value & cursor from refs (never stale).
|
|
88
|
+
+ const v = valueRef.current;
|
|
89
|
+
+ const ci = cursorRef.current;
|
|
90
|
+
const submitKey = keyBindings?.submit ?? ((key2) => key2.return && key2.ctrl);
|
|
91
|
+
const newlineKey = keyBindings?.newline ?? ((key2) => key2.return);
|
|
92
|
+
if (submitKey(key)) {
|
|
93
|
+
- onSubmit?.(value);
|
|
94
|
+
+ onSubmit?.(v);
|
|
95
|
+
return;
|
|
96
|
+
} else if (newlineKey(key)) {
|
|
97
|
+
- const newValue = value.slice(0, cursorIndex) + "\n" + value.slice(cursorIndex);
|
|
98
|
+
+ const newValue = v.slice(0, ci) + "\n" + v.slice(ci);
|
|
99
|
+
+ valueRef.current = newValue;
|
|
100
|
+
+ cursorRef.current = ci + 1;
|
|
101
|
+
onChange(newValue);
|
|
102
|
+
- setCursorIndex(cursorIndex + 1);
|
|
103
|
+
+ lastNotifiedRef.current = newValue;
|
|
104
|
+
+ setCursorIndex(ci + 1);
|
|
105
|
+
setPasteLength(0);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
@@ -272,9 +312,12 @@ var MultilineInput = ({
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (keyBindings?.newline?.(key)) {
|
|
112
|
+
- const newValue = value.slice(0, cursorIndex) + "\n" + value.slice(cursorIndex);
|
|
113
|
+
+ const newValue = v.slice(0, ci) + "\n" + v.slice(ci);
|
|
114
|
+
+ valueRef.current = newValue;
|
|
115
|
+
+ cursorRef.current = ci + 1;
|
|
116
|
+
onChange(newValue);
|
|
117
|
+
- setCursorIndex(cursorIndex + 1);
|
|
118
|
+
+ lastNotifiedRef.current = newValue;
|
|
119
|
+
+ setCursorIndex(ci + 1);
|
|
120
|
+
setPasteLength(0);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
@@ -284,7 +327,7 @@ var MultilineInput = ({
|
|
124
|
+
}
|
|
125
|
+
if (key.upArrow) {
|
|
126
|
+
if (showCursor) {
|
|
127
|
+
- const lines = normalizeLineEndings(value).split("\n");
|
|
128
|
+
+ const lines = normalizeLineEndings(v).split("\n");
|
|
129
|
+
let currentLineIndex = 0;
|
|
130
|
+
let currentPos = 0;
|
|
131
|
+
let col = 0;
|
|
132
|
+
@@ -293,9 +336,9 @@ var MultilineInput = ({
|
|
133
|
+
if (line === void 0) continue;
|
|
134
|
+
const lineLen = line.length;
|
|
135
|
+
const lineEnd = currentPos + lineLen;
|
|
136
|
+
- if (cursorIndex >= currentPos && cursorIndex <= lineEnd) {
|
|
137
|
+
+ if (ci >= currentPos && ci <= lineEnd) {
|
|
138
|
+
currentLineIndex = i;
|
|
139
|
+
- col = cursorIndex - currentPos;
|
|
140
|
+
+ col = ci - currentPos;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
currentPos = lineEnd + 1;
|
|
144
|
+
@@ -311,6 +354,7 @@ var MultilineInput = ({
|
|
145
|
+
newIndex += lines[i].length + 1;
|
|
146
|
+
}
|
|
147
|
+
newIndex += newCol;
|
|
148
|
+
+ cursorRef.current = newIndex;
|
|
149
|
+
setCursorIndex(newIndex);
|
|
150
|
+
setPasteLength(0);
|
|
151
|
+
}
|
|
152
|
+
@@ -318,7 +362,7 @@ var MultilineInput = ({
|
|
153
|
+
}
|
|
154
|
+
} else if (key.downArrow) {
|
|
155
|
+
if (showCursor) {
|
|
156
|
+
- const lines = normalizeLineEndings(value).split("\n");
|
|
157
|
+
+ const lines = normalizeLineEndings(v).split("\n");
|
|
158
|
+
let currentLineIndex = 0;
|
|
159
|
+
let currentPos = 0;
|
|
160
|
+
let col = 0;
|
|
161
|
+
@@ -327,9 +371,9 @@ var MultilineInput = ({
|
|
162
|
+
if (line === void 0) continue;
|
|
163
|
+
const lineLen = line.length;
|
|
164
|
+
const lineEnd = currentPos + lineLen;
|
|
165
|
+
- if (cursorIndex >= currentPos && cursorIndex <= lineEnd) {
|
|
166
|
+
+ if (ci >= currentPos && ci <= lineEnd) {
|
|
167
|
+
currentLineIndex = i;
|
|
168
|
+
- col = cursorIndex - currentPos;
|
|
169
|
+
+ col = ci - currentPos;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
currentPos = lineEnd + 1;
|
|
173
|
+
@@ -345,6 +389,7 @@ var MultilineInput = ({
|
|
174
|
+
newIndex += lines[i].length + 1;
|
|
175
|
+
}
|
|
176
|
+
newIndex += newCol;
|
|
177
|
+
+ cursorRef.current = newIndex;
|
|
178
|
+
setCursorIndex(newIndex);
|
|
179
|
+
setPasteLength(0);
|
|
180
|
+
}
|
|
181
|
+
@@ -352,31 +397,118 @@ var MultilineInput = ({
|
|
182
|
+
}
|
|
183
|
+
} else if (key.leftArrow) {
|
|
184
|
+
if (showCursor) {
|
|
185
|
+
- setCursorIndex(Math.max(0, cursorIndex - 1));
|
|
186
|
+
+ const nc = Math.max(0, ci - 1);
|
|
187
|
+
+ cursorRef.current = nc;
|
|
188
|
+
+ setCursorIndex(nc);
|
|
189
|
+
setPasteLength(0);
|
|
190
|
+
}
|
|
191
|
+
} else if (key.rightArrow) {
|
|
192
|
+
if (showCursor) {
|
|
193
|
+
- setCursorIndex(Math.min(value.length, cursorIndex + 1));
|
|
194
|
+
+ const nc = Math.min(v.length, ci + 1);
|
|
195
|
+
+ cursorRef.current = nc;
|
|
196
|
+
+ setCursorIndex(nc);
|
|
7
197
|
setPasteLength(0);
|
|
8
198
|
}
|
|
9
199
|
+ } else if (key.ctrl && (input === "a" || input === "\u0001")) {
|
|
10
200
|
+ if (showCursor) {
|
|
11
|
-
+ const lines = normalizeLineEndings(
|
|
201
|
+
+ const lines = normalizeLineEndings(v).split("\n");
|
|
12
202
|
+ let currentPos = 0;
|
|
13
203
|
+ for (let i = 0; i < lines.length; i++) {
|
|
14
204
|
+ const line = lines[i];
|
|
15
205
|
+ if (line === void 0) continue;
|
|
16
206
|
+ const lineEnd = currentPos + line.length;
|
|
17
|
-
+ if (
|
|
207
|
+
+ if (ci >= currentPos && ci <= lineEnd) {
|
|
208
|
+
+ cursorRef.current = currentPos;
|
|
18
209
|
+ setCursorIndex(currentPos);
|
|
19
210
|
+ setPasteLength(0);
|
|
20
211
|
+ break;
|
|
@@ -24,13 +215,14 @@ index b1262e7..e6e7fca 100644
|
|
|
24
215
|
+ }
|
|
25
216
|
+ } else if (key.ctrl && (input === "e" || input === "\u0005")) {
|
|
26
217
|
+ if (showCursor) {
|
|
27
|
-
+ const lines = normalizeLineEndings(
|
|
218
|
+
+ const lines = normalizeLineEndings(v).split("\n");
|
|
28
219
|
+ let currentPos = 0;
|
|
29
220
|
+ for (let i = 0; i < lines.length; i++) {
|
|
30
221
|
+ const line = lines[i];
|
|
31
222
|
+ if (line === void 0) continue;
|
|
32
223
|
+ const lineEnd = currentPos + line.length;
|
|
33
|
-
+ if (
|
|
224
|
+
+ if (ci >= currentPos && ci <= lineEnd) {
|
|
225
|
+
+ cursorRef.current = lineEnd;
|
|
34
226
|
+ setCursorIndex(lineEnd);
|
|
35
227
|
+ setPasteLength(0);
|
|
36
228
|
+ break;
|
|
@@ -39,16 +231,18 @@ index b1262e7..e6e7fca 100644
|
|
|
39
231
|
+ }
|
|
40
232
|
+ }
|
|
41
233
|
+ } else if (key.ctrl && (input === "k" || input === "\u000b")) {
|
|
42
|
-
+ if (showCursor &&
|
|
43
|
-
+ const lines = normalizeLineEndings(
|
|
234
|
+
+ if (showCursor && ci < v.length) {
|
|
235
|
+
+ const lines = normalizeLineEndings(v).split("\n");
|
|
44
236
|
+ let currentPos = 0;
|
|
45
237
|
+ for (let i = 0; i < lines.length; i++) {
|
|
46
238
|
+ const line = lines[i];
|
|
47
239
|
+ if (line === void 0) continue;
|
|
48
240
|
+ const lineEnd = currentPos + line.length;
|
|
49
|
-
+ if (
|
|
50
|
-
+ const newValue =
|
|
241
|
+
+ if (ci >= currentPos && ci <= lineEnd) {
|
|
242
|
+
+ const newValue = v.slice(0, ci) + v.slice(lineEnd);
|
|
243
|
+
+ valueRef.current = newValue;
|
|
51
244
|
+ onChange(newValue);
|
|
245
|
+
+ lastNotifiedRef.current = newValue;
|
|
52
246
|
+ setPasteLength(0);
|
|
53
247
|
+ break;
|
|
54
248
|
+ }
|
|
@@ -56,16 +250,19 @@ index b1262e7..e6e7fca 100644
|
|
|
56
250
|
+ }
|
|
57
251
|
+ }
|
|
58
252
|
+ } else if (key.ctrl && (input === "u" || input === "\u0015")) {
|
|
59
|
-
+ if (showCursor &&
|
|
60
|
-
+ const lines = normalizeLineEndings(
|
|
253
|
+
+ if (showCursor && ci > 0) {
|
|
254
|
+
+ const lines = normalizeLineEndings(v).split("\n");
|
|
61
255
|
+ let currentPos = 0;
|
|
62
256
|
+ for (let i = 0; i < lines.length; i++) {
|
|
63
257
|
+ const line = lines[i];
|
|
64
258
|
+ if (line === void 0) continue;
|
|
65
259
|
+ const lineEnd = currentPos + line.length;
|
|
66
|
-
+ if (
|
|
67
|
-
+ const newValue =
|
|
260
|
+
+ if (ci >= currentPos && ci <= lineEnd) {
|
|
261
|
+
+ const newValue = v.slice(0, currentPos) + v.slice(ci);
|
|
262
|
+
+ valueRef.current = newValue;
|
|
263
|
+
+ cursorRef.current = currentPos;
|
|
68
264
|
+ onChange(newValue);
|
|
265
|
+
+ lastNotifiedRef.current = newValue;
|
|
69
266
|
+ setCursorIndex(currentPos);
|
|
70
267
|
+ setPasteLength(0);
|
|
71
268
|
+ break;
|
|
@@ -74,5 +271,38 @@ index b1262e7..e6e7fca 100644
|
|
|
74
271
|
+ }
|
|
75
272
|
+ }
|
|
76
273
|
} else if (key.return) {
|
|
77
|
-
|
|
274
|
+
- const newValue = value.slice(0, cursorIndex) + "\n" + value.slice(cursorIndex);
|
|
275
|
+
+ const newValue = v.slice(0, ci) + "\n" + v.slice(ci);
|
|
276
|
+
+ valueRef.current = newValue;
|
|
277
|
+
+ cursorRef.current = ci + 1;
|
|
78
278
|
onChange(newValue);
|
|
279
|
+
- setCursorIndex(cursorIndex + 1);
|
|
280
|
+
+ lastNotifiedRef.current = newValue;
|
|
281
|
+
+ setCursorIndex(ci + 1);
|
|
282
|
+
setPasteLength(0);
|
|
283
|
+
} else if (key.backspace || key.delete) {
|
|
284
|
+
- if (cursorIndex > 0) {
|
|
285
|
+
- const newValue = value.slice(0, cursorIndex - 1) + value.slice(cursorIndex);
|
|
286
|
+
+ if (ci > 0) {
|
|
287
|
+
+ const newValue = v.slice(0, ci - 1) + v.slice(ci);
|
|
288
|
+
+ valueRef.current = newValue;
|
|
289
|
+
+ cursorRef.current = ci - 1;
|
|
290
|
+
onChange(newValue);
|
|
291
|
+
- setCursorIndex(cursorIndex - 1);
|
|
292
|
+
+ lastNotifiedRef.current = newValue;
|
|
293
|
+
+ setCursorIndex(ci - 1);
|
|
294
|
+
setPasteLength(0);
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
if (input) {
|
|
298
|
+
- const newValue = value.slice(0, cursorIndex) + input + value.slice(cursorIndex);
|
|
299
|
+
+ const newValue = v.slice(0, ci) + input + v.slice(ci);
|
|
300
|
+
+ valueRef.current = newValue;
|
|
301
|
+
+ cursorRef.current = ci + input.length;
|
|
302
|
+
onChange(newValue);
|
|
303
|
+
- setCursorIndex(cursorIndex + input.length);
|
|
304
|
+
+ lastNotifiedRef.current = newValue;
|
|
305
|
+
+ setCursorIndex(ci + input.length);
|
|
306
|
+
setPasteLength(nextPasteLength);
|
|
307
|
+
}
|
|
308
|
+
}
|