arc402-cli 0.9.18 → 0.10.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 (358) hide show
  1. package/README.md +41 -2
  2. package/dist/abis.d.ts +1 -0
  3. package/dist/abis.d.ts.map +1 -1
  4. package/dist/abis.js +45 -14
  5. package/dist/abis.js.map +1 -1
  6. package/dist/bundler.d.ts +1 -1
  7. package/dist/bundler.d.ts.map +1 -1
  8. package/dist/bundler.js +61 -27
  9. package/dist/bundler.js.map +1 -1
  10. package/dist/client.d.ts +1 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +9 -5
  13. package/dist/client.js.map +1 -1
  14. package/dist/coinbase-smart-wallet.js +4 -1
  15. package/dist/coinbase-smart-wallet.js.map +1 -1
  16. package/dist/commands/accept.js +28 -25
  17. package/dist/commands/accept.js.map +1 -1
  18. package/dist/commands/agent-handshake.js +18 -15
  19. package/dist/commands/agent-handshake.js.map +1 -1
  20. package/dist/commands/agent.js +104 -98
  21. package/dist/commands/agent.js.map +1 -1
  22. package/dist/commands/agreements.js +98 -62
  23. package/dist/commands/agreements.js.map +1 -1
  24. package/dist/commands/arbitrator.js +81 -45
  25. package/dist/commands/arbitrator.js.map +1 -1
  26. package/dist/commands/arena-handshake.d.ts.map +1 -1
  27. package/dist/commands/arena-handshake.js +35 -53
  28. package/dist/commands/arena-handshake.js.map +1 -1
  29. package/dist/commands/arena.js +18 -12
  30. package/dist/commands/arena.js.map +1 -1
  31. package/dist/commands/backup.js +36 -30
  32. package/dist/commands/backup.js.map +1 -1
  33. package/dist/commands/cancel.js +18 -15
  34. package/dist/commands/cancel.js.map +1 -1
  35. package/dist/commands/channel.js +81 -45
  36. package/dist/commands/channel.js.map +1 -1
  37. package/dist/commands/coldstart.js +34 -31
  38. package/dist/commands/coldstart.js.map +1 -1
  39. package/dist/commands/compute.d.ts +14 -0
  40. package/dist/commands/compute.d.ts.map +1 -0
  41. package/dist/commands/compute.js +466 -0
  42. package/dist/commands/compute.js.map +1 -0
  43. package/dist/commands/config.js +30 -24
  44. package/dist/commands/config.js.map +1 -1
  45. package/dist/commands/contract-interaction.js +15 -12
  46. package/dist/commands/contract-interaction.js.map +1 -1
  47. package/dist/commands/daemon.d.ts.map +1 -1
  48. package/dist/commands/daemon.js +135 -98
  49. package/dist/commands/daemon.js.map +1 -1
  50. package/dist/commands/deliver.js +76 -37
  51. package/dist/commands/deliver.js.map +1 -1
  52. package/dist/commands/discover.js +27 -24
  53. package/dist/commands/discover.js.map +1 -1
  54. package/dist/commands/dispute.js +110 -104
  55. package/dist/commands/dispute.js.map +1 -1
  56. package/dist/commands/doctor.js +55 -16
  57. package/dist/commands/doctor.js.map +1 -1
  58. package/dist/commands/endpoint.js +95 -56
  59. package/dist/commands/endpoint.js.map +1 -1
  60. package/dist/commands/feed.js +18 -11
  61. package/dist/commands/feed.js.map +1 -1
  62. package/dist/commands/hire.js +40 -37
  63. package/dist/commands/hire.js.map +1 -1
  64. package/dist/commands/migrate.js +33 -30
  65. package/dist/commands/migrate.js.map +1 -1
  66. package/dist/commands/negotiate.d.ts.map +1 -1
  67. package/dist/commands/negotiate.js +36 -34
  68. package/dist/commands/negotiate.js.map +1 -1
  69. package/dist/commands/openshell.js +104 -68
  70. package/dist/commands/openshell.js.map +1 -1
  71. package/dist/commands/owner.js +20 -17
  72. package/dist/commands/owner.js.map +1 -1
  73. package/dist/commands/policy.js +43 -41
  74. package/dist/commands/policy.js.map +1 -1
  75. package/dist/commands/relay.d.ts.map +1 -1
  76. package/dist/commands/relay.js +51 -18
  77. package/dist/commands/relay.js.map +1 -1
  78. package/dist/commands/remediate.js +23 -20
  79. package/dist/commands/remediate.js.map +1 -1
  80. package/dist/commands/reputation.js +27 -25
  81. package/dist/commands/reputation.js.map +1 -1
  82. package/dist/commands/setup.js +104 -65
  83. package/dist/commands/setup.js.map +1 -1
  84. package/dist/commands/trust.js +20 -17
  85. package/dist/commands/trust.js.map +1 -1
  86. package/dist/commands/verify.js +21 -18
  87. package/dist/commands/verify.js.map +1 -1
  88. package/dist/commands/wallet.d.ts.map +1 -1
  89. package/dist/commands/wallet.js +645 -679
  90. package/dist/commands/wallet.js.map +1 -1
  91. package/dist/commands/watch.js +36 -33
  92. package/dist/commands/watch.js.map +1 -1
  93. package/dist/commands/watchtower.js +73 -37
  94. package/dist/commands/watchtower.js.map +1 -1
  95. package/dist/commands/workroom.d.ts.map +1 -1
  96. package/dist/commands/workroom.js +282 -143
  97. package/dist/commands/workroom.js.map +1 -1
  98. package/dist/config.d.ts +3 -0
  99. package/dist/config.d.ts.map +1 -1
  100. package/dist/config.js +71 -22
  101. package/dist/config.js.map +1 -1
  102. package/dist/daemon/compute-metering.d.ts +61 -0
  103. package/dist/daemon/compute-metering.d.ts.map +1 -0
  104. package/dist/daemon/compute-metering.js +299 -0
  105. package/dist/daemon/compute-metering.js.map +1 -0
  106. package/dist/daemon/compute-session.d.ts +100 -0
  107. package/dist/daemon/compute-session.d.ts.map +1 -0
  108. package/dist/daemon/compute-session.js +231 -0
  109. package/dist/daemon/compute-session.js.map +1 -0
  110. package/dist/daemon/config.d.ts +19 -1
  111. package/dist/daemon/config.d.ts.map +1 -1
  112. package/dist/daemon/config.js +90 -16
  113. package/dist/daemon/config.js.map +1 -1
  114. package/dist/daemon/credentials.d.ts +24 -0
  115. package/dist/daemon/credentials.d.ts.map +1 -0
  116. package/dist/daemon/credentials.js +80 -0
  117. package/dist/daemon/credentials.js.map +1 -0
  118. package/dist/daemon/delivery-client.d.ts +35 -0
  119. package/dist/daemon/delivery-client.d.ts.map +1 -0
  120. package/dist/daemon/delivery-client.js +231 -0
  121. package/dist/daemon/delivery-client.js.map +1 -0
  122. package/dist/daemon/file-delivery.d.ts +98 -0
  123. package/dist/daemon/file-delivery.d.ts.map +1 -0
  124. package/dist/daemon/file-delivery.js +461 -0
  125. package/dist/daemon/file-delivery.js.map +1 -0
  126. package/dist/daemon/hire-listener.d.ts +3 -3
  127. package/dist/daemon/hire-listener.d.ts.map +1 -1
  128. package/dist/daemon/hire-listener.js +47 -13
  129. package/dist/daemon/hire-listener.js.map +1 -1
  130. package/dist/daemon/index.d.ts +2 -1
  131. package/dist/daemon/index.d.ts.map +1 -1
  132. package/dist/daemon/index.js +526 -53
  133. package/dist/daemon/index.js.map +1 -1
  134. package/dist/daemon/job-lifecycle.d.ts +1 -1
  135. package/dist/daemon/job-lifecycle.d.ts.map +1 -1
  136. package/dist/daemon/job-lifecycle.js +51 -11
  137. package/dist/daemon/job-lifecycle.js.map +1 -1
  138. package/dist/daemon/notify.d.ts +1 -1
  139. package/dist/daemon/notify.d.ts.map +1 -1
  140. package/dist/daemon/notify.js +53 -19
  141. package/dist/daemon/notify.js.map +1 -1
  142. package/dist/daemon/token-metering.js +47 -8
  143. package/dist/daemon/token-metering.js.map +1 -1
  144. package/dist/daemon/userops.d.ts +2 -2
  145. package/dist/daemon/userops.d.ts.map +1 -1
  146. package/dist/daemon/userops.js +27 -23
  147. package/dist/daemon/userops.js.map +1 -1
  148. package/dist/daemon/wallet-monitor.d.ts +1 -1
  149. package/dist/daemon/wallet-monitor.d.ts.map +1 -1
  150. package/dist/daemon/wallet-monitor.js +12 -8
  151. package/dist/daemon/wallet-monitor.js.map +1 -1
  152. package/dist/daemon/worker-executor.d.ts +71 -0
  153. package/dist/daemon/worker-executor.d.ts.map +1 -0
  154. package/dist/daemon/worker-executor.js +382 -0
  155. package/dist/daemon/worker-executor.js.map +1 -0
  156. package/dist/drain-v4.js +64 -26
  157. package/dist/drain-v4.js.map +1 -1
  158. package/dist/endpoint-config.js +63 -20
  159. package/dist/endpoint-config.js.map +1 -1
  160. package/dist/endpoint-notify.js +48 -9
  161. package/dist/endpoint-notify.js.map +1 -1
  162. package/dist/index.js +50 -18
  163. package/dist/index.js.map +1 -1
  164. package/dist/openshell-runtime.d.ts.map +1 -1
  165. package/dist/openshell-runtime.js +82 -38
  166. package/dist/openshell-runtime.js.map +1 -1
  167. package/dist/program.d.ts.map +1 -1
  168. package/dist/program.js +85 -78
  169. package/dist/program.js.map +1 -1
  170. package/dist/repl.js +31 -25
  171. package/dist/repl.js.map +1 -1
  172. package/dist/signing.js +6 -3
  173. package/dist/signing.js.map +1 -1
  174. package/dist/telegram-notify.js +40 -3
  175. package/dist/telegram-notify.js.map +1 -1
  176. package/dist/tui/App.d.ts.map +1 -1
  177. package/dist/tui/App.js +56 -89
  178. package/dist/tui/App.js.map +1 -1
  179. package/dist/tui/Footer.js +7 -4
  180. package/dist/tui/Footer.js.map +1 -1
  181. package/dist/tui/Header.d.ts +1 -1
  182. package/dist/tui/Header.d.ts.map +1 -1
  183. package/dist/tui/Header.js +14 -9
  184. package/dist/tui/Header.js.map +1 -1
  185. package/dist/tui/InputLine.d.ts +2 -1
  186. package/dist/tui/InputLine.d.ts.map +1 -1
  187. package/dist/tui/InputLine.js +47 -97
  188. package/dist/tui/InputLine.js.map +1 -1
  189. package/dist/tui/Viewport.d.ts +1 -2
  190. package/dist/tui/Viewport.d.ts.map +1 -1
  191. package/dist/tui/Viewport.js +26 -6
  192. package/dist/tui/Viewport.js.map +1 -1
  193. package/dist/tui/WalletConnectPairing.js +19 -16
  194. package/dist/tui/WalletConnectPairing.js.map +1 -1
  195. package/dist/tui/components/Button.js +9 -6
  196. package/dist/tui/components/Button.js.map +1 -1
  197. package/dist/tui/components/CeremonyView.js +8 -5
  198. package/dist/tui/components/CeremonyView.js.map +1 -1
  199. package/dist/tui/components/CompletionDropdown.js +9 -6
  200. package/dist/tui/components/CompletionDropdown.js.map +1 -1
  201. package/dist/tui/components/ConfirmPrompt.js +8 -5
  202. package/dist/tui/components/ConfirmPrompt.js.map +1 -1
  203. package/dist/tui/components/CustomTextInput.js +14 -11
  204. package/dist/tui/components/CustomTextInput.js.map +1 -1
  205. package/dist/tui/components/InteractiveTable.js +12 -9
  206. package/dist/tui/components/InteractiveTable.js.map +1 -1
  207. package/dist/tui/components/StepSpinner.js +13 -10
  208. package/dist/tui/components/StepSpinner.js.map +1 -1
  209. package/dist/tui/components/Toast.js +12 -8
  210. package/dist/tui/components/Toast.js.map +1 -1
  211. package/dist/tui/index.d.ts.map +1 -1
  212. package/dist/tui/index.js +21 -28
  213. package/dist/tui/index.js.map +1 -1
  214. package/dist/tui/useChat.js +19 -13
  215. package/dist/tui/useChat.js.map +1 -1
  216. package/dist/tui/useCommand.d.ts +2 -3
  217. package/dist/tui/useCommand.d.ts.map +1 -1
  218. package/dist/tui/useCommand.js +24 -100
  219. package/dist/tui/useCommand.js.map +1 -1
  220. package/dist/tui/useNotifications.js +8 -5
  221. package/dist/tui/useNotifications.js.map +1 -1
  222. package/dist/tui/useScroll.d.ts.map +1 -1
  223. package/dist/tui/useScroll.js +12 -15
  224. package/dist/tui/useScroll.js.map +1 -1
  225. package/dist/ui/banner.d.ts +0 -12
  226. package/dist/ui/banner.d.ts.map +1 -1
  227. package/dist/ui/banner.js +19 -35
  228. package/dist/ui/banner.js.map +1 -1
  229. package/dist/ui/colors.js +19 -13
  230. package/dist/ui/colors.js.map +1 -1
  231. package/dist/ui/format.js +14 -6
  232. package/dist/ui/format.js.map +1 -1
  233. package/dist/ui/qr-render.js +11 -4
  234. package/dist/ui/qr-render.js.map +1 -1
  235. package/dist/ui/rpc-fallback.js +11 -6
  236. package/dist/ui/rpc-fallback.js.map +1 -1
  237. package/dist/ui/spinner.js +12 -6
  238. package/dist/ui/spinner.js.map +1 -1
  239. package/dist/ui/tree.js +6 -3
  240. package/dist/ui/tree.js.map +1 -1
  241. package/dist/utils/format.js +41 -27
  242. package/dist/utils/format.js.map +1 -1
  243. package/dist/utils/hash.js +42 -4
  244. package/dist/utils/hash.js.map +1 -1
  245. package/dist/utils/time.js +6 -2
  246. package/dist/utils/time.js.map +1 -1
  247. package/dist/wallet-router.d.ts +1 -1
  248. package/dist/wallet-router.d.ts.map +1 -1
  249. package/dist/wallet-router.js +19 -12
  250. package/dist/wallet-router.js.map +1 -1
  251. package/dist/walletconnect-session.d.ts +1 -1
  252. package/dist/walletconnect-session.d.ts.map +1 -1
  253. package/dist/walletconnect-session.js +11 -6
  254. package/dist/walletconnect-session.js.map +1 -1
  255. package/dist/walletconnect.d.ts +5 -6
  256. package/dist/walletconnect.d.ts.map +1 -1
  257. package/dist/walletconnect.js +35 -32
  258. package/dist/walletconnect.js.map +1 -1
  259. package/package.json +11 -10
  260. package/INK6-UX-SPEC.md +0 -446
  261. package/MIGRATION-SPEC.md +0 -108
  262. package/TUI-SPEC.md +0 -214
  263. package/scripts/authorize-machine-key.ts +0 -43
  264. package/scripts/drain-wallet.ts +0 -149
  265. package/scripts/execute-spend-only.ts +0 -81
  266. package/scripts/register-agent-userop.ts +0 -186
  267. package/src/abis.ts +0 -187
  268. package/src/bundler.ts +0 -235
  269. package/src/client.ts +0 -36
  270. package/src/coinbase-smart-wallet.ts +0 -51
  271. package/src/commands/accept.ts +0 -64
  272. package/src/commands/agent-handshake.ts +0 -72
  273. package/src/commands/agent.ts +0 -691
  274. package/src/commands/agreements.ts +0 -350
  275. package/src/commands/arbitrator.ts +0 -180
  276. package/src/commands/arena-handshake.ts +0 -274
  277. package/src/commands/arena.ts +0 -122
  278. package/src/commands/backup.ts +0 -117
  279. package/src/commands/cancel.ts +0 -35
  280. package/src/commands/channel.ts +0 -218
  281. package/src/commands/coldstart.ts +0 -165
  282. package/src/commands/config.ts +0 -68
  283. package/src/commands/contract-interaction.ts +0 -166
  284. package/src/commands/daemon.ts +0 -1054
  285. package/src/commands/deliver.ts +0 -148
  286. package/src/commands/discover.ts +0 -350
  287. package/src/commands/dispute.ts +0 -375
  288. package/src/commands/doctor.ts +0 -172
  289. package/src/commands/endpoint.ts +0 -620
  290. package/src/commands/feed.ts +0 -229
  291. package/src/commands/hire.ts +0 -245
  292. package/src/commands/migrate.ts +0 -177
  293. package/src/commands/negotiate.ts +0 -272
  294. package/src/commands/openshell.ts +0 -1055
  295. package/src/commands/owner.ts +0 -35
  296. package/src/commands/policy.ts +0 -263
  297. package/src/commands/relay.ts +0 -277
  298. package/src/commands/remediate.ts +0 -24
  299. package/src/commands/reputation.ts +0 -79
  300. package/src/commands/setup.ts +0 -343
  301. package/src/commands/trust.ts +0 -27
  302. package/src/commands/verify.ts +0 -91
  303. package/src/commands/wallet.ts +0 -3548
  304. package/src/commands/watch.ts +0 -220
  305. package/src/commands/watchtower.ts +0 -248
  306. package/src/commands/workroom.ts +0 -963
  307. package/src/config.ts +0 -220
  308. package/src/daemon/config.ts +0 -344
  309. package/src/daemon/hire-listener.ts +0 -226
  310. package/src/daemon/index.ts +0 -1089
  311. package/src/daemon/job-lifecycle.ts +0 -215
  312. package/src/daemon/notify.ts +0 -297
  313. package/src/daemon/token-metering.ts +0 -183
  314. package/src/daemon/userops.ts +0 -119
  315. package/src/daemon/wallet-monitor.ts +0 -90
  316. package/src/drain-v4.ts +0 -159
  317. package/src/endpoint-config.ts +0 -83
  318. package/src/endpoint-notify.ts +0 -129
  319. package/src/index.ts +0 -74
  320. package/src/openshell-runtime.ts +0 -281
  321. package/src/program.ts +0 -88
  322. package/src/repl.ts +0 -178
  323. package/src/signing.ts +0 -28
  324. package/src/telegram-notify.ts +0 -88
  325. package/src/tui/App.tsx +0 -263
  326. package/src/tui/Footer.tsx +0 -18
  327. package/src/tui/Header.tsx +0 -45
  328. package/src/tui/InputLine.tsx +0 -243
  329. package/src/tui/Viewport.tsx +0 -51
  330. package/src/tui/WalletConnectPairing.tsx +0 -114
  331. package/src/tui/components/Button.tsx +0 -38
  332. package/src/tui/components/CeremonyView.tsx +0 -39
  333. package/src/tui/components/CompletionDropdown.tsx +0 -56
  334. package/src/tui/components/ConfirmPrompt.tsx +0 -36
  335. package/src/tui/components/CustomTextInput.tsx +0 -132
  336. package/src/tui/components/InteractiveTable.tsx +0 -106
  337. package/src/tui/components/StepSpinner.tsx +0 -84
  338. package/src/tui/components/Toast.tsx +0 -59
  339. package/src/tui/index.tsx +0 -90
  340. package/src/tui/useChat.ts +0 -103
  341. package/src/tui/useCommand.ts +0 -238
  342. package/src/tui/useNotifications.ts +0 -28
  343. package/src/tui/useScroll.ts +0 -69
  344. package/src/ui/banner.ts +0 -78
  345. package/src/ui/colors.ts +0 -30
  346. package/src/ui/format.ts +0 -78
  347. package/src/ui/qr-render.ts +0 -92
  348. package/src/ui/rpc-fallback.ts +0 -59
  349. package/src/ui/spinner.ts +0 -56
  350. package/src/ui/tree.ts +0 -16
  351. package/src/utils/format.ts +0 -48
  352. package/src/utils/hash.ts +0 -5
  353. package/src/utils/time.ts +0 -15
  354. package/src/wallet-router.ts +0 -178
  355. package/src/walletconnect-session.ts +0 -27
  356. package/src/walletconnect.ts +0 -309
  357. package/test/time.test.js +0 -11
  358. package/tsconfig.json +0 -33
