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.
Files changed (309) hide show
  1. package/INK6-UX-SPEC.md +446 -0
  2. package/MIGRATION-SPEC.md +108 -0
  3. package/dist/abis.js +14 -17
  4. package/dist/abis.js.map +1 -1
  5. package/dist/bundler.d.ts +1 -1
  6. package/dist/bundler.d.ts.map +1 -1
  7. package/dist/bundler.js +27 -61
  8. package/dist/bundler.js.map +1 -1
  9. package/dist/client.d.ts +1 -1
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +5 -9
  12. package/dist/client.js.map +1 -1
  13. package/dist/coinbase-smart-wallet.js +1 -4
  14. package/dist/coinbase-smart-wallet.js.map +1 -1
  15. package/dist/commands/accept.js +25 -28
  16. package/dist/commands/accept.js.map +1 -1
  17. package/dist/commands/agent-handshake.js +15 -18
  18. package/dist/commands/agent-handshake.js.map +1 -1
  19. package/dist/commands/agent.js +98 -104
  20. package/dist/commands/agent.js.map +1 -1
  21. package/dist/commands/agreements.js +62 -98
  22. package/dist/commands/agreements.js.map +1 -1
  23. package/dist/commands/arbitrator.js +45 -81
  24. package/dist/commands/arbitrator.js.map +1 -1
  25. package/dist/commands/arena-handshake.js +27 -30
  26. package/dist/commands/arena-handshake.js.map +1 -1
  27. package/dist/commands/arena.js +12 -18
  28. package/dist/commands/arena.js.map +1 -1
  29. package/dist/commands/backup.js +30 -36
  30. package/dist/commands/backup.js.map +1 -1
  31. package/dist/commands/cancel.js +15 -18
  32. package/dist/commands/cancel.js.map +1 -1
  33. package/dist/commands/channel.js +45 -81
  34. package/dist/commands/channel.js.map +1 -1
  35. package/dist/commands/coldstart.js +31 -34
  36. package/dist/commands/coldstart.js.map +1 -1
  37. package/dist/commands/config.js +23 -29
  38. package/dist/commands/config.js.map +1 -1
  39. package/dist/commands/contract-interaction.js +12 -15
  40. package/dist/commands/contract-interaction.js.map +1 -1
  41. package/dist/commands/daemon.d.ts.map +1 -1
  42. package/dist/commands/daemon.js +98 -135
  43. package/dist/commands/daemon.js.map +1 -1
  44. package/dist/commands/deliver.js +37 -76
  45. package/dist/commands/deliver.js.map +1 -1
  46. package/dist/commands/discover.js +24 -27
  47. package/dist/commands/discover.js.map +1 -1
  48. package/dist/commands/dispute.js +104 -110
  49. package/dist/commands/dispute.js.map +1 -1
  50. package/dist/commands/doctor.js +16 -55
  51. package/dist/commands/doctor.js.map +1 -1
  52. package/dist/commands/endpoint.js +56 -95
  53. package/dist/commands/endpoint.js.map +1 -1
  54. package/dist/commands/feed.js +11 -18
  55. package/dist/commands/feed.js.map +1 -1
  56. package/dist/commands/hire.js +37 -40
  57. package/dist/commands/hire.js.map +1 -1
  58. package/dist/commands/migrate.js +30 -33
  59. package/dist/commands/migrate.js.map +1 -1
  60. package/dist/commands/negotiate.d.ts.map +1 -1
  61. package/dist/commands/negotiate.js +34 -36
  62. package/dist/commands/negotiate.js.map +1 -1
  63. package/dist/commands/openshell.js +68 -104
  64. package/dist/commands/openshell.js.map +1 -1
  65. package/dist/commands/owner.js +17 -20
  66. package/dist/commands/owner.js.map +1 -1
  67. package/dist/commands/policy.js +41 -43
  68. package/dist/commands/policy.js.map +1 -1
  69. package/dist/commands/relay.d.ts.map +1 -1
  70. package/dist/commands/relay.js +18 -51
  71. package/dist/commands/relay.js.map +1 -1
  72. package/dist/commands/remediate.js +20 -23
  73. package/dist/commands/remediate.js.map +1 -1
  74. package/dist/commands/reputation.js +25 -27
  75. package/dist/commands/reputation.js.map +1 -1
  76. package/dist/commands/setup.js +65 -104
  77. package/dist/commands/setup.js.map +1 -1
  78. package/dist/commands/trust.js +17 -20
  79. package/dist/commands/trust.js.map +1 -1
  80. package/dist/commands/verify.js +18 -21
  81. package/dist/commands/verify.js.map +1 -1
  82. package/dist/commands/wallet.js +619 -625
  83. package/dist/commands/wallet.js.map +1 -1
  84. package/dist/commands/watch.js +33 -36
  85. package/dist/commands/watch.js.map +1 -1
  86. package/dist/commands/watchtower.js +37 -73
  87. package/dist/commands/watchtower.js.map +1 -1
  88. package/dist/commands/workroom.d.ts.map +1 -1
  89. package/dist/commands/workroom.js +138 -171
  90. package/dist/commands/workroom.js.map +1 -1
  91. package/dist/config.js +21 -65
  92. package/dist/config.js.map +1 -1
  93. package/dist/daemon/config.d.ts.map +1 -1
  94. package/dist/daemon/config.js +16 -53
  95. package/dist/daemon/config.js.map +1 -1
  96. package/dist/daemon/hire-listener.d.ts +3 -3
  97. package/dist/daemon/hire-listener.d.ts.map +1 -1
  98. package/dist/daemon/hire-listener.js +13 -47
  99. package/dist/daemon/hire-listener.js.map +1 -1
  100. package/dist/daemon/index.d.ts +1 -1
  101. package/dist/daemon/index.d.ts.map +1 -1
  102. package/dist/daemon/index.js +50 -88
  103. package/dist/daemon/index.js.map +1 -1
  104. package/dist/daemon/job-lifecycle.d.ts +1 -1
  105. package/dist/daemon/job-lifecycle.d.ts.map +1 -1
  106. package/dist/daemon/job-lifecycle.js +11 -51
  107. package/dist/daemon/job-lifecycle.js.map +1 -1
  108. package/dist/daemon/notify.d.ts +1 -1
  109. package/dist/daemon/notify.d.ts.map +1 -1
  110. package/dist/daemon/notify.js +19 -53
  111. package/dist/daemon/notify.js.map +1 -1
  112. package/dist/daemon/token-metering.js +8 -47
  113. package/dist/daemon/token-metering.js.map +1 -1
  114. package/dist/daemon/userops.d.ts +2 -2
  115. package/dist/daemon/userops.d.ts.map +1 -1
  116. package/dist/daemon/userops.js +23 -27
  117. package/dist/daemon/userops.js.map +1 -1
  118. package/dist/daemon/wallet-monitor.d.ts +1 -1
  119. package/dist/daemon/wallet-monitor.d.ts.map +1 -1
  120. package/dist/daemon/wallet-monitor.js +8 -12
  121. package/dist/daemon/wallet-monitor.js.map +1 -1
  122. package/dist/drain-v4.js +26 -64
  123. package/dist/drain-v4.js.map +1 -1
  124. package/dist/endpoint-config.js +20 -63
  125. package/dist/endpoint-config.js.map +1 -1
  126. package/dist/endpoint-notify.js +9 -48
  127. package/dist/endpoint-notify.js.map +1 -1
  128. package/dist/index.js +16 -50
  129. package/dist/index.js.map +1 -1
  130. package/dist/openshell-runtime.d.ts.map +1 -1
  131. package/dist/openshell-runtime.js +38 -82
  132. package/dist/openshell-runtime.js.map +1 -1
  133. package/dist/program.d.ts.map +1 -1
  134. package/dist/program.js +77 -83
  135. package/dist/program.js.map +1 -1
  136. package/dist/repl.js +25 -31
  137. package/dist/repl.js.map +1 -1
  138. package/dist/signing.js +3 -6
  139. package/dist/signing.js.map +1 -1
  140. package/dist/telegram-notify.js +3 -40
  141. package/dist/telegram-notify.js.map +1 -1
  142. package/dist/tui/App.d.ts +1 -9
  143. package/dist/tui/App.d.ts.map +1 -1
  144. package/dist/tui/App.js +87 -65
  145. package/dist/tui/App.js.map +1 -1
  146. package/dist/tui/Footer.js +4 -7
  147. package/dist/tui/Footer.js.map +1 -1
  148. package/dist/tui/Header.d.ts +1 -2
  149. package/dist/tui/Header.d.ts.map +1 -1
  150. package/dist/tui/Header.js +9 -14
  151. package/dist/tui/Header.js.map +1 -1
  152. package/dist/tui/InputLine.d.ts +1 -2
  153. package/dist/tui/InputLine.d.ts.map +1 -1
  154. package/dist/tui/InputLine.js +92 -46
  155. package/dist/tui/InputLine.js.map +1 -1
  156. package/dist/tui/Viewport.d.ts +5 -4
  157. package/dist/tui/Viewport.d.ts.map +1 -1
  158. package/dist/tui/Viewport.js +20 -13
  159. package/dist/tui/Viewport.js.map +1 -1
  160. package/dist/tui/WalletConnectPairing.d.ts +23 -0
  161. package/dist/tui/WalletConnectPairing.d.ts.map +1 -0
  162. package/dist/tui/WalletConnectPairing.js +75 -0
  163. package/dist/tui/WalletConnectPairing.js.map +1 -0
  164. package/dist/tui/components/Button.d.ts +7 -0
  165. package/dist/tui/components/Button.d.ts.map +1 -0
  166. package/dist/tui/components/Button.js +18 -0
  167. package/dist/tui/components/Button.js.map +1 -0
  168. package/dist/tui/components/CeremonyView.d.ts +13 -0
  169. package/dist/tui/components/CeremonyView.d.ts.map +1 -0
  170. package/dist/tui/components/CeremonyView.js +7 -0
  171. package/dist/tui/components/CeremonyView.js.map +1 -0
  172. package/dist/tui/components/CompletionDropdown.d.ts +7 -0
  173. package/dist/tui/components/CompletionDropdown.d.ts.map +1 -0
  174. package/dist/tui/components/CompletionDropdown.js +20 -0
  175. package/dist/tui/components/CompletionDropdown.js.map +1 -0
  176. package/dist/tui/components/ConfirmPrompt.d.ts +9 -0
  177. package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
  178. package/dist/tui/components/ConfirmPrompt.js +7 -0
  179. package/dist/tui/components/ConfirmPrompt.js.map +1 -0
  180. package/dist/tui/components/InteractiveTable.d.ts +14 -0
  181. package/dist/tui/components/InteractiveTable.d.ts.map +1 -0
  182. package/dist/tui/components/InteractiveTable.js +58 -0
  183. package/dist/tui/components/InteractiveTable.js.map +1 -0
  184. package/dist/tui/components/StepSpinner.d.ts +11 -0
  185. package/dist/tui/components/StepSpinner.d.ts.map +1 -0
  186. package/dist/tui/components/StepSpinner.js +29 -0
  187. package/dist/tui/components/StepSpinner.js.map +1 -0
  188. package/dist/tui/components/Toast.d.ts +18 -0
  189. package/dist/tui/components/Toast.d.ts.map +1 -0
  190. package/dist/tui/components/Toast.js +25 -0
  191. package/dist/tui/components/Toast.js.map +1 -0
  192. package/dist/tui/index.d.ts.map +1 -1
  193. package/dist/tui/index.js +28 -21
  194. package/dist/tui/index.js.map +1 -1
  195. package/dist/tui/useChat.js +13 -19
  196. package/dist/tui/useChat.js.map +1 -1
  197. package/dist/tui/useCommand.d.ts +2 -7
  198. package/dist/tui/useCommand.d.ts.map +1 -1
  199. package/dist/tui/useCommand.js +77 -165
  200. package/dist/tui/useCommand.js.map +1 -1
  201. package/dist/tui/useNotifications.d.ts +9 -0
  202. package/dist/tui/useNotifications.d.ts.map +1 -0
  203. package/dist/tui/useNotifications.js +14 -0
  204. package/dist/tui/useNotifications.js.map +1 -0
  205. package/dist/tui/useScroll.js +9 -12
  206. package/dist/tui/useScroll.js.map +1 -1
  207. package/dist/ui/banner.d.ts +12 -0
  208. package/dist/ui/banner.d.ts.map +1 -1
  209. package/dist/ui/banner.js +35 -19
  210. package/dist/ui/banner.js.map +1 -1
  211. package/dist/ui/colors.js +13 -19
  212. package/dist/ui/colors.js.map +1 -1
  213. package/dist/ui/format.js +6 -14
  214. package/dist/ui/format.js.map +1 -1
  215. package/dist/ui/spinner.js +6 -12
  216. package/dist/ui/spinner.js.map +1 -1
  217. package/dist/ui/tree.js +3 -6
  218. package/dist/ui/tree.js.map +1 -1
  219. package/dist/utils/format.js +27 -41
  220. package/dist/utils/format.js.map +1 -1
  221. package/dist/utils/hash.js +4 -42
  222. package/dist/utils/hash.js.map +1 -1
  223. package/dist/utils/time.js +2 -6
  224. package/dist/utils/time.js.map +1 -1
  225. package/dist/wallet-router.d.ts +1 -1
  226. package/dist/wallet-router.d.ts.map +1 -1
  227. package/dist/wallet-router.js +12 -19
  228. package/dist/wallet-router.js.map +1 -1
  229. package/dist/walletconnect-session.d.ts +1 -1
  230. package/dist/walletconnect-session.d.ts.map +1 -1
  231. package/dist/walletconnect-session.js +6 -11
  232. package/dist/walletconnect-session.js.map +1 -1
  233. package/dist/walletconnect.d.ts +6 -1
  234. package/dist/walletconnect.d.ts.map +1 -1
  235. package/dist/walletconnect.js +32 -35
  236. package/dist/walletconnect.js.map +1 -1
  237. package/package.json +7 -6
  238. package/src/bundler.ts +1 -1
  239. package/src/client.ts +1 -1
  240. package/src/commands/accept.ts +7 -7
  241. package/src/commands/agent-handshake.ts +4 -4
  242. package/src/commands/agent.ts +9 -9
  243. package/src/commands/agreements.ts +8 -8
  244. package/src/commands/arbitrator.ts +5 -5
  245. package/src/commands/arena-handshake.ts +6 -6
  246. package/src/commands/arena.ts +2 -2
  247. package/src/commands/backup.ts +1 -1
  248. package/src/commands/cancel.ts +6 -6
  249. package/src/commands/channel.ts +6 -6
  250. package/src/commands/coldstart.ts +5 -5
  251. package/src/commands/config.ts +2 -2
  252. package/src/commands/contract-interaction.ts +2 -2
  253. package/src/commands/daemon.ts +14 -11
  254. package/src/commands/deliver.ts +9 -9
  255. package/src/commands/discover.ts +5 -5
  256. package/src/commands/dispute.ts +7 -7
  257. package/src/commands/doctor.ts +2 -2
  258. package/src/commands/endpoint.ts +6 -6
  259. package/src/commands/feed.ts +1 -1
  260. package/src/commands/hire.ts +10 -10
  261. package/src/commands/migrate.ts +7 -7
  262. package/src/commands/negotiate.ts +6 -5
  263. package/src/commands/openshell.ts +4 -4
  264. package/src/commands/owner.ts +5 -5
  265. package/src/commands/policy.ts +5 -5
  266. package/src/commands/relay.ts +5 -1
  267. package/src/commands/remediate.ts +5 -5
  268. package/src/commands/reputation.ts +6 -6
  269. package/src/commands/setup.ts +1 -1
  270. package/src/commands/trust.ts +6 -6
  271. package/src/commands/verify.ts +6 -6
  272. package/src/commands/wallet.ts +15 -15
  273. package/src/commands/watch.ts +3 -3
  274. package/src/commands/watchtower.ts +6 -6
  275. package/src/commands/workroom.ts +14 -10
  276. package/src/daemon/config.ts +2 -1
  277. package/src/daemon/hire-listener.ts +3 -3
  278. package/src/daemon/index.ts +10 -9
  279. package/src/daemon/job-lifecycle.ts +1 -1
  280. package/src/daemon/notify.ts +4 -4
  281. package/src/daemon/userops.ts +4 -4
  282. package/src/daemon/wallet-monitor.ts +2 -2
  283. package/src/endpoint-notify.ts +1 -1
  284. package/src/index.ts +8 -7
  285. package/src/openshell-runtime.ts +5 -1
  286. package/src/program.ts +36 -36
  287. package/src/repl.ts +3 -3
  288. package/src/tui/App.tsx +75 -52
  289. package/src/tui/Header.tsx +26 -12
  290. package/src/tui/InputLine.tsx +108 -33
  291. package/src/tui/Viewport.tsx +22 -18
  292. package/src/tui/WalletConnectPairing.tsx +131 -0
  293. package/src/tui/components/Button.tsx +38 -0
  294. package/src/tui/components/CeremonyView.tsx +39 -0
  295. package/src/tui/components/CompletionDropdown.tsx +59 -0
  296. package/src/tui/components/ConfirmPrompt.tsx +36 -0
  297. package/src/tui/components/InteractiveTable.tsx +112 -0
  298. package/src/tui/components/StepSpinner.tsx +84 -0
  299. package/src/tui/components/Toast.tsx +59 -0
  300. package/src/tui/index.tsx +27 -9
  301. package/src/tui/useChat.ts +1 -1
  302. package/src/tui/useCommand.ts +86 -183
  303. package/src/tui/useNotifications.ts +28 -0
  304. package/src/ui/banner.ts +29 -2
  305. package/src/ui/tree.ts +1 -1
  306. package/src/wallet-router.ts +2 -2
  307. package/src/walletconnect-session.ts +1 -1
  308. package/src/walletconnect.ts +20 -5
  309. package/tsconfig.json +16 -7
