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