arc402-cli 0.9.4 → 0.9.6
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/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +14 -0
- package/dist/commands/wallet.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +28 -5
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/InputLine.js +3 -3
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts +2 -1
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +5 -13
- package/dist/tui/Viewport.js.map +1 -1
- package/dist/tui/components/CustomTextInput.d.ts +13 -0
- package/dist/tui/components/CustomTextInput.d.ts.map +1 -0
- package/dist/tui/components/CustomTextInput.js +94 -0
- package/dist/tui/components/CustomTextInput.js.map +1 -0
- package/dist/tui/useCommand.js +2 -2
- package/dist/tui/useCommand.js.map +1 -1
- package/dist/tui/useScroll.d.ts.map +1 -1
- package/dist/tui/useScroll.js +6 -0
- package/dist/tui/useScroll.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/wallet.ts +14 -0
- package/src/tui/App.tsx +29 -4
- package/src/tui/InputLine.tsx +3 -3
- package/src/tui/Viewport.tsx +12 -23
- package/src/tui/components/CustomTextInput.tsx +127 -0
- package/src/tui/useCommand.ts +2 -2
- package/src/tui/useScroll.ts +4 -0
package/src/tui/Viewport.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
|
|
4
4
|
interface ViewportProps {
|
|
5
5
|
lines: string[];
|
|
6
6
|
scrollOffset: number;
|
|
7
7
|
isAutoScroll: boolean;
|
|
8
|
+
height: number;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -13,49 +14,37 @@ interface ViewportProps {
|
|
|
13
14
|
* scrollOffset=0 means pinned to bottom (auto-scroll).
|
|
14
15
|
* Positive scrollOffset means scrolled up by that many lines.
|
|
15
16
|
*/
|
|
16
|
-
export function Viewport({ lines, scrollOffset, isAutoScroll }: ViewportProps) {
|
|
17
|
-
const
|
|
18
|
-
const termRows = stdout?.rows ?? 24;
|
|
19
|
-
|
|
20
|
-
// Approximate viewport height for scroll slicing.
|
|
21
|
-
// The actual flex layout handles visual sizing; this is for computing
|
|
22
|
-
// which lines to show in the scroll window.
|
|
23
|
-
const viewportHeight = Math.max(1, termRows - 18);
|
|
17
|
+
export function Viewport({ lines, scrollOffset, isAutoScroll, height }: ViewportProps) {
|
|
18
|
+
const viewportHeight = Math.max(1, height);
|
|
24
19
|
|
|
25
20
|
// Compute the window slice
|
|
26
|
-
// scrollOffset=0 → show last viewportHeight lines
|
|
27
|
-
// scrollOffset=N → show lines ending viewportHeight+N from end
|
|
28
21
|
const totalLines = lines.length;
|
|
29
22
|
let endIdx: number;
|
|
30
23
|
let startIdx: number;
|
|
31
24
|
|
|
32
25
|
if (scrollOffset === 0) {
|
|
33
|
-
// Auto-scroll: pinned to bottom
|
|
34
26
|
endIdx = totalLines;
|
|
35
27
|
startIdx = Math.max(0, endIdx - viewportHeight);
|
|
36
28
|
} else {
|
|
37
|
-
// Scrolled up: scrollOffset lines from bottom
|
|
38
29
|
endIdx = Math.max(0, totalLines - scrollOffset);
|
|
39
30
|
startIdx = Math.max(0, endIdx - viewportHeight);
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
const visibleLines = lines.slice(startIdx, endIdx);
|
|
43
34
|
|
|
35
|
+
const canScrollUp = startIdx > 0;
|
|
44
36
|
const canScrollDown = scrollOffset > 0;
|
|
45
37
|
|
|
46
38
|
return (
|
|
47
39
|
<Box flexDirection="column" flexGrow={1}>
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<Box flexGrow={1} />
|
|
40
|
+
{canScrollUp && (
|
|
41
|
+
<Text dimColor>{" \u2191 more (Shift+Up)"}</Text>
|
|
42
|
+
)}
|
|
43
|
+
{visibleLines.map((line, i) => (
|
|
44
|
+
<Text key={startIdx + i}>{line}</Text>
|
|
45
|
+
))}
|
|
55
46
|
{canScrollDown && !isAutoScroll && (
|
|
56
|
-
<
|
|
57
|
-
<Text dimColor>↓ more</Text>
|
|
58
|
-
</Box>
|
|
47
|
+
<Text dimColor>{" \u2193 more (Shift+Down / Esc)"}</Text>
|
|
59
48
|
)}
|
|
60
49
|
</Box>
|
|
61
50
|
);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
|
+
|
|
4
|
+
interface CustomTextInputProps {
|
|
5
|
+
value: string;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
onSubmit?: (value: string) => void;
|
|
8
|
+
focus?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Minimal text input that does NOT intercept Tab, Up, Down, Escape, or Ctrl+C,
|
|
13
|
+
* allowing parent useInput handlers to receive those keys.
|
|
14
|
+
*/
|
|
15
|
+
export function CustomTextInput({
|
|
16
|
+
value,
|
|
17
|
+
onChange,
|
|
18
|
+
onSubmit,
|
|
19
|
+
focus = true,
|
|
20
|
+
}: CustomTextInputProps) {
|
|
21
|
+
const [cursorPos, setCursorPos] = useState(value.length);
|
|
22
|
+
const [cursorVisible, setCursorVisible] = useState(true);
|
|
23
|
+
|
|
24
|
+
// Keep cursor within bounds when value changes externally
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
setCursorPos((pos) => Math.min(pos, value.length));
|
|
27
|
+
}, [value]);
|
|
28
|
+
|
|
29
|
+
// Blink cursor
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!focus) return;
|
|
32
|
+
const timer = setInterval(() => {
|
|
33
|
+
setCursorVisible((v) => !v);
|
|
34
|
+
}, 530);
|
|
35
|
+
return () => clearInterval(timer);
|
|
36
|
+
}, [focus]);
|
|
37
|
+
|
|
38
|
+
useInput(
|
|
39
|
+
(input, key) => {
|
|
40
|
+
// Keys we explicitly do NOT handle — let parent see them:
|
|
41
|
+
// Tab, Up, Down, Escape, Ctrl+C
|
|
42
|
+
if (key.tab || key.upArrow || key.downArrow || key.escape) return;
|
|
43
|
+
if (input === "\x03") return; // Ctrl+C
|
|
44
|
+
|
|
45
|
+
// Enter/Return — submit
|
|
46
|
+
if (key.return) {
|
|
47
|
+
onSubmit?.(value);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Backspace
|
|
52
|
+
if (key.backspace || key.delete) {
|
|
53
|
+
if (cursorPos > 0) {
|
|
54
|
+
const next = value.slice(0, cursorPos - 1) + value.slice(cursorPos);
|
|
55
|
+
setCursorPos(cursorPos - 1);
|
|
56
|
+
onChange(next);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Left arrow
|
|
62
|
+
if (key.leftArrow) {
|
|
63
|
+
setCursorPos((p) => Math.max(0, p - 1));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Right arrow
|
|
68
|
+
if (key.rightArrow) {
|
|
69
|
+
setCursorPos((p) => Math.min(value.length, p + 1));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Home (Ctrl+A)
|
|
74
|
+
if (input === "\x01") {
|
|
75
|
+
setCursorPos(0);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// End (Ctrl+E)
|
|
80
|
+
if (input === "\x05") {
|
|
81
|
+
setCursorPos(value.length);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Ctrl+U — clear line
|
|
86
|
+
if (input === "\x15") {
|
|
87
|
+
setCursorPos(0);
|
|
88
|
+
onChange("");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Ctrl+K — kill to end of line
|
|
93
|
+
if (input === "\x0B") {
|
|
94
|
+
onChange(value.slice(0, cursorPos));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Ignore other control characters
|
|
99
|
+
if (input.length > 0 && input.charCodeAt(0) < 32) return;
|
|
100
|
+
|
|
101
|
+
// Regular character input (including paste)
|
|
102
|
+
if (input.length > 0) {
|
|
103
|
+
const next =
|
|
104
|
+
value.slice(0, cursorPos) + input + value.slice(cursorPos);
|
|
105
|
+
setCursorPos(cursorPos + input.length);
|
|
106
|
+
onChange(next);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{ isActive: focus }
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (!focus) {
|
|
113
|
+
return <Text dimColor>{value}</Text>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const before = value.slice(0, cursorPos);
|
|
117
|
+
const cursorChar = cursorPos < value.length ? value[cursorPos] : " ";
|
|
118
|
+
const after = value.slice(cursorPos + 1);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Text>
|
|
122
|
+
{before}
|
|
123
|
+
<Text inverse={cursorVisible}>{cursorChar}</Text>
|
|
124
|
+
{after}
|
|
125
|
+
</Text>
|
|
126
|
+
);
|
|
127
|
+
}
|
package/src/tui/useCommand.ts
CHANGED
|
@@ -68,14 +68,14 @@ export function useCommand(): UseCommandResult {
|
|
|
68
68
|
stdoutRemainder += chunk.toString("utf8");
|
|
69
69
|
const lines = stdoutRemainder.split("\n");
|
|
70
70
|
stdoutRemainder = lines.pop() ?? "";
|
|
71
|
-
for (const line of lines) onLine(line);
|
|
71
|
+
for (const line of lines) onLine(line.replace(/\r$/, ""));
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
child.stderr?.on("data", (chunk: Buffer) => {
|
|
75
75
|
stderrRemainder += chunk.toString("utf8");
|
|
76
76
|
const lines = stderrRemainder.split("\n");
|
|
77
77
|
stderrRemainder = lines.pop() ?? "";
|
|
78
|
-
for (const line of lines) onLine(line);
|
|
78
|
+
for (const line of lines) onLine(line.replace(/\r$/, ""));
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
child.on("close", (code) => {
|
package/src/tui/useScroll.ts
CHANGED
|
@@ -50,6 +50,10 @@ export function useScroll(viewportHeight: number): UseScrollResult {
|
|
|
50
50
|
scrollUp(viewportHeight);
|
|
51
51
|
} else if (key.pageDown) {
|
|
52
52
|
scrollDown(viewportHeight);
|
|
53
|
+
} else if (key.shift && key.upArrow) {
|
|
54
|
+
scrollUp(1);
|
|
55
|
+
} else if (key.shift && key.downArrow) {
|
|
56
|
+
scrollDown(1);
|
|
53
57
|
}
|
|
54
58
|
});
|
|
55
59
|
|