arc402-cli 0.7.5 → 0.9.0
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/INK6-UX-SPEC.md +446 -0
- package/MIGRATION-SPEC.md +108 -0
- package/dist/abis.js +14 -17
- package/dist/abis.js.map +1 -1
- package/dist/bundler.d.ts +1 -1
- package/dist/bundler.d.ts.map +1 -1
- package/dist/bundler.js +27 -61
- package/dist/bundler.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -9
- package/dist/client.js.map +1 -1
- package/dist/coinbase-smart-wallet.js +1 -4
- package/dist/coinbase-smart-wallet.js.map +1 -1
- package/dist/commands/accept.js +25 -28
- package/dist/commands/accept.js.map +1 -1
- package/dist/commands/agent-handshake.js +15 -18
- package/dist/commands/agent-handshake.js.map +1 -1
- package/dist/commands/agent.js +98 -104
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/agreements.js +62 -98
- package/dist/commands/agreements.js.map +1 -1
- package/dist/commands/arbitrator.js +45 -81
- package/dist/commands/arbitrator.js.map +1 -1
- package/dist/commands/arena-handshake.js +27 -30
- package/dist/commands/arena-handshake.js.map +1 -1
- package/dist/commands/arena.js +12 -18
- package/dist/commands/arena.js.map +1 -1
- package/dist/commands/backup.js +30 -36
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cancel.js +15 -18
- package/dist/commands/cancel.js.map +1 -1
- package/dist/commands/channel.js +45 -81
- package/dist/commands/channel.js.map +1 -1
- package/dist/commands/coldstart.js +31 -34
- package/dist/commands/coldstart.js.map +1 -1
- package/dist/commands/config.js +23 -29
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contract-interaction.js +12 -15
- package/dist/commands/contract-interaction.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +98 -135
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/deliver.js +37 -76
- package/dist/commands/deliver.js.map +1 -1
- package/dist/commands/discover.js +24 -27
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/dispute.js +104 -110
- package/dist/commands/dispute.js.map +1 -1
- package/dist/commands/doctor.js +16 -55
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/endpoint.js +56 -95
- package/dist/commands/endpoint.js.map +1 -1
- package/dist/commands/feed.js +11 -18
- package/dist/commands/feed.js.map +1 -1
- package/dist/commands/hire.js +37 -40
- package/dist/commands/hire.js.map +1 -1
- package/dist/commands/migrate.js +30 -33
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/negotiate.d.ts.map +1 -1
- package/dist/commands/negotiate.js +34 -36
- package/dist/commands/negotiate.js.map +1 -1
- package/dist/commands/openshell.js +68 -104
- package/dist/commands/openshell.js.map +1 -1
- package/dist/commands/owner.js +17 -20
- package/dist/commands/owner.js.map +1 -1
- package/dist/commands/policy.js +41 -43
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/relay.d.ts.map +1 -1
- package/dist/commands/relay.js +18 -51
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/remediate.js +20 -23
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/reputation.js +25 -27
- package/dist/commands/reputation.js.map +1 -1
- package/dist/commands/setup.js +65 -104
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/trust.js +17 -20
- package/dist/commands/trust.js.map +1 -1
- package/dist/commands/verify.js +18 -21
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/wallet.js +619 -625
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.js +33 -36
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/watchtower.js +37 -73
- package/dist/commands/watchtower.js.map +1 -1
- package/dist/commands/workroom.d.ts.map +1 -1
- package/dist/commands/workroom.js +138 -171
- package/dist/commands/workroom.js.map +1 -1
- package/dist/config.js +21 -65
- package/dist/config.js.map +1 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +16 -53
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/hire-listener.d.ts +3 -3
- package/dist/daemon/hire-listener.d.ts.map +1 -1
- package/dist/daemon/hire-listener.js +13 -47
- package/dist/daemon/hire-listener.js.map +1 -1
- package/dist/daemon/index.d.ts +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +50 -88
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/job-lifecycle.d.ts +1 -1
- package/dist/daemon/job-lifecycle.d.ts.map +1 -1
- package/dist/daemon/job-lifecycle.js +11 -51
- package/dist/daemon/job-lifecycle.js.map +1 -1
- package/dist/daemon/notify.d.ts +1 -1
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +19 -53
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/token-metering.js +8 -47
- package/dist/daemon/token-metering.js.map +1 -1
- package/dist/daemon/userops.d.ts +2 -2
- package/dist/daemon/userops.d.ts.map +1 -1
- package/dist/daemon/userops.js +23 -27
- package/dist/daemon/userops.js.map +1 -1
- package/dist/daemon/wallet-monitor.d.ts +1 -1
- package/dist/daemon/wallet-monitor.d.ts.map +1 -1
- package/dist/daemon/wallet-monitor.js +8 -12
- package/dist/daemon/wallet-monitor.js.map +1 -1
- package/dist/drain-v4.js +26 -64
- package/dist/drain-v4.js.map +1 -1
- package/dist/endpoint-config.js +20 -63
- package/dist/endpoint-config.js.map +1 -1
- package/dist/endpoint-notify.js +9 -48
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +16 -50
- package/dist/index.js.map +1 -1
- package/dist/openshell-runtime.d.ts.map +1 -1
- package/dist/openshell-runtime.js +38 -82
- package/dist/openshell-runtime.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +77 -83
- package/dist/program.js.map +1 -1
- package/dist/repl.js +25 -31
- package/dist/repl.js.map +1 -1
- package/dist/signing.js +3 -6
- package/dist/signing.js.map +1 -1
- package/dist/telegram-notify.js +3 -40
- package/dist/telegram-notify.js.map +1 -1
- package/dist/tui/App.d.ts +1 -9
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +87 -65
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Footer.js +4 -7
- package/dist/tui/Footer.js.map +1 -1
- package/dist/tui/Header.d.ts +1 -2
- package/dist/tui/Header.d.ts.map +1 -1
- package/dist/tui/Header.js +9 -14
- package/dist/tui/Header.js.map +1 -1
- package/dist/tui/InputLine.d.ts +1 -2
- package/dist/tui/InputLine.d.ts.map +1 -1
- package/dist/tui/InputLine.js +92 -46
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts +5 -4
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +20 -13
- package/dist/tui/Viewport.js.map +1 -1
- package/dist/tui/WalletConnectPairing.d.ts +23 -0
- package/dist/tui/WalletConnectPairing.d.ts.map +1 -0
- package/dist/tui/WalletConnectPairing.js +75 -0
- package/dist/tui/WalletConnectPairing.js.map +1 -0
- package/dist/tui/components/Button.d.ts +7 -0
- package/dist/tui/components/Button.d.ts.map +1 -0
- package/dist/tui/components/Button.js +18 -0
- package/dist/tui/components/Button.js.map +1 -0
- package/dist/tui/components/CeremonyView.d.ts +13 -0
- package/dist/tui/components/CeremonyView.d.ts.map +1 -0
- package/dist/tui/components/CeremonyView.js +7 -0
- package/dist/tui/components/CeremonyView.js.map +1 -0
- package/dist/tui/components/CompletionDropdown.d.ts +7 -0
- package/dist/tui/components/CompletionDropdown.d.ts.map +1 -0
- package/dist/tui/components/CompletionDropdown.js +20 -0
- package/dist/tui/components/CompletionDropdown.js.map +1 -0
- package/dist/tui/components/ConfirmPrompt.d.ts +9 -0
- package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
- package/dist/tui/components/ConfirmPrompt.js +7 -0
- package/dist/tui/components/ConfirmPrompt.js.map +1 -0
- package/dist/tui/components/InteractiveTable.d.ts +14 -0
- package/dist/tui/components/InteractiveTable.d.ts.map +1 -0
- package/dist/tui/components/InteractiveTable.js +58 -0
- package/dist/tui/components/InteractiveTable.js.map +1 -0
- package/dist/tui/components/StepSpinner.d.ts +11 -0
- package/dist/tui/components/StepSpinner.d.ts.map +1 -0
- package/dist/tui/components/StepSpinner.js +29 -0
- package/dist/tui/components/StepSpinner.js.map +1 -0
- package/dist/tui/components/Toast.d.ts +18 -0
- package/dist/tui/components/Toast.d.ts.map +1 -0
- package/dist/tui/components/Toast.js +25 -0
- package/dist/tui/components/Toast.js.map +1 -0
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +28 -21
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/useChat.js +13 -19
- package/dist/tui/useChat.js.map +1 -1
- package/dist/tui/useCommand.d.ts +2 -7
- package/dist/tui/useCommand.d.ts.map +1 -1
- package/dist/tui/useCommand.js +77 -165
- package/dist/tui/useCommand.js.map +1 -1
- package/dist/tui/useNotifications.d.ts +9 -0
- package/dist/tui/useNotifications.d.ts.map +1 -0
- package/dist/tui/useNotifications.js +14 -0
- package/dist/tui/useNotifications.js.map +1 -0
- package/dist/tui/useScroll.js +9 -12
- package/dist/tui/useScroll.js.map +1 -1
- package/dist/ui/banner.d.ts +12 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +35 -19
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/colors.js +13 -19
- package/dist/ui/colors.js.map +1 -1
- package/dist/ui/format.js +6 -14
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/spinner.js +6 -12
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/tree.js +3 -6
- package/dist/ui/tree.js.map +1 -1
- package/dist/utils/format.js +27 -41
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/hash.js +4 -42
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/time.js +2 -6
- package/dist/utils/time.js.map +1 -1
- package/dist/wallet-router.d.ts +1 -1
- package/dist/wallet-router.d.ts.map +1 -1
- package/dist/wallet-router.js +12 -19
- package/dist/wallet-router.js.map +1 -1
- package/dist/walletconnect-session.d.ts +1 -1
- package/dist/walletconnect-session.d.ts.map +1 -1
- package/dist/walletconnect-session.js +6 -11
- package/dist/walletconnect-session.js.map +1 -1
- package/dist/walletconnect.d.ts +6 -1
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +32 -35
- package/dist/walletconnect.js.map +1 -1
- package/package.json +7 -6
- package/src/bundler.ts +1 -1
- package/src/client.ts +1 -1
- package/src/commands/accept.ts +7 -7
- package/src/commands/agent-handshake.ts +4 -4
- package/src/commands/agent.ts +9 -9
- package/src/commands/agreements.ts +8 -8
- package/src/commands/arbitrator.ts +5 -5
- package/src/commands/arena-handshake.ts +6 -6
- package/src/commands/arena.ts +2 -2
- package/src/commands/backup.ts +1 -1
- package/src/commands/cancel.ts +6 -6
- package/src/commands/channel.ts +6 -6
- package/src/commands/coldstart.ts +5 -5
- package/src/commands/config.ts +2 -2
- package/src/commands/contract-interaction.ts +2 -2
- package/src/commands/daemon.ts +14 -11
- package/src/commands/deliver.ts +9 -9
- package/src/commands/discover.ts +5 -5
- package/src/commands/dispute.ts +7 -7
- package/src/commands/doctor.ts +2 -2
- package/src/commands/endpoint.ts +6 -6
- package/src/commands/feed.ts +1 -1
- package/src/commands/hire.ts +10 -10
- package/src/commands/migrate.ts +7 -7
- package/src/commands/negotiate.ts +6 -5
- package/src/commands/openshell.ts +4 -4
- package/src/commands/owner.ts +5 -5
- package/src/commands/policy.ts +5 -5
- package/src/commands/relay.ts +5 -1
- package/src/commands/remediate.ts +5 -5
- package/src/commands/reputation.ts +6 -6
- package/src/commands/setup.ts +1 -1
- package/src/commands/trust.ts +6 -6
- package/src/commands/verify.ts +6 -6
- package/src/commands/wallet.ts +15 -15
- package/src/commands/watch.ts +3 -3
- package/src/commands/watchtower.ts +6 -6
- package/src/commands/workroom.ts +14 -10
- package/src/daemon/config.ts +2 -1
- package/src/daemon/hire-listener.ts +3 -3
- package/src/daemon/index.ts +10 -9
- package/src/daemon/job-lifecycle.ts +1 -1
- package/src/daemon/notify.ts +4 -4
- package/src/daemon/userops.ts +4 -4
- package/src/daemon/wallet-monitor.ts +2 -2
- package/src/endpoint-notify.ts +1 -1
- package/src/index.ts +8 -7
- package/src/openshell-runtime.ts +5 -1
- package/src/program.ts +36 -36
- package/src/repl.ts +3 -3
- package/src/tui/App.tsx +75 -52
- package/src/tui/Header.tsx +26 -12
- package/src/tui/InputLine.tsx +108 -33
- package/src/tui/Viewport.tsx +22 -18
- package/src/tui/WalletConnectPairing.tsx +131 -0
- package/src/tui/components/Button.tsx +38 -0
- package/src/tui/components/CeremonyView.tsx +39 -0
- package/src/tui/components/CompletionDropdown.tsx +59 -0
- package/src/tui/components/ConfirmPrompt.tsx +36 -0
- package/src/tui/components/InteractiveTable.tsx +112 -0
- package/src/tui/components/StepSpinner.tsx +84 -0
- package/src/tui/components/Toast.tsx +59 -0
- package/src/tui/index.tsx +27 -9
- package/src/tui/useChat.ts +1 -1
- package/src/tui/useCommand.ts +86 -183
- package/src/tui/useNotifications.ts +28 -0
- package/src/ui/banner.ts +29 -2
- package/src/ui/tree.ts +1 -1
- package/src/wallet-router.ts +2 -2
- package/src/walletconnect-session.ts +1 -1
- package/src/walletconnect.ts +20 -5
- package/tsconfig.json +16 -7
package/src/tui/InputLine.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, { useState, useCallback } from "react";
|
|
1
|
+
import React, { useState, useCallback, useMemo } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import TextInput from "ink-text-input";
|
|
4
|
-
import { createProgram } from "../program";
|
|
4
|
+
import { createProgram } from "../program.js";
|
|
5
|
+
import { CompletionDropdown } from "./components/CompletionDropdown.js";
|
|
5
6
|
|
|
6
7
|
const BUILTIN_CMDS = ["help", "exit", "quit", "clear", "status"];
|
|
7
8
|
|
|
@@ -11,8 +12,7 @@ interface InputLineProps {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Input line with command history navigation
|
|
15
|
-
* Uses ink-text-input for text input with cursor.
|
|
15
|
+
* Input line with command history navigation, tab completion, and dropdown.
|
|
16
16
|
*/
|
|
17
17
|
export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
18
18
|
const [value, setValue] = useState("");
|
|
@@ -20,6 +20,11 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
20
20
|
const [historyIdx, setHistoryIdx] = useState(-1);
|
|
21
21
|
const [historyTemp, setHistoryTemp] = useState("");
|
|
22
22
|
|
|
23
|
+
// Dropdown state
|
|
24
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
25
|
+
const [dropdownIdx, setDropdownIdx] = useState(0);
|
|
26
|
+
const [dropdownCandidates, setDropdownCandidates] = useState<string[]>([]);
|
|
27
|
+
|
|
23
28
|
// Lazily build command list for tab completion
|
|
24
29
|
const [topCmds] = useState<string[]>(() => {
|
|
25
30
|
try {
|
|
@@ -44,12 +49,30 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
44
49
|
}
|
|
45
50
|
});
|
|
46
51
|
|
|
52
|
+
const getCompletions = useCallback(
|
|
53
|
+
(input: string): string[] => {
|
|
54
|
+
const allTop = [...BUILTIN_CMDS, ...topCmds];
|
|
55
|
+
const trimmed = input.trimStart();
|
|
56
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
57
|
+
|
|
58
|
+
if (spaceIdx === -1) {
|
|
59
|
+
return allTop.filter((cmd) => cmd.startsWith(trimmed));
|
|
60
|
+
}
|
|
61
|
+
const parent = trimmed.slice(0, spaceIdx);
|
|
62
|
+
const rest = trimmed.slice(spaceIdx + 1);
|
|
63
|
+
const subs = subCmds.get(parent) ?? [];
|
|
64
|
+
return subs
|
|
65
|
+
.filter((s) => s.startsWith(rest))
|
|
66
|
+
.map((s) => `${parent} ${s}`);
|
|
67
|
+
},
|
|
68
|
+
[topCmds, subCmds]
|
|
69
|
+
);
|
|
70
|
+
|
|
47
71
|
const handleSubmit = useCallback(
|
|
48
72
|
(val: string) => {
|
|
49
73
|
const trimmed = val.trim();
|
|
50
74
|
if (!trimmed) return;
|
|
51
75
|
|
|
52
|
-
// Add to history (avoid duplicate of last entry)
|
|
53
76
|
setHistory((prev) => {
|
|
54
77
|
if (prev[prev.length - 1] === trimmed) return prev;
|
|
55
78
|
return [...prev, trimmed];
|
|
@@ -57,6 +80,7 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
57
80
|
setHistoryIdx(-1);
|
|
58
81
|
setHistoryTemp("");
|
|
59
82
|
setValue("");
|
|
83
|
+
setShowDropdown(false);
|
|
60
84
|
|
|
61
85
|
onSubmit(trimmed);
|
|
62
86
|
},
|
|
@@ -67,7 +91,33 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
67
91
|
(_input, key) => {
|
|
68
92
|
if (isDisabled) return;
|
|
69
93
|
|
|
70
|
-
//
|
|
94
|
+
// ── Dropdown navigation ───────────────────────────────────────────
|
|
95
|
+
if (showDropdown) {
|
|
96
|
+
if (key.upArrow) {
|
|
97
|
+
setDropdownIdx((i) => Math.max(0, i - 1));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (key.downArrow) {
|
|
101
|
+
setDropdownIdx((i) =>
|
|
102
|
+
Math.min(dropdownCandidates.length - 1, i + 1)
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (key.return) {
|
|
107
|
+
// Select the highlighted candidate
|
|
108
|
+
if (dropdownCandidates[dropdownIdx]) {
|
|
109
|
+
setValue(dropdownCandidates[dropdownIdx] + " ");
|
|
110
|
+
}
|
|
111
|
+
setShowDropdown(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (key.escape) {
|
|
115
|
+
setShowDropdown(false);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── History navigation ────────────────────────────────────────────
|
|
71
121
|
if (key.upArrow) {
|
|
72
122
|
setHistory((hist) => {
|
|
73
123
|
setHistoryIdx((idx) => {
|
|
@@ -88,7 +138,6 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
88
138
|
return;
|
|
89
139
|
}
|
|
90
140
|
|
|
91
|
-
// Down arrow — history next
|
|
92
141
|
if (key.downArrow) {
|
|
93
142
|
setHistory((hist) => {
|
|
94
143
|
setHistoryIdx((idx) => {
|
|
@@ -109,32 +158,27 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
109
158
|
return;
|
|
110
159
|
}
|
|
111
160
|
|
|
112
|
-
// Tab — completion
|
|
161
|
+
// ── Tab — completion / dropdown ───────────────────────────────────
|
|
113
162
|
if (_input === "\t") {
|
|
114
|
-
const
|
|
115
|
-
const trimmed = value.trimStart();
|
|
116
|
-
const spaceIdx = trimmed.indexOf(" ");
|
|
163
|
+
const completions = getCompletions(value);
|
|
117
164
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
} else {
|
|
122
|
-
const parent = trimmed.slice(0, spaceIdx);
|
|
123
|
-
const rest = trimmed.slice(spaceIdx + 1);
|
|
124
|
-
const subs = subCmds.get(parent) ?? [];
|
|
125
|
-
completions = subs
|
|
126
|
-
.filter((s) => s.startsWith(rest))
|
|
127
|
-
.map((s) => `${parent} ${s}`);
|
|
165
|
+
if (completions.length === 0) {
|
|
166
|
+
setShowDropdown(false);
|
|
167
|
+
return;
|
|
128
168
|
}
|
|
129
169
|
|
|
130
|
-
if (completions.length === 0) return;
|
|
131
|
-
|
|
132
170
|
if (completions.length === 1) {
|
|
133
171
|
setValue(completions[0] + " ");
|
|
172
|
+
setShowDropdown(false);
|
|
134
173
|
return;
|
|
135
174
|
}
|
|
136
175
|
|
|
137
|
-
//
|
|
176
|
+
// Show dropdown with multiple candidates
|
|
177
|
+
setDropdownCandidates(completions);
|
|
178
|
+
setDropdownIdx(0);
|
|
179
|
+
setShowDropdown(true);
|
|
180
|
+
|
|
181
|
+
// Also advance to common prefix
|
|
138
182
|
const common = completions.reduce((a, b) => {
|
|
139
183
|
let i = 0;
|
|
140
184
|
while (i < a.length && i < b.length && a[i] === b[i]) i++;
|
|
@@ -144,21 +188,52 @@ export function InputLine({ onSubmit, isDisabled = false }: InputLineProps) {
|
|
|
144
188
|
setValue(common);
|
|
145
189
|
}
|
|
146
190
|
}
|
|
191
|
+
|
|
192
|
+
// ── Escape — dismiss dropdown ─────────────────────────────────────
|
|
193
|
+
if (key.escape) {
|
|
194
|
+
setShowDropdown(false);
|
|
195
|
+
}
|
|
147
196
|
},
|
|
148
197
|
{ isActive: !isDisabled }
|
|
149
198
|
);
|
|
150
199
|
|
|
200
|
+
// Dismiss dropdown when value changes (user types more)
|
|
201
|
+
const handleChange = useCallback(
|
|
202
|
+
(newVal: string) => {
|
|
203
|
+
setValue(newVal);
|
|
204
|
+
// If dropdown is open, update candidates live
|
|
205
|
+
if (showDropdown) {
|
|
206
|
+
const completions = getCompletions(newVal);
|
|
207
|
+
if (completions.length <= 1) {
|
|
208
|
+
setShowDropdown(false);
|
|
209
|
+
} else {
|
|
210
|
+
setDropdownCandidates(completions);
|
|
211
|
+
setDropdownIdx(0);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
[showDropdown, getCompletions]
|
|
216
|
+
);
|
|
217
|
+
|
|
151
218
|
return (
|
|
152
|
-
<Box>
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
onChange={setValue}
|
|
159
|
-
onSubmit={handleSubmit}
|
|
160
|
-
focus={!isDisabled}
|
|
219
|
+
<Box flexDirection="column">
|
|
220
|
+
{/* Completion dropdown renders above the input */}
|
|
221
|
+
<CompletionDropdown
|
|
222
|
+
candidates={dropdownCandidates}
|
|
223
|
+
selectedIndex={dropdownIdx}
|
|
224
|
+
visible={showDropdown}
|
|
161
225
|
/>
|
|
226
|
+
<Box>
|
|
227
|
+
<Text color="cyan">◈</Text>
|
|
228
|
+
<Text dimColor> arc402 </Text>
|
|
229
|
+
<Text color="white">{">"} </Text>
|
|
230
|
+
<TextInput
|
|
231
|
+
value={value}
|
|
232
|
+
onChange={handleChange}
|
|
233
|
+
onSubmit={handleSubmit}
|
|
234
|
+
focus={!isDisabled}
|
|
235
|
+
/>
|
|
236
|
+
</Box>
|
|
162
237
|
</Box>
|
|
163
238
|
);
|
|
164
239
|
}
|
package/src/tui/Viewport.tsx
CHANGED
|
@@ -5,30 +5,39 @@ interface ViewportProps {
|
|
|
5
5
|
lines: string[];
|
|
6
6
|
scrollOffset: number;
|
|
7
7
|
isAutoScroll: boolean;
|
|
8
|
-
innerWidth?: number;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
* Scrollable output area
|
|
13
|
-
*
|
|
11
|
+
* Scrollable output area that fills remaining terminal space.
|
|
12
|
+
* Renders a window slice of the buffer, not terminal scroll.
|
|
13
|
+
* scrollOffset=0 means pinned to bottom (auto-scroll).
|
|
14
|
+
* Positive scrollOffset means scrolled up by that many lines.
|
|
14
15
|
*/
|
|
15
|
-
export function Viewport({ lines, scrollOffset, isAutoScroll
|
|
16
|
+
export function Viewport({ lines, scrollOffset, isAutoScroll }: ViewportProps) {
|
|
16
17
|
const { stdout } = useStdout();
|
|
17
18
|
const termRows = stdout?.rows ?? 24;
|
|
18
19
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// We'll compute the viewport height: total rows minus fixed areas
|
|
21
|
+
// Header is approximately bannerLines + separator (~14-16 rows)
|
|
22
|
+
// Footer is 1 row
|
|
23
|
+
// We'll use a reasonable estimate here; the parent App can pass exact height
|
|
24
|
+
const HEADER_ROWS = 15; // approximate
|
|
25
|
+
const FOOTER_ROWS = 1;
|
|
26
|
+
const viewportHeight = Math.max(1, termRows - HEADER_ROWS - FOOTER_ROWS);
|
|
22
27
|
|
|
23
28
|
// Compute the window slice
|
|
29
|
+
// scrollOffset=0 → show last viewportHeight lines
|
|
30
|
+
// scrollOffset=N → show lines ending viewportHeight+N from end
|
|
24
31
|
const totalLines = lines.length;
|
|
25
32
|
let endIdx: number;
|
|
26
33
|
let startIdx: number;
|
|
27
34
|
|
|
28
35
|
if (scrollOffset === 0) {
|
|
36
|
+
// Auto-scroll: pinned to bottom
|
|
29
37
|
endIdx = totalLines;
|
|
30
38
|
startIdx = Math.max(0, endIdx - viewportHeight);
|
|
31
39
|
} else {
|
|
40
|
+
// Scrolled up: scrollOffset lines from bottom
|
|
32
41
|
endIdx = Math.max(0, totalLines - scrollOffset);
|
|
33
42
|
startIdx = Math.max(0, endIdx - viewportHeight);
|
|
34
43
|
}
|
|
@@ -46,19 +55,14 @@ export function Viewport({ lines, scrollOffset, isAutoScroll, innerWidth = 58 }:
|
|
|
46
55
|
|
|
47
56
|
return (
|
|
48
57
|
<Box flexDirection="column" flexGrow={1}>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<Text
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<Text dimColor>│</Text>
|
|
55
|
-
</Box>
|
|
56
|
-
))}
|
|
58
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
59
|
+
{paddedLines.map((line, i) => (
|
|
60
|
+
<Text key={i}>{line}</Text>
|
|
61
|
+
))}
|
|
62
|
+
</Box>
|
|
57
63
|
{canScrollDown && !isAutoScroll && (
|
|
58
64
|
<Box justifyContent="flex-end">
|
|
59
|
-
<Text dimColor
|
|
60
|
-
<Box flexGrow={1} />
|
|
61
|
-
<Text dimColor>│</Text>
|
|
65
|
+
<Text dimColor>↓ more</Text>
|
|
62
66
|
</Box>
|
|
63
67
|
)}
|
|
64
68
|
</Box>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import type { WCCallbacks } from "../walletconnect.js";
|
|
4
|
+
|
|
5
|
+
export type WCStage = "connecting" | "connected" | "chain-switching" | "ready" | "error";
|
|
6
|
+
|
|
7
|
+
interface WalletConnectPairingProps {
|
|
8
|
+
projectId: string;
|
|
9
|
+
chainId: number;
|
|
10
|
+
onComplete: (result: { account: string }) => void;
|
|
11
|
+
onError: (err: string) => void;
|
|
12
|
+
/** Called once the component mounts — parent passes the connect function */
|
|
13
|
+
connect: (callbacks: WCCallbacks) => Promise<{ account: string }>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders WalletConnect pairing inside the Ink TUI viewport:
|
|
18
|
+
* - ASCII QR code
|
|
19
|
+
* - Deep links for MetaMask, Rainbow, Trust, etc.
|
|
20
|
+
* - Status transitions: connecting → connected → chain-switching → ready
|
|
21
|
+
*/
|
|
22
|
+
export function WalletConnectPairing({
|
|
23
|
+
onComplete,
|
|
24
|
+
onError,
|
|
25
|
+
connect,
|
|
26
|
+
}: WalletConnectPairingProps) {
|
|
27
|
+
const [stage, setStage] = useState<WCStage>("connecting");
|
|
28
|
+
const [uri, setUri] = useState<string | null>(null);
|
|
29
|
+
const [links, setLinks] = useState<Record<string, string>>({});
|
|
30
|
+
const [account, setAccount] = useState<string | null>(null);
|
|
31
|
+
const [qrLines, setQrLines] = useState<string[]>([]);
|
|
32
|
+
const [detail, setDetail] = useState<string>("");
|
|
33
|
+
|
|
34
|
+
const handleUri = useCallback((wcUri: string, wcLinks: Record<string, string>) => {
|
|
35
|
+
setUri(wcUri);
|
|
36
|
+
setLinks(wcLinks);
|
|
37
|
+
// Generate ASCII QR
|
|
38
|
+
try {
|
|
39
|
+
// qrcode-terminal writes to stdout — capture it
|
|
40
|
+
const origWrite = process.stdout.write.bind(process.stdout);
|
|
41
|
+
const captured: string[] = [];
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
(process.stdout as any).write = (chunk: string | Uint8Array) => {
|
|
44
|
+
const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
45
|
+
captured.push(str);
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
49
|
+
import("qrcode-terminal").then((qr) => {
|
|
50
|
+
qr.default.generate(wcUri, { small: true }, (code: string) => {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
(process.stdout as any).write = origWrite;
|
|
53
|
+
setQrLines(code.split("\n"));
|
|
54
|
+
});
|
|
55
|
+
}).catch(() => {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
(process.stdout as any).write = origWrite;
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
// QR rendering is best-effort
|
|
61
|
+
}
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const handleStatus = useCallback((status: WCStage, statusDetail?: string) => {
|
|
65
|
+
setStage(status);
|
|
66
|
+
if (statusDetail) setDetail(statusDetail);
|
|
67
|
+
if (status === "connected" && statusDetail) {
|
|
68
|
+
setAccount(statusDetail);
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const callbacks: WCCallbacks = {
|
|
74
|
+
onUri: handleUri,
|
|
75
|
+
onStatus: handleStatus,
|
|
76
|
+
};
|
|
77
|
+
connect(callbacks)
|
|
78
|
+
.then((result) => onComplete(result))
|
|
79
|
+
.catch((err: unknown) => onError(err instanceof Error ? err.message : String(err)));
|
|
80
|
+
}, [connect, handleUri, handleStatus, onComplete, onError]);
|
|
81
|
+
|
|
82
|
+
const statusIcon = stage === "error" ? "✗" : stage === "ready" ? "✓" : "◈";
|
|
83
|
+
const statusColor = stage === "error" ? "red" : stage === "ready" ? "green" : "cyan";
|
|
84
|
+
|
|
85
|
+
const statusMessages: Record<WCStage, string> = {
|
|
86
|
+
connecting: "Waiting for wallet approval...",
|
|
87
|
+
connected: `Connected: ${account ?? ""}`,
|
|
88
|
+
"chain-switching": `Switching chain${detail ? `: ${detail}` : ""}...`,
|
|
89
|
+
ready: `Ready — ${account ?? ""}`,
|
|
90
|
+
error: detail || "Connection failed",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Box flexDirection="column" paddingLeft={1}>
|
|
95
|
+
<Text color="cyan" bold>WalletConnect Pairing</Text>
|
|
96
|
+
<Text> </Text>
|
|
97
|
+
|
|
98
|
+
{/* Status */}
|
|
99
|
+
<Box>
|
|
100
|
+
<Text color={statusColor}>{statusIcon} </Text>
|
|
101
|
+
<Text>{statusMessages[stage]}</Text>
|
|
102
|
+
</Box>
|
|
103
|
+
<Text> </Text>
|
|
104
|
+
|
|
105
|
+
{/* Deep links */}
|
|
106
|
+
{uri && stage === "connecting" && (
|
|
107
|
+
<>
|
|
108
|
+
<Text dimColor>Tap a link for your wallet app:</Text>
|
|
109
|
+
<Text> </Text>
|
|
110
|
+
{Object.entries(links).map(([name, link]) => (
|
|
111
|
+
<Box key={name} flexDirection="column">
|
|
112
|
+
<Text color="white">{name}:</Text>
|
|
113
|
+
<Text dimColor>{link}</Text>
|
|
114
|
+
<Text> </Text>
|
|
115
|
+
</Box>
|
|
116
|
+
))}
|
|
117
|
+
|
|
118
|
+
{/* QR code */}
|
|
119
|
+
{qrLines.length > 0 && (
|
|
120
|
+
<>
|
|
121
|
+
<Text dimColor>Or scan QR:</Text>
|
|
122
|
+
{qrLines.map((line, i) => (
|
|
123
|
+
<Text key={i}>{line}</Text>
|
|
124
|
+
))}
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
129
|
+
</Box>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, useFocus, useInput } from "ink";
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps {
|
|
5
|
+
label: string;
|
|
6
|
+
onPress: () => void;
|
|
7
|
+
variant?: "primary" | "danger" | "dim";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const VARIANT_COLORS: Record<string, string> = {
|
|
11
|
+
primary: "cyan",
|
|
12
|
+
danger: "red",
|
|
13
|
+
dim: "gray",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function Button({ label, onPress, variant = "primary" }: ButtonProps) {
|
|
17
|
+
const { isFocused } = useFocus();
|
|
18
|
+
|
|
19
|
+
useInput(
|
|
20
|
+
(_input, key) => {
|
|
21
|
+
if (key.return) {
|
|
22
|
+
onPress();
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{ isActive: isFocused }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const color = isFocused ? VARIANT_COLORS[variant] ?? "cyan" : "white";
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box>
|
|
32
|
+
<Text color={color} bold={isFocused}>
|
|
33
|
+
{isFocused ? "▸ " : " "}
|
|
34
|
+
{label}
|
|
35
|
+
</Text>
|
|
36
|
+
</Box>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { StepSpinner } from "./StepSpinner.js";
|
|
4
|
+
import type { StepStatus } from "./StepSpinner.js";
|
|
5
|
+
|
|
6
|
+
export interface CeremonyStep {
|
|
7
|
+
label: string;
|
|
8
|
+
status: StepStatus;
|
|
9
|
+
detail?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CeremonyViewProps {
|
|
14
|
+
title: string;
|
|
15
|
+
steps: CeremonyStep[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function CeremonyView({ title, steps }: CeremonyViewProps) {
|
|
19
|
+
return (
|
|
20
|
+
<Box flexDirection="column">
|
|
21
|
+
<Box marginBottom={1}>
|
|
22
|
+
<Text bold color="cyan">
|
|
23
|
+
◈ {title}
|
|
24
|
+
</Text>
|
|
25
|
+
</Box>
|
|
26
|
+
{steps.map((step, i) => (
|
|
27
|
+
<StepSpinner
|
|
28
|
+
key={i}
|
|
29
|
+
step={i + 1}
|
|
30
|
+
total={steps.length}
|
|
31
|
+
label={step.label}
|
|
32
|
+
status={step.status}
|
|
33
|
+
detail={step.detail}
|
|
34
|
+
error={step.error}
|
|
35
|
+
/>
|
|
36
|
+
))}
|
|
37
|
+
</Box>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
export interface CompletionDropdownProps {
|
|
5
|
+
candidates: string[];
|
|
6
|
+
selectedIndex: number;
|
|
7
|
+
visible: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MAX_VISIBLE = 8;
|
|
11
|
+
|
|
12
|
+
export function CompletionDropdown({
|
|
13
|
+
candidates,
|
|
14
|
+
selectedIndex,
|
|
15
|
+
visible,
|
|
16
|
+
}: CompletionDropdownProps) {
|
|
17
|
+
if (!visible || candidates.length === 0) return null;
|
|
18
|
+
|
|
19
|
+
// Window the list if there are too many candidates
|
|
20
|
+
let startIdx = 0;
|
|
21
|
+
if (candidates.length > MAX_VISIBLE) {
|
|
22
|
+
startIdx = Math.max(0, selectedIndex - Math.floor(MAX_VISIBLE / 2));
|
|
23
|
+
startIdx = Math.min(startIdx, candidates.length - MAX_VISIBLE);
|
|
24
|
+
}
|
|
25
|
+
const visibleCandidates = candidates.slice(
|
|
26
|
+
startIdx,
|
|
27
|
+
startIdx + MAX_VISIBLE
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box flexDirection="column" marginLeft={4}>
|
|
32
|
+
<Box>
|
|
33
|
+
<Text dimColor>{"┌─ completions ─"}</Text>
|
|
34
|
+
</Box>
|
|
35
|
+
{visibleCandidates.map((candidate, i) => {
|
|
36
|
+
const actualIdx = startIdx + i;
|
|
37
|
+
const isSelected = actualIdx === selectedIndex;
|
|
38
|
+
return (
|
|
39
|
+
<Box key={candidate}>
|
|
40
|
+
<Text dimColor>{"│"}</Text>
|
|
41
|
+
<Text color={isSelected ? "cyan" : "white"} bold={isSelected}>
|
|
42
|
+
{isSelected ? " ▸ " : " "}
|
|
43
|
+
{candidate}
|
|
44
|
+
</Text>
|
|
45
|
+
</Box>
|
|
46
|
+
);
|
|
47
|
+
})}
|
|
48
|
+
<Box>
|
|
49
|
+
<Text dimColor>{"└─"}</Text>
|
|
50
|
+
{candidates.length > MAX_VISIBLE && (
|
|
51
|
+
<Text dimColor>
|
|
52
|
+
{" "}
|
|
53
|
+
({candidates.length} total)
|
|
54
|
+
</Text>
|
|
55
|
+
)}
|
|
56
|
+
</Box>
|
|
57
|
+
</Box>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { Button } from "./Button.js";
|
|
4
|
+
|
|
5
|
+
export interface ConfirmPromptProps {
|
|
6
|
+
message: string;
|
|
7
|
+
onConfirm: () => void;
|
|
8
|
+
onCancel: () => void;
|
|
9
|
+
confirmLabel?: string;
|
|
10
|
+
cancelLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ConfirmPrompt({
|
|
14
|
+
message,
|
|
15
|
+
onConfirm,
|
|
16
|
+
onCancel,
|
|
17
|
+
confirmLabel = "Confirm",
|
|
18
|
+
cancelLabel = "Cancel",
|
|
19
|
+
}: ConfirmPromptProps) {
|
|
20
|
+
return (
|
|
21
|
+
<Box flexDirection="column">
|
|
22
|
+
<Box marginBottom={1}>
|
|
23
|
+
<Text bold color="cyan">
|
|
24
|
+
◈ {message}
|
|
25
|
+
</Text>
|
|
26
|
+
</Box>
|
|
27
|
+
<Box gap={2}>
|
|
28
|
+
<Button label={confirmLabel} onPress={onConfirm} variant="primary" />
|
|
29
|
+
<Button label={cancelLabel} onPress={onCancel} variant="dim" />
|
|
30
|
+
</Box>
|
|
31
|
+
<Box marginTop={1}>
|
|
32
|
+
<Text dimColor>Tab to switch · Enter to select</Text>
|
|
33
|
+
</Box>
|
|
34
|
+
</Box>
|
|
35
|
+
);
|
|
36
|
+
}
|