@@ -0,0 +1,112 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ export interface Column {
5
+ header: string;
6
+ key: string;
7
+ width?: number;
8
+ align?: "left" | "right";
9
+ }
10
+
11
+ export interface InteractiveTableProps {
12
+ columns: Column[];
13
+ rows: Record<string, string>[];
14
+ onSelect?: (row: Record<string, string>, index: number) => void;
15
+ selectedIndex?: number;
16
+ }
17
+
18
+ const MAX_VISIBLE_ROWS = 15;
19
+
20
+ export function InteractiveTable({
21
+ columns,
22
+ rows,
23
+ onSelect,
24
+ selectedIndex: controlledIdx,
25
+ }: InteractiveTableProps) {
26
+ const [internalIdx, setInternalIdx] = useState(0);
27
+ const selectedIndex = controlledIdx ?? internalIdx;
28
+
29
+ useInput((_input, key) => {
30
+ if (key.upArrow) {
31
+ setInternalIdx((i) => Math.max(0, i - 1));
32
+ }
33
+ if (key.downArrow) {
34
+ setInternalIdx((i) => Math.min(rows.length - 1, i + 1));
35
+ }
36
+ if (key.return && onSelect && rows[selectedIndex]) {
37
+ onSelect(rows[selectedIndex], selectedIndex);
38
+ }
39
+ });
40
+
41
+ // Compute column widths
42
+ const colWidths = columns.map((col) => {
43
+ if (col.width) return col.width;
44
+ let max = col.header.length;
45
+ for (const row of rows) {
46
+ const val = row[col.key] ?? "";
47
+ if (val.length > max) max = val.length;
48
+ }
49
+ return Math.min(max + 2, 30);
50
+ });
51
+
52
+ const pad = (text: string, width: number, align: "left" | "right" = "left"): string => {
53
+ const truncated = text.length > width ? text.slice(0, width - 1) + "…" : text;
54
+ if (align === "right") return truncated.padStart(width);
55
+ return truncated.padEnd(width);
56
+ };
57
+
58
+ // Window visible rows
59
+ let startRow = 0;
60
+ if (rows.length > MAX_VISIBLE_ROWS) {
61
+ startRow = Math.max(0, selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2));
62
+ startRow = Math.min(startRow, rows.length - MAX_VISIBLE_ROWS);
63
+ }
64
+ const visibleRows = rows.slice(startRow, startRow + MAX_VISIBLE_ROWS);
65
+
66
+ // Header
67
+ const headerLine = columns
68
+ .map((col, i) => pad(col.header, colWidths[i], col.align))
69
+ .join(" ");
70
+ const separatorLine = "─".repeat(headerLine.length);
71
+
72
+ return (
73
+ <Box flexDirection="column">
74
+ <Box>
75
+ <Text bold color="white">
76
+ {" "}
77
+ {headerLine}
78
+ </Text>
79
+ </Box>
80
+ <Box>
81
+ <Text dimColor> {separatorLine}</Text>
82
+ </Box>
83
+ {visibleRows.map((row, vi) => {
84
+ const actualIdx = startRow + vi;
85
+ const isSelected = actualIdx === selectedIndex;
86
+ const line = columns
87
+ .map((col, i) => pad(row[col.key] ?? "", colWidths[i], col.align))
88
+ .join(" ");
89
+ return (
90
+ <Box key={actualIdx}>
91
+ <Text color={isSelected ? "cyan" : "white"} bold={isSelected}>
92
+ {isSelected ? "▸" : " "} {line}
93
+ </Text>
94
+ </Box>
95
+ );
96
+ })}
97
+ {rows.length > MAX_VISIBLE_ROWS && (
98
+ <Box>
99
+ <Text dimColor>
100
+ {" "}
101
+ ({rows.length} rows · ↑↓ navigate · Enter select)
102
+ </Text>
103
+ </Box>
104
+ )}
105
+ {rows.length <= MAX_VISIBLE_ROWS && rows.length > 0 && (
106
+ <Box>
107
+ <Text dimColor> (↑↓ navigate · Enter select)</Text>
108
+ </Box>
109
+ )}
110
+ </Box>
111
+ );
112
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { Box, Text } from "ink";
3
+
4
+ const SPINNER_FRAMES = ["◈", "◇", "◆", "◇"];
5
+ const SPINNER_INTERVAL = 120;
6
+
7
+ export type StepStatus = "pending" | "running" | "done" | "error";
8
+
9
+ export interface StepSpinnerProps {
10
+ step: number;
11
+ total: number;
12
+ label: string;
13
+ status: StepStatus;
14
+ detail?: string;
15
+ error?: string;
16
+ }
17
+
18
+ export function StepSpinner({
19
+ step,
20
+ total,
21
+ label,
22
+ status,
23
+ detail,
24
+ error,
25
+ }: StepSpinnerProps) {
26
+ const [frame, setFrame] = useState(0);
27
+
28
+ useEffect(() => {
29
+ if (status !== "running") return;
30
+ const timer = setInterval(() => {
31
+ setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
32
+ }, SPINNER_INTERVAL);
33
+ return () => clearInterval(timer);
34
+ }, [status]);
35
+
36
+ const prefix = `Step ${step}/${total}`;
37
+
38
+ if (status === "pending") {
39
+ return (
40
+ <Box>
41
+ <Text dimColor> {prefix} — {label}</Text>
42
+ </Box>
43
+ );
44
+ }
45
+
46
+ if (status === "running") {
47
+ return (
48
+ <Box flexDirection="column">
49
+ <Box>
50
+ <Text color="cyan"> {SPINNER_FRAMES[frame]} {prefix} — {label}...</Text>
51
+ </Box>
52
+ </Box>
53
+ );
54
+ }
55
+
56
+ if (status === "done") {
57
+ return (
58
+ <Box flexDirection="column">
59
+ <Box>
60
+ <Text color="green"> ✓ {prefix} — {label}</Text>
61
+ </Box>
62
+ {detail && (
63
+ <Box>
64
+ <Text dimColor> └ {detail}</Text>
65
+ </Box>
66
+ )}
67
+ </Box>
68
+ );
69
+ }
70
+
71
+ // error
72
+ return (
73
+ <Box flexDirection="column">
74
+ <Box>
75
+ <Text color="red"> ✗ {prefix} — {label}</Text>
76
+ </Box>
77
+ {error && (
78
+ <Box>
79
+ <Text color="red"> └ {error}</Text>
80
+ </Box>
81
+ )}
82
+ </Box>
83
+ );
84
+ }
@@ -0,0 +1,59 @@
1
+ import React, { useEffect } from "react";
2
+ import { Box, Text } from "ink";
3
+
4
+ export type ToastVariant = "info" | "success" | "warning" | "error";
5
+
6
+ export interface ToastData {
7
+ id: string;
8
+ message: string;
9
+ variant: ToastVariant;
10
+ duration?: number;
11
+ }
12
+
13
+ export interface ToastProps {
14
+ toast: ToastData;
15
+ onDismiss: (id: string) => void;
16
+ }
17
+
18
+ const VARIANT_CONFIG: Record<ToastVariant, { icon: string; color: string }> = {
19
+ info: { icon: "◈", color: "cyan" },
20
+ success: { icon: "✓", color: "green" },
21
+ warning: { icon: "⚠", color: "yellow" },
22
+ error: { icon: "✗", color: "red" },
23
+ };
24
+
25
+ export function Toast({ toast, onDismiss }: ToastProps) {
26
+ const { icon, color } = VARIANT_CONFIG[toast.variant];
27
+
28
+ useEffect(() => {
29
+ const timer = setTimeout(() => {
30
+ onDismiss(toast.id);
31
+ }, toast.duration ?? 5000);
32
+ return () => clearTimeout(timer);
33
+ }, [toast.id, toast.duration, onDismiss]);
34
+
35
+ return (
36
+ <Box>
37
+ <Text color={color}>
38
+ {icon} {toast.message}
39
+ </Text>
40
+ </Box>
41
+ );
42
+ }
43
+
44
+ export interface ToastContainerProps {
45
+ toasts: ToastData[];
46
+ onDismiss: (id: string) => void;
47
+ }
48
+
49
+ export function ToastContainer({ toasts, onDismiss }: ToastContainerProps) {
50
+ if (toasts.length === 0) return null;
51
+
52
+ return (
53
+ <Box flexDirection="column">
54
+ {toasts.map((toast) => (
55
+ <Toast key={toast.id} toast={toast} onDismiss={onDismiss} />
56
+ ))}
57
+ </Box>
58
+ );
59
+ }
package/src/tui/index.tsx CHANGED
@@ -1,12 +1,12 @@
1
1
  import React from "react";
