arc402-cli 0.8.0 → 0.9.1
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/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +23 -7
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Header.d.ts +1 -1
- package/dist/tui/Header.d.ts.map +1 -1
- package/dist/tui/Header.js +6 -5
- 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 +76 -24
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +4 -7
- package/dist/tui/Viewport.js.map +1 -1
- 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 +15 -1
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/useCommand.d.ts +3 -2
- package/dist/tui/useCommand.d.ts.map +1 -1
- package/dist/tui/useCommand.js +89 -7
- 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/ui/banner.d.ts +12 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +23 -0
- package/dist/ui/banner.js.map +1 -1
- package/package.json +1 -1
- package/src/tui/App.tsx +36 -17
- package/src/tui/Header.tsx +26 -5
- package/src/tui/InputLine.tsx +107 -32
- package/src/tui/Viewport.tsx +4 -7
- 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 +20 -1
- package/src/tui/useCommand.ts +98 -8
- package/src/tui/useNotifications.ts +28 -0
- package/src/ui/banner.ts +27 -0
package/src/tui/index.tsx
CHANGED
|
@@ -58,14 +58,33 @@ export async function launchTUI(): Promise<void> {
|
|
|
58
58
|
balance = await getBalance(config.rpcUrl, config.walletContractAddress);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// Enter alternate screen buffer (full-screen mode)
|
|
62
|
+
process.stdout.write("\x1b[?1049h");
|
|
63
|
+
// Hide cursor initially — Ink manages it
|
|
64
|
+
process.stdout.write("\x1b[?25l");
|
|
65
|
+
|
|
66
|
+
const restore = () => {
|
|
67
|
+
process.stdout.write("\x1b[?25h"); // show cursor
|
|
68
|
+
process.stdout.write("\x1b[?1049l"); // leave alternate buffer
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Ensure restore on unexpected exit
|
|
72
|
+
process.on("exit", restore);
|
|
73
|
+
process.on("SIGINT", restore);
|
|
74
|
+
process.on("SIGTERM", restore);
|
|
75
|
+
|
|
61
76
|
const { waitUntilExit } = render(
|
|
62
77
|
<App
|
|
63
78
|
version={pkg.version}
|
|
64
79
|
network={config.network}
|
|
65
80
|
wallet={walletDisplay}
|
|
66
81
|
balance={balance}
|
|
67
|
-
|
|
82
|
+
/>,
|
|
83
|
+
{ exitOnCtrlC: true }
|
|
68
84
|
);
|
|
69
85
|
|
|
70
86
|
await waitUntilExit();
|
|
87
|
+
|
|
88
|
+
// Clean restore
|
|
89
|
+
restore();
|
|
71
90
|
}
|
package/src/tui/useCommand.ts
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import { useState, useCallback } from "react";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { createProgram } from "../program.js";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
import { c } from "../ui/colors.js";
|
|
5
6
|
|
|
7
|
+
const INTERACTIVE_COMMANDS = new Set([
|
|
8
|
+
"wallet deploy",
|
|
9
|
+
"wallet set-guardian",
|
|
10
|
+
"wallet unfreeze",
|
|
11
|
+
"wallet authorize-machine-key",
|
|
12
|
+
"wallet revoke-machine-key",
|
|
13
|
+
"wallet set-passkey",
|
|
14
|
+
"wallet set-interceptor",
|
|
15
|
+
"wallet set-velocity-limit",
|
|
16
|
+
"wallet upgrade-registry",
|
|
17
|
+
"wallet execute-registry-upgrade",
|
|
18
|
+
"wallet cancel-registry-upgrade",
|
|
19
|
+
"wallet register-policy",
|
|
20
|
+
"wallet whitelist-contract",
|
|
21
|
+
"wallet governance setup",
|
|
22
|
+
"wallet policy set-limit",
|
|
23
|
+
"wallet policy set-daily-limit",
|
|
24
|
+
"wallet policy set",
|
|
25
|
+
"wallet import",
|
|
26
|
+
"config init",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
function isInteractiveCommand(input: string): boolean {
|
|
30
|
+
const trimmed = input.trim();
|
|
31
|
+
for (const cmd of INTERACTIVE_COMMANDS) {
|
|
32
|
+
if (trimmed === cmd || trimmed.startsWith(cmd + " ")) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
6
39
|
interface UseCommandResult {
|
|
7
40
|
execute: (input: string, onLine: (line: string) => void) => Promise<void>;
|
|
8
41
|
isRunning: boolean;
|
|
@@ -10,16 +43,61 @@ interface UseCommandResult {
|
|
|
10
43
|
|
|
11
44
|
/**
|
|
12
45
|
* Dispatches parsed commands to the commander program.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
46
|
+
* Interactive commands (WalletConnect, prompts) spawn a child process
|
|
47
|
+
* because Ink holds stdin in raw mode.
|
|
48
|
+
* Non-interactive commands run in-process with stdout monkey-patching.
|
|
15
49
|
*/
|
|
16
50
|
export function useCommand(): UseCommandResult {
|
|
17
51
|
const [isRunning, setIsRunning] = useState(false);
|
|
18
52
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
53
|
+
const executeInteractive = useCallback(
|
|
54
|
+
(input: string, onLine: (line: string) => void): Promise<void> => {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
const tokens = parseTokens(input);
|
|
57
|
+
|
|
58
|
+
const child = spawn(process.execPath, [process.argv[1], ...tokens], {
|
|
59
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
60
|
+
env: { ...process.env, ARC402_NO_TUI: "1", FORCE_COLOR: "1" },
|
|
61
|
+
cwd: process.cwd(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
let stdoutRemainder = "";
|
|
65
|
+
let stderrRemainder = "";
|
|
66
|
+
|
|
67
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
68
|
+
stdoutRemainder += chunk.toString("utf8");
|
|
69
|
+
const lines = stdoutRemainder.split("\n");
|
|
70
|
+
stdoutRemainder = lines.pop() ?? "";
|
|
71
|
+
for (const line of lines) onLine(line);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
75
|
+
stderrRemainder += chunk.toString("utf8");
|
|
76
|
+
const lines = stderrRemainder.split("\n");
|
|
77
|
+
stderrRemainder = lines.pop() ?? "";
|
|
78
|
+
for (const line of lines) onLine(line);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
child.on("close", (code) => {
|
|
82
|
+
if (stdoutRemainder.trim()) onLine(stdoutRemainder);
|
|
83
|
+
if (stderrRemainder.trim()) onLine(stderrRemainder);
|
|
84
|
+
if (code !== 0 && code !== null) {
|
|
85
|
+
onLine(` ${c.failure} ${chalk.red(`Command exited with code ${code}`)}`);
|
|
86
|
+
}
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
22
89
|
|
|
90
|
+
child.on("error", (err) => {
|
|
91
|
+
onLine(` ${c.failure} ${chalk.red(`Failed to spawn: ${err.message}`)}`);
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
[]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const executeInProcess = useCallback(
|
|
100
|
+
async (input: string, onLine: (line: string) => void): Promise<void> => {
|
|
23
101
|
// Capture stdout/stderr
|
|
24
102
|
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
25
103
|
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
@@ -46,14 +124,12 @@ export function useCommand(): UseCommandResult {
|
|
|
46
124
|
: Buffer.from(chunk).toString("utf8");
|
|
47
125
|
captureBuffer += str;
|
|
48
126
|
flushBuffer();
|
|
49
|
-
// call callback if provided
|
|
50
127
|
const callback =
|
|
51
128
|
typeof encodingOrCb === "function" ? encodingOrCb : cb;
|
|
52
129
|
if (callback) callback();
|
|
53
130
|
return true;
|
|
54
131
|
};
|
|
55
132
|
|
|
56
|
-
// Monkey-patch (cast through unknown to bypass strict overload checking)
|
|
57
133
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
134
|
(process.stdout as any).write = capturedWrite;
|
|
59
135
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -100,11 +176,25 @@ export function useCommand(): UseCommandResult {
|
|
|
100
176
|
(process.stdout as any).write = originalStdoutWrite;
|
|
101
177
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
178
|
(process.stderr as any).write = originalStderrWrite;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
[]
|
|
182
|
+
);
|
|
103
183
|
|
|
184
|
+
const execute = useCallback(
|
|
185
|
+
async (input: string, onLine: (line: string) => void): Promise<void> => {
|
|
186
|
+
setIsRunning(true);
|
|
187
|
+
try {
|
|
188
|
+
if (isInteractiveCommand(input)) {
|
|
189
|
+
await executeInteractive(input, onLine);
|
|
190
|
+
} else {
|
|
191
|
+
await executeInProcess(input, onLine);
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
104
194
|
setIsRunning(false);
|
|
105
195
|
}
|
|
106
196
|
},
|
|
107
|
-
[]
|
|
197
|
+
[executeInteractive, executeInProcess]
|
|
108
198
|
);
|
|
109
199
|
|
|
110
200
|
return { execute, isRunning };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import type { ToastData, ToastVariant } from "./components/Toast.js";
|
|
3
|
+
|
|
4
|
+
let _nextId = 0;
|
|
5
|
+
|
|
6
|
+
interface UseNotificationsResult {
|
|
7
|
+
toasts: ToastData[];
|
|
8
|
+
push: (message: string, variant?: ToastVariant, duration?: number) => void;
|
|
9
|
+
dismiss: (id: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useNotifications(): UseNotificationsResult {
|
|
13
|
+
const [toasts, setToasts] = useState<ToastData[]>([]);
|
|
14
|
+
|
|
15
|
+
const push = useCallback(
|
|
16
|
+
(message: string, variant: ToastVariant = "info", duration?: number) => {
|
|
17
|
+
const id = `toast-${++_nextId}`;
|
|
18
|
+
setToasts((prev) => [...prev, { id, message, variant, duration }]);
|
|
19
|
+
},
|
|
20
|
+
[]
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const dismiss = useCallback((id: string) => {
|
|
24
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return { toasts, push, dismiss };
|
|
28
|
+
}
|
package/src/ui/banner.ts
CHANGED
|
@@ -44,6 +44,33 @@ export function getBannerLines(config?: BannerConfig): string[] {
|
|
|
44
44
|
return lines;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export interface StatusItem {
|
|
48
|
+
label: string;
|
|
49
|
+
value: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Returns the ASCII art lines (no status info) and a subtitle line. */
|
|
53
|
+
export function getBannerArt(): { artLines: string[]; subtitle: string; separator: string } {
|
|
54
|
+
const artLines: string[] = [];
|
|
55
|
+
for (const l of ART.split("\n").slice(1)) {
|
|
56
|
+
artLines.push(chalk.cyan(l));
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
artLines,
|
|
60
|
+
subtitle: " " + chalk.dim(`agent-to-agent arcing · v${_pkg.version}`),
|
|
61
|
+
separator: " " + SEPARATOR,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Returns structured status items for flexWrap rendering. */
|
|
66
|
+
export function getStatusItems(config?: BannerConfig): StatusItem[] {
|
|
67
|
+
const items: StatusItem[] = [];
|
|
68
|
+
if (config?.network) items.push({ label: "Network", value: config.network });
|
|
69
|
+
if (config?.wallet) items.push({ label: "Wallet", value: config.wallet });
|
|
70
|
+
if (config?.balance) items.push({ label: "Balance", value: config.balance });
|
|
71
|
+
return items;
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
export function renderBanner(config?: BannerConfig): void {
|
|
48
75
|
for (const line of getBannerLines(config)) {
|
|
49
76
|
console.log(line);
|