arc402-cli 0.9.19 → 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 (359) 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.d.ts.map +1 -1
  161. package/dist/endpoint-notify.js +49 -15
  162. package/dist/endpoint-notify.js.map +1 -1
  163. package/dist/index.js +50 -18
  164. package/dist/index.js.map +1 -1
  165. package/dist/openshell-runtime.d.ts.map +1 -1
  166. package/dist/openshell-runtime.js +82 -38
  167. package/dist/openshell-runtime.js.map +1 -1
  168. package/dist/program.d.ts.map +1 -1
  169. package/dist/program.js +85 -78
  170. package/dist/program.js.map +1 -1
  171. package/dist/repl.js +31 -25
  172. package/dist/repl.js.map +1 -1
  173. package/dist/signing.js +6 -3
  174. package/dist/signing.js.map +1 -1
  175. package/dist/telegram-notify.js +40 -3
  176. package/dist/telegram-notify.js.map +1 -1
  177. package/dist/tui/App.d.ts.map +1 -1
  178. package/dist/tui/App.js +56 -89
  179. package/dist/tui/App.js.map +1 -1
  180. package/dist/tui/Footer.js +7 -4
  181. package/dist/tui/Footer.js.map +1 -1
  182. package/dist/tui/Header.d.ts +1 -1
  183. package/dist/tui/Header.d.ts.map +1 -1
  184. package/dist/tui/Header.js +14 -9
  185. package/dist/tui/Header.js.map +1 -1
  186. package/dist/tui/InputLine.d.ts +2 -1
  187. package/dist/tui/InputLine.d.ts.map +1 -1
  188. package/dist/tui/InputLine.js +47 -97
  189. package/dist/tui/InputLine.js.map +1 -1
  190. package/dist/tui/Viewport.d.ts +1 -2
  191. package/dist/tui/Viewport.d.ts.map +1 -1
  192. package/dist/tui/Viewport.js +26 -6
  193. package/dist/tui/Viewport.js.map +1 -1
  194. package/dist/tui/WalletConnectPairing.js +19 -16
  195. package/dist/tui/WalletConnectPairing.js.map +1 -1
  196. package/dist/tui/components/Button.js +9 -6
  197. package/dist/tui/components/Button.js.map +1 -1
  198. package/dist/tui/components/CeremonyView.js +8 -5
  199. package/dist/tui/components/CeremonyView.js.map +1 -1
  200. package/dist/tui/components/CompletionDropdown.js +9 -6
  201. package/dist/tui/components/CompletionDropdown.js.map +1 -1
  202. package/dist/tui/components/ConfirmPrompt.js +8 -5
  203. package/dist/tui/components/ConfirmPrompt.js.map +1 -1
  204. package/dist/tui/components/CustomTextInput.js +14 -11
  205. package/dist/tui/components/CustomTextInput.js.map +1 -1
  206. package/dist/tui/components/InteractiveTable.js +12 -9
  207. package/dist/tui/components/InteractiveTable.js.map +1 -1
  208. package/dist/tui/components/StepSpinner.js +13 -10
  209. package/dist/tui/components/StepSpinner.js.map +1 -1
  210. package/dist/tui/components/Toast.js +12 -8
  211. package/dist/tui/components/Toast.js.map +1 -1
  212. package/dist/tui/index.d.ts.map +1 -1
  213. package/dist/tui/index.js +21 -28
  214. package/dist/tui/index.js.map +1 -1
  215. package/dist/tui/useChat.js +19 -13
  216. package/dist/tui/useChat.js.map +1 -1
  217. package/dist/tui/useCommand.d.ts +2 -3
  218. package/dist/tui/useCommand.d.ts.map +1 -1
  219. package/dist/tui/useCommand.js +24 -100
  220. package/dist/tui/useCommand.js.map +1 -1
  221. package/dist/tui/useNotifications.js +8 -5
  222. package/dist/tui/useNotifications.js.map +1 -1
  223. package/dist/tui/useScroll.d.ts.map +1 -1
  224. package/dist/tui/useScroll.js +12 -15
  225. package/dist/tui/useScroll.js.map +1 -1
  226. package/dist/ui/banner.d.ts +0 -12
  227. package/dist/ui/banner.d.ts.map +1 -1
  228. package/dist/ui/banner.js +19 -35
  229. package/dist/ui/banner.js.map +1 -1
  230. package/dist/ui/colors.js +19 -13
  231. package/dist/ui/colors.js.map +1 -1
  232. package/dist/ui/format.js +14 -6
  233. package/dist/ui/format.js.map +1 -1
  234. package/dist/ui/qr-render.js +11 -4
  235. package/dist/ui/qr-render.js.map +1 -1
  236. package/dist/ui/rpc-fallback.js +11 -6
  237. package/dist/ui/rpc-fallback.js.map +1 -1
  238. package/dist/ui/spinner.js +12 -6
  239. package/dist/ui/spinner.js.map +1 -1
  240. package/dist/ui/tree.js +6 -3
  241. package/dist/ui/tree.js.map +1 -1
  242. package/dist/utils/format.js +41 -27
  243. package/dist/utils/format.js.map +1 -1
  244. package/dist/utils/hash.js +42 -4
  245. package/dist/utils/hash.js.map +1 -1
  246. package/dist/utils/time.js +6 -2
  247. package/dist/utils/time.js.map +1 -1
  248. package/dist/wallet-router.d.ts +1 -1
  249. package/dist/wallet-router.d.ts.map +1 -1
  250. package/dist/wallet-router.js +19 -12
  251. package/dist/wallet-router.js.map +1 -1
  252. package/dist/walletconnect-session.d.ts +1 -1
  253. package/dist/walletconnect-session.d.ts.map +1 -1
  254. package/dist/walletconnect-session.js +11 -6
  255. package/dist/walletconnect-session.js.map +1 -1
  256. package/dist/walletconnect.d.ts +5 -6
  257. package/dist/walletconnect.d.ts.map +1 -1
  258. package/dist/walletconnect.js +35 -32
  259. package/dist/walletconnect.js.map +1 -1
  260. package/package.json +11 -10
  261. package/INK6-UX-SPEC.md +0 -446
  262. package/MIGRATION-SPEC.md +0 -108
  263. package/TUI-SPEC.md +0 -214
  264. package/scripts/authorize-machine-key.ts +0 -43
  265. package/scripts/drain-wallet.ts +0 -149
  266. package/scripts/execute-spend-only.ts +0 -81
  267. package/scripts/register-agent-userop.ts +0 -186
  268. package/src/abis.ts +0 -187
  269. package/src/bundler.ts +0 -235
  270. package/src/client.ts +0 -36
  271. package/src/coinbase-smart-wallet.ts +0 -51
  272. package/src/commands/accept.ts +0 -64
  273. package/src/commands/agent-handshake.ts +0 -72
  274. package/src/commands/agent.ts +0 -691
  275. package/src/commands/agreements.ts +0 -350
  276. package/src/commands/arbitrator.ts +0 -180
  277. package/src/commands/arena-handshake.ts +0 -274
  278. package/src/commands/arena.ts +0 -122
  279. package/src/commands/backup.ts +0 -117
  280. package/src/commands/cancel.ts +0 -35
  281. package/src/commands/channel.ts +0 -218
  282. package/src/commands/coldstart.ts +0 -165
  283. package/src/commands/config.ts +0 -68
  284. package/src/commands/contract-interaction.ts +0 -166
  285. package/src/commands/daemon.ts +0 -1054
  286. package/src/commands/deliver.ts +0 -148
  287. package/src/commands/discover.ts +0 -350
  288. package/src/commands/dispute.ts +0 -375
  289. package/src/commands/doctor.ts +0 -172
  290. package/src/commands/endpoint.ts +0 -620
  291. package/src/commands/feed.ts +0 -229
  292. package/src/commands/hire.ts +0 -245
  293. package/src/commands/migrate.ts +0 -177
  294. package/src/commands/negotiate.ts +0 -272
  295. package/src/commands/openshell.ts +0 -1055
  296. package/src/commands/owner.ts +0 -35
  297. package/src/commands/policy.ts +0 -263
  298. package/src/commands/relay.ts +0 -277
  299. package/src/commands/remediate.ts +0 -24
  300. package/src/commands/reputation.ts +0 -79
  301. package/src/commands/setup.ts +0 -343
  302. package/src/commands/trust.ts +0 -27
  303. package/src/commands/verify.ts +0 -91
  304. package/src/commands/wallet.ts +0 -3548
  305. package/src/commands/watch.ts +0 -220
  306. package/src/commands/watchtower.ts +0 -248
  307. package/src/commands/workroom.ts +0 -963
  308. package/src/config.ts +0 -220
  309. package/src/daemon/config.ts +0 -344
  310. package/src/daemon/hire-listener.ts +0 -226
  311. package/src/daemon/index.ts +0 -1089
  312. package/src/daemon/job-lifecycle.ts +0 -215
  313. package/src/daemon/notify.ts +0 -297
  314. package/src/daemon/token-metering.ts +0 -183
  315. package/src/daemon/userops.ts +0 -119
  316. package/src/daemon/wallet-monitor.ts +0 -90
  317. package/src/drain-v4.ts +0 -159
  318. package/src/endpoint-config.ts +0 -83
  319. package/src/endpoint-notify.ts +0 -134
  320. package/src/index.ts +0 -74
  321. package/src/openshell-runtime.ts +0 -281
  322. package/src/program.ts +0 -88
  323. package/src/repl.ts +0 -178
  324. package/src/signing.ts +0 -28
  325. package/src/telegram-notify.ts +0 -88
  326. package/src/tui/App.tsx +0 -263
  327. package/src/tui/Footer.tsx +0 -18
  328. package/src/tui/Header.tsx +0 -45
  329. package/src/tui/InputLine.tsx +0 -243
  330. package/src/tui/Viewport.tsx +0 -51
  331. package/src/tui/WalletConnectPairing.tsx +0 -114
  332. package/src/tui/components/Button.tsx +0 -38
  333. package/src/tui/components/CeremonyView.tsx +0 -39
  334. package/src/tui/components/CompletionDropdown.tsx +0 -56
  335. package/src/tui/components/ConfirmPrompt.tsx +0 -36
  336. package/src/tui/components/CustomTextInput.tsx +0 -132
  337. package/src/tui/components/InteractiveTable.tsx +0 -106
  338. package/src/tui/components/StepSpinner.tsx +0 -84
  339. package/src/tui/components/Toast.tsx +0 -59
  340. package/src/tui/index.tsx +0 -90
  341. package/src/tui/useChat.ts +0 -103
  342. package/src/tui/useCommand.ts +0 -238
  343. package/src/tui/useNotifications.ts +0 -28
  344. package/src/tui/useScroll.ts +0 -69
  345. package/src/ui/banner.ts +0 -78
  346. package/src/ui/colors.ts +0 -30
  347. package/src/ui/format.ts +0 -78
  348. package/src/ui/qr-render.ts +0 -92
  349. package/src/ui/rpc-fallback.ts +0 -59
  350. package/src/ui/spinner.ts +0 -56
  351. package/src/ui/tree.ts +0 -16
  352. package/src/utils/format.ts +0 -48
  353. package/src/utils/hash.ts +0 -5
  354. package/src/utils/time.ts +0 -15
  355. package/src/wallet-router.ts +0 -178
  356. package/src/walletconnect-session.ts +0 -27
  357. package/src/walletconnect.ts +0 -309
  358. package/test/time.test.js +0 -11
  359. package/tsconfig.json +0 -33
