arc402-cli 0.9.18 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/README.md +41 -2
  2. package/dist/abis.d.ts +1 -0
  3. package/dist/abis.d.ts.map +1 -1
  4. package/dist/abis.js +45 -14
  5. package/dist/abis.js.map +1 -1
  6. package/dist/bundler.d.ts +1 -1
  7. package/dist/bundler.d.ts.map +1 -1
  8. package/dist/bundler.js +61 -27
  9. package/dist/bundler.js.map +1 -1
  10. package/dist/client.d.ts +1 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +9 -5
  13. package/dist/client.js.map +1 -1
  14. package/dist/coinbase-smart-wallet.js +4 -1
  15. package/dist/coinbase-smart-wallet.js.map +1 -1
  16. package/dist/commands/accept.js +28 -25
  17. package/dist/commands/accept.js.map +1 -1
  18. package/dist/commands/agent-handshake.js +18 -15
  19. package/dist/commands/agent-handshake.js.map +1 -1
  20. package/dist/commands/agent.js +104 -98
  21. package/dist/commands/agent.js.map +1 -1
  22. package/dist/commands/agreements.js +98 -62
  23. package/dist/commands/agreements.js.map +1 -1
  24. package/dist/commands/arbitrator.js +81 -45
  25. package/dist/commands/arbitrator.js.map +1 -1
  26. package/dist/commands/arena-handshake.d.ts.map +1 -1
  27. package/dist/commands/arena-handshake.js +35 -53
  28. package/dist/commands/arena-handshake.js.map +1 -1
  29. package/dist/commands/arena.js +18 -12
  30. package/dist/commands/arena.js.map +1 -1
  31. package/dist/commands/backup.js +36 -30
  32. package/dist/commands/backup.js.map +1 -1
  33. package/dist/commands/cancel.js +18 -15
  34. package/dist/commands/cancel.js.map +1 -1
  35. package/dist/commands/channel.js +81 -45
  36. package/dist/commands/channel.js.map +1 -1
  37. package/dist/commands/coldstart.js +34 -31
  38. package/dist/commands/coldstart.js.map +1 -1
  39. package/dist/commands/compute.d.ts +14 -0
  40. package/dist/commands/compute.d.ts.map +1 -0
  41. package/dist/commands/compute.js +466 -0
  42. package/dist/commands/compute.js.map +1 -0
  43. package/dist/commands/config.js +30 -24
  44. package/dist/commands/config.js.map +1 -1
  45. package/dist/commands/contract-interaction.js +15 -12
  46. package/dist/commands/contract-interaction.js.map +1 -1
  47. package/dist/commands/daemon.d.ts.map +1 -1
  48. package/dist/commands/daemon.js +135 -98
  49. package/dist/commands/daemon.js.map +1 -1
  50. package/dist/commands/deliver.js +76 -37
  51. package/dist/commands/deliver.js.map +1 -1
  52. package/dist/commands/discover.js +27 -24
  53. package/dist/commands/discover.js.map +1 -1
  54. package/dist/commands/dispute.js +110 -104
  55. package/dist/commands/dispute.js.map +1 -1
  56. package/dist/commands/doctor.js +55 -16
  57. package/dist/commands/doctor.js.map +1 -1
  58. package/dist/commands/endpoint.js +95 -56
  59. package/dist/commands/endpoint.js.map +1 -1
  60. package/dist/commands/feed.js +18 -11
  61. package/dist/commands/feed.js.map +1 -1
  62. package/dist/commands/hire.js +40 -37
  63. package/dist/commands/hire.js.map +1 -1
  64. package/dist/commands/migrate.js +33 -30
  65. package/dist/commands/migrate.js.map +1 -1
  66. package/dist/commands/negotiate.d.ts.map +1 -1
  67. package/dist/commands/negotiate.js +36 -34
  68. package/dist/commands/negotiate.js.map +1 -1
  69. package/dist/commands/openshell.js +104 -68
  70. package/dist/commands/openshell.js.map +1 -1
  71. package/dist/commands/owner.js +20 -17
  72. package/dist/commands/owner.js.map +1 -1
  73. package/dist/commands/policy.js +43 -41
  74. package/dist/commands/policy.js.map +1 -1
  75. package/dist/commands/relay.d.ts.map +1 -1
  76. package/dist/commands/relay.js +51 -18
  77. package/dist/commands/relay.js.map +1 -1
  78. package/dist/commands/remediate.js +23 -20
  79. package/dist/commands/remediate.js.map +1 -1
  80. package/dist/commands/reputation.js +27 -25
  81. package/dist/commands/reputation.js.map +1 -1
  82. package/dist/commands/setup.js +104 -65
  83. package/dist/commands/setup.js.map +1 -1
  84. package/dist/commands/trust.js +20 -17
  85. package/dist/commands/trust.js.map +1 -1
  86. package/dist/commands/verify.js +21 -18
  87. package/dist/commands/verify.js.map +1 -1
  88. package/dist/commands/wallet.d.ts.map +1 -1
  89. package/dist/commands/wallet.js +645 -679
  90. package/dist/commands/wallet.js.map +1 -1
  91. package/dist/commands/watch.js +36 -33
  92. package/dist/commands/watch.js.map +1 -1
  93. package/dist/commands/watchtower.js +73 -37
  94. package/dist/commands/watchtower.js.map +1 -1
  95. package/dist/commands/workroom.d.ts.map +1 -1
  96. package/dist/commands/workroom.js +282 -143
  97. package/dist/commands/workroom.js.map +1 -1
  98. package/dist/config.d.ts +3 -0
  99. package/dist/config.d.ts.map +1 -1
  100. package/dist/config.js +71 -22
  101. package/dist/config.js.map +1 -1
  102. package/dist/daemon/compute-metering.d.ts +61 -0
  103. package/dist/daemon/compute-metering.d.ts.map +1 -0
  104. package/dist/daemon/compute-metering.js +299 -0
  105. package/dist/daemon/compute-metering.js.map +1 -0
  106. package/dist/daemon/compute-session.d.ts +100 -0
  107. package/dist/daemon/compute-session.d.ts.map +1 -0
  108. package/dist/daemon/compute-session.js +231 -0
  109. package/dist/daemon/compute-session.js.map +1 -0
  110. package/dist/daemon/config.d.ts +19 -1
  111. package/dist/daemon/config.d.ts.map +1 -1
  112. package/dist/daemon/config.js +90 -16
  113. package/dist/daemon/config.js.map +1 -1
  114. package/dist/daemon/credentials.d.ts +24 -0
  115. package/dist/daemon/credentials.d.ts.map +1 -0
  116. package/dist/daemon/credentials.js +80 -0
  117. package/dist/daemon/credentials.js.map +1 -0
  118. package/dist/daemon/delivery-client.d.ts +35 -0
  119. package/dist/daemon/delivery-client.d.ts.map +1 -0
  120. package/dist/daemon/delivery-client.js +231 -0
  121. package/dist/daemon/delivery-client.js.map +1 -0
  122. package/dist/daemon/file-delivery.d.ts +98 -0
  123. package/dist/daemon/file-delivery.d.ts.map +1 -0
  124. package/dist/daemon/file-delivery.js +461 -0
  125. package/dist/daemon/file-delivery.js.map +1 -0
  126. package/dist/daemon/hire-listener.d.ts +3 -3
  127. package/dist/daemon/hire-listener.d.ts.map +1 -1
  128. package/dist/daemon/hire-listener.js +47 -13
  129. package/dist/daemon/hire-listener.js.map +1 -1
  130. package/dist/daemon/index.d.ts +2 -1
  131. package/dist/daemon/index.d.ts.map +1 -1
  132. package/dist/daemon/index.js +526 -53
  133. package/dist/daemon/index.js.map +1 -1
  134. package/dist/daemon/job-lifecycle.d.ts +1 -1
  135. package/dist/daemon/job-lifecycle.d.ts.map +1 -1
  136. package/dist/daemon/job-lifecycle.js +51 -11
  137. package/dist/daemon/job-lifecycle.js.map +1 -1
  138. package/dist/daemon/notify.d.ts +1 -1
  139. package/dist/daemon/notify.d.ts.map +1 -1
  140. package/dist/daemon/notify.js +53 -19
  141. package/dist/daemon/notify.js.map +1 -1
  142. package/dist/daemon/token-metering.js +47 -8
  143. package/dist/daemon/token-metering.js.map +1 -1
  144. package/dist/daemon/userops.d.ts +2 -2
  145. package/dist/daemon/userops.d.ts.map +1 -1
  146. package/dist/daemon/userops.js +27 -23
  147. package/dist/daemon/userops.js.map +1 -1
  148. package/dist/daemon/wallet-monitor.d.ts +1 -1
  149. package/dist/daemon/wallet-monitor.d.ts.map +1 -1
  150. package/dist/daemon/wallet-monitor.js +12 -8
  151. package/dist/daemon/wallet-monitor.js.map +1 -1
  152. package/dist/daemon/worker-executor.d.ts +71 -0
  153. package/dist/daemon/worker-executor.d.ts.map +1 -0
  154. package/dist/daemon/worker-executor.js +382 -0
  155. package/dist/daemon/worker-executor.js.map +1 -0
  156. package/dist/drain-v4.js +64 -26
  157. package/dist/drain-v4.js.map +1 -1
  158. package/dist/endpoint-config.js +63 -20
  159. package/dist/endpoint-config.js.map +1 -1
  160. package/dist/endpoint-notify.js +48 -9
  161. package/dist/endpoint-notify.js.map +1 -1
  162. package/dist/index.js +50 -18
  163. package/dist/index.js.map +1 -1
  164. package/dist/openshell-runtime.d.ts.map +1 -1
  165. package/dist/openshell-runtime.js +82 -38
  166. package/dist/openshell-runtime.js.map +1 -1
  167. package/dist/program.d.ts.map +1 -1
  168. package/dist/program.js +85 -78
  169. package/dist/program.js.map +1 -1
  170. package/dist/repl.js +31 -25
  171. package/dist/repl.js.map +1 -1
  172. package/dist/signing.js +6 -3
  173. package/dist/signing.js.map +1 -1
  174. package/dist/telegram-notify.js +40 -3
  175. package/dist/telegram-notify.js.map +1 -1
  176. package/dist/tui/App.d.ts.map +1 -1
  177. package/dist/tui/App.js +56 -89
  178. package/dist/tui/App.js.map +1 -1
  179. package/dist/tui/Footer.js +7 -4
  180. package/dist/tui/Footer.js.map +1 -1
  181. package/dist/tui/Header.d.ts +1 -1
  182. package/dist/tui/Header.d.ts.map +1 -1
  183. package/dist/tui/Header.js +14 -9
  184. package/dist/tui/Header.js.map +1 -1
  185. package/dist/tui/InputLine.d.ts +2 -1
  186. package/dist/tui/InputLine.d.ts.map +1 -1
  187. package/dist/tui/InputLine.js +47 -97
  188. package/dist/tui/InputLine.js.map +1 -1
  189. package/dist/tui/Viewport.d.ts +1 -2
  190. package/dist/tui/Viewport.d.ts.map +1 -1
  191. package/dist/tui/Viewport.js +26 -6
  192. package/dist/tui/Viewport.js.map +1 -1
  193. package/dist/tui/WalletConnectPairing.js +19 -16
  194. package/dist/tui/WalletConnectPairing.js.map +1 -1
  195. package/dist/tui/components/Button.js +9 -6
  196. package/dist/tui/components/Button.js.map +1 -1
  197. package/dist/tui/components/CeremonyView.js +8 -5
  198. package/dist/tui/components/CeremonyView.js.map +1 -1
  199. package/dist/tui/components/CompletionDropdown.js +9 -6
  200. package/dist/tui/components/CompletionDropdown.js.map +1 -1
  201. package/dist/tui/components/ConfirmPrompt.js +8 -5
  202. package/dist/tui/components/ConfirmPrompt.js.map +1 -1
  203. package/dist/tui/components/CustomTextInput.js +14 -11
  204. package/dist/tui/components/CustomTextInput.js.map +1 -1
  205. package/dist/tui/components/InteractiveTable.js +12 -9
  206. package/dist/tui/components/InteractiveTable.js.map +1 -1
  207. package/dist/tui/components/StepSpinner.js +13 -10
  208. package/dist/tui/components/StepSpinner.js.map +1 -1
  209. package/dist/tui/components/Toast.js +12 -8
  210. package/dist/tui/components/Toast.js.map +1 -1
  211. package/dist/tui/index.d.ts.map +1 -1
  212. package/dist/tui/index.js +21 -28
  213. package/dist/tui/index.js.map +1 -1
  214. package/dist/tui/useChat.js +19 -13
  215. package/dist/tui/useChat.js.map +1 -1
  216. package/dist/tui/useCommand.d.ts +2 -3
  217. package/dist/tui/useCommand.d.ts.map +1 -1
  218. package/dist/tui/useCommand.js +24 -100
  219. package/dist/tui/useCommand.js.map +1 -1
  220. package/dist/tui/useNotifications.js +8 -5
  221. package/dist/tui/useNotifications.js.map +1 -1
  222. package/dist/tui/useScroll.d.ts.map +1 -1
  223. package/dist/tui/useScroll.js +12 -15
  224. package/dist/tui/useScroll.js.map +1 -1
  225. package/dist/ui/banner.d.ts +0 -12
  226. package/dist/ui/banner.d.ts.map +1 -1
  227. package/dist/ui/banner.js +19 -35
  228. package/dist/ui/banner.js.map +1 -1
  229. package/dist/ui/colors.js +19 -13
  230. package/dist/ui/colors.js.map +1 -1
  231. package/dist/ui/format.js +14 -6
  232. package/dist/ui/format.js.map +1 -1
  233. package/dist/ui/qr-render.js +11 -4
  234. package/dist/ui/qr-render.js.map +1 -1
  235. package/dist/ui/rpc-fallback.js +11 -6
  236. package/dist/ui/rpc-fallback.js.map +1 -1
  237. package/dist/ui/spinner.js +12 -6
  238. package/dist/ui/spinner.js.map +1 -1
  239. package/dist/ui/tree.js +6 -3
  240. package/dist/ui/tree.js.map +1 -1
  241. package/dist/utils/format.js +41 -27
  242. package/dist/utils/format.js.map +1 -1
  243. package/dist/utils/hash.js +42 -4
  244. package/dist/utils/hash.js.map +1 -1
  245. package/dist/utils/time.js +6 -2
  246. package/dist/utils/time.js.map +1 -1
  247. package/dist/wallet-router.d.ts +1 -1
  248. package/dist/wallet-router.d.ts.map +1 -1
  249. package/dist/wallet-router.js +19 -12
  250. package/dist/wallet-router.js.map +1 -1
  251. package/dist/walletconnect-session.d.ts +1 -1
  252. package/dist/walletconnect-session.d.ts.map +1 -1
  253. package/dist/walletconnect-session.js +11 -6
  254. package/dist/walletconnect-session.js.map +1 -1
  255. package/dist/walletconnect.d.ts +5 -6
  256. package/dist/walletconnect.d.ts.map +1 -1
  257. package/dist/walletconnect.js +35 -32
  258. package/dist/walletconnect.js.map +1 -1
  259. package/package.json +11 -10
  260. package/INK6-UX-SPEC.md +0 -446
  261. package/MIGRATION-SPEC.md +0 -108
  262. package/TUI-SPEC.md +0 -214
  263. package/scripts/authorize-machine-key.ts +0 -43
  264. package/scripts/drain-wallet.ts +0 -149
  265. package/scripts/execute-spend-only.ts +0 -81
  266. package/scripts/register-agent-userop.ts +0 -186
  267. package/src/abis.ts +0 -187
  268. package/src/bundler.ts +0 -235
  269. package/src/client.ts +0 -36
  270. package/src/coinbase-smart-wallet.ts +0 -51
  271. package/src/commands/accept.ts +0 -64
  272. package/src/commands/agent-handshake.ts +0 -72
  273. package/src/commands/agent.ts +0 -691
  274. package/src/commands/agreements.ts +0 -350
  275. package/src/commands/arbitrator.ts +0 -180
  276. package/src/commands/arena-handshake.ts +0 -274
  277. package/src/commands/arena.ts +0 -122
  278. package/src/commands/backup.ts +0 -117
  279. package/src/commands/cancel.ts +0 -35
  280. package/src/commands/channel.ts +0 -218
  281. package/src/commands/coldstart.ts +0 -165
  282. package/src/commands/config.ts +0 -68
  283. package/src/commands/contract-interaction.ts +0 -166
  284. package/src/commands/daemon.ts +0 -1054
  285. package/src/commands/deliver.ts +0 -148
  286. package/src/commands/discover.ts +0 -350
  287. package/src/commands/dispute.ts +0 -375
  288. package/src/commands/doctor.ts +0 -172
  289. package/src/commands/endpoint.ts +0 -620
  290. package/src/commands/feed.ts +0 -229
  291. package/src/commands/hire.ts +0 -245
  292. package/src/commands/migrate.ts +0 -177
  293. package/src/commands/negotiate.ts +0 -272
  294. package/src/commands/openshell.ts +0 -1055
  295. package/src/commands/owner.ts +0 -35
  296. package/src/commands/policy.ts +0 -263
  297. package/src/commands/relay.ts +0 -277
  298. package/src/commands/remediate.ts +0 -24
  299. package/src/commands/reputation.ts +0 -79
  300. package/src/commands/setup.ts +0 -343
  301. package/src/commands/trust.ts +0 -27
  302. package/src/commands/verify.ts +0 -91
  303. package/src/commands/wallet.ts +0 -3548
  304. package/src/commands/watch.ts +0 -220
  305. package/src/commands/watchtower.ts +0 -248
  306. package/src/commands/workroom.ts +0 -963
  307. package/src/config.ts +0 -220
  308. package/src/daemon/config.ts +0 -344
  309. package/src/daemon/hire-listener.ts +0 -226
  310. package/src/daemon/index.ts +0 -1089
  311. package/src/daemon/job-lifecycle.ts +0 -215
  312. package/src/daemon/notify.ts +0 -297
  313. package/src/daemon/token-metering.ts +0 -183
  314. package/src/daemon/userops.ts +0 -119
  315. package/src/daemon/wallet-monitor.ts +0 -90
  316. package/src/drain-v4.ts +0 -159
  317. package/src/endpoint-config.ts +0 -83
  318. package/src/endpoint-notify.ts +0 -129
  319. package/src/index.ts +0 -74
  320. package/src/openshell-runtime.ts +0 -281
  321. package/src/program.ts +0 -88
  322. package/src/repl.ts +0 -178
  323. package/src/signing.ts +0 -28
  324. package/src/telegram-notify.ts +0 -88
  325. package/src/tui/App.tsx +0 -263
  326. package/src/tui/Footer.tsx +0 -18
  327. package/src/tui/Header.tsx +0 -45
  328. package/src/tui/InputLine.tsx +0 -243
  329. package/src/tui/Viewport.tsx +0 -51
  330. package/src/tui/WalletConnectPairing.tsx +0 -114
  331. package/src/tui/components/Button.tsx +0 -38
  332. package/src/tui/components/CeremonyView.tsx +0 -39
  333. package/src/tui/components/CompletionDropdown.tsx +0 -56
  334. package/src/tui/components/ConfirmPrompt.tsx +0 -36
  335. package/src/tui/components/CustomTextInput.tsx +0 -132
  336. package/src/tui/components/InteractiveTable.tsx +0 -106
  337. package/src/tui/components/StepSpinner.tsx +0 -84
  338. package/src/tui/components/Toast.tsx +0 -59
  339. package/src/tui/index.tsx +0 -90
  340. package/src/tui/useChat.ts +0 -103
  341. package/src/tui/useCommand.ts +0 -238
  342. package/src/tui/useNotifications.ts +0 -28
  343. package/src/tui/useScroll.ts +0 -69
  344. package/src/ui/banner.ts +0 -78
  345. package/src/ui/colors.ts +0 -30
  346. package/src/ui/format.ts +0 -78
  347. package/src/ui/qr-render.ts +0 -92
  348. package/src/ui/rpc-fallback.ts +0 -59
  349. package/src/ui/spinner.ts +0 -56
  350. package/src/ui/tree.ts +0 -16
  351. package/src/utils/format.ts +0 -48
  352. package/src/utils/hash.ts +0 -5
  353. package/src/utils/time.ts +0 -15
  354. package/src/wallet-router.ts +0 -178
  355. package/src/walletconnect-session.ts +0 -27
  356. package/src/walletconnect.ts +0 -309
  357. package/test/time.test.js +0 -11
  358. package/tsconfig.json +0 -33
@@ -1,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
- }