2
2
  import { render } from "ink";
3
- import { App } from "./App";
4
- import fs from "fs";
5
- import path from "path";
6
- import os from "os";
3
+ import { App } from "./App.js";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import os from "node:os";
7
+ import { createRequire } from "node:module";
7
8
 
8
- // eslint-disable-next-line @typescript-eslint/no-var-requires
9
- const pkg = require("../../package.json") as { version: string };
9
+ const pkg = createRequire(import.meta.url)("../../package.json") as { version: string };
10
10
 
11
11
  const CONFIG_PATH = path.join(os.homedir(), ".arc402", "config.json");
12
12
 
@@ -30,8 +30,7 @@ async function getBalance(
30
30
  address: string
31
31
  ): Promise<string | undefined> {
32
32
  try {
33
- // eslint-disable-next-line @typescript-eslint/no-var-requires
34
- const ethersLib = require("ethers") as typeof import("ethers");
33
+ const ethersLib = await import("ethers");
35
34
  const provider = new ethersLib.ethers.JsonRpcProvider(rpcUrl);
36
35
  const bal = await Promise.race([
37
36
  provider.getBalance(address),
@@ -59,14 +58,33 @@ export async function launchTUI(): Promise<void> {
59
58
  balance = await getBalance(config.rpcUrl, config.walletContractAddress);
60
59
  }
61
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
+
62
76
  const { waitUntilExit } = render(
63
77
  <App
64
78
  version={pkg.version}
65
79
  network={config.network}
66
80
  wallet={walletDisplay}
67
81
  balance={balance}
68
- />
82
+ />,
83
+ { exitOnCtrlC: true }
69
84
  );
70
85
 
71
86
  await waitUntilExit();
87
+
88
+ // Clean restore
89
+ restore();
72
90
  }
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from "react";
2
2
  import chalk from "chalk";
3
- import { c } from "../ui/colors";
3
+ import { c } from "../ui/colors.js";
4
4
 
5
5
  interface UseChatResult {
6
6
  send: (message: string, onLine: (line: string) => void) => Promise<void>;
@@ -1,43 +1,7 @@
1
1
  import { useState, useCallback } from "react";
2
- import { createProgram } from "../program";
3
- import { spawn } from "child_process";
2
+ import { createProgram } from "../program.js";
4
3
  import chalk from "chalk";
5
- import { c } from "../ui/colors";
6
-
7
- /**
8
- * Commands that use interactive prompts or WalletConnect QR flows.
9
- * These MUST run in a child process so they can own stdin/stdout directly,
10
- * because Ink holds stdin in raw mode for its own input handling.
11
- */
12
- const INTERACTIVE_COMMANDS = new Set([
13
- "wallet deploy",
14
- "wallet set-guardian",
15
- "wallet unfreeze",
16
- "wallet authorize-machine-key",
17
- "wallet revoke-machine-key",
18
- "wallet set-passkey",
19
- "wallet set-interceptor",
20
- "wallet set-velocity-limit",
21
- "wallet upgrade-registry",
22
- "wallet execute-registry-upgrade",
23
- "wallet cancel-registry-upgrade",
24
- "wallet register-policy",
25
- "wallet whitelist-contract",
26
- "wallet governance setup",
27
- "wallet policy set-limit",
28
- "wallet policy set-daily-limit",
29
- "wallet policy set",
30
- "wallet import",
31
- "config init",
32
- ]);
33
-
34
- function isInteractiveCommand(input: string): boolean {
35
- const normalized = input.trim();
36
- for (const cmd of INTERACTIVE_COMMANDS) {
37
- if (normalized === cmd || normalized.startsWith(cmd + " ")) return true;
38
- }
39
- return false;
40
- }
4
+ import { c } from "../ui/colors.js";
41
5
 
42
6
  interface UseCommandResult {
43
7
  execute: (input: string, onLine: (line: string) => void) => Promise<void>;
@@ -46,13 +10,8 @@ interface UseCommandResult {
46
10
 
47
11
  /**
48
12
  * Dispatches parsed commands to the commander program.
49
- *
50
- * NON-INTERACTIVE commands: captures stdout/stderr by monkey-patching
51
- * process.stdout.write and routes output to the viewport.
52
- *
53
- * INTERACTIVE commands (WalletConnect, prompts): spawns a child process
54
- * running `arc402 <command>` with inherited stdin so that QR codes render
55
- * and prompts accept input. Output is captured line-by-line into the viewport.
13
+ * Captures stdout/stderr by monkey-patching process.stdout.write
14
+ * and routes all output to the viewport buffer via onLine callback.
56
15
  */
57
16
  export function useCommand(): UseCommandResult {
58
17
  const [isRunning, setIsRunning] = useState(false);
@@ -61,13 +20,89 @@ export function useCommand(): UseCommandResult {
61
20
  async (input: string, onLine: (line: string) => void): Promise<void> => {
62
21
  setIsRunning(true);
63
22
 
64
- if (isInteractiveCommand(input)) {
65
- await executeInteractive(input, onLine);
66
- } else {
67
- await executeInProcess(input, onLine);
23
+ // Capture stdout/stderr
24
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
25
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
26
+
27
+ let captureBuffer = "";
28
+
29
+ const flushBuffer = (): void => {
30
+ if (!captureBuffer) return;
31
+ const lines = captureBuffer.split("\n");
32
+ captureBuffer = lines.pop() ?? "";
33
+ for (const line of lines) {
34
+ onLine(line);
35
+ }
36
+ };
37
+
38
+ const capturedWrite = (
39
+ chunk: string | Uint8Array,
40
+ encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
41
+ cb?: (err?: Error | null) => void
42
+ ): boolean => {
43
+ const str =
44
+ typeof chunk === "string"
45
+ ? chunk
46
+ : Buffer.from(chunk).toString("utf8");
47
+ captureBuffer += str;
48
+ flushBuffer();
49
+ // call callback if provided
50
+ const callback =
51
+ typeof encodingOrCb === "function" ? encodingOrCb : cb;
52
+ if (callback) callback();
53
+ return true;
54
+ };
55
+
56
+ // Monkey-patch (cast through unknown to bypass strict overload checking)
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ (process.stdout as any).write = capturedWrite;
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ (process.stderr as any).write = capturedWrite;
61
+
62
+ try {
63
+ const tokens = parseTokens(input);
64
+ const prog = createProgram();
65
+ prog.exitOverride();
66
+ prog.configureOutput({
67
+ writeOut: (str: string) => process.stdout.write(str),
68
+ writeErr: (str: string) => process.stderr.write(str),
69
+ });
70
+
71
+ await prog.parseAsync(["node", "arc402", ...tokens]);
72
+ } catch (err) {
73
+ const e = err as { code?: string; message?: string };
74
+ if (
75
+ e.code === "commander.helpDisplayed" ||
76
+ e.code === "commander.version" ||
77
+ e.code === "commander.executeSubCommandAsync"
78
+ ) {
79
+ // already written or normal exit — no-op
80
+ } else if (e.code === "commander.unknownCommand") {
81
+ const tokens = parseTokens(input);
82
+ onLine(
83
+ ` ${c.failure} ${chalk.red(`Unknown command: ${chalk.white(tokens[0])}`)} `
84
+ );
85
+ onLine(chalk.dim(" Type 'help' for available commands"));
86
+ } else if (e.code?.startsWith("commander.")) {
87
+ onLine(` ${c.failure} ${chalk.red(e.message ?? String(err))}`);
88
+ } else {
89
+ onLine(` ${c.failure} ${chalk.red(e.message ?? String(err))}`);
90
+ }
91
+ } finally {
92
+ // Flush remaining buffer
93
+ if (captureBuffer.trim()) {
94
+ onLine(captureBuffer);
95
+ captureBuffer = "";
96
+ }
97
+
98
+ // Restore original write functions
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ (process.stdout as any).write = originalStdoutWrite;
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ (process.stderr as any).write = originalStderrWrite;
103
+
104
+ setIsRunning(false);
68
105
  }
69
-
70
- setIsRunning(false);
71
106
  },
72
107
  []
73
108
  );
@@ -75,138 +110,6 @@ export function useCommand(): UseCommandResult {
75
110
  return { execute, isRunning };
76
111
  }
77
112
 
78
- /**
79
- * Run an interactive command as a child process with inherited stdin.
80
- * Ink is temporarily suspended so the child can own the terminal.
81
- */
82
- async function executeInteractive(
83
- input: string,
84
- onLine: (line: string) => void
85
- ): Promise<void> {
86
- const tokens = parseTokens(input);
87
-
88
- onLine(chalk.dim(" ◈ Launching interactive session..."));
89
- onLine("");
90
-
91
- return new Promise<void>((resolve) => {
92
- // Spawn arc402 as a child process with full terminal access.
93
- // Use stdio: 'inherit' so the child owns stdin/stdout/stderr directly.
94
- // This means its output goes straight to the terminal (not captured in viewport)
95
- // but that's correct — the child needs raw terminal access for QR codes, prompts, etc.
96
- const child = spawn(
97
- process.execPath,
98
- [process.argv[1], ...tokens],
99
- {
100
- stdio: "inherit",
101
- env: { ...process.env, ARC402_NO_TUI: "1" }, // prevent child from launching its own TUI
102
- cwd: process.cwd(),
103
- }
104
- );
105
-
106
- child.on("close", (code) => {
107
- onLine("");
108
- if (code === 0) {
109
- onLine(` ${c.success} ${chalk.dim("Command completed")}`);
110
- } else {
111
- onLine(` ${c.failure} ${chalk.red(`Command exited with code ${code}`)}`);
112
- }
113
- resolve();
114
- });
115
-
116
- child.on("error", (err) => {
117
- onLine(` ${c.failure} ${chalk.red(`Failed to spawn: ${err.message}`)}`);
118
- resolve();
119
- });
120
- });
121
- }
122
-
123
- /**
124
- * Run a non-interactive command in-process with captured output.
125
- */
126
- async function executeInProcess(
127
- input: string,
128
- onLine: (line: string) => void
129
- ): Promise<void> {
130
- // Capture stdout/stderr
131
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
132
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
133
-
134
- let captureBuffer = "";
135
-
136
- const flushBuffer = (): void => {
137
- if (!captureBuffer) return;
138
- const lines = captureBuffer.split("\n");
139
- captureBuffer = lines.pop() ?? "";
140
- for (const line of lines) {
141
- onLine(line);
142
- }
143
- };
144
-
145
- const capturedWrite = (
146
- chunk: string | Uint8Array,
147
- encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
148
- cb?: (err?: Error | null) => void
149
- ): boolean => {
150
- const str =
151
- typeof chunk === "string"
152
- ? chunk
153
- : Buffer.from(chunk).toString("utf8");
154
- captureBuffer += str;
155
- flushBuffer();
156
- const callback =
157
- typeof encodingOrCb === "function" ? encodingOrCb : cb;
158
- if (callback) callback();
159
- return true;
160
- };
161
-
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- (process.stdout as any).write = capturedWrite;
164
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
- (process.stderr as any).write = capturedWrite;
166
-
167
- try {
168
- const tokens = parseTokens(input);
169
- const prog = createProgram();
170
- prog.exitOverride();
171
- prog.configureOutput({
172
- writeOut: (str) => process.stdout.write(str),
173
- writeErr: (str) => process.stderr.write(str),
174
- });
175
-
176
- await prog.parseAsync(["node", "arc402", ...tokens]);
177
- } catch (err) {
178
- const e = err as { code?: string; message?: string };
179
- if (
180
- e.code === "commander.helpDisplayed" ||
181
- e.code === "commander.version" ||
182
- e.code === "commander.executeSubCommandAsync"
183
- ) {
184
- // already written or normal exit — no-op
185
- } else if (e.code === "commander.unknownCommand") {
186
- const tokens = parseTokens(input);
187
- onLine(
188
- ` ${c.failure} ${chalk.red(`Unknown command: ${chalk.white(tokens[0])}`)} `
189
- );
190
- onLine(chalk.dim(" Type 'help' for available commands"));
191
- } else if (e.code?.startsWith("commander.")) {
192
- onLine(` ${c.failure} ${chalk.red(e.message ?? String(err))}`);
193
- } else {
194
- onLine(` ${c.failure} ${chalk.red(e.message ?? String(err))}`);
195
- }
196
- } finally {
197
- // Flush remaining buffer
198
- if (captureBuffer.trim()) {
199
- onLine(captureBuffer);
200
- captureBuffer = "";
201
- }
202
-
203
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
204
- (process.stdout as any).write = originalStdoutWrite;
205
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
- (process.stderr as any).write = originalStderrWrite;
207
- }
208
- }
209
-
210
113
  // Shell-style tokenizer
211
114
  function parseTokens(input: string): string[] {
212
115
  const tokens: string[] = [];
@@ -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
@@ -1,7 +1,7 @@
1
+ import { createRequire } from "node:module";
1
2
  import chalk from "chalk";
2
3
 
3
- // eslint-disable-next-line @typescript-eslint/no-var-requires
4
- const _pkg = require("../../package.json") as { version: string };
4
+ const _pkg = createRequire(import.meta.url)("../../package.json") as { version: string };
5
5
 
6
6
  const ART = `
7
7
  ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗
@@ -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);