@@ -1,963 +0,0 @@
1
- import { Command } from "commander";
2
- import * as crypto from "node:crypto";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import * as os from "os";
6
- import * as http from "http";
7
- import { spawnSync, execSync } from "child_process";
8
- import {
9
- ARC402_DIR,
10
- runCmd,
11
- } from "../openshell-runtime.js";
12
- import { DAEMON_LOG, DAEMON_TOML, loadDaemonConfig } from "../daemon/config.js";
13
- import { c } from "../ui/colors.js";
14
- import { startSpinner } from "../ui/spinner.js";
15
- import { renderTree } from "../ui/tree.js";
16
- import { formatAddress } from "../ui/format.js";
17
-
18
- import { fileURLToPath } from "node:url";
19
- const __filename = fileURLToPath(import.meta.url);
20
- const __dirname = path.dirname(__filename);
21
-
22
- // ─── Daemon lifecycle notify ──────────────────────────────────────────────────
23
-
24
- function notifyDaemonWorkroomStatus(
25
- event: "entered" | "exited" | "job_started" | "job_completed",
26
- agentAddress?: string,
27
- jobId?: string,
28
- port = 4402
29
- ): void {
30
- try {
31
- // Try to read port from daemon config
32
- let daemonPort = port;
33
- if (fs.existsSync(DAEMON_TOML)) {
34
- try {
35
- const cfg = loadDaemonConfig();
36
- daemonPort = cfg.relay?.listen_port ?? port;
37
- } catch { /* use default */ }
38
- }
39
-
40
- const payload = JSON.stringify({
41
- event,
42
- agentAddress: agentAddress ?? "",
43
- jobId,
44
- timestamp: Date.now(),
45
- });
46
-
47
- const req = http.request({
48
- hostname: "127.0.0.1",
49
- port: daemonPort,
50
- path: "/workroom/status",
51
- method: "POST",
52
- headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) },
53
- });
54
- req.on("error", () => { /* non-fatal */ });
55
- req.write(payload);
56
- req.end();
57
- } catch { /* non-fatal */ }
58
- }
59
-
60
- // ─── Constants ────────────────────────────────────────────────────────────────
61
-
62
- const WORKROOM_IMAGE = "arc402-workroom";
63
- const WORKROOM_CONTAINER = "arc402-workroom";
64
- const POLICY_FILE = path.join(ARC402_DIR, "openshell-policy.yaml");
65
- const ARENA_POLICY_FILE = path.join(ARC402_DIR, "arena-policy.yaml");
66
- const ARENA_DATA_DIR = path.join(ARC402_DIR, "arena");
67
- const WORKROOM_DIR = path.join(__dirname, "..", "..", "..", "workroom"); // relative to cli/dist
68
-
69
- // ─── Helpers ──────────────────────────────────────────────────────────────────
70
-
71
- function dockerAvailable(): boolean {
72
- const r = runCmd("docker", ["info", "--format", "{{.ServerVersion}}"]);
73
- return r.ok;
74
- }
75
-
76
- function containerExists(): boolean {
77
- const r = runCmd("docker", ["inspect", WORKROOM_CONTAINER, "--format", "{{.State.Status}}"]);
78
- return r.ok;
79
- }
80
-
81
- function containerRunning(): boolean {
82
- const r = runCmd("docker", ["inspect", WORKROOM_CONTAINER, "--format", "{{.State.Running}}"]);
83
- return r.ok && r.stdout.trim() === "true";
84
- }
85
-
86
- function imageExists(): boolean {
87
- const r = runCmd("docker", ["image", "inspect", WORKROOM_IMAGE, "--format", "{{.Id}}"]);
88
- return r.ok;
89
- }
90
-
91
- function buildImage(): boolean {
92
- // Find the workroom directory (contains Dockerfile)
93
- const workroomSrc = path.resolve(__dirname, "..", "..", "..", "workroom");
94
- if (!fs.existsSync(path.join(workroomSrc, "Dockerfile"))) {
95
- console.error(`Dockerfile not found at ${workroomSrc}/Dockerfile`);
96
- return false;
97
- }
98
- console.log("Building ARC-402 Workroom image...");
99
- const result = spawnSync("docker", ["build", "-t", WORKROOM_IMAGE, workroomSrc], {
100
- stdio: "inherit",
101
- });
102
- return result.status === 0;
103
- }
104
-
105
- function getPolicyHash(): string {
106
- if (!fs.existsSync(POLICY_FILE)) return "(no policy file)";
107
- const content = fs.readFileSync(POLICY_FILE, "utf-8");
108
- // crypto imported at top level
109
- return "0x" + crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
110
- }
111
-
112
- // ─── Commands ─────────────────────────────────────────────────────────────────
113
-
114
- export function registerWorkroomCommands(program: Command): void {
115
- const workroom = program
116
- .command("workroom")
117
- .description("ARC-402 Workroom — governed execution environment for hired work. Your OpenClaw stays on the host; work runs inside the workroom.");
118
-
119
- // ── init ──────────────────────────────────────────────────────────────────
120
- workroom
121
- .command("init")
122
- .description("Create the ARC-402 Workroom: build Docker image, validate policy, prepare runtime bundle.")
123
- .action(async () => {
124
- console.log(c.brightCyan("ARC-402 Workroom Init"));
125
- console.log(c.dim("─────────────────────"));
126
-
127
- // Check Docker
128
- if (!dockerAvailable()) {
129
- console.error(c.failure + " " + c.red("Docker is not available. Install Docker Desktop and try again."));
130
- process.exit(1);
131
- }
132
- console.log(" " + c.success + c.white(" Docker available"));
133
-
134
- // Check policy file
135
- if (!fs.existsSync(POLICY_FILE)) {
136
- console.log(c.dim("No policy file found. Generating default..."));
137
- // Import and call the existing policy generator
138
- const { registerOpenShellCommands } = await import("./openshell.js");
139
- console.log(c.dim(`Policy file will be generated at: ${POLICY_FILE}`));
140
- console.log(c.dim("Run 'arc402 workroom policy preset core-launch' after init to apply defaults."));
141
- } else {
142
- console.log(" " + c.success + c.dim(" Policy file: ") + c.white(POLICY_FILE));
143
- }
144
-
145
- // Check daemon.toml
146
- if (!fs.existsSync(DAEMON_TOML)) {
147
- console.error(c.failure + " " + c.red("daemon.toml not found. Run 'arc402 daemon init' first."));
148
- process.exit(1);
149
- }
150
- console.log(" " + c.success + c.white(" daemon.toml found"));
151
-
152
- // Set up Arena directories and default policy
153
- if (!fs.existsSync(ARENA_DATA_DIR)) {
154
- fs.mkdirSync(ARENA_DATA_DIR, { recursive: true });
155
- for (const sub of ["feed", "profile", "state", "queue"]) {
156
- fs.mkdirSync(path.join(ARENA_DATA_DIR, sub), { recursive: true });
157
- }
158
- console.log(" " + c.success + c.white(" Arena directories created"));
159
- } else {
160
- console.log(" " + c.success + c.white(" Arena directories exist"));
161
- }
162
-
163
- // Copy default arena policy if not present
164
- if (!fs.existsSync(ARENA_POLICY_FILE)) {
165
- const defaultArenaPolicy = path.join(WORKROOM_DIR, "arena-policy.yaml");
166
- if (fs.existsSync(defaultArenaPolicy)) {
167
- fs.copyFileSync(defaultArenaPolicy, ARENA_POLICY_FILE);
168
- console.log(" " + c.success + c.white(" Arena policy: default installed"));
169
- } else {
170
- console.log(" " + c.warning + " " + c.yellow("Arena policy template not found — create manually at " + ARENA_POLICY_FILE));
171
- }
172
- } else {
173
- console.log(" " + c.success + c.white(" Arena policy exists"));
174
- }
175
-
176
- // Build image
177
- if (!imageExists()) {
178
- if (!buildImage()) {
179
- console.error(c.failure + " " + c.red("Failed to build workroom image."));
180
- process.exit(1);
181
- }
182
- }
183
- console.log(" " + c.success + c.dim(" Image: ") + c.white(WORKROOM_IMAGE));
184
-
185
- // Package CLI runtime for the workroom
186
- const cliDist = path.resolve(__dirname, "..", "..");
187
- const cliPackage = path.resolve(__dirname, "..", "..", "..", "package.json");
188
- if (fs.existsSync(cliDist) && fs.existsSync(cliPackage)) {
189
- console.log(" " + c.success + c.white(" CLI runtime available for workroom mount"));
190
- } else {
191
- console.warn(" " + c.warning + " " + c.yellow("CLI dist not found — workroom will need runtime bundle"));
192
- }
193
-
194
- console.log("\n" + c.success + c.white(" Workroom initialized. Start with: arc402 workroom start"));
195
- console.log(c.dim("Policy hash: ") + c.white(getPolicyHash()));
196
- });
197
-
198
- // ── start ─────────────────────────────────────────────────────────────────
199
- workroom
200
- .command("start")
201
- .description("Start the ARC-402 Workroom (always-on governed container with daemon inside).")
202
- .action(async () => {
203
- if (!dockerAvailable()) {
204
- console.error("Docker is not available.");
205
- process.exit(1);
206
- }
207
-
208
- if (containerRunning()) {
209
- console.log("Workroom is already running.");
210
- process.exit(0);
211
- }
212
-
213
- // Remove stopped container if exists
214
- if (containerExists()) {
215
- runCmd("docker", ["rm", "-f", WORKROOM_CONTAINER]);
216
- }
217
-
218
- // Build image if needed
219
- if (!imageExists()) {
220
- if (!buildImage()) {
221
- console.error("Failed to build workroom image.");
222
- process.exit(1);
223
- }
224
- }
225
-
226
- // Resolve secrets from local config
227
- const machineKey = process.env.ARC402_MACHINE_KEY || "";
228
- const telegramBot = process.env.TELEGRAM_BOT_TOKEN || "";
229
- const telegramChat = process.env.TELEGRAM_CHAT_ID || "";
230
-
231
- if (!machineKey) {
232
- console.error("ARC402_MACHINE_KEY not set in environment.");
233
- console.error("Export it before starting: export ARC402_MACHINE_KEY=0x...");
234
- process.exit(1);
235
- }
236
-
237
- // CLI runtime path
238
- const cliRoot = path.resolve(__dirname, "..", "..", "..");
239
-
240
- console.log(c.dim("Starting ARC-402 Workroom..."));
241
-
242
- const args = [
243
- "run", "-d",
244
- "--name", WORKROOM_CONTAINER,
245
- "--restart", "unless-stopped",
246
- "--cap-add", "NET_ADMIN", // Required for iptables
247
- // Mount config (read-write for daemon state/logs)
248
- "-v", `${ARC402_DIR}:/workroom/.arc402:rw`,
249
- // Mount CLI runtime (read-only)
250
- "-v", `${cliRoot}:/workroom/runtime:ro`,
251
- // Mount jobs directory
252
- "-v", `${path.join(ARC402_DIR, "jobs")}:/workroom/jobs:rw`,
253
- // Mount worker directory (identity, memory, skills, knowledge)
254
- "-v", `${path.join(ARC402_DIR, "worker")}:/workroom/worker:rw`,
255
- // Mount Arena data directory (feed index, profile cache, state, queue)
256
- "-v", `${ARENA_DATA_DIR}:/workroom/arena:rw`,
257
- // Inject secrets as env vars
258
- "-e", `ARC402_MACHINE_KEY=${machineKey}`,
259
- "-e", `TELEGRAM_BOT_TOKEN=${telegramBot}`,
260
- "-e", `TELEGRAM_CHAT_ID=${telegramChat}`,
261
- "-e", `ARC402_DAEMON_PROCESS=1`,
262
- "-e", `ARC402_DAEMON_FOREGROUND=1`,
263
- // Expose relay port
264
- "-p", "4402:4402",
265
- WORKROOM_IMAGE,
266
- ];
267
-
268
- const result = spawnSync("docker", args, { stdio: "inherit" });
269
- if (result.status !== 0) {
270
- console.error("Failed to start workroom container.");
271
- process.exit(1);
272
- }
273
-
274
- // Wait briefly and check health
275
- spawnSync("sleep", ["2"]);
276
-
277
- if (containerRunning()) {
278
- console.log("\n" + c.success + c.white(" ARC-402 Workroom is running"));
279
- renderTree([
280
- { label: "Container", value: WORKROOM_CONTAINER },
281
- { label: "Policy hash", value: getPolicyHash() },
282
- { label: "Relay port", value: "4402" },
283
- { label: "Logs", value: "arc402 workroom logs", last: true },
284
- ]);
285
- // Notify local daemon of workroom entry
286
- notifyDaemonWorkroomStatus("entered");
287
- } else {
288
- console.error(c.failure + " " + c.red("Workroom started but exited immediately. Check logs:"));
289
- console.error(c.dim(" docker logs arc402-workroom"));
290
- process.exit(1);
291
- }
292
- });
293
-
294
- // ── stop ──────────────────────────────────────────────────────────────────
295
- workroom
296
- .command("stop")
297
- .description("Stop the ARC-402 Workroom.")
298
- .action(async () => {
299
- if (!containerRunning()) {
300
- console.log("Workroom is not running.");
301
- return;
302
- }
303
- console.log(c.dim("Stopping ARC-402 Workroom..."));
304
- // Notify daemon before stopping (daemon may be inside container)
305
- notifyDaemonWorkroomStatus("exited");
306
- runCmd("docker", ["stop", WORKROOM_CONTAINER]);
307
- console.log(" " + c.success + c.white(" Workroom stopped"));
308
- });
309
-
310
- // ── status ────────────────────────────────────────────────────────────────
311
- workroom
312
- .command("status")
313
- .description("Show ARC-402 Workroom health, policy, and active state.")
314
- .action(async () => {
315
- console.log(c.brightCyan("ARC-402 Workroom Status"));
316
- console.log(c.dim("───────────────────────"));
317
-
318
- // Docker
319
- if (!dockerAvailable()) {
320
- console.log(c.dim("Docker: ") + c.failure + " " + c.red("not available"));
321
- return;
322
- }
323
- console.log(c.dim("Docker: ") + c.success + c.white(" available"));
324
-
325
- // Image
326
- if (imageExists()) {
327
- console.log(c.dim("Image: ") + c.success + " " + c.white(WORKROOM_IMAGE));
328
- } else {
329
- console.log(c.dim("Image: ") + c.failure + " " + c.red("not built (run: arc402 workroom init)"));
330
- }
331
-
332
- // Container
333
- if (containerRunning()) {
334
- console.log(c.dim("Container: ") + c.success + c.white(` running (${WORKROOM_CONTAINER})`));
335
-
336
- // Get container uptime
337
- const inspect = runCmd("docker", ["inspect", WORKROOM_CONTAINER, "--format", "{{.State.StartedAt}}"]);
338
- if (inspect.ok) {
339
- const started = new Date(inspect.stdout.trim());
340
- const uptime = Math.floor((Date.now() - started.getTime()) / 1000);
341
- const h = Math.floor(uptime / 3600);
342
- const m = Math.floor((uptime % 3600) / 60);
343
- console.log(c.dim("Uptime: ") + c.white(`${h}h ${m}m`));
344
- }
345
-
346
- // Get iptables rule count from inside container
347
- const rules = runCmd("docker", ["exec", WORKROOM_CONTAINER, "iptables", "-L", "OUTPUT", "-n", "--line-numbers"]);
348
- if (rules.ok) {
349
- const ruleCount = rules.stdout.split("\n").filter(l => l.match(/^\d+/)).length;
350
- console.log(c.dim("Network rules: ") + c.white(`${ruleCount} iptables rules enforced`));
351
- }
352
- } else if (containerExists()) {
353
- console.log(c.dim("Container: ") + c.warning + " " + c.yellow("stopped (run: arc402 workroom start)"));
354
- } else {
355
- console.log(c.dim("Container: ") + c.failure + " " + c.red("not created (run: arc402 workroom init)"));
356
- }
357
-
358
- // Policy
359
- if (fs.existsSync(POLICY_FILE)) {
360
- console.log(c.dim("Policy file: ") + c.success + " " + c.white(POLICY_FILE));
361
- } else {
362
- console.log(c.dim("Policy file: ") + c.failure + " " + c.red("missing"));
363
- }
364
- console.log(c.dim("Policy hash: ") + c.white(getPolicyHash()));
365
-
366
- // Arena
367
- const arenaExists = fs.existsSync(ARENA_DATA_DIR);
368
- const arenaPolicy = fs.existsSync(ARENA_POLICY_FILE);
369
- if (arenaExists) {
370
- console.log(c.dim("Arena data: ") + c.success + " " + c.white(ARENA_DATA_DIR));
371
- } else {
372
- console.log(c.dim("Arena data: ") + c.failure + " " + c.red("missing (run: arc402 workroom init)"));
373
- }
374
- console.log(c.dim("Arena policy: ") + (arenaPolicy ? c.success + c.white(" loaded") : c.failure + " " + c.red("missing")));
375
-
376
- // Arena queue (pending approvals)
377
- if (arenaExists) {
378
- const queueDir = path.join(ARENA_DATA_DIR, "queue");
379
- if (fs.existsSync(queueDir)) {
380
- const pending = fs.readdirSync(queueDir).filter(f => f.endsWith(".json")).length;
381
- if (pending > 0) {
382
- console.log(c.dim("Arena queue: ") + c.warning + " " + c.yellow(`${pending} action(s) awaiting approval`));
383
- } else {
384
- console.log(c.dim("Arena queue: ") + c.success + c.white(" empty"));
385
- }
386
- }
387
- }
388
- });
389
-
390
- // ── logs ──────────────────────────────────────────────────────────────────
391
- workroom
392
- .command("logs")
393
- .description("Tail workroom daemon logs.")
394
- .option("--follow", "Stream live log output")
395
- .option("-n, --lines <n>", "Number of lines", "50")
396
- .action(async (opts) => {
397
- const args = ["logs"];
398
- if (opts.follow) args.push("-f");
399
- args.push("--tail", opts.lines);
400
- args.push(WORKROOM_CONTAINER);
401
-
402
- spawnSync("docker", args, { stdio: "inherit" });
403
- });
404
-
405
- // ── shell ─────────────────────────────────────────────────────────────────
406
- workroom
407
- .command("shell")
408
- .description("Open a shell inside the workroom for debugging.")
409
- .action(async () => {
410
- if (!containerRunning()) {
411
- console.error("Workroom is not running.");
412
- process.exit(1);
413
- }
414
- spawnSync("docker", ["exec", "-it", WORKROOM_CONTAINER, "/bin/bash"], {
415
- stdio: "inherit",
416
- });
417
- });
418
-
419
- // ── doctor ────────────────────────────────────────────────────────────────
420
- workroom
421
- .command("doctor")
422
- .description("Diagnose workroom health: Docker, image, container, network, policy, daemon.")
423
- .action(async () => {
424
- console.log(c.brightCyan("ARC-402 Workroom Doctor"));
425
- console.log(c.dim("───────────────────────"));
426
-
427
- const checks: Array<{ label: string; pass: boolean; detail: string }> = [];
428
-
429
- // Docker
430
- const docker = dockerAvailable();
431
- checks.push({ label: "Docker", pass: docker, detail: docker ? "available" : "not available — install Docker Desktop" });
432
-
433
- // Image
434
- const img = imageExists();
435
- checks.push({ label: "Image", pass: img, detail: img ? WORKROOM_IMAGE : "not built — run: arc402 workroom init" });
436
-
437
- // Container
438
- const running = containerRunning();
439
- checks.push({ label: "Container", pass: running, detail: running ? "running" : "not running — run: arc402 workroom start" });
440
-
441
- // Policy
442
- const policyExists = fs.existsSync(POLICY_FILE);
443
- checks.push({ label: "Policy file", pass: policyExists, detail: policyExists ? POLICY_FILE : "missing" });
444
-
445
- // daemon.toml
446
- const daemonCfg = fs.existsSync(DAEMON_TOML);
447
- checks.push({ label: "daemon.toml", pass: daemonCfg, detail: daemonCfg ? "found" : "missing — run: arc402 daemon init" });
448
-
449
- // Machine key env
450
- const mk = !!process.env.ARC402_MACHINE_KEY;
451
- checks.push({ label: "Machine key env", pass: mk, detail: mk ? "set" : "ARC402_MACHINE_KEY not in environment" });
452
-
453
- // Network connectivity (if running)
454
- if (running) {
455
- const rpcTest = runCmd("docker", ["exec", WORKROOM_CONTAINER, "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "5", "https://mainnet.base.org"]);
456
- const rpcOk = rpcTest.ok && rpcTest.stdout.trim() !== "000";
457
- checks.push({ label: "Base RPC from workroom", pass: rpcOk, detail: rpcOk ? `HTTP ${rpcTest.stdout.trim()}` : "FAILED — network policy may be blocking RPC" });
458
- }
459
-
460
- // Print results
461
- for (const chk of checks) {
462
- if (chk.pass) {
463
- console.log(" " + c.success + " " + c.dim(chk.label + ":") + " " + c.white(chk.detail));
464
- } else {
465
- console.log(" " + c.failure + " " + c.dim(chk.label + ":") + " " + c.red(chk.detail) + c.yellow(" ← FIX"));
466
- }
467
- }
468
-
469
- const failures = checks.filter(chk => !chk.pass);
470
- if (failures.length === 0) {
471
- console.log("\n" + c.success + c.white(" All checks passed. Workroom is healthy."));
472
- } else {
473
- console.log("\n" + c.failure + " " + c.red(`${failures.length} issue(s) found.`));
474
- }
475
- });
476
-
477
- // ── policy (delegate to existing openshell policy commands) ───────────────
478
- workroom
479
- .command("policy")
480
- .description("Manage workroom network policy. Delegates to the existing policy UX.")
481
- .action(() => {
482
- console.log("Use the policy subcommands:");
483
- console.log(" arc402 workroom policy list");
484
- console.log(" arc402 workroom policy preset <name>");
485
- console.log(" arc402 workroom policy peer add <host>");
486
- console.log(" arc402 workroom policy test <host>");
487
- console.log(" arc402 workroom policy hash");
488
- console.log(" arc402 workroom policy reload");
489
- console.log("\nFor now, these delegate to 'arc402 openshell policy' commands.");
490
- console.log("Full native workroom policy management coming in next release.");
491
- });
492
-
493
- // ── policy hash ──────────────────────────────────────────────────────────
494
- const policyCmd = workroom.command("policy-hash")
495
- .description("Get the SHA-256 hash of the current workroom policy (for AgentRegistry).")
496
- .action(async () => {
497
- console.log(getPolicyHash());
498
- });
499
-
500
- // ── policy test ──────────────────────────────────────────────────────────
501
- workroom
502
- .command("policy-test <host>")
503
- .description("Test if a specific host is reachable from inside the workroom.")
504
- .action(async (host) => {
505
- if (!containerRunning()) {
506
- console.error("Workroom is not running. Start it first: arc402 workroom start");
507
- process.exit(1);
508
- }
509
- console.log(c.dim(`Testing connectivity to ${host} from inside workroom...`));
510
- const result = runCmd("docker", [
511
- "exec", WORKROOM_CONTAINER,
512
- "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "5",
513
- `https://${host}`,
514
- ]);
515
- if (result.ok && result.stdout.trim() !== "000") {
516
- console.log(" " + c.success + " " + c.white(host) + c.dim(` is reachable (HTTP ${result.stdout.trim()})`));
517
- } else {
518
- console.log(" " + c.failure + " " + c.red(`${host} is NOT reachable from the workroom`));
519
- console.log(c.dim(" This host may not be in the workroom policy."));
520
- console.log(c.dim(" Add it with: arc402 openshell policy add <name> <host>"));
521
- }
522
- });
523
-
524
- // ── worker ─────────────────────────────────────────────────────────────────
525
- const worker = workroom.command("worker").description("Manage the workroom worker — the agent identity that executes hired tasks.");
526
-
527
- worker
528
- .command("init")
529
- .description("Initialize the workroom worker identity and configuration.")
530
- .option("--name <name>", "Worker display name", "Worker")
531
- .option("--model <model>", "Preferred LLM model for task execution")
532
- .action(async (opts) => {
533
- const workerDir = path.join(ARC402_DIR, "worker");
534
- const memoryDir = path.join(workerDir, "memory");
535
- const skillsDir = path.join(workerDir, "skills");
536
-
537
- fs.mkdirSync(memoryDir, { recursive: true });
538
- fs.mkdirSync(skillsDir, { recursive: true });
539
-
540
- // Generate default worker SOUL.md
541
- const soulPath = path.join(workerDir, "SOUL.md");
542
- if (!fs.existsSync(soulPath)) {
543
- fs.writeFileSync(soulPath, `# Worker Identity — ${opts.name}
544
-
545
- You are a professional worker operating under an ARC-402 governed workroom.
546
-
547
- ## Your role
548
- - Execute hired tasks within governance bounds
549
- - Produce high-quality deliverables on deadline
550
- - Follow the task specification precisely
551
- - Report issues early if the task cannot be completed as specified
552
-
553
- ## What you have access to
554
- - The task specification from the hiring agreement
555
- - Skills relevant to your registered capabilities
556
- - Accumulated learnings from previous jobs (in memory/learnings.md)
557
- - Network access only to policy-approved hosts
558
-
559
- ## What you do NOT have access to
560
- - The operator's personal conversations or memory
561
- - The operator's other agents or their state
562
- - Network hosts not in the workroom policy
563
- - Files outside the workroom
564
-
565
- ## How you learn
566
- After completing each job, reflect on:
567
- - What techniques worked well
568
- - What patterns you noticed in the task
569
- - What domain knowledge you acquired
570
- - What you would do differently next time
571
-
572
- Write these learnings concisely. They will be available on your next job.
573
-
574
- ## Professional standards
575
- - Deliver on time or communicate blockers before the deadline
576
- - Never fabricate data or claim work was done when it wasn't
577
- - If the task is unclear, produce the best interpretation and document assumptions
578
- - Every deliverable must be verifiable against the task spec
579
- `);
580
- console.log(" " + c.success + c.dim(` Worker SOUL.md created: ${soulPath}`));
581
- } else {
582
- console.log(" " + c.success + c.dim(` Worker SOUL.md already exists: ${soulPath}`));
583
- }
584
-
585
- // Generate default MEMORY.md
586
- const memoryPath = path.join(workerDir, "MEMORY.md");
587
- if (!fs.existsSync(memoryPath)) {
588
- fs.writeFileSync(memoryPath, `# Worker Memory
589
-
590
- *Last updated: ${new Date().toISOString().split("T")[0]}*
591
-
592
- ## Job count: 0
593
- ## Total earned: 0 ETH
594
-
595
- ## Learnings
596
-
597
- No jobs completed yet. Learnings will accumulate here as the worker completes hired tasks.
598
- `);
599
- console.log(" " + c.success + c.dim(` Worker MEMORY.md created: ${memoryPath}`));
600
- }
601
-
602
- // Generate learnings.md
603
- const learningsPath = path.join(memoryDir, "learnings.md");
604
- if (!fs.existsSync(learningsPath)) {
605
- fs.writeFileSync(learningsPath, `# Accumulated Learnings
606
-
607
- *Distilled from completed jobs. Available to the worker on every new task.*
608
-
609
- ---
610
-
611
- No learnings yet. Complete your first hired task to start accumulating expertise.
612
- `);
613
- console.log(" " + c.success + c.dim(` Learnings file created: ${learningsPath}`));
614
- }
615
-
616
- // Worker config
617
- const configPath = path.join(workerDir, "config.json");
618
- if (!fs.existsSync(configPath)) {
619
- const config = {
620
- name: opts.name,
621
- model: opts.model || "default",
622
- capabilities: [],
623
- created: new Date().toISOString(),
624
- job_count: 0,
625
- total_earned_eth: "0",
626
- };
627
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
628
- console.log(" " + c.success + c.dim(` Worker config created: ${configPath}`));
629
- }
630
-
631
- console.log("\n" + c.success + c.white(` Worker initialized at: ${workerDir}`));
632
- console.log(c.dim("Next: customize the worker SOUL.md and add skills."));
633
- console.log(c.dim(" arc402 workroom worker set-soul <file>"));
634
- console.log(c.dim(" arc402 workroom worker set-skills <dir>"));
635
- });
636
-
637
- worker
638
- .command("status")
639
- .description("Show worker identity, job count, learnings, and configuration.")
640
- .action(async () => {
641
- const workerDir = path.join(ARC402_DIR, "worker");
642
- const configPath = path.join(workerDir, "config.json");
643
-
644
- if (!fs.existsSync(configPath)) {
645
- console.error("Worker not initialized. Run: arc402 workroom worker init");
646
- process.exit(1);
647
- }
648
-
649
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
650
- const memoryDir = path.join(workerDir, "memory");
651
- const jobFiles = fs.existsSync(memoryDir)
652
- ? fs.readdirSync(memoryDir).filter(f => f.startsWith("job-")).length
653
- : 0;
654
- const learningsPath = path.join(memoryDir, "learnings.md");
655
- const learningsSize = fs.existsSync(learningsPath)
656
- ? fs.statSync(learningsPath).size
657
- : 0;
658
- const skillsDir = path.join(workerDir, "skills");
659
- const skillCount = fs.existsSync(skillsDir)
660
- ? fs.readdirSync(skillsDir).length
661
- : 0;
662
-
663
- console.log(c.brightCyan("ARC-402 Workroom Worker"));
664
- console.log(c.dim("───────────────────────"));
665
- renderTree([
666
- { label: "Name", value: config.name },
667
- { label: "Model", value: config.model },
668
- { label: "Created", value: config.created },
669
- { label: "Jobs done", value: String(config.job_count) },
670
- { label: "Job memories", value: String(jobFiles) },
671
- { label: "Learnings", value: learningsSize > 200 ? Math.round(learningsSize / 1024) + " KB" : "empty" },
672
- { label: "Skills", value: String(skillCount) },
673
- { label: "Total earned", value: config.total_earned_eth + " ETH", last: true },
674
- ]);
675
- });
676
-
677
- worker
678
- .command("set-soul <file>")
679
- .description("Upload a custom worker SOUL.md.")
680
- .action(async (file) => {
681
- if (!fs.existsSync(file)) {
682
- console.error(`File not found: ${file}`);
683
- process.exit(1);
684
- }
685
- const dest = path.join(ARC402_DIR, "worker", "SOUL.md");
686
- fs.mkdirSync(path.dirname(dest), { recursive: true });
687
- fs.copyFileSync(file, dest);
688
- console.log(" " + c.success + c.white(` Worker SOUL.md updated from: ${file}`));
689
- });
690
-
691
- worker
692
- .command("set-skills <dir>")
693
- .description("Copy skills into the workroom worker.")
694
- .action(async (dir) => {
695
- if (!fs.existsSync(dir)) {
696
- console.error(`Directory not found: ${dir}`);
697
- process.exit(1);
698
- }
699
- const dest = path.join(ARC402_DIR, "worker", "skills");
700
- fs.mkdirSync(dest, { recursive: true });
701
- // Copy all files from source to dest
702
- const files = fs.readdirSync(dir);
703
- for (const f of files) {
704
- const src = path.join(dir, f);
705
- const dst = path.join(dest, f);
706
- if (fs.statSync(src).isFile()) {
707
- fs.copyFileSync(src, dst);
708
- } else if (fs.statSync(src).isDirectory()) {
709
- // Recursive copy for skill directories
710
- fs.cpSync(src, dst, { recursive: true });
711
- }
712
- }
713
- console.log(" " + c.success + c.white(` ${files.length} items copied to worker skills`));
714
- });
715
-
716
- worker
717
- .command("set-knowledge <dir>")
718
- .description("Mount a knowledge directory into the workroom. Contains reference materials, training data, domain docs — anything the worker needs to deliver its services.")
719
- .action(async (dir) => {
720
- if (!fs.existsSync(dir)) {
721
- console.error(`Directory not found: ${dir}`);
722
- process.exit(1);
723
- }
724
- const dest = path.join(ARC402_DIR, "worker", "knowledge");
725
- fs.mkdirSync(dest, { recursive: true });
726
- const files = fs.readdirSync(dir);
727
- let count = 0;
728
- for (const f of files) {
729
- const src = path.join(dir, f);
730
- const dst = path.join(dest, f);
731
- if (fs.statSync(src).isFile()) {
732
- fs.copyFileSync(src, dst);
733
- count++;
734
- } else if (fs.statSync(src).isDirectory()) {
735
- fs.cpSync(src, dst, { recursive: true });
736
- count++;
737
- }
738
- }
739
- console.log(" " + c.success + c.white(` ${count} items copied to worker knowledge`));
740
- console.log(" " + c.dim("Path:") + " " + c.white(dest));
741
- console.log(c.dim(" The worker can reference these files during hired tasks."));
742
- console.log(c.dim(" To update: run this command again with the updated directory."));
743
- });
744
-
745
- worker
746
- .command("knowledge")
747
- .description("List the worker's knowledge directory contents.")
748
- .action(async () => {
749
- const knowledgeDir = path.join(ARC402_DIR, "worker", "knowledge");
750
- if (!fs.existsSync(knowledgeDir)) {
751
- console.log("No knowledge directory. Add one with: arc402 workroom worker set-knowledge <dir>");
752
- return;
753
- }
754
- const files = fs.readdirSync(knowledgeDir, { recursive: true, withFileTypes: false }) as string[];
755
- if (files.length === 0) {
756
- console.log("Knowledge directory is empty.");
757
- return;
758
- }
759
- console.log(`Worker knowledge (${files.length} items):\n`);
760
- for (const f of fs.readdirSync(knowledgeDir)) {
761
- const stat = fs.statSync(path.join(knowledgeDir, f));
762
- const size = stat.isDirectory() ? "dir" : `${(stat.size / 1024).toFixed(1)} KB`;
763
- console.log(` ${f.padEnd(40)} ${size}`);
764
- }
765
- });
766
-
767
- worker
768
- .command("memory")
769
- .description("Show the worker's accumulated learnings.")
770
- .action(async () => {
771
- const learningsPath = path.join(ARC402_DIR, "worker", "memory", "learnings.md");
772
- if (!fs.existsSync(learningsPath)) {
773
- console.log("No learnings yet. Complete a hired task first.");
774
- return;
775
- }
776
- console.log(fs.readFileSync(learningsPath, "utf-8"));
777
- });
778
-
779
- worker
780
- .command("memory-reset")
781
- .description("Clear the worker's accumulated memory (start fresh).")
782
- .action(async () => {
783
- const memoryDir = path.join(ARC402_DIR, "worker", "memory");
784
- if (fs.existsSync(memoryDir)) {
785
- const files = fs.readdirSync(memoryDir);
786
- for (const f of files) fs.unlinkSync(path.join(memoryDir, f));
787
- fs.writeFileSync(path.join(memoryDir, "learnings.md"), `# Accumulated Learnings\n\n*Reset: ${new Date().toISOString()}*\n\nNo learnings yet.\n`);
788
- }
789
- // Reset job count in config
790
- const configPath = path.join(ARC402_DIR, "worker", "config.json");
791
- if (fs.existsSync(configPath)) {
792
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
793
- config.job_count = 0;
794
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
795
- }
796
- console.log(" " + c.success + c.white(" Worker memory cleared. Starting fresh."));
797
- });
798
-
799
- // ── token usage ──────────────────────────────────────────────────────────
800
- workroom
801
- .command("token-usage [agreementId]")
802
- .description("Show token usage for a specific agreement or across all jobs.")
803
- .action(async (agreementId) => {
804
- const { readUsageReport, formatUsageReport } = await import("../daemon/token-metering.js");
805
-
806
- if (agreementId) {
807
- const usage = readUsageReport(agreementId);
808
- if (!usage) {
809
- console.log(`No token usage data for agreement: ${agreementId}`);
810
- return;
811
- }
812
- console.log(formatUsageReport(usage));
813
- } else {
814
- // Aggregate across all receipts
815
- const receiptsDir = path.join(ARC402_DIR, "receipts");
816
- if (!fs.existsSync(receiptsDir)) {
817
- console.log("No receipts yet.");
818
- return;
819
- }
820
- const files = fs.readdirSync(receiptsDir).filter((f: string) => f.endsWith(".json"));
821
- let totalInput = 0;
822
- let totalOutput = 0;
823
- let totalCost = 0;
824
- let jobsWithUsage = 0;
825
-
826
- for (const f of files) {
827
- try {
828
- const receipt = JSON.parse(fs.readFileSync(path.join(receiptsDir, f), "utf-8"));
829
- if (receipt.token_usage) {
830
- totalInput += receipt.token_usage.total_input || 0;
831
- totalOutput += receipt.token_usage.total_output || 0;
832
- totalCost += receipt.token_usage.estimated_cost_usd || 0;
833
- jobsWithUsage++;
834
- }
835
- } catch { /* skip */ }
836
- }
837
-
838
- if (jobsWithUsage === 0) {
839
- console.log("No token usage data in any receipts yet.");
840
- return;
841
- }
842
-
843
- console.log("Aggregate Token Usage");
844
- console.log("─────────────────────");
845
- console.log(`Jobs with data: ${jobsWithUsage}`);
846
- console.log(`Total tokens: ${(totalInput + totalOutput).toLocaleString()} (${totalInput.toLocaleString()} in / ${totalOutput.toLocaleString()} out)`);
847
- console.log(`Est. total cost: $${totalCost.toFixed(4)}`);
848
- if (jobsWithUsage > 0) {
849
- console.log(`Avg per job: $${(totalCost / jobsWithUsage).toFixed(4)}`);
850
- }
851
- }
852
- });
853
-
854
- // ── receipts + earnings ──────────────────────────────────────────────────
855
- workroom
856
- .command("receipts")
857
- .description("List all execution receipts from completed jobs.")
858
- .action(async () => {
859
- const receiptsDir = path.join(ARC402_DIR, "receipts");
860
- if (!fs.existsSync(receiptsDir)) {
861
- console.log("No receipts yet.");
862
- return;
863
- }
864
- const files = fs.readdirSync(receiptsDir).filter(f => f.endsWith(".json")).sort();
865
- if (files.length === 0) {
866
- console.log("No receipts yet.");
867
- return;
868
- }
869
- console.log(`${files.length} execution receipt(s):\n`);
870
- for (const f of files) {
871
- try {
872
- const receipt = JSON.parse(fs.readFileSync(path.join(receiptsDir, f), "utf-8"));
873
- const id = receipt.agreement_id || f.replace(".json", "");
874
- const time = receipt.completed_at || "unknown";
875
- const hash = receipt.deliverable_hash ? receipt.deliverable_hash.slice(0, 10) + "..." : "—";
876
- console.log(` ${id} ${time} deliverable: ${hash}`);
877
- } catch {
878
- console.log(` ${f} (unreadable)`);
879
- }
880
- }
881
- });
882
-
883
- workroom
884
- .command("receipt <agreementId>")
885
- .description("Show full execution receipt for a specific job.")
886
- .action(async (agreementId) => {
887
- const receiptPath = path.join(ARC402_DIR, "receipts", `${agreementId}.json`);
888
- if (!fs.existsSync(receiptPath)) {
889
- console.error(`No receipt found for agreement: ${agreementId}`);
890
- process.exit(1);
891
- }
892
- console.log(fs.readFileSync(receiptPath, "utf-8"));
893
- });
894
-
895
- workroom
896
- .command("earnings")
897
- .description("Show total earnings from completed jobs.")
898
- .option("--period <period>", "Time period (e.g. 7d, 30d, all)", "all")
899
- .action(async (opts) => {
900
- const configPath = path.join(ARC402_DIR, "worker", "config.json");
901
- if (!fs.existsSync(configPath)) {
902
- console.log("No worker configured. Run: arc402 workroom worker init");
903
- return;
904
- }
905
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
906
- console.log(c.brightCyan("ARC-402 Earnings"));
907
- console.log(c.dim("────────────────"));
908
- const earningsItems: { label: string; value: string; last?: boolean }[] = [
909
- { label: "Total earned", value: config.total_earned_eth + " ETH" },
910
- { label: "Jobs completed", value: String(config.job_count) },
911
- ];
912
- if (config.job_count > 0) {
913
- const avg = (parseFloat(config.total_earned_eth) / config.job_count).toFixed(6);
914
- earningsItems.push({ label: "Average/job", value: avg + " ETH" });
915
- }
916
- earningsItems[earningsItems.length - 1].last = true;
917
- renderTree(earningsItems);
918
- });
919
-
920
- workroom
921
- .command("history")
922
- .description("Show job history with outcomes and earnings.")
923
- .action(async () => {
924
- const memoryDir = path.join(ARC402_DIR, "worker", "memory");
925
- if (!fs.existsSync(memoryDir)) {
926
- console.log("No job history yet.");
927
- return;
928
- }
929
- const jobFiles = fs.readdirSync(memoryDir).filter(f => f.startsWith("job-")).sort();
930
- if (jobFiles.length === 0) {
931
- console.log("No job history yet.");
932
- return;
933
- }
934
- console.log(`${jobFiles.length} completed job(s):\n`);
935
- for (const f of jobFiles) {
936
- const content = fs.readFileSync(path.join(memoryDir, f), "utf-8");
937
- const firstLine = content.split("\n").find(l => l.startsWith("#")) || f;
938
- console.log(` ${f.replace(".md", "")} ${firstLine.replace(/^#+\s*/, "")}`);
939
- }
940
- });
941
-
942
- // ── policy reload ────────────────────────────────────────────────────────
943
- workroom
944
- .command("policy-reload")
945
- .description("Re-read the policy file and update iptables rules inside the running workroom.")
946
- .action(async () => {
947
- if (!containerRunning()) {
948
- console.error("Workroom is not running.");
949
- process.exit(1);
950
- }
951
- console.log(c.dim("Reloading workroom policy..."));
952
- // Trigger DNS refresh manually (which re-reads policy and updates iptables)
953
- const result = runCmd("docker", [
954
- "exec", WORKROOM_CONTAINER,
955
- "bash", "-c", "/dns-refresh.sh /workroom/.arc402/openshell-policy.yaml &",
956
- ]);
957
- if (result.ok) {
958
- console.log(" " + c.success + c.white(" Policy reload triggered"));
959
- } else {
960
- console.error(c.failure + " " + c.red("Failed to reload policy"));
961
- }
962
- });
963
- }