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.
- package/README.md +41 -2
- package/dist/abis.d.ts +1 -0
- package/dist/abis.d.ts.map +1 -1
- package/dist/abis.js +45 -14
- package/dist/abis.js.map +1 -1
- package/dist/bundler.d.ts +1 -1
- package/dist/bundler.d.ts.map +1 -1
- package/dist/bundler.js +61 -27
- package/dist/bundler.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +9 -5
- package/dist/client.js.map +1 -1
- package/dist/coinbase-smart-wallet.js +4 -1
- package/dist/coinbase-smart-wallet.js.map +1 -1
- package/dist/commands/accept.js +28 -25
- package/dist/commands/accept.js.map +1 -1
- package/dist/commands/agent-handshake.js +18 -15
- package/dist/commands/agent-handshake.js.map +1 -1
- package/dist/commands/agent.js +104 -98
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/agreements.js +98 -62
- package/dist/commands/agreements.js.map +1 -1
- package/dist/commands/arbitrator.js +81 -45
- package/dist/commands/arbitrator.js.map +1 -1
- package/dist/commands/arena-handshake.d.ts.map +1 -1
- package/dist/commands/arena-handshake.js +35 -53
- package/dist/commands/arena-handshake.js.map +1 -1
- package/dist/commands/arena.js +18 -12
- package/dist/commands/arena.js.map +1 -1
- package/dist/commands/backup.js +36 -30
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cancel.js +18 -15
- package/dist/commands/cancel.js.map +1 -1
- package/dist/commands/channel.js +81 -45
- package/dist/commands/channel.js.map +1 -1
- package/dist/commands/coldstart.js +34 -31
- package/dist/commands/coldstart.js.map +1 -1
- package/dist/commands/compute.d.ts +14 -0
- package/dist/commands/compute.d.ts.map +1 -0
- package/dist/commands/compute.js +466 -0
- package/dist/commands/compute.js.map +1 -0
- package/dist/commands/config.js +30 -24
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contract-interaction.js +15 -12
- package/dist/commands/contract-interaction.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +135 -98
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/deliver.js +76 -37
- package/dist/commands/deliver.js.map +1 -1
- package/dist/commands/discover.js +27 -24
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/dispute.js +110 -104
- package/dist/commands/dispute.js.map +1 -1
- package/dist/commands/doctor.js +55 -16
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/endpoint.js +95 -56
- package/dist/commands/endpoint.js.map +1 -1
- package/dist/commands/feed.js +18 -11
- package/dist/commands/feed.js.map +1 -1
- package/dist/commands/hire.js +40 -37
- package/dist/commands/hire.js.map +1 -1
- package/dist/commands/migrate.js +33 -30
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/negotiate.d.ts.map +1 -1
- package/dist/commands/negotiate.js +36 -34
- package/dist/commands/negotiate.js.map +1 -1
- package/dist/commands/openshell.js +104 -68
- package/dist/commands/openshell.js.map +1 -1
- package/dist/commands/owner.js +20 -17
- package/dist/commands/owner.js.map +1 -1
- package/dist/commands/policy.js +43 -41
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/relay.d.ts.map +1 -1
- package/dist/commands/relay.js +51 -18
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/remediate.js +23 -20
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/reputation.js +27 -25
- package/dist/commands/reputation.js.map +1 -1
- package/dist/commands/setup.js +104 -65
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/trust.js +20 -17
- package/dist/commands/trust.js.map +1 -1
- package/dist/commands/verify.js +21 -18
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +645 -679
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.js +36 -33
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/watchtower.js +73 -37
- package/dist/commands/watchtower.js.map +1 -1
- package/dist/commands/workroom.d.ts.map +1 -1
- package/dist/commands/workroom.js +282 -143
- package/dist/commands/workroom.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +71 -22
- package/dist/config.js.map +1 -1
- package/dist/daemon/compute-metering.d.ts +61 -0
- package/dist/daemon/compute-metering.d.ts.map +1 -0
- package/dist/daemon/compute-metering.js +299 -0
- package/dist/daemon/compute-metering.js.map +1 -0
- package/dist/daemon/compute-session.d.ts +100 -0
- package/dist/daemon/compute-session.d.ts.map +1 -0
- package/dist/daemon/compute-session.js +231 -0
- package/dist/daemon/compute-session.js.map +1 -0
- package/dist/daemon/config.d.ts +19 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +90 -16
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/credentials.d.ts +24 -0
- package/dist/daemon/credentials.d.ts.map +1 -0
- package/dist/daemon/credentials.js +80 -0
- package/dist/daemon/credentials.js.map +1 -0
- package/dist/daemon/delivery-client.d.ts +35 -0
- package/dist/daemon/delivery-client.d.ts.map +1 -0
- package/dist/daemon/delivery-client.js +231 -0
- package/dist/daemon/delivery-client.js.map +1 -0
- package/dist/daemon/file-delivery.d.ts +98 -0
- package/dist/daemon/file-delivery.d.ts.map +1 -0
- package/dist/daemon/file-delivery.js +461 -0
- package/dist/daemon/file-delivery.js.map +1 -0
- package/dist/daemon/hire-listener.d.ts +3 -3
- package/dist/daemon/hire-listener.d.ts.map +1 -1
- package/dist/daemon/hire-listener.js +47 -13
- package/dist/daemon/hire-listener.js.map +1 -1
- package/dist/daemon/index.d.ts +2 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +526 -53
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/job-lifecycle.d.ts +1 -1
- package/dist/daemon/job-lifecycle.d.ts.map +1 -1
- package/dist/daemon/job-lifecycle.js +51 -11
- package/dist/daemon/job-lifecycle.js.map +1 -1
- package/dist/daemon/notify.d.ts +1 -1
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +53 -19
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/token-metering.js +47 -8
- package/dist/daemon/token-metering.js.map +1 -1
- package/dist/daemon/userops.d.ts +2 -2
- package/dist/daemon/userops.d.ts.map +1 -1
- package/dist/daemon/userops.js +27 -23
- package/dist/daemon/userops.js.map +1 -1
- package/dist/daemon/wallet-monitor.d.ts +1 -1
- package/dist/daemon/wallet-monitor.d.ts.map +1 -1
- package/dist/daemon/wallet-monitor.js +12 -8
- package/dist/daemon/wallet-monitor.js.map +1 -1
- package/dist/daemon/worker-executor.d.ts +71 -0
- package/dist/daemon/worker-executor.d.ts.map +1 -0
- package/dist/daemon/worker-executor.js +382 -0
- package/dist/daemon/worker-executor.js.map +1 -0
- package/dist/drain-v4.js +64 -26
- package/dist/drain-v4.js.map +1 -1
- package/dist/endpoint-config.js +63 -20
- package/dist/endpoint-config.js.map +1 -1
- package/dist/endpoint-notify.d.ts.map +1 -1
- package/dist/endpoint-notify.js +49 -15
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +50 -18
- package/dist/index.js.map +1 -1
- package/dist/openshell-runtime.d.ts.map +1 -1
- package/dist/openshell-runtime.js +82 -38
- package/dist/openshell-runtime.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +85 -78
- package/dist/program.js.map +1 -1
- package/dist/repl.js +31 -25
- package/dist/repl.js.map +1 -1
- package/dist/signing.js +6 -3
- package/dist/signing.js.map +1 -1
- package/dist/telegram-notify.js +40 -3
- package/dist/telegram-notify.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +56 -89
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Footer.js +7 -4
- package/dist/tui/Footer.js.map +1 -1
- package/dist/tui/Header.d.ts +1 -1
- package/dist/tui/Header.d.ts.map +1 -1
- package/dist/tui/Header.js +14 -9
- package/dist/tui/Header.js.map +1 -1
- package/dist/tui/InputLine.d.ts +2 -1
- package/dist/tui/InputLine.d.ts.map +1 -1
- package/dist/tui/InputLine.js +47 -97
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts +1 -2
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +26 -6
- package/dist/tui/Viewport.js.map +1 -1
- package/dist/tui/WalletConnectPairing.js +19 -16
- package/dist/tui/WalletConnectPairing.js.map +1 -1
- package/dist/tui/components/Button.js +9 -6
- package/dist/tui/components/Button.js.map +1 -1
- package/dist/tui/components/CeremonyView.js +8 -5
- package/dist/tui/components/CeremonyView.js.map +1 -1
- package/dist/tui/components/CompletionDropdown.js +9 -6
- package/dist/tui/components/CompletionDropdown.js.map +1 -1
- package/dist/tui/components/ConfirmPrompt.js +8 -5
- package/dist/tui/components/ConfirmPrompt.js.map +1 -1
- package/dist/tui/components/CustomTextInput.js +14 -11
- package/dist/tui/components/CustomTextInput.js.map +1 -1
- package/dist/tui/components/InteractiveTable.js +12 -9
- package/dist/tui/components/InteractiveTable.js.map +1 -1
- package/dist/tui/components/StepSpinner.js +13 -10
- package/dist/tui/components/StepSpinner.js.map +1 -1
- package/dist/tui/components/Toast.js +12 -8
- package/dist/tui/components/Toast.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +21 -28
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/useChat.js +19 -13
- package/dist/tui/useChat.js.map +1 -1
- package/dist/tui/useCommand.d.ts +2 -3
- package/dist/tui/useCommand.d.ts.map +1 -1
- package/dist/tui/useCommand.js +24 -100
- package/dist/tui/useCommand.js.map +1 -1
- package/dist/tui/useNotifications.js +8 -5
- package/dist/tui/useNotifications.js.map +1 -1
- package/dist/tui/useScroll.d.ts.map +1 -1
- package/dist/tui/useScroll.js +12 -15
- package/dist/tui/useScroll.js.map +1 -1
- package/dist/ui/banner.d.ts +0 -12
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +19 -35
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/colors.js +19 -13
- package/dist/ui/colors.js.map +1 -1
- package/dist/ui/format.js +14 -6
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/qr-render.js +11 -4
- package/dist/ui/qr-render.js.map +1 -1
- package/dist/ui/rpc-fallback.js +11 -6
- package/dist/ui/rpc-fallback.js.map +1 -1
- package/dist/ui/spinner.js +12 -6
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/tree.js +6 -3
- package/dist/ui/tree.js.map +1 -1
- package/dist/utils/format.js +41 -27
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/hash.js +42 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/time.js +6 -2
- package/dist/utils/time.js.map +1 -1
- package/dist/wallet-router.d.ts +1 -1
- package/dist/wallet-router.d.ts.map +1 -1
- package/dist/wallet-router.js +19 -12
- package/dist/wallet-router.js.map +1 -1
- package/dist/walletconnect-session.d.ts +1 -1
- package/dist/walletconnect-session.d.ts.map +1 -1
- package/dist/walletconnect-session.js +11 -6
- package/dist/walletconnect-session.js.map +1 -1
- package/dist/walletconnect.d.ts +5 -6
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +35 -32
- package/dist/walletconnect.js.map +1 -1
- package/package.json +11 -10
- package/INK6-UX-SPEC.md +0 -446
- package/MIGRATION-SPEC.md +0 -108
- package/TUI-SPEC.md +0 -214
- package/scripts/authorize-machine-key.ts +0 -43
- package/scripts/drain-wallet.ts +0 -149
- package/scripts/execute-spend-only.ts +0 -81
- package/scripts/register-agent-userop.ts +0 -186
- package/src/abis.ts +0 -187
- package/src/bundler.ts +0 -235
- package/src/client.ts +0 -36
- package/src/coinbase-smart-wallet.ts +0 -51
- package/src/commands/accept.ts +0 -64
- package/src/commands/agent-handshake.ts +0 -72
- package/src/commands/agent.ts +0 -691
- package/src/commands/agreements.ts +0 -350
- package/src/commands/arbitrator.ts +0 -180
- package/src/commands/arena-handshake.ts +0 -274
- package/src/commands/arena.ts +0 -122
- package/src/commands/backup.ts +0 -117
- package/src/commands/cancel.ts +0 -35
- package/src/commands/channel.ts +0 -218
- package/src/commands/coldstart.ts +0 -165
- package/src/commands/config.ts +0 -68
- package/src/commands/contract-interaction.ts +0 -166
- package/src/commands/daemon.ts +0 -1054
- package/src/commands/deliver.ts +0 -148
- package/src/commands/discover.ts +0 -350
- package/src/commands/dispute.ts +0 -375
- package/src/commands/doctor.ts +0 -172
- package/src/commands/endpoint.ts +0 -620
- package/src/commands/feed.ts +0 -229
- package/src/commands/hire.ts +0 -245
- package/src/commands/migrate.ts +0 -177
- package/src/commands/negotiate.ts +0 -272
- package/src/commands/openshell.ts +0 -1055
- package/src/commands/owner.ts +0 -35
- package/src/commands/policy.ts +0 -263
- package/src/commands/relay.ts +0 -277
- package/src/commands/remediate.ts +0 -24
- package/src/commands/reputation.ts +0 -79
- package/src/commands/setup.ts +0 -343
- package/src/commands/trust.ts +0 -27
- package/src/commands/verify.ts +0 -91
- package/src/commands/wallet.ts +0 -3548
- package/src/commands/watch.ts +0 -220
- package/src/commands/watchtower.ts +0 -248
- package/src/commands/workroom.ts +0 -963
- package/src/config.ts +0 -220
- package/src/daemon/config.ts +0 -344
- package/src/daemon/hire-listener.ts +0 -226
- package/src/daemon/index.ts +0 -1089
- package/src/daemon/job-lifecycle.ts +0 -215
- package/src/daemon/notify.ts +0 -297
- package/src/daemon/token-metering.ts +0 -183
- package/src/daemon/userops.ts +0 -119
- package/src/daemon/wallet-monitor.ts +0 -90
- package/src/drain-v4.ts +0 -159
- package/src/endpoint-config.ts +0 -83
- package/src/endpoint-notify.ts +0 -134
- package/src/index.ts +0 -74
- package/src/openshell-runtime.ts +0 -281
- package/src/program.ts +0 -88
- package/src/repl.ts +0 -178
- package/src/signing.ts +0 -28
- package/src/telegram-notify.ts +0 -88
- package/src/tui/App.tsx +0 -263
- package/src/tui/Footer.tsx +0 -18
- package/src/tui/Header.tsx +0 -45
- package/src/tui/InputLine.tsx +0 -243
- package/src/tui/Viewport.tsx +0 -51
- package/src/tui/WalletConnectPairing.tsx +0 -114
- package/src/tui/components/Button.tsx +0 -38
- package/src/tui/components/CeremonyView.tsx +0 -39
- package/src/tui/components/CompletionDropdown.tsx +0 -56
- package/src/tui/components/ConfirmPrompt.tsx +0 -36
- package/src/tui/components/CustomTextInput.tsx +0 -132
- package/src/tui/components/InteractiveTable.tsx +0 -106
- package/src/tui/components/StepSpinner.tsx +0 -84
- package/src/tui/components/Toast.tsx +0 -59
- package/src/tui/index.tsx +0 -90
- package/src/tui/useChat.ts +0 -103
- package/src/tui/useCommand.ts +0 -238
- package/src/tui/useNotifications.ts +0 -28
- package/src/tui/useScroll.ts +0 -69
- package/src/ui/banner.ts +0 -78
- package/src/ui/colors.ts +0 -30
- package/src/ui/format.ts +0 -78
- package/src/ui/qr-render.ts +0 -92
- package/src/ui/rpc-fallback.ts +0 -59
- package/src/ui/spinner.ts +0 -56
- package/src/ui/tree.ts +0 -16
- package/src/utils/format.ts +0 -48
- package/src/utils/hash.ts +0 -5
- package/src/utils/time.ts +0 -15
- package/src/wallet-router.ts +0 -178
- package/src/walletconnect-session.ts +0 -27
- package/src/walletconnect.ts +0 -309
- package/test/time.test.js +0 -11
- 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
|
-
}
|