@@ -1,375 +0,0 @@
1
- import { Command } from "commander";
2
- import { ethers } from "ethers";
3
- import prompts from "prompts";
4
- import { ArbitrationVote, DirectDisputeReason, DisputeClass, DisputeMode, DisputeOutcome, EvidenceType, ServiceAgreementClient, DisputeArbitrationClient } from "@arc402/sdk";
5
- import { loadConfig } from "../config.js";
6
- import { getClient, requireSigner } from "../client.js";
7
- import { hashFile, hashString } from "../utils/hash.js";
8
- import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router.js";
9
- import { SERVICE_AGREEMENT_ABI } from "../abis.js";
10
- import { c } from '../ui/colors.js';
11
- import { startSpinner } from '../ui/spinner.js';
12
-
13
- export function registerDisputeCommand(program: Command): void {
14
- const dispute = program.command("dispute").description("Formal dispute workflow; remediation-first by default, with narrow hard-fail direct-dispute exceptions");
15
-
16
- // Fee quote (requires DisputeArbitration configured) — read-only, no wallet routing needed
17
- dispute.command("fee-quote <agreementId>")
18
- .description("Get dispute fee quote for an agreement")
19
- .requiredOption("--price <price>", "Agreement price in wei/token units")
20
- .requiredOption("--token <token>", "Token address (0x0 for ETH)")
21
- .requiredOption("--mode <mode>", "unilateral|mutual")
22
- .requiredOption("--class <class>", "hard-failure|ambiguity|high-sensitivity")
23
- .action(async (agreementId, opts) => {
24
- const config = loadConfig();
25
- if (!config.disputeArbitrationAddress) throw new Error("disputeArbitrationAddress missing in config");
26
- const { provider } = await getClient(config);
27
- const client = new DisputeArbitrationClient(config.disputeArbitrationAddress, provider);
28
- const modeMap: Record<string, DisputeMode> = { unilateral: DisputeMode.UNILATERAL, mutual: DisputeMode.MUTUAL };
29
- const classMap: Record<string, DisputeClass> = {
30
- 'hard-failure': DisputeClass.HARD_FAILURE,
31
- 'ambiguity': DisputeClass.AMBIGUITY_QUALITY,
32
- 'high-sensitivity': DisputeClass.HIGH_SENSITIVITY,
33
- };
34
- const mode = modeMap[String(opts.mode).toLowerCase()];
35
- const disputeClass = classMap[String(opts.class).toLowerCase()];
36
- if (!mode || !disputeClass) throw new Error("Invalid --mode or --class");
37
- const feeInTokens = await client.getFeeQuote(BigInt(opts.price), opts.token, mode, disputeClass);
38
- console.log(' ' + c.mark + c.white(` Fee quote — agreement #${agreementId}: ${feeInTokens.toString()} tokens`));
39
- });
40
-
41
- // Open with explicit mode/class
42
- dispute.command("open-with-mode <agreementId>")
43
- .description("Open dispute with specific mode and class (requires fee in msg.value for ETH)")
44
- .requiredOption("--mode <mode>", "unilateral|mutual")
45
- .requiredOption("--class <class>", "hard-failure|ambiguity|high-sensitivity")
46
- .requiredOption("--reason <reason>")
47
- .option("--fee <fee>", "Fee in wei (for ETH agreements)", "0")
48
- .action(async (agreementId, opts) => {
49
- const config = loadConfig();
50
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
51
- const { signer } = await requireSigner(config);
52
- const modeMap: Record<string, DisputeMode> = { unilateral: DisputeMode.UNILATERAL, mutual: DisputeMode.MUTUAL };
53
- const classMap: Record<string, DisputeClass> = {
54
- 'hard-failure': DisputeClass.HARD_FAILURE,
55
- 'ambiguity': DisputeClass.AMBIGUITY_QUALITY,
56
- 'high-sensitivity': DisputeClass.HIGH_SENSITIVITY,
57
- };
58
- const mode = modeMap[String(opts.mode).toLowerCase()];
59
- const disputeClass = classMap[String(opts.class).toLowerCase()];
60
- if (!mode || !disputeClass) throw new Error("Invalid --mode or --class");
61
- printSenderInfo(config);
62
- if (config.walletContractAddress) {
63
- await executeContractWriteViaWallet(
64
- config.walletContractAddress, signer, config.serviceAgreementAddress,
65
- SERVICE_AGREEMENT_ABI, "openDisputeWithMode",
66
- [BigInt(agreementId), mode, disputeClass, opts.reason],
67
- BigInt(opts.fee),
68
- );
69
- } else {
70
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
71
- await client.openDisputeWithMode(BigInt(agreementId), mode, disputeClass, opts.reason, BigInt(opts.fee));
72
- }
73
- console.log(' ' + c.success + c.white(` Dispute opened — agreement #${agreementId}`));
74
- });
75
-
76
- // Join mutual dispute (respondent pays their half) — DisputeArbitration contract, no wallet routing
77
- dispute.command("join <agreementId>")
78
- .description("Join a mutual dispute as respondent (pays half the fee)")
79
- .option("--fee <fee>", "Half-fee in wei (for ETH agreements)", "0")
80
- .action(async (agreementId, opts) => {
81
- const config = loadConfig();
82
- if (!config.disputeArbitrationAddress) throw new Error("disputeArbitrationAddress missing in config");
83
- const { signer } = await requireSigner(config);
84
- const client = new DisputeArbitrationClient(config.disputeArbitrationAddress, signer);
85
- await client.joinMutualDispute(BigInt(agreementId), BigInt(opts.fee));
86
- console.log(' ' + c.success + c.white(` Joined mutual dispute — agreement #${agreementId}`));
87
- });
88
-
89
- dispute.command("open <id>")
90
- .requiredOption("--reason <reason>")
91
- .option("--escalated", "Use escalateToDispute after remediation", false)
92
- .option("--direct <type>", "Direct-dispute hard-fail reason: no-delivery|deadline-breach|invalid-deliverable|safety-critical")
93
- .action(async (id, opts) => {
94
- const config = loadConfig();
95
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
96
-
97
- // Pre-flight: check disputeModule is configured (J4-01)
98
- {
99
- const { provider: dpProvider } = await getClient(config);
100
- const saCheck = new ethers.Contract(
101
- config.serviceAgreementAddress,
102
- ["function disputeModule() external view returns (address)"],
103
- dpProvider,
104
- );
105
- let disputeModuleAddr: string = ethers.ZeroAddress;
106
- try {
107
- disputeModuleAddr = await saCheck.disputeModule();
108
- } catch { /* assume not configured */ }
109
- if (disputeModuleAddr === ethers.ZeroAddress) {
110
- console.error(`No dispute module configured on this ServiceAgreement.`);
111
- console.error(`Disputes require a DisputeModule to be set by the SA owner.`);
112
- console.error(`This protocol deployment may not support formal disputes.`);
113
- process.exit(1);
114
- }
115
- }
116
-
117
- // Pre-flight: read dispute fee and prompt user (J4-02)
118
- if (config.disputeArbitrationAddress) {
119
- const { provider: feeProvider } = await getClient(config);
120
- const daCheck = new ethers.Contract(
121
- config.disputeArbitrationAddress,
122
- ["function getDisputeFee() external view returns (uint256)"],
123
- feeProvider,
124
- );
125
- let feeWei = 0n;
126
- try {
127
- feeWei = await daCheck.getDisputeFee();
128
- } catch { /* fee getter may not exist — assume 0 */ }
129
- if (feeWei > 0n) {
130
- const feeEth = ethers.formatEther(feeWei);
131
- console.log(`\nDispute fee: ${feeEth} ETH. This will be deducted from your wallet.`);
132
- const { proceed } = await prompts({
133
- type: "confirm",
134
- name: "proceed",
135
- message: "Continue?",
136
- initial: true,
137
- });
138
- if (!proceed) { console.log("Aborted."); process.exit(0); }
139
- }
140
- }
141
-
142
- const { signer } = await requireSigner(config);
143
- const directMap: Record<string, DirectDisputeReason> = {
144
- 'no-delivery': DirectDisputeReason.NO_DELIVERY,
145
- 'deadline-breach': DirectDisputeReason.HARD_DEADLINE_BREACH,
146
- 'invalid-deliverable': DirectDisputeReason.INVALID_OR_FRAUDULENT_DELIVERABLE,
147
- 'safety-critical': DirectDisputeReason.SAFETY_CRITICAL_VIOLATION,
148
- };
149
- if (opts.escalated && opts.direct) throw new Error('Choose either --escalated or --direct, not both');
150
- printSenderInfo(config);
151
- if (config.walletContractAddress) {
152
- if (opts.direct) {
153
- const directReason = directMap[String(opts.direct).toLowerCase()];
154
- if (directReason === undefined) throw new Error('Unsupported --direct reason');
155
- await executeContractWriteViaWallet(
156
- config.walletContractAddress, signer, config.serviceAgreementAddress,
157
- SERVICE_AGREEMENT_ABI, "directDispute", [BigInt(id), directReason, opts.reason],
158
- );
159
- } else if (opts.escalated) {
160
- await executeContractWriteViaWallet(
161
- config.walletContractAddress, signer, config.serviceAgreementAddress,
162
- SERVICE_AGREEMENT_ABI, "escalateToDispute", [BigInt(id), opts.reason],
163
- );
164
- } else {
165
- await executeContractWriteViaWallet(
166
- config.walletContractAddress, signer, config.serviceAgreementAddress,
167
- SERVICE_AGREEMENT_ABI, "dispute", [BigInt(id), opts.reason],
168
- );
169
- }
170
- } else {
171
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
172
- if (opts.direct) {
173
- const directReason = directMap[String(opts.direct).toLowerCase()];
174
- if (directReason === undefined) throw new Error('Unsupported --direct reason');
175
- await client.directDispute(BigInt(id), directReason, opts.reason);
176
- } else if (opts.escalated) {
177
- await client.escalateToDispute(BigInt(id), opts.reason);
178
- } else {
179
- await client.dispute(BigInt(id), opts.reason);
180
- }
181
- }
182
- console.log(' ' + c.success + c.white(` Dispute opened — agreement #${id}`));
183
-
184
- // J4-04: Display arbitration selection window deadline
185
- try {
186
- const { provider: dpAW } = await getClient(config);
187
- const saAW = new ethers.Contract(
188
- config.serviceAgreementAddress,
189
- ["function ARBITRATION_SELECTION_WINDOW() external view returns (uint256)"],
190
- dpAW,
191
- );
192
- const selectionWindow: bigint = await saAW.ARBITRATION_SELECTION_WINDOW();
193
- const deadlineDate = new Date(Date.now() + Number(selectionWindow) * 1000);
194
- console.log(`Arbitration selection window closes: ${deadlineDate.toLocaleString()}. An arbitrator must be assigned before then.`);
195
- } catch { /* not available on this deployment */ }
196
- });
197
-
198
- dispute.command("evidence <id>").requiredOption("--type <type>", "transcript|deliverable|acceptance|communication|external|other").option("--file <path>").option("--text <text>").option("--uri <uri>", "External evidence URI", "").action(async (id, opts) => {
199
- const config = loadConfig();
200
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
201
- const { signer } = await requireSigner(config);
202
- const mapping: Record<string, EvidenceType> = { transcript: EvidenceType.TRANSCRIPT, deliverable: EvidenceType.DELIVERABLE, acceptance: EvidenceType.ACCEPTANCE_CRITERIA, communication: EvidenceType.COMMUNICATION, external: EvidenceType.EXTERNAL_REFERENCE, other: EvidenceType.OTHER };
203
- const hash = opts.file ? hashFile(opts.file) : hashString(opts.text ?? opts.uri ?? `evidence:${id}`);
204
- const evidenceType = mapping[String(opts.type).toLowerCase()] ?? EvidenceType.OTHER;
205
- printSenderInfo(config);
206
- if (config.walletContractAddress) {
207
- await executeContractWriteViaWallet(
208
- config.walletContractAddress, signer, config.serviceAgreementAddress,
209
- SERVICE_AGREEMENT_ABI, "submitDisputeEvidence", [BigInt(id), evidenceType, hash, opts.uri],
210
- );
211
- } else {
212
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
213
- await client.submitDisputeEvidence(BigInt(id), evidenceType, hash, opts.uri);
214
- }
215
- console.log(' ' + c.success + c.white(` Evidence submitted — agreement #${id}`));
216
- });
217
-
218
- // status — read-only, no wallet routing needed
219
- dispute.command("status <id>").option("--json").action(async (id, opts) => {
220
- const config = loadConfig();
221
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
222
- const { provider } = await getClient(config); const client = new ServiceAgreementClient(config.serviceAgreementAddress, provider);
223
- const result = {
224
- case: await client.getDisputeCase(BigInt(id)),
225
- arbitration: await client.getArbitrationCase(BigInt(id)),
226
- evidence: await client.getDisputeEvidenceAll(BigInt(id)),
227
- };
228
- console.log(JSON.stringify(result, (_k, value) => typeof value === 'bigint' ? value.toString() : value, opts.json ? 2 : 2));
229
- });
230
-
231
- dispute.command("nominate <id>")
232
- .description("Nominate an arbitrator during the on-chain arbitration phase")
233
- .requiredOption("--arbitrator <address>")
234
- .action(async (id, opts) => {
235
- const config = loadConfig();
236
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
237
-
238
- // Pre-flight: check arbitrator is approved (J4-03)
239
- if (config.disputeArbitrationAddress) {
240
- const { provider: arbProvider } = await getClient(config);
241
- const daCheck = new ethers.Contract(
242
- config.disputeArbitrationAddress,
243
- ["function isApprovedArbitrator(address arbitrator) external view returns (bool)"],
244
- arbProvider,
245
- );
246
- let isApproved = true;
247
- try {
248
- isApproved = await daCheck.isApprovedArbitrator(opts.arbitrator);
249
- } catch { /* assume approved if read fails */ }
250
- if (!isApproved) {
251
- console.error(`Arbitrator ${opts.arbitrator} is not approved.`);
252
- console.error(`Use \`arc402 dispute list-arbitrators\` to see approved arbitrators.`);
253
- process.exit(1);
254
- }
255
- }
256
-
257
- const { signer } = await requireSigner(config);
258
- printSenderInfo(config);
259
- if (config.walletContractAddress) {
260
- await executeContractWriteViaWallet(
261
- config.walletContractAddress, signer, config.serviceAgreementAddress,
262
- SERVICE_AGREEMENT_ABI, "nominateArbitrator", [BigInt(id), opts.arbitrator],
263
- );
264
- } else {
265
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
266
- await client.nominateArbitrator(BigInt(id), opts.arbitrator);
267
- }
268
- console.log(' ' + c.success + c.white(` Arbitrator nominated — agreement #${id}`));
269
- });
270
-
271
- dispute.command("vote <id>")
272
- .description("Cast an arbitration vote on-chain")
273
- .requiredOption("--vote <vote>", "provider|refund|split|human-review")
274
- .option("--provider-award <amount>", "Wei/token units", "0")
275
- .option("--client-award <amount>", "Wei/token units", "0")
276
- .action(async (id, opts) => {
277
- const config = loadConfig();
278
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
279
- const { signer, address: voterAddress, provider: voteProvider } = await requireSigner(config);
280
-
281
- // J4-05: Pre-flight — verify caller is on the arbitration panel
282
- try {
283
- const saClient = new ServiceAgreementClient(config.serviceAgreementAddress, voteProvider);
284
- const arbCase = await saClient.getArbitrationCase(BigInt(id));
285
- const onPanel = arbCase.arbitrators.map((a: string) => a.toLowerCase()).includes(voterAddress.toLowerCase());
286
- if (!onPanel) {
287
- console.error(`You are not on the arbitration panel for agreement ${id}. Only assigned arbitrators can vote.`);
288
- process.exit(1);
289
- }
290
- } catch (e) {
291
- if ((e as NodeJS.ErrnoException)?.code === 'ERR_USE_AFTER_CLOSE' || String(e).includes('process.exit')) throw e;
292
- // If read fails, skip the check and let the transaction reveal the error
293
- }
294
-
295
- const mapping: Record<string, ArbitrationVote> = {
296
- provider: ArbitrationVote.PROVIDER_WINS,
297
- refund: ArbitrationVote.CLIENT_REFUND,
298
- split: ArbitrationVote.SPLIT,
299
- 'human-review': ArbitrationVote.HUMAN_REVIEW_REQUIRED,
300
- };
301
- const vote = mapping[String(opts.vote).toLowerCase()];
302
- if (vote === undefined) throw new Error('Unsupported --vote value');
303
- printSenderInfo(config);
304
- if (config.walletContractAddress) {
305
- await executeContractWriteViaWallet(
306
- config.walletContractAddress, signer, config.serviceAgreementAddress,
307
- SERVICE_AGREEMENT_ABI, "castArbitrationVote",
308
- [BigInt(id), vote, BigInt(opts.providerAward), BigInt(opts.clientAward)],
309
- );
310
- } else {
311
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
312
- await client.castArbitrationVote(BigInt(id), vote, BigInt(opts.providerAward), BigInt(opts.clientAward));
313
- }
314
- console.log(' ' + c.success + c.white(` Vote recorded — agreement #${id}`));
315
- });
316
-
317
- dispute.command("human <id>")
318
- .description("Request human escalation when arbitration stalls or requires human backstop")
319
- .requiredOption("--reason <reason>")
320
- .action(async (id, opts) => {
321
- const config = loadConfig();
322
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
323
- const { signer } = await requireSigner(config);
324
- printSenderInfo(config);
325
- if (config.walletContractAddress) {
326
- await executeContractWriteViaWallet(
327
- config.walletContractAddress, signer, config.serviceAgreementAddress,
328
- SERVICE_AGREEMENT_ABI, "requestHumanEscalation", [BigInt(id), opts.reason],
329
- );
330
- } else {
331
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
332
- await client.requestHumanEscalation(BigInt(id), opts.reason);
333
- }
334
- console.log(' ' + c.success + c.white(` Escalated to human — agreement #${id}`));
335
- });
336
-
337
- dispute.command("resolve <id>").description("Owner-only admin path if you are operating the dispute contract").requiredOption("--outcome <outcome>", "provider|refund|partial-provider|partial-client|mutual-cancel|human-review").option("--provider-award <amount>", "Wei/token units", "0").option("--client-award <amount>", "Wei/token units", "0").action(async (id, opts) => {
338
- const config = loadConfig();
339
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
340
- const { signer } = await requireSigner(config);
341
- const mapping: Record<string, DisputeOutcome> = { provider: DisputeOutcome.PROVIDER_WINS, refund: DisputeOutcome.CLIENT_REFUND, 'partial-provider': DisputeOutcome.PARTIAL_PROVIDER, 'partial-client': DisputeOutcome.PARTIAL_CLIENT, 'mutual-cancel': DisputeOutcome.MUTUAL_CANCEL, 'human-review': DisputeOutcome.HUMAN_REVIEW_REQUIRED };
342
- printSenderInfo(config);
343
- if (config.walletContractAddress) {
344
- await executeContractWriteViaWallet(
345
- config.walletContractAddress, signer, config.serviceAgreementAddress,
346
- SERVICE_AGREEMENT_ABI, "resolveDisputeDetailed",
347
- [BigInt(id), mapping[String(opts.outcome)], BigInt(opts.providerAward), BigInt(opts.clientAward)],
348
- );
349
- } else {
350
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
351
- await client.resolveDisputeDetailed(BigInt(id), mapping[String(opts.outcome)], BigInt(opts.providerAward), BigInt(opts.clientAward));
352
- }
353
- console.log(' ' + c.success + c.white(` Resolved — agreement #${id}`));
354
- });
355
-
356
- dispute.command("owner-resolve <agreementId>")
357
- .description("Owner-only: resolve a dispute directly in favor of provider or client. Requires DISPUTED or ESCALATED_TO_HUMAN status.")
358
- .option("--favor-provider", "Resolve in favor of the provider (default: false = favor client)", false)
359
- .action(async (agreementId, opts) => {
360
- const config = loadConfig();
361
- if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
362
- const { signer } = await requireSigner(config);
363
- printSenderInfo(config);
364
- if (config.walletContractAddress) {
365
- await executeContractWriteViaWallet(
366
- config.walletContractAddress, signer, config.serviceAgreementAddress,
367
- SERVICE_AGREEMENT_ABI, "ownerResolveDispute", [BigInt(agreementId), !!opts.favorProvider],
368
- );
369
- } else {
370
- const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
371
- await client.ownerResolveDispute(BigInt(agreementId), !!opts.favorProvider);
372
- }
373
- console.log(' ' + c.success + c.white(` Owner resolved — agreement #${agreementId}`));
374
- });
375
- }
@@ -1,172 +0,0 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import * as os from "os";
6
- import { spawnSync } from "child_process";
7
- import { configExists, loadConfig } from "../config.js";
8
- import { c } from "../ui/colors.js";
9
-
10
- const ARC402_DIR = path.join(os.homedir(), ".arc402");
11
- const CONFIG_PATH = path.join(ARC402_DIR, "config.json");
12
- const DAEMON_PID_PATH = path.join(ARC402_DIR, "daemon.pid");
13
-
14
- function ok(label: string, detail?: string): void {
15
- process.stdout.write(
16
- " " + chalk.green("✓") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
17
- );
18
- }
19
-
20
- function fail(label: string, detail?: string): void {
21
- process.stdout.write(
22
- " " + chalk.red("✗") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
23
- );
24
- }
25
-
26
- function warn(label: string, detail?: string): void {
27
- process.stdout.write(
28
- " " + chalk.yellow("⚠") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
29
- );
30
- }
31
-
32
- async function fetchJson(url: string, timeoutMs = 4000): Promise<unknown> {
33
- const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
34
- return res.json();
35
- }
36
-
37
- export function registerDoctorCommand(program: Command): void {
38
- program
39
- .command("doctor")
40
- .description("Check ARC-402 environment health")
41
- .action(async () => {
42
- process.stdout.write("\n" + c.cyan("◈ ") + chalk.white("arc402 doctor") + "\n\n");
43
-
44
- // ── 1. Config exists ───────────────────────────────────────────────────
45
- if (configExists()) {
46
- ok("Config found", CONFIG_PATH);
47
- } else {
48
- fail("Config not found", "Run: arc402 config init");
49
- process.stdout.write("\n");
50
- return;
51
- }
52
-
53
- const config = loadConfig();
54
-
55
- // ── 2. RPC reachable ───────────────────────────────────────────────────
56
- try {
57
- const body = JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 });
58
- const res = await fetch(config.rpcUrl, {
59
- method: "POST",
60
- headers: { "Content-Type": "application/json" },
61
- body,
62
- signal: AbortSignal.timeout(5000),
63
- });
64
- const json = await res.json() as { result?: string };
65
- if (json.result) {
66
- const block = parseInt(json.result, 16);
67
- ok("RPC reachable", `block #${block.toLocaleString()}`);
68
- } else {
69
- fail("RPC returned no block", config.rpcUrl);
70
- }
71
- } catch (err) {
72
- fail("RPC unreachable", config.rpcUrl + " — " + (err instanceof Error ? err.message : String(err)));
73
- }
74
-
75
- // ── 3. Wallet deployed ─────────────────────────────────────────────────
76
- if (config.walletContractAddress) {
77
- try {
78
- const body = JSON.stringify({
79
- jsonrpc: "2.0",
80
- method: "eth_getCode",
81
- params: [config.walletContractAddress, "latest"],
82
- id: 1,
83
- });
84
- const res = await fetch(config.rpcUrl, {
85
- method: "POST",
86
- headers: { "Content-Type": "application/json" },
87
- body,
88
- signal: AbortSignal.timeout(5000),
89
- });
90
- const json = await res.json() as { result?: string };
91
- if (json.result && json.result !== "0x" && json.result.length > 2) {
92
- ok("Wallet deployed", config.walletContractAddress);
93
- } else {
94
- fail("Wallet not deployed", config.walletContractAddress + " — no code at address");
95
- }
96
- } catch (err) {
97
- warn("Wallet check failed", err instanceof Error ? err.message : String(err));
98
- }
99
- } else {
100
- warn("Wallet not configured", "Run: arc402 wallet deploy");
101
- }
102
-
103
- // ── 4. Machine key authorized ──────────────────────────────────────────
104
- if (config.privateKey && config.walletContractAddress) {
105
- try {
106
- const { ethers } = await import("ethers");
107
- const machineKey = new ethers.Wallet(config.privateKey);
108
- const provider = new ethers.JsonRpcProvider(config.rpcUrl);
109
- const iface = new ethers.Interface(["function authorizedMachineKeys(address) external view returns (bool)"]);
110
- const data = iface.encodeFunctionData("authorizedMachineKeys", [machineKey.address]);
111
- const result = await Promise.race([
112
- provider.call({ to: config.walletContractAddress, data }),
113
- new Promise<never>((_, r) => setTimeout(() => r(new Error("timeout")), 4000)),
114
- ]);
115
- const authorized = iface.decodeFunctionResult("authorizedMachineKeys", result)[0] as boolean;
116
- if (authorized) {
117
- ok("Machine key authorized", machineKey.address);
118
- } else {
119
- fail("Machine key not authorized", machineKey.address + " — run: arc402 wallet onboard");
120
- }
121
- } catch (err) {
122
- warn("Machine key check failed", err instanceof Error ? err.message : String(err));
123
- }
124
- } else if (!config.privateKey) {
125
- warn("Machine key not configured", "No privateKey in config");
126
- }
127
-
128
- // ── 5. Daemon running ──────────────────────────────────────────────────
129
- let daemonRunning = false;
130
- if (fs.existsSync(DAEMON_PID_PATH)) {
131
- try {
132
- const pid = parseInt(fs.readFileSync(DAEMON_PID_PATH, "utf-8").trim(), 10);
133
- // Check if process is alive
134
- process.kill(pid, 0);
135
- daemonRunning = true;
136
- ok("Daemon running", `PID ${pid}`);
137
- } catch {
138
- fail("Daemon PID file stale", "Run: arc402 daemon start");
139
- }
140
- } else {
141
- fail("Daemon not running", "Run: arc402 daemon start");
142
- }
143
-
144
- // ── 6. Docker available ────────────────────────────────────────────────
145
- try {
146
- const docker = spawnSync("docker", ["--version"], { encoding: "utf-8", timeout: 3000 });
147
- if (docker.status === 0) {
148
- ok("Docker available", docker.stdout.trim().split("\n")[0]);
149
- } else {
150
- warn("Docker not found", "Install from https://docs.docker.com/get-docker/");
151
- }
152
- } catch {
153
- warn("Docker not found", "Install from https://docs.docker.com/get-docker/");
154
- }
155
-
156
- // ── 7. HTTP relay reachable (if daemon is running) ─────────────────────
157
- if (daemonRunning) {
158
- try {
159
- const health = await fetchJson("http://localhost:4402/health") as { status?: string };
160
- if (health.status === "online") {
161
- ok("HTTP relay reachable", "http://localhost:4402");
162
- } else {
163
- warn("HTTP relay responded", JSON.stringify(health));
164
- }
165
- } catch {
166
- fail("HTTP relay not reachable", "http://localhost:4402 — daemon may still be starting");
167
- }
168
- }
169
-
170
- process.stdout.write("\n");
171
- });
172
- }