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,1055 +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 YAML from "yaml";
6
- import { c } from '../ui/colors.js';
7
- import { startSpinner } from '../ui/spinner.js';
8
- import {
9
- ARC402_DIR,
10
- DEFAULT_RUNTIME_REMOTE_ROOT,
11
- OPENSHELL_TOML,
12
- buildOpenShellSshConfig,
13
- detectDockerAccess,
14
- provisionRuntimeToSandbox,
15
- readOpenShellConfig,
16
- resolveOpenShellSecrets,
17
- runCmd,
18
- writeOpenShellConfig,
19
- } from "../openshell-runtime.js";
20
- import { DAEMON_PID, DAEMON_LOG, DAEMON_TOML } from "../daemon/config.js";
21
-
22
- // ─── Constants ────────────────────────────────────────────────────────────────
23
-
24
- const POLICY_FILE = path.join(ARC402_DIR, "openshell-policy.yaml");
25
- const SANDBOX_NAME = "arc402-daemon";
26
- const NODE_BINARIES = [
27
- { path: "/usr/bin/node" },
28
- { path: "/usr/local/bin/node" },
29
- ];
30
- const PYTHON_BINARIES = [
31
- { path: "/usr/bin/python3" },
32
- { path: "/usr/local/bin/python3" },
33
- ];
34
- const DEFAULT_POLICY_KEYS = ["base_rpc", "base_rpc_alchemy", "base_rpc_llama", "arc402_relay", "bundler", "telegram"] as const;
35
- const CORE_LAUNCH_HOSTS = [
36
- ["mainnet.base.org", "Base RPC (public)"],
37
- ["base-mainnet.g.alchemy.com", "Base RPC (Alchemy)"],
38
- ["base.llamarpc.com", "Base RPC (Llama)"],
39
- ["relay.arc402.xyz", "ARC-402 relay"],
40
- ["public.pimlico.io", "Bundler"],
41
- ["api.telegram.org", "Telegram notifications"],
42
- ] as const;
43
-
44
- const EXPANSION_PACKS: Record<string, Array<{ key: string; label: string; host: string; binaries?: Array<{ path: string }> }>> = {
45
- harness: [
46
- { key: "api_openai", label: "OpenAI", host: "api.openai.com" },
47
- { key: "api_anthropic", label: "Anthropic", host: "api.anthropic.com" },
48
- { key: "api_google_generativeai", label: "Google Gemini", host: "generativelanguage.googleapis.com" },
49
- ],
50
- search: [
51
- { key: "api_brave_search", label: "Brave Search", host: "api.search.brave.com" },
52
- { key: "api_serpapi", label: "SerpAPI", host: "serpapi.com" },
53
- ],
54
- all: [],
55
- };
56
- EXPANSION_PACKS.all = [...EXPANSION_PACKS.harness, ...EXPANSION_PACKS.search];
57
-
58
- // ─── Types ────────────────────────────────────────────────────────────────────
59
-
60
- interface NetworkEndpoint {
61
- host: string;
62
- port: number;
63
- protocol: string;
64
- tls: string;
65
- enforcement: string;
66
- access: string;
67
- }
68
-
69
- interface NetworkPolicy {
70
- name: string;
71
- endpoints: NetworkEndpoint[];
72
- binaries: Array<{ path: string }>;
73
- }
74
-
75
- interface PolicyFile {
76
- version: number;
77
- filesystem_policy: {
78
- include_workdir: boolean;
79
- read_only: string[];
80
- read_write: string[];
81
- };
82
- landlock: { compatibility: string };
83
- process: { run_as_user: string; run_as_group: string };
84
- network_policies: Record<string, NetworkPolicy>;
85
- }
86
-
87
- // ─── Default policy ───────────────────────────────────────────────────────────
88
-
89
- function buildDefaultPolicy(): PolicyFile {
90
- return {
91
- version: 1,
92
- filesystem_policy: {
93
- include_workdir: true,
94
- read_only: ["/usr", "/lib", "/proc", "/etc", "/var/log"],
95
- read_write: [path.join(os.homedir(), ".arc402"), "/tmp", "/dev/null"],
96
- },
97
- landlock: {
98
- compatibility: "best_effort",
99
- },
100
- process: {
101
- run_as_user: "sandbox",
102
- run_as_group: "sandbox",
103
- },
104
- network_policies: {
105
- base_rpc: buildPolicyEntry("base-mainnet-rpc", "mainnet.base.org"),
106
- base_rpc_alchemy: buildPolicyEntry("base-mainnet-rpc-alchemy", "base-mainnet.g.alchemy.com"),
107
- base_rpc_llama: buildPolicyEntry("base-mainnet-rpc-llama", "base.llamarpc.com"),
108
- arc402_relay: buildPolicyEntry("arc402-relay", "relay.arc402.xyz"),
109
- bundler: buildPolicyEntry("pimlico-bundler", "public.pimlico.io"),
110
- telegram: buildPolicyEntry("telegram-notifications", "api.telegram.org"),
111
- },
112
- };
113
- }
114
-
115
- // ─── Check helpers ────────────────────────────────────────────────────────────
116
-
117
- function checkOpenShellInstalled(): string | null {
118
- const r = runCmd("which", ["openshell"]);
119
- if (!r.ok) return null;
120
- return r.stdout;
121
- }
122
-
123
- function ensureDockerAccessOrExit(prefix = "Docker", docker = detectDockerAccess()): void {
124
- if (docker.ok) return;
125
- if (docker.detail.includes("permission")) {
126
- console.error("Grant this shell access to the Docker daemon, then retry.");
127
- } else if (docker.detail.includes("not running")) {
128
- console.error("Start Docker Desktop / the Docker daemon, then retry.");
129
- } else if (docker.detail.includes("not installed")) {
130
- console.error("Install Docker first, then retry.");
131
- }
132
- process.exit(1);
133
- }
134
-
135
- interface OpenShellDoctorCheck {
136
- label: string;
137
- ok: boolean;
138
- detail: string;
139
- fix?: string;
140
- }
141
-
142
- function isProcessAlive(pid: number): boolean {
143
- try {
144
- process.kill(pid, 0);
145
- return true;
146
- } catch {
147
- return false;
148
- }
149
- }
150
-
151
- function buildOpenShellDoctorChecks(): OpenShellDoctorCheck[] {
152
- const checks: OpenShellDoctorCheck[] = [];
153
- const shellPath = checkOpenShellInstalled();
154
- if (!shellPath) {
155
- checks.push({
156
- label: "OpenShell CLI",
157
- ok: false,
158
- detail: "not installed",
159
- fix: "Run `arc402 openshell install`.",
160
- });
161
- return checks;
162
- }
163
-
164
- const version = runCmd("openshell", ["--version"]);
165
- checks.push({
166
- label: "OpenShell CLI",
167
- ok: version.ok,
168
- detail: version.ok ? `installed (${version.stdout || shellPath})` : (version.stderr || version.stdout || `installed at ${shellPath}`),
169
- fix: version.ok ? undefined : "Reinstall OpenShell and verify `openshell --version` works.",
170
- });
171
-
172
- const docker = detectDockerAccess();
173
- checks.push({
174
- label: "Docker",
175
- ok: docker.ok,
176
- detail: docker.detail,
177
- fix: docker.ok ? undefined : "Start/install Docker or grant this shell Docker access, then retry.",
178
- });
179
-
180
- const gatewayStatus = runCmd("openshell", ["status"], { timeout: 30000 });
181
- checks.push({
182
- label: "OpenShell gateway",
183
- ok: gatewayStatus.ok,
184
- detail: gatewayStatus.ok ? (gatewayStatus.stdout || "reachable") : (gatewayStatus.stderr || gatewayStatus.stdout || "status unavailable"),
185
- fix: gatewayStatus.ok ? undefined : "Run `openshell gateway start` (or `openshell doctor`) and retry.",
186
- });
187
-
188
- const config = readOpenShellConfig();
189
- if (!config) {
190
- checks.push({
191
- label: "ARC-402 OpenShell config",
192
- ok: false,
193
- detail: `missing (${OPENSHELL_TOML})`,
194
- fix: "Run `arc402 openshell init`.",
195
- });
196
- return checks;
197
- }
198
-
199
- checks.push({
200
- label: "ARC-402 OpenShell config",
201
- ok: true,
202
- detail: `${config.sandbox.name} / remote root ${config.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT}`,
203
- });
204
-
205
- const sandboxLookup = runCmd("openshell", ["sandbox", "get", config.sandbox.name], { timeout: 120000 });
206
- checks.push({
207
- label: "Sandbox",
208
- ok: sandboxLookup.ok,
209
- detail: sandboxLookup.ok ? `${config.sandbox.name} exists` : (sandboxLookup.stderr || sandboxLookup.stdout || `${config.sandbox.name} not found`),
210
- fix: sandboxLookup.ok ? undefined : "Re-run `arc402 openshell init` to create/update the sandbox.",
211
- });
212
-
213
- const providers = runCmd("openshell", ["provider", "list"]);
214
- const hasMachine = providers.ok && providers.stdout.includes("arc402-machine-key");
215
- const hasNotif = providers.ok && providers.stdout.includes("arc402-notifications");
216
- checks.push({
217
- label: "Credential providers",
218
- ok: providers.ok && hasMachine && hasNotif,
219
- detail: !providers.ok
220
- ? (providers.stderr || providers.stdout || "provider list unavailable")
221
- : `machine=${hasMachine ? "yes" : "no"}, notifications=${hasNotif ? "yes" : "no"}` ,
222
- fix: providers.ok && hasMachine && hasNotif ? undefined : "Run `arc402 openshell init` again after verifying ARC-402 machine-key / Telegram config.",
223
- });
224
-
225
- const secrets = resolveOpenShellSecrets();
226
- checks.push({
227
- label: "Local secret material",
228
- ok: Boolean(secrets.machineKey),
229
- detail: secrets.machineKey
230
- ? `machine key present${secrets.telegramBotToken && secrets.telegramChatId ? "; Telegram notifications configured" : "; Telegram notifications optional/incomplete"}`
231
- : "machine key missing from env + ARC-402 config",
232
- fix: secrets.machineKey ? undefined : "Set ARC402_MACHINE_KEY or run `arc402 config init` so daemon launch can materialize sandbox secrets.",
233
- });
234
-
235
- if (fs.existsSync(POLICY_FILE)) {
236
- const policy = loadPolicyFile();
237
- checks.push({
238
- label: "Policy file",
239
- ok: Boolean(policy),
240
- detail: policy ? `${POLICY_FILE} loaded (${Object.keys(policy.network_policies ?? {}).length} outbound entries)` : `${POLICY_FILE} is unreadable`,
241
- fix: policy ? undefined : "Re-run `arc402 openshell init` to regenerate the policy file.",
242
- });
243
- } else {
244
- checks.push({
245
- label: "Policy file",
246
- ok: false,
247
- detail: `missing (${POLICY_FILE})`,
248
- fix: "Run `arc402 openshell init`.",
249
- });
250
- }
251
-
252
- if (fs.existsSync(DAEMON_TOML)) {
253
- checks.push({ label: "Daemon config", ok: true, detail: `${DAEMON_TOML} present` });
254
- } else {
255
- checks.push({
256
- label: "Daemon config",
257
- ok: false,
258
- detail: `missing (${DAEMON_TOML})`,
259
- fix: "Run `arc402 daemon init` before starting the runtime.",
260
- });
261
- }
262
-
263
- try {
264
- const { configPath, host } = buildOpenShellSshConfig(config.sandbox.name);
265
- const remoteDaemonEntry = path.posix.join(
266
- config.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT,
267
- "dist/daemon/index.js",
268
- );
269
- const runtimeProbe = runCmd("ssh", ["-F", configPath, host, `test -f ${JSON.stringify(remoteDaemonEntry)} && echo present || echo missing`], { timeout: 60000 });
270
- checks.push({
271
- label: "Remote runtime bundle",
272
- ok: runtimeProbe.ok && runtimeProbe.stdout.includes("present"),
273
- detail: runtimeProbe.ok ? (runtimeProbe.stdout || remoteDaemonEntry) : (runtimeProbe.stderr || runtimeProbe.stdout || "probe failed"),
274
- fix: runtimeProbe.ok && runtimeProbe.stdout.includes("present") ? undefined : "Run `arc402 openshell sync-runtime` (or `arc402 openshell init`) to upload the ARC-402 runtime bundle.",
275
- });
276
-
277
- const secretProbe = runCmd("ssh", ["-F", configPath, host, "printf '%s' \"${ARC402_MACHINE_KEY:-missing}\""], { timeout: 60000 });
278
- const secretDetail = !secretProbe.ok
279
- ? (secretProbe.stderr || secretProbe.stdout || "probe failed")
280
- : secretProbe.stdout.startsWith("openshell:resolve:env:")
281
- ? "raw SSH shows OpenShell env placeholders; ARC-402 launch overlay path is expected"
282
- : secretProbe.stdout && secretProbe.stdout !== "missing"
283
- ? "sandbox env already materialized"
284
- : "sandbox secret not visible via raw SSH";
285
- checks.push({
286
- label: "Secret launch seam",
287
- ok: secretProbe.ok && secretDetail !== "sandbox secret not visible via raw SSH",
288
- detail: secretDetail,
289
- fix: secretProbe.ok && secretDetail !== "sandbox secret not visible via raw SSH" ? undefined : "Ensure ARC402_MACHINE_KEY resolves locally, then restart with `arc402 daemon start`.",
290
- });
291
- } catch {
292
- checks.push({
293
- label: "Remote runtime bundle",
294
- ok: false,
295
- detail: "could not build OpenShell SSH proof for the sandbox",
296
- fix: "Check `openshell sandbox ssh-config` and re-run `arc402 openshell init`."
297
- });
298
- checks.push({
299
- label: "Secret launch seam",
300
- ok: false,
301
- detail: "could not verify sandbox secret/materialization behavior",
302
- fix: "Confirm OpenShell sandbox access and ARC-402 machine-key config, then retry."
303
- });
304
- }
305
-
306
- if (fs.existsSync(DAEMON_PID)) {
307
- const rawPid = fs.readFileSync(DAEMON_PID, "utf-8").trim();
308
- const pid = Number(rawPid);
309
- if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid)) {
310
- checks.push({ label: "Daemon process", ok: true, detail: `running (PID ${pid})` });
311
- } else {
312
- checks.push({
313
- label: "Daemon process",
314
- ok: false,
315
- detail: `stale or invalid PID file (${JSON.stringify(rawPid)})`,
316
- fix: "Remove the stale PID file or restart with `arc402 daemon start`.",
317
- });
318
- }
319
- } else {
320
- checks.push({
321
- label: "Daemon process",
322
- ok: false,
323
- detail: "not running yet (no PID file)",
324
- fix: "Run `arc402 daemon start` after init completes.",
325
- });
326
- }
327
-
328
- checks.push({
329
- label: "Daemon log",
330
- ok: fs.existsSync(DAEMON_LOG),
331
- detail: fs.existsSync(DAEMON_LOG) ? `${DAEMON_LOG} present` : `${DAEMON_LOG} not created yet`,
332
- fix: fs.existsSync(DAEMON_LOG) ? undefined : "Start the runtime once to generate daemon logs.",
333
- });
334
-
335
- return checks;
336
- }
337
-
338
- // ─── Policy file helpers ──────────────────────────────────────────────────────
339
-
340
- function loadPolicyFile(): PolicyFile | null {
341
- if (!fs.existsSync(POLICY_FILE)) return null;
342
- try {
343
- const raw = fs.readFileSync(POLICY_FILE, "utf-8");
344
- return YAML.parse(raw) as PolicyFile;
345
- } catch {
346
- return null;
347
- }
348
- }
349
-
350
- function writePolicyFile(policy: PolicyFile): void {
351
- fs.mkdirSync(ARC402_DIR, { recursive: true, mode: 0o700 });
352
- fs.writeFileSync(POLICY_FILE, YAML.stringify(policy), { mode: 0o600 });
353
- }
354
-
355
- function hotReloadPolicy(): void {
356
- const r = runCmd("openshell", [
357
- "policy", "set", SANDBOX_NAME,
358
- "--policy", POLICY_FILE,
359
- "--wait",
360
- ]);
361
- if (!r.ok) {
362
- console.warn(` Warning: hot-reload failed: ${r.stderr}`);
363
- }
364
- }
365
-
366
- function requirePolicyFile(): PolicyFile {
367
- const policy = loadPolicyFile();
368
- if (!policy) {
369
- console.error(`Policy file not found: ${POLICY_FILE}`);
370
- console.error("Run: arc402 openshell init");
371
- process.exit(1);
372
- }
373
- return policy;
374
- }
375
-
376
- function buildPolicyEntry(name: string, host: string, binaries = NODE_BINARIES): NetworkPolicy {
377
- return {
378
- name,
379
- endpoints: [
380
- {
381
- host,
382
- port: 443,
383
- protocol: "rest",
384
- tls: "terminate",
385
- enforcement: "enforce",
386
- access: "read-write",
387
- },
388
- ],
389
- binaries,
390
- };
391
- }
392
-
393
- function sanitizeKeySegment(input: string): string {
394
- return input
395
- .toLowerCase()
396
- .replace(/^https?:\/\//, "")
397
- .replace(/[^a-z0-9]+/g, "_")
398
- .replace(/^_+|_+$/g, "")
399
- .slice(0, 64) || "entry";
400
- }
401
-
402
- function peerPolicyKey(host: string): string {
403
- return `peer_${sanitizeKeySegment(host)}`;
404
- }
405
-
406
- function customPolicyKey(name: string): string {
407
- return `custom_${sanitizeKeySegment(name)}`;
408
- }
409
-
410
- function ensurePolicyEntry(policy: PolicyFile, key: string, entry: NetworkPolicy): "added" | "updated" | "unchanged" {
411
- const existing = policy.network_policies[key];
412
- const next = JSON.stringify(entry);
413
- if (!existing) {
414
- policy.network_policies[key] = entry;
415
- return "added";
416
- }
417
- if (JSON.stringify(existing) === next) {
418
- return "unchanged";
419
- }
420
- policy.network_policies[key] = entry;
421
- return "updated";
422
- }
423
-
424
- function removePolicyEntry(policy: PolicyFile, key: string): boolean {
425
- if (!policy.network_policies[key]) return false;
426
- delete policy.network_policies[key];
427
- return true;
428
- }
429
-
430
- function applyAndPersistPolicy(policy: PolicyFile): void {
431
- writePolicyFile(policy);
432
- hotReloadPolicy();
433
- }
434
-
435
- function summarizeCategory(key: string): string {
436
- if (DEFAULT_POLICY_KEYS.includes(key as typeof DEFAULT_POLICY_KEYS[number])) return "core-launch";
437
- if (key.startsWith("peer_")) return "peer-agent";
438
- if (key.startsWith("api_")) return "harness/api";
439
- if (key.startsWith("custom_")) return "custom";
440
- return "other";
441
- }
442
-
443
- function printPolicyTable(policy: PolicyFile): void {
444
- const policies = Object.entries(policy.network_policies ?? {});
445
- if (policies.length === 0) {
446
- console.log("No network policies defined.");
447
- return;
448
- }
449
-
450
- console.log("Network policies (allowed outbound):");
451
- console.log();
452
- const col1 = 24;
453
- const col2 = 32;
454
- const col3 = 16;
455
- console.log(
456
- "Key".padEnd(col1) +
457
- "Host".padEnd(col2) +
458
- "Category".padEnd(col3) +
459
- "Name"
460
- );
461
- console.log("─".repeat(col1 + col2 + col3 + 24));
462
-
463
- for (const [key, np] of policies) {
464
- for (const ep of np.endpoints) {
465
- console.log(
466
- key.padEnd(col1) +
467
- ep.host.padEnd(col2) +
468
- summarizeCategory(key).padEnd(col3) +
469
- np.name,
470
- );
471
- }
472
- }
473
- }
474
-
475
- function ensureCoreLaunchPreset(policy: PolicyFile): Array<{ key: string; result: string }> {
476
- const entries: Array<{ key: string; result: string }> = [];
477
- entries.push({ key: "base_rpc", result: ensurePolicyEntry(policy, "base_rpc", buildPolicyEntry("base-mainnet-rpc", "mainnet.base.org")) });
478
- entries.push({ key: "arc402_relay", result: ensurePolicyEntry(policy, "arc402_relay", buildPolicyEntry("arc402-relay", "relay.arc402.xyz")) });
479
- entries.push({ key: "bundler", result: ensurePolicyEntry(policy, "bundler", buildPolicyEntry("pimlico-bundler", "public.pimlico.io")) });
480
- entries.push({ key: "telegram", result: ensurePolicyEntry(policy, "telegram", buildPolicyEntry("telegram-notifications", "api.telegram.org")) });
481
- return entries;
482
- }
483
-
484
- function applyExpansionPack(policy: PolicyFile, packName: string): Array<{ key: string; label: string; result: string }> {
485
- const pack = EXPANSION_PACKS[packName];
486
- if (!pack) {
487
- console.error(`Unknown expansion pack '${packName}'. Use: harness, search, all`);
488
- process.exit(1);
489
- }
490
- return pack.map((item) => ({
491
- key: item.key,
492
- label: item.label,
493
- result: ensurePolicyEntry(
494
- policy,
495
- item.key,
496
- buildPolicyEntry(item.label, item.host, [...NODE_BINARIES, ...PYTHON_BINARIES]),
497
- ),
498
- }));
499
- }
500
-
501
- function removeExpansionPack(policy: PolicyFile, packName: string): Array<{ key: string; label: string; removed: boolean }> {
502
- const pack = EXPANSION_PACKS[packName];
503
- if (!pack) {
504
- console.error(`Unknown expansion pack '${packName}'. Use: harness, search, all`);
505
- process.exit(1);
506
- }
507
- return pack.map((item) => ({ key: item.key, label: item.label, removed: removePolicyEntry(policy, item.key) }));
508
- }
509
-
510
- function printPolicyConcepts(): void {
511
- console.log("Policy concepts");
512
- console.log("───────────────");
513
- console.log("core-launch Default outbound runtime policy for launch: Base RPC, relay, bundler, Telegram.");
514
- console.log("peer-agent Explicit HTTPS allowlist for one counterparty host at a time. No *.arc402.xyz wildcard trust.");
515
- console.log("harness/api Optional expansion packs for model APIs and search APIs used by your harness/tools.");
516
- console.log();
517
- console.log("Important separation:");
518
- console.log(" • public endpoint / tunnel ingress tells the outside world how to reach you");
519
- console.log(" • OpenShell policy tells your sandboxed runtime what it may call outbound");
520
- console.log(" • registering https://agent.arc402.xyz does NOT grant outbound trust to that host");
521
- }
522
-
523
- // ─── Command registration ─────────────────────────────────────────────────────
524
-
525
- export function registerOpenShellCommands(program: Command): void {
526
- const openshell = program
527
- .command("openshell")
528
- .description("OpenShell sandbox integration for the ARC-402 daemon (Spec 34)");
529
-
530
- // ── openshell install ──────────────────────────────────────────────────────
531
- openshell
532
- .command("install")
533
- .description("Install OpenShell from the official source (requires Docker).")
534
- .action(() => {
535
- console.log("OpenShell Install");
536
- console.log("─────────────────");
537
-
538
- process.stdout.write("Checking Docker... ");
539
- const docker = detectDockerAccess();
540
- if (!docker.ok) {
541
- console.log(docker.detail);
542
- ensureDockerAccessOrExit("Docker", docker);
543
- }
544
- console.log(docker.detail);
545
-
546
- console.log("\nDownloading OpenShell from github.com/NVIDIA/OpenShell ...");
547
- const install = runCmd("sh", ["-c",
548
- "curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh"
549
- ], { timeout: 120000 });
550
-
551
- if (!install.ok) {
552
- console.error("Install failed:");
553
- console.error(install.stderr || install.stdout);
554
- process.exit(1);
555
- }
556
-
557
- if (install.stdout) console.log(install.stdout);
558
-
559
- process.stdout.write("Verifying... ");
560
- const verify = runCmd("openshell", ["--version"]);
561
- if (!verify.ok) {
562
- console.log("not found in PATH");
563
- console.error("openshell not found after install. Ensure ~/.local/bin is in your PATH.");
564
- console.error(" export PATH=\"$HOME/.local/bin:$PATH\"");
565
- process.exit(1);
566
- }
567
- console.log(verify.stdout);
568
-
569
- const status = runCmd("openshell", ["status"]);
570
- if (!status.ok) {
571
- console.log("\nOpenShell installed, but the gateway is not healthy yet.");
572
- console.log("Run one of:");
573
- console.log(" openshell gateway start");
574
- console.log(" openshell doctor");
575
- }
576
-
577
- console.log("\nOpenShell installed successfully.");
578
- console.log("Run: arc402 openshell init");
579
- });
580
-
581
- // ── openshell init ─────────────────────────────────────────────────────────
582
- openshell
583
- .command("init")
584
- .description("Initialize the launch runtime once: create the arc402-daemon sandbox, write the default policy, and hide OpenShell wiring behind ARC-402 commands.")
585
- .action(() => {
586
- console.log("OpenShell Init");
587
- console.log("──────────────");
588
-
589
- process.stdout.write("OpenShell: ");
590
- const shellPath = checkOpenShellInstalled();
591
- if (!shellPath) {
592
- console.log("not installed");
593
- console.error("OpenShell is not installed. Run: arc402 openshell install");
594
- process.exit(1);
595
- }
596
- const vr = runCmd("openshell", ["--version"]);
597
- console.log(vr.stdout || "installed");
598
-
599
- const gatewayStatus = runCmd("openshell", ["status"], { timeout: 30000 });
600
- process.stdout.write("Docker: ");
601
- const docker = detectDockerAccess();
602
- if (!docker.ok) {
603
- if (gatewayStatus.ok) {
604
- console.log(`${docker.detail} (continuing because OpenShell gateway is already connected)`);
605
- } else {
606
- console.log(docker.detail);
607
- ensureDockerAccessOrExit("Docker", docker);
608
- }
609
- } else {
610
- console.log(docker.detail);
611
- }
612
-
613
- console.log("\nGenerating policy file...");
614
- const policy = buildDefaultPolicy();
615
- writePolicyFile(policy);
616
- console.log(` Written: ${POLICY_FILE}`);
617
-
618
- console.log("\nCreating credential providers...");
619
- const secrets = resolveOpenShellSecrets();
620
- const providerResult = (name: string, credentials: string[], missingMessage: string) => {
621
- if (credentials.length === 0) {
622
- console.warn(` Warning: ${name}: ${missingMessage}`);
623
- return;
624
- }
625
-
626
- const createArgs = ["provider", "create", "--name", name, "--type", "generic"];
627
- for (const credential of credentials) createArgs.push("--credential", credential);
628
- const created = runCmd("openshell", createArgs);
629
- if (created.ok) {
630
- console.log(` Ready: ${name}`);
631
- return;
632
- }
633
-
634
- if ((created.stderr || created.stdout).includes("already exists")) {
635
- const updateArgs = ["provider", "update", name];
636
- for (const credential of credentials) updateArgs.push("--credential", credential);
637
- const updated = runCmd("openshell", updateArgs);
638
- if (updated.ok) {
639
- console.log(` Updated: ${name}`);
640
- return;
641
- }
642
- console.warn(` Warning: ${name}: ${updated.stderr || updated.stdout}`);
643
- return;
644
- }
645
-
646
- console.warn(` Warning: ${name}: ${created.stderr || created.stdout}`);
647
- };
648
-
649
- providerResult(
650
- "arc402-machine-key",
651
- secrets.machineKey ? [`ARC402_MACHINE_KEY=${secrets.machineKey}`] : [],
652
- "machine key not found in env or arc402 config; provider left unchanged",
653
- );
654
-
655
- providerResult(
656
- "arc402-notifications",
657
- [
658
- secrets.telegramBotToken ? `TELEGRAM_BOT_TOKEN=${secrets.telegramBotToken}` : "",
659
- secrets.telegramChatId ? `TELEGRAM_CHAT_ID=${secrets.telegramChatId}` : "",
660
- ].filter(Boolean),
661
- "Telegram credentials not found in env or arc402 config; provider left unchanged",
662
- );
663
-
664
- console.log("\nEnsuring sandbox exists...");
665
- const sandboxLookup = runCmd("openshell", ["sandbox", "get", SANDBOX_NAME], { timeout: 120000 });
666
- if (!sandboxLookup.ok) {
667
- const createSandbox = runCmd("openshell", [
668
- "sandbox", "create",
669
- "--name", SANDBOX_NAME,
670
- "--from", "openclaw",
671
- "--policy", POLICY_FILE,
672
- "--provider", "arc402-machine-key",
673
- "--provider", "arc402-notifications",
674
- "--",
675
- "true",
676
- ], { timeout: 180000 });
677
- if (!createSandbox.ok) {
678
- console.error(`Failed to create sandbox: ${createSandbox.stderr || createSandbox.stdout}`);
679
- process.exit(1);
680
- }
681
- console.log(` Created: ${SANDBOX_NAME}`);
682
- } else {
683
- console.log(` Reusing: ${SANDBOX_NAME}`);
684
- }
685
-
686
- console.log("\nProvisioning ARC-402 runtime bundle into the sandbox...");
687
- let tarballPath = "";
688
- let remoteRoot = DEFAULT_RUNTIME_REMOTE_ROOT;
689
- try {
690
- const provisioned = provisionRuntimeToSandbox(SANDBOX_NAME, DEFAULT_RUNTIME_REMOTE_ROOT);
691
- tarballPath = provisioned.tarballPath;
692
- remoteRoot = provisioned.remoteRoot;
693
- console.log(` Uploaded: ${tarballPath}`);
694
- console.log(` Remote: ${remoteRoot}`);
695
- } catch (err) {
696
- console.error(`Failed to provision runtime bundle: ${err instanceof Error ? err.message : String(err)}`);
697
- process.exit(1);
698
- }
699
-
700
- writeOpenShellConfig({
701
- sandbox: {
702
- name: SANDBOX_NAME,
703
- policy: POLICY_FILE,
704
- providers: ["arc402-machine-key", "arc402-notifications"],
705
- },
706
- runtime: {
707
- local_tarball: tarballPath,
708
- remote_root: remoteRoot,
709
- synced_at: new Date().toISOString(),
710
- },
711
- });
712
- console.log(`\nConfig: ${OPENSHELL_TOML}`);
713
-
714
- console.log(`
715
- OpenShell integration configured.
716
-
717
- Sandbox: ${SANDBOX_NAME}
718
- Policy: ${POLICY_FILE}
719
- Runtime: daemon + workers run inside the sandbox from a synced ARC-402 CLI bundle
720
- Remote: ${remoteRoot}
721
-
722
- arc402 daemon start will now use the provisioned ARC-402 runtime inside ${SANDBOX_NAME}.
723
- Default policy: Base RPC + relay + bundler + Telegram API. All other network access blocked.
724
-
725
- To allow additional endpoints for your harness or worker tools:
726
- See the launch-safe presets and toggles: arc402 openshell policy concepts
727
- Core preset: arc402 openshell policy preset core-launch
728
- Peer agent allow: arc402 openshell policy peer add gigabrain.arc402.xyz
729
- Harness/API pack: arc402 openshell policy preset harness
730
- List current policy: arc402 openshell policy list
731
- No daemon restart needed.
732
-
733
- If you update the local CLI build and want the sandbox to pick it up immediately:
734
- arc402 openshell sync-runtime`);
735
- });
736
-
737
- // ── openshell sync-runtime ────────────────────────────────────────────────
738
- openshell
739
- .command("sync-runtime")
740
- .description("Package the local ARC-402 CLI and upload it into the configured OpenShell sandbox so daemon startup is genuinely one-click.")
741
- .action(() => {
742
- const cfg = readOpenShellConfig();
743
- if (!cfg?.sandbox?.name) {
744
- console.error("OpenShell is not configured yet. Run: arc402 openshell init");
745
- process.exit(1);
746
- }
747
-
748
- console.log("Syncing ARC-402 runtime into OpenShell...");
749
- try {
750
- const provisioned = provisionRuntimeToSandbox(
751
- cfg.sandbox.name,
752
- cfg.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT,
753
- );
754
- writeOpenShellConfig({
755
- sandbox: cfg.sandbox,
756
- runtime: {
757
- local_tarball: provisioned.tarballPath,
758
- remote_root: provisioned.remoteRoot,
759
- synced_at: new Date().toISOString(),
760
- },
761
- });
762
- console.log(' ' + c.success + c.white(` Runtime synced to ${provisioned.remoteRoot}`));
763
- } catch (err) {
764
- console.error(`Runtime sync failed: ${err instanceof Error ? err.message : String(err)}`);
765
- process.exit(1);
766
- }
767
- });
768
-
769
- // ── openshell doctor ───────────────────────────────────────────────────────
770
- openshell
771
- .command("doctor")
772
- .description("Prove where MacBook/clean-room setup is blocked: Docker, OpenShell, sandbox, providers, runtime sync, or daemon boot.")
773
- .action(() => {
774
- console.log("ARC-402 OpenShell Doctor");
775
- console.log("─────────────────────");
776
-
777
- const checks = buildOpenShellDoctorChecks();
778
- let failures = 0;
779
- for (const check of checks) {
780
- if (check.ok) {
781
- console.log(`✓ ${check.label.padEnd(22)} ${check.detail}`);
782
- } else {
783
- failures += 1;
784
- console.log(`✗ ${check.label.padEnd(22)} ${check.detail}`);
785
- if (check.fix) console.log(` Fix: ${check.fix}`);
786
- }
787
- }
788
-
789
- if (failures === 0) {
790
- console.log("\nOpenShell launch path looks ready for current launch scope.");
791
- } else {
792
- console.log(`\n${failures} readiness gap(s) found.`);
793
- }
794
-
795
- console.log("\nLayer order:");
796
- console.log(" 1. Docker / OpenShell substrate");
797
- console.log(" 2. ARC-402 OpenShell config + providers");
798
- console.log(" 3. sandbox existence + policy file");
799
- console.log(" 4. remote runtime bundle sync");
800
- console.log(" 5. daemon config + launch seam");
801
- console.log(" 6. daemon process/log proof");
802
- });
803
-
804
- // ── openshell status ───────────────────────────────────────────────────────
805
- openshell
806
- .command("status")
807
- .description("Show OpenShell integration status.")
808
- .action(() => {
809
- console.log("OpenShell Integration");
810
- console.log("─────────────────────");
811
-
812
- const line = (label: string, value: string) =>
813
- console.log(`${label.padEnd(14)}${value}`);
814
-
815
- const shellPath = checkOpenShellInstalled();
816
- if (shellPath) {
817
- const vr = runCmd("openshell", ["--version"]);
818
- line("Installed:", `yes (${vr.stdout || "unknown version"})`);
819
- } else {
820
- line("Installed:", "no ← run: arc402 openshell install");
821
- }
822
-
823
- const docker = detectDockerAccess();
824
- line("Docker:", docker.detail);
825
-
826
- if (shellPath) {
827
- const listR = runCmd("sh", ["-c",
828
- `openshell sandbox list 2>/dev/null | grep "${SANDBOX_NAME}"`
829
- ]);
830
- if (listR.ok && listR.stdout) {
831
- line("Sandbox:", `${SANDBOX_NAME} (found)`);
832
- } else {
833
- line("Sandbox:", `${SANDBOX_NAME} not found ← run: arc402 openshell init`);
834
- }
835
- }
836
-
837
- if (fs.existsSync(POLICY_FILE)) {
838
- line("Policy file:", `${POLICY_FILE} ✓`);
839
- } else {
840
- line("Policy file:", `${POLICY_FILE} (not found)`);
841
- }
842
-
843
- const openShellConfig = readOpenShellConfig();
844
- if (openShellConfig) {
845
- line("Daemon mode:", "OpenShell-owned governed workroom runtime");
846
- line("Public mode:", "separate layer — endpoint/tunnel ingress is host-facing, not a sandbox policy toggle");
847
- line("Runtime root:", openShellConfig.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT);
848
- line("Last sync:", openShellConfig.runtime?.synced_at ?? "unknown");
849
-
850
- try {
851
- const { configPath, host } = buildOpenShellSshConfig(openShellConfig.sandbox.name);
852
- const remoteDaemonEntry = path.posix.join(
853
- openShellConfig.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT,
854
- "dist/daemon/index.js",
855
- );
856
- const runtimeProbe = runCmd("ssh", ["-F", configPath, host, `test -f ${JSON.stringify(remoteDaemonEntry)} && echo present || echo missing`], { timeout: 60000 });
857
- line("Runtime sync:", runtimeProbe.ok && runtimeProbe.stdout.includes("present") ? "remote daemon bundle present ✓" : "remote daemon bundle missing");
858
-
859
- const secretProbe = runCmd("ssh", [
860
- "-F", configPath,
861
- host,
862
- "printf '%s' \"${ARC402_MACHINE_KEY:-missing}\"",
863
- ], { timeout: 60000 });
864
- if (secretProbe.ok && secretProbe.stdout.startsWith("openshell:resolve:env:")) {
865
- line("Secret mode:", "raw SSH shows OpenShell placeholders; ARC-402 overlays real launch envs from local config ✓");
866
- } else if (secretProbe.ok && secretProbe.stdout && secretProbe.stdout !== "missing") {
867
- line("Secret mode:", "sandbox env already materialized ✓");
868
- } else {
869
- line("Secret mode:", "could not confirm machine-key materialization");
870
- }
871
- } catch {
872
- line("Runtime sync:", "could not verify remote bundle");
873
- }
874
- } else {
875
- line("Daemon mode:", "not configured for launch (run: arc402 openshell init)");
876
- }
877
-
878
- const policy = loadPolicyFile();
879
- if (policy?.network_policies) {
880
- console.log("\nNetwork policy (allowed outbound):");
881
- for (const [key, np] of Object.entries(policy.network_policies)) {
882
- for (const ep of np.endpoints) {
883
- console.log(` ${ep.host.padEnd(30)} (${summarizeCategory(key)} · ${np.name})`);
884
- }
885
- }
886
- console.log(" [all others blocked]");
887
- }
888
-
889
- if (shellPath) {
890
- console.log("\nCredential providers:");
891
- const provListR = runCmd("openshell", ["provider", "list"]);
892
- if (provListR.ok && provListR.stdout) {
893
- const hasKey = provListR.stdout.includes("arc402-machine-key");
894
- const hasNotif = provListR.stdout.includes("arc402-notifications");
895
- console.log(` arc402-machine-key ${hasKey ? "✓" : "✗ (not found)"}`);
896
- console.log(` arc402-notifications ${hasNotif ? "✓" : "✗ (not found)"}`);
897
- } else {
898
- console.log(" (could not retrieve provider list)");
899
- }
900
- }
901
- });
902
-
903
- // ── openshell policy ───────────────────────────────────────────────────────
904
- const policyCmd = openshell
905
- .command("policy")
906
- .description("Manage the OpenShell outbound network policy for the arc402-daemon sandbox. This is separate from public endpoint / tunnel ingress.");
907
-
908
- policyCmd
909
- .command("concepts")
910
- .description("Explain the launch-safe policy UX: core launch preset, peer-agent HTTPS allowlist, harness/API expansion packs, and ingress wording.")
911
- .action(() => {
912
- printPolicyConcepts();
913
- });
914
-
915
- policyCmd
916
- .command("preset <name>")
917
- .description("Apply a launch-safe preset. Supported: core-launch, harness, search, all")
918
- .action((name: string) => {
919
- const policy = requirePolicyFile();
920
-
921
- if (name === "core-launch") {
922
- const changes = ensureCoreLaunchPreset(policy);
923
- applyAndPersistPolicy(policy);
924
- console.log("Applied core-launch preset:");
925
- for (const change of changes) {
926
- console.log(` ${change.key}: ${change.result}`);
927
- }
928
- console.log();
929
- console.log("Launch core outbound hosts:");
930
- for (const [host, label] of CORE_LAUNCH_HOSTS) console.log(` ${host} — ${label}`);
931
- console.log(" Peer-agent wildcard trust remains OFF by default.");
932
- return;
933
- }
934
-
935
- const changes = applyExpansionPack(policy, name);
936
- applyAndPersistPolicy(policy);
937
- console.log(`Applied ${name} expansion pack:`);
938
- for (const change of changes) {
939
- console.log(` ${change.label} (${change.key}): ${change.result}`);
940
- }
941
- });
942
-
943
- policyCmd
944
- .command("preset-remove <name>")
945
- .description("Remove a previously applied expansion preset. Supported: harness, search, all")
946
- .action((name: string) => {
947
- if (name === "core-launch") {
948
- console.error("core-launch is the launch baseline. Remove individual entries only if you explicitly want to break the default runtime path.");
949
- process.exit(1);
950
- }
951
- const policy = requirePolicyFile();
952
- const changes = removeExpansionPack(policy, name);
953
- applyAndPersistPolicy(policy);
954
- console.log(`Removed ${name} expansion pack entries:`);
955
- for (const change of changes) {
956
- console.log(` ${change.label} (${change.key}): ${change.removed ? "removed" : "not present"}`);
957
- }
958
- });
959
-
960
- const peerCmd = policyCmd
961
- .command("peer")
962
- .description("Manage explicit peer-agent HTTPS allowlist entries. Public registration does not imply outbound trust.");
963
-
964
- peerCmd
965
- .command("add <host>")
966
- .description("Allow outbound HTTPS calls to one peer agent host. No *.arc402.xyz wildcard support.")
967
- .action((host: string) => {
968
- if (host.includes("*")) {
969
- console.error("Wildcard peer trust is not allowed. Add one host at a time.");
970
- process.exit(1);
971
- }
972
- const cleanedHost = host.replace(/^https?:\/\//, "").replace(/\/$/, "");
973
- const policy = requirePolicyFile();
974
- const key = peerPolicyKey(cleanedHost);
975
- const result = ensurePolicyEntry(policy, key, buildPolicyEntry(`peer-agent:${cleanedHost}`, cleanedHost));
976
- applyAndPersistPolicy(policy);
977
- console.log(' ' + c.success + c.white(` Peer agent host ${cleanedHost} ${result} under ${key}`));
978
- console.log(" This only affects sandbox outbound access. It does not claim or expose a public endpoint.");
979
- });
980
-
981
- peerCmd
982
- .command("remove <host>")
983
- .description("Revoke outbound HTTPS access to a peer agent host.")
984
- .action((host: string) => {
985
- const cleanedHost = host.replace(/^https?:\/\//, "").replace(/\/$/, "");
986
- const policy = requirePolicyFile();
987
- const key = peerPolicyKey(cleanedHost);
988
- const removed = removePolicyEntry(policy, key);
989
- if (!removed) {
990
- console.error(`Peer host ${cleanedHost} is not allowlisted.`);
991
- process.exit(1);
992
- }
993
- applyAndPersistPolicy(policy);
994
- console.log(' ' + c.success + c.white(` Peer agent host ${cleanedHost} removed (${key})`));
995
- });
996
-
997
- peerCmd
998
- .command("list")
999
- .description("List all explicit peer-agent outbound allowlist entries.")
1000
- .action(() => {
1001
- const policy = requirePolicyFile();
1002
- const peers = Object.entries(policy.network_policies).filter(([key]) => key.startsWith("peer_"));
1003
- if (peers.length === 0) {
1004
- console.log("No peer-agent HTTPS hosts allowlisted.");
1005
- return;
1006
- }
1007
- console.log("Peer-agent HTTPS allowlist:");
1008
- for (const [key, entry] of peers) {
1009
- const host = entry.endpoints[0]?.host ?? "unknown";
1010
- console.log(` ${host} (${key})`);
1011
- }
1012
- });
1013
-
1014
- // ── openshell policy add <name> <host> ────────────────────────────────────
1015
- policyCmd
1016
- .command("add <name> <host>")
1017
- .description("Add a custom outbound allowlist endpoint to the sandbox policy and hot-reload it. Prefer preset/peer commands when they fit.")
1018
- .action((name: string, host: string) => {
1019
- const policy = requirePolicyFile();
1020
- const key = customPolicyKey(name);
1021
- const result = ensurePolicyEntry(policy, key, buildPolicyEntry(name, host, [...NODE_BINARIES, ...PYTHON_BINARIES]));
1022
- applyAndPersistPolicy(policy);
1023
- console.log(' ' + c.success + c.white(` ${host} ${result} as ${key}`));
1024
- console.log(" Prefer `arc402 openshell policy peer add <host>` for peer agents or `preset <name>` for launch-safe expansion packs.");
1025
- });
1026
-
1027
- // ── openshell policy list ─────────────────────────────────────────────────
1028
- policyCmd
1029
- .command("list")
1030
- .description("List all sandbox outbound allowlist endpoints grouped by launch-safe category. These are runtime egress rules, not endpoint/tunnel registrations.")
1031
- .action(() => {
1032
- const policy = requirePolicyFile();
1033
- printPolicyTable(policy);
1034
- });
1035
-
1036
- // ── openshell policy remove <name> ────────────────────────────────────────
1037
- policyCmd
1038
- .command("remove <name>")
1039
- .description("Remove a named outbound allowlist entry and hot-reload the sandbox. For peers, prefer `peer remove <host>`.")
1040
- .action((name: string) => {
1041
- const policy = requirePolicyFile();
1042
- const key = [name, customPolicyKey(name), peerPolicyKey(name)].find((candidate) => policy.network_policies[candidate]);
1043
- if (!key) {
1044
- console.error(`Policy entry '${name}' not found.`);
1045
- console.error("Run: arc402 openshell policy list");
1046
- process.exit(1);
1047
- }
1048
-
1049
- const removedHost = policy.network_policies[key]?.endpoints[0]?.host ?? key;
1050
- delete policy.network_policies[key];
1051
-
1052
- applyAndPersistPolicy(policy);
1053
- console.log(' ' + c.success + c.white(` ${removedHost} removed from daemon sandbox policy (hot-reloaded)`));
1054
- });
1055
- }