windows-development-sandbox 1.5.0 → 1.7.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 +1 -0
- package/config.example.json +17 -1
- package/dist/cli/args.d.ts +13 -2
- package/dist/cli/args.js +95 -24
- package/dist/cli/commands/accounts.js +45 -17
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +9 -2
- package/dist/cli/commands/data/clean.js +32 -9
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/launch/add-root.d.ts +3 -2
- package/dist/cli/commands/launch/add-root.js +11 -10
- package/dist/cli/commands/launch/launch.js +57 -4
- package/dist/cli/commands/launch/prepare.js +3 -1
- package/dist/cli/commands/maintenance/audit.js +1 -1
- package/dist/cli/commands/maintenance/clean.d.ts +1 -1
- package/dist/cli/commands/maintenance/clean.js +17 -3
- package/dist/cli/commands/maintenance/config.d.ts +1 -1
- package/dist/cli/commands/maintenance/config.js +112 -10
- package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
- package/dist/cli/commands/maintenance/doctor.js +2 -4
- package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
- package/dist/cli/commands/maintenance/migrate.js +19 -4
- package/dist/cli/commands/maintenance/sync.js +2 -0
- package/dist/cli/commands/maintenance/update.d.ts +2 -0
- package/dist/cli/commands/maintenance/update.js +27 -4
- package/dist/cli/commands/projects/here.js +12 -7
- package/dist/cli/commands/projects/projects.js +26 -17
- package/dist/cli/commands/registry.js +7 -0
- package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
- package/dist/cli/commands/repair/checks/agents.js +70 -0
- package/dist/cli/commands/repair/checks/common.d.ts +8 -0
- package/dist/cli/commands/repair/checks/common.js +22 -0
- package/dist/cli/commands/repair/checks/data.d.ts +2 -0
- package/dist/cli/commands/repair/checks/data.js +241 -0
- package/dist/cli/commands/repair/checks/index.d.ts +2 -0
- package/dist/cli/commands/repair/checks/index.js +20 -0
- package/dist/cli/commands/repair/checks/install.d.ts +5 -0
- package/dist/cli/commands/repair/checks/install.js +318 -0
- package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
- package/dist/cli/commands/repair/checks/machine.js +232 -0
- package/dist/cli/commands/repair/checks/network.d.ts +6 -0
- package/dist/cli/commands/repair/checks/network.js +60 -0
- package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
- package/dist/cli/commands/repair/checks/pc.js +55 -0
- package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
- package/dist/cli/commands/repair/checks/sandbox.js +267 -0
- package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
- package/dist/cli/commands/repair/checks/settings.js +169 -0
- package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
- package/dist/cli/commands/repair/checks/vpn.js +53 -0
- package/dist/cli/commands/repair/engine.d.ts +32 -0
- package/dist/cli/commands/repair/engine.js +127 -0
- package/dist/cli/commands/repair/env.d.ts +7 -0
- package/dist/cli/commands/repair/env.js +49 -0
- package/dist/cli/commands/repair/index.d.ts +1 -9
- package/dist/cli/commands/repair/index.js +2 -54
- package/dist/cli/commands/repair/log.d.ts +17 -0
- package/dist/cli/commands/repair/log.js +82 -0
- package/dist/cli/commands/repair/printer.d.ts +32 -0
- package/dist/cli/commands/repair/printer.js +132 -0
- package/dist/cli/commands/repair/run.d.ts +33 -0
- package/dist/cli/commands/repair/run.js +133 -0
- package/dist/cli/commands/repair/types.d.ts +97 -9
- package/dist/cli/commands/repair/types.js +11 -7
- package/dist/cli/commands/resume.js +2 -2
- package/dist/cli/commands/sessions/list.js +5 -2
- package/dist/cli/commands/sessions/status.d.ts +2 -0
- package/dist/cli/commands/sessions/status.js +27 -6
- package/dist/cli/commands/setup/index.d.ts +5 -0
- package/dist/cli/commands/setup/index.js +25 -1
- package/dist/cli/commands/storage/index.d.ts +2 -0
- package/dist/cli/commands/storage/index.js +2 -0
- package/dist/cli/commands/storage/offer.d.ts +3 -0
- package/dist/cli/commands/storage/offer.js +85 -0
- package/dist/cli/commands/storage/storage.d.ts +8 -0
- package/dist/cli/commands/storage/storage.js +171 -0
- package/dist/cli/commands/user-config.js +1 -1
- package/dist/cli/commands/vpn.d.ts +4 -0
- package/dist/cli/commands/vpn.js +75 -0
- package/dist/cli/commands/web.d.ts +4 -0
- package/dist/cli/commands/web.js +95 -0
- package/dist/cli/history.js +1 -1
- package/dist/cli/index.js +46 -3
- package/dist/cli/manifest.d.ts +303 -29
- package/dist/cli/manifest.js +173 -43
- package/dist/cli/menu/add-agent.d.ts +47 -0
- package/dist/cli/menu/add-agent.js +168 -0
- package/dist/cli/menu/apps.d.ts +31 -2
- package/dist/cli/menu/apps.js +44 -5
- package/dist/cli/menu/command-fields.d.ts +18 -0
- package/dist/cli/menu/command-fields.js +61 -0
- package/dist/cli/menu/command-form.d.ts +22 -0
- package/dist/cli/menu/command-form.js +169 -46
- package/dist/cli/menu/data-folder.d.ts +18 -0
- package/dist/cli/menu/data-folder.js +99 -0
- package/dist/cli/menu/menu.d.ts +1 -1
- package/dist/cli/menu/menu.js +11 -8
- package/dist/cli/menu/pages/accounts-page.js +55 -24
- package/dist/cli/menu/pages/agents-page.d.ts +66 -0
- package/dist/cli/menu/pages/agents-page.js +338 -0
- package/dist/cli/menu/pages/apps-page.js +1 -2
- package/dist/cli/menu/pages/list-page.d.ts +16 -3
- package/dist/cli/menu/pages/list-page.js +40 -8
- package/dist/cli/menu/pages/maintenance-page.d.ts +9 -0
- package/dist/cli/menu/pages/maintenance-page.js +161 -72
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +132 -43
- package/dist/cli/menu/pages/sessions-page.d.ts +4 -0
- package/dist/cli/menu/pages/sessions-page.js +41 -25
- package/dist/cli/menu/pages/settings-page.d.ts +8 -4
- package/dist/cli/menu/pages/settings-page.js +36 -13
- package/dist/cli/menu/settings/categories.d.ts +2 -0
- package/dist/cli/menu/settings/categories.js +19 -44
- package/dist/cli/menu/settings/collections/collection-editor.d.ts +7 -1
- package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
- package/dist/cli/menu/settings/collections/map-editor.js +5 -1
- package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
- package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
- package/dist/cli/menu/settings/collections/string-list-editor.d.ts +11 -1
- package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
- package/dist/cli/menu/settings/index.d.ts +1 -1
- package/dist/cli/menu/settings/index.js +1 -1
- package/dist/cli/menu/settings/object-editor.d.ts +5 -1
- package/dist/cli/menu/settings/object-editor.js +15 -4
- package/dist/cli/menu/settings/path-kind.d.ts +20 -0
- package/dist/cli/menu/settings/path-kind.js +44 -0
- package/dist/cli/menu/settings/schema-node.d.ts +9 -0
- package/dist/cli/menu/settings/schema-node.js +26 -0
- package/dist/cli/menu/settings/value-editor.d.ts +5 -0
- package/dist/cli/menu/settings/value-editor.js +51 -18
- package/dist/cli/menu/several.js +7 -5
- package/dist/cli/menu/shell.d.ts +37 -10
- package/dist/cli/menu/shell.js +90 -10
- package/dist/cli/menu/vpn-login.d.ts +16 -0
- package/dist/cli/menu/vpn-login.js +66 -0
- package/dist/cli/output/print.d.ts +2 -0
- package/dist/cli/output/print.js +8 -1
- package/dist/cli/services/accounts-service.d.ts +2 -0
- package/dist/cli/services/accounts-service.js +2 -2
- package/dist/cli/services/health-service.d.ts +0 -6
- package/dist/cli/services/health-service.js +5 -42
- package/dist/cli/services/index.d.ts +3 -1
- package/dist/cli/services/index.js +3 -1
- package/dist/cli/services/migrate-service.js +1 -1
- package/dist/cli/services/update-service.d.ts +2 -0
- package/dist/cli/services/update-service.js +13 -6
- package/dist/cli/services/vpn-service.d.ts +28 -0
- package/dist/cli/services/vpn-service.js +46 -0
- package/dist/cli/services/web-service.d.ts +22 -0
- package/dist/cli/services/web-service.js +111 -0
- package/dist/cli/tui/app.d.ts +6 -3
- package/dist/cli/tui/app.js +12 -5
- package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
- package/dist/cli/tui/dialogs/path-listing.js +127 -0
- package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
- package/dist/cli/tui/dialogs/path-picker.js +423 -0
- package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
- package/dist/cli/tui/dialogs/text-screen.js +10 -3
- package/dist/cli/tui/frame.d.ts +7 -10
- package/dist/cli/tui/frame.js +19 -10
- package/dist/cli/tui/index.d.ts +4 -3
- package/dist/cli/tui/index.js +2 -1
- package/dist/cli/tui/list/list-screen.d.ts +14 -2
- package/dist/cli/tui/list/list-screen.js +68 -32
- package/dist/cli/tui/list/list-types.d.ts +8 -4
- package/dist/cli/tui/nav.d.ts +29 -0
- package/dist/cli/tui/nav.js +67 -0
- package/dist/cli/validate.d.ts +1 -1
- package/dist/cli/validate.js +3 -3
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +11 -0
- package/dist/core/config/defaults.js +5 -1
- package/dist/core/config/loader.js +12 -2
- package/dist/core/local-config/overrides.js +1 -0
- package/dist/core/prompt.d.ts +19 -0
- package/dist/core/prompt.js +43 -3
- package/dist/core/schema/common.d.ts +6 -0
- package/dist/core/schema/common.js +8 -0
- package/dist/core/schema/config.d.ts +23 -0
- package/dist/core/schema/config.js +10 -1
- package/dist/core/schema/harness.d.ts +1 -0
- package/dist/core/schema/harness.js +8 -2
- package/dist/core/schema/index.d.ts +3 -1
- package/dist/core/schema/index.js +3 -1
- package/dist/core/schema/profile.d.ts +1 -0
- package/dist/core/schema/profile.js +3 -0
- package/dist/core/schema/redact.d.ts +13 -0
- package/dist/core/schema/redact.js +75 -0
- package/dist/core/schema/sandbox.js +1 -0
- package/dist/core/schema/vpn.d.ts +17 -0
- package/dist/core/schema/vpn.js +22 -0
- package/dist/core/schema/web.d.ts +6 -0
- package/dist/core/schema/web.js +14 -0
- package/dist/core/types.d.ts +26 -0
- package/dist/guest/agents/agent-files.d.ts +49 -0
- package/dist/guest/agents/agent-files.js +63 -0
- package/dist/guest/agents/agent-installer.js +2 -9
- package/dist/guest/agents/local-state.d.ts +12 -0
- package/dist/guest/agents/local-state.js +198 -0
- package/dist/guest/agents/sbx-agent.d.ts +2 -0
- package/dist/guest/agents/sbx-agent.js +35 -0
- package/dist/guest/boot/boot.d.ts +4 -0
- package/dist/guest/boot/boot.js +50 -11
- package/dist/guest/daemon/daemon.js +4 -1
- package/dist/guest/daemon/exec.js +2 -1
- package/dist/guest/daemon/idle-shutdown.js +2 -0
- package/dist/guest/daemon/sessions.js +4 -1
- package/dist/guest/entries/agent.d.ts +1 -0
- package/dist/guest/entries/agent.js +10 -0
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/runtime/secrets.d.ts +12 -0
- package/dist/guest/runtime/secrets.js +63 -0
- package/dist/guest/setup/claude-profile.d.ts +2 -1
- package/dist/guest/setup/claude-profile.js +12 -1
- package/dist/guest/setup/environment.d.ts +6 -0
- package/dist/guest/setup/environment.js +56 -6
- package/dist/guest/setup/path-plan.d.ts +32 -0
- package/dist/guest/setup/path-plan.js +67 -0
- package/dist/guest/setup/shell-path.d.ts +1 -0
- package/dist/guest/setup/shell-path.js +22 -0
- package/dist/guest/toolchains/languages/python.js +1 -1
- package/dist/guest/toolchains/runtimes.js +2 -2
- package/dist/guest/vpn/index.d.ts +1 -0
- package/dist/guest/vpn/index.js +1 -0
- package/dist/guest/vpn/openvpn.d.ts +9 -0
- package/dist/guest/vpn/openvpn.js +47 -0
- package/dist/guest/vpn/tunnel.d.ts +17 -0
- package/dist/guest/vpn/tunnel.js +78 -0
- package/dist/guest/vpn/vpn.d.ts +6 -0
- package/dist/guest/vpn/vpn.js +126 -0
- package/dist/host/channel/attach.d.ts +3 -1
- package/dist/host/channel/attach.js +1 -1
- package/dist/host/channel/channel.d.ts +7 -0
- package/dist/host/channel/channel.js +26 -2
- package/dist/host/health/acl-read.d.ts +17 -0
- package/dist/host/health/acl-read.js +45 -0
- package/dist/host/health/config-backup.d.ts +11 -0
- package/dist/host/health/config-backup.js +73 -0
- package/dist/host/health/index.d.ts +6 -0
- package/dist/host/health/index.js +7 -0
- package/dist/host/health/install-state.d.ts +33 -0
- package/dist/host/health/install-state.js +110 -0
- package/dist/host/health/path-copies.d.ts +32 -0
- package/dist/host/health/path-copies.js +82 -0
- package/dist/host/health/stale.d.ts +45 -0
- package/dist/host/health/stale.js +120 -0
- package/dist/host/health/system.d.ts +37 -0
- package/dist/host/health/system.js +87 -0
- package/dist/host/install/copies.d.ts +13 -0
- package/dist/host/install/copies.js +63 -0
- package/dist/host/install/index.d.ts +1 -0
- package/dist/host/install/index.js +1 -0
- package/dist/host/launch/generate.js +25 -1
- package/dist/host/launch/session-file.js +4 -1
- package/dist/host/launch/stage.js +1 -1
- package/dist/host/migrate/backup.js +3 -0
- package/dist/host/migrate/migrations/config-file.d.ts +2 -0
- package/dist/host/migrate/migrations/config-file.js +7 -0
- package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
- package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
- package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
- package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
- package/dist/host/migrate/registry.js +2 -1
- package/dist/host/profile/accounts.d.ts +25 -1
- package/dist/host/profile/accounts.js +96 -13
- package/dist/host/profile/agent-accounts.d.ts +5 -1
- package/dist/host/profile/agent-accounts.js +35 -5
- package/dist/host/profile/index.d.ts +2 -1
- package/dist/host/profile/index.js +1 -0
- package/dist/host/profile/plugin-share.d.ts +26 -0
- package/dist/host/profile/plugin-share.js +146 -0
- package/dist/host/projects/join.d.ts +7 -1
- package/dist/host/projects/join.js +21 -4
- package/dist/host/projects/registry.d.ts +16 -10
- package/dist/host/projects/registry.js +74 -6
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +21 -2
- package/dist/host/secrets/index.d.ts +2 -0
- package/dist/host/secrets/index.js +2 -0
- package/dist/host/secrets/launch-secrets.d.ts +17 -0
- package/dist/host/secrets/launch-secrets.js +93 -0
- package/dist/host/secrets/secret-store.d.ts +25 -0
- package/dist/host/secrets/secret-store.js +89 -0
- package/dist/host/sharing/acl.d.ts +16 -2
- package/dist/host/sharing/acl.js +35 -10
- package/dist/host/sharing/current.js +11 -2
- package/dist/host/sharing/doctor.d.ts +2 -0
- package/dist/host/sharing/doctor.js +44 -0
- package/dist/host/sharing/group-access.d.ts +6 -0
- package/dist/host/sharing/group-access.js +24 -0
- package/dist/host/sharing/index.d.ts +2 -1
- package/dist/host/sharing/index.js +2 -1
- package/dist/host/sharing/lifecycle.d.ts +8 -2
- package/dist/host/sharing/lifecycle.js +16 -7
- package/dist/host/storage/cleanup.d.ts +2 -0
- package/dist/host/storage/cleanup.js +2 -1
- package/dist/host/storage/history.d.ts +21 -0
- package/dist/host/storage/history.js +49 -0
- package/dist/host/storage/index.d.ts +4 -0
- package/dist/host/storage/index.js +4 -0
- package/dist/host/storage/move.d.ts +95 -0
- package/dist/host/storage/move.js +384 -0
- package/dist/host/storage/offer.d.ts +11 -0
- package/dist/host/storage/offer.js +36 -0
- package/dist/host/storage/root.d.ts +18 -0
- package/dist/host/storage/root.js +77 -0
- package/dist/host/tools/windows-terminal.d.ts +2 -0
- package/dist/host/tools/windows-terminal.js +23 -5
- package/dist/host/update/index.d.ts +1 -1
- package/dist/host/update/index.js +1 -1
- package/dist/host/update/notice.d.ts +2 -0
- package/dist/host/update/notice.js +13 -1
- package/dist/host/update/update-state.d.ts +6 -0
- package/dist/host/update/update-state.js +2 -0
- package/dist/host/update/updater.d.ts +19 -0
- package/dist/host/update/updater.js +88 -12
- package/dist/host/vpn/index.d.ts +4 -0
- package/dist/host/vpn/index.js +4 -0
- package/dist/host/vpn/login.d.ts +24 -0
- package/dist/host/vpn/login.js +79 -0
- package/dist/host/vpn/nordvpn.d.ts +21 -0
- package/dist/host/vpn/nordvpn.js +77 -0
- package/dist/host/vpn/prepare.d.ts +17 -0
- package/dist/host/vpn/prepare.js +77 -0
- package/dist/host/vpn/status.d.ts +12 -0
- package/dist/host/vpn/status.js +53 -0
- package/dist/host/web/auth.d.ts +38 -0
- package/dist/host/web/auth.js +96 -0
- package/dist/host/web/backend.d.ts +14 -0
- package/dist/host/web/backend.js +1 -0
- package/dist/host/web/bridge.d.ts +27 -0
- package/dist/host/web/bridge.js +144 -0
- package/dist/host/web/browser.d.ts +1 -0
- package/dist/host/web/browser.js +10 -0
- package/dist/host/web/control.d.ts +17 -0
- package/dist/host/web/control.js +67 -0
- package/dist/host/web/index.d.ts +9 -0
- package/dist/host/web/index.js +8 -0
- package/dist/host/web/runner.d.ts +14 -0
- package/dist/host/web/runner.js +45 -0
- package/dist/host/web/server.d.ts +40 -0
- package/dist/host/web/server.js +218 -0
- package/dist/host/web/state.d.ts +20 -0
- package/dist/host/web/state.js +41 -0
- package/dist/host/web/static-files.d.ts +13 -0
- package/dist/host/web/static-files.js +57 -0
- package/dist/runtime/agent.js +27614 -0
- package/dist/runtime/daemon.js +1012 -618
- package/dist/runtime/db.js +236 -106
- package/dist/runtime/main.js +1093 -250
- package/dist/runtime/netproxy.js +102 -3
- package/dist/runtime/view.js +102 -3
- package/dist/selftest/accounts.test.js +40 -0
- package/dist/selftest/agent-paths.test.d.ts +2 -0
- package/dist/selftest/agent-paths.test.js +115 -0
- package/dist/selftest/channel.test.js +5 -0
- package/dist/selftest/cli.test.js +34 -4
- package/dist/selftest/credentials.test.d.ts +2 -0
- package/dist/selftest/credentials.test.js +198 -0
- package/dist/selftest/generate.test.js +1 -1
- package/dist/selftest/health.test.d.ts +2 -0
- package/dist/selftest/health.test.js +470 -0
- package/dist/selftest/host-sim.test.js +2 -0
- package/dist/selftest/index.js +19 -0
- package/dist/selftest/layout.test.js +2 -0
- package/dist/selftest/menu-screens.d.ts +7 -0
- package/dist/selftest/menu-screens.js +65 -16
- package/dist/selftest/menu.test.js +574 -21
- package/dist/selftest/migrate.test.js +1 -1
- package/dist/selftest/personalization.test.js +3 -1
- package/dist/selftest/projects.test.js +39 -1
- package/dist/selftest/shared-use.test.js +64 -2
- package/dist/selftest/sharing.test.js +50 -1
- package/dist/selftest/storage.test.d.ts +2 -0
- package/dist/selftest/storage.test.js +363 -0
- package/dist/selftest/support/fixtures.d.ts +14 -0
- package/dist/selftest/support/releases.js +1 -1
- package/dist/selftest/toolchains.test.js +21 -0
- package/dist/selftest/update-service.test.js +87 -3
- package/dist/selftest/vpn.test.d.ts +2 -0
- package/dist/selftest/vpn.test.js +279 -0
- package/dist/selftest/web.test.d.ts +2 -0
- package/dist/selftest/web.test.js +304 -0
- package/dist/shared/apps/catalogue.js +31 -0
- package/dist/shared/channel/index.d.ts +1 -0
- package/dist/shared/channel/index.js +1 -0
- package/dist/shared/channel/run-dir.d.ts +6 -1
- package/dist/shared/channel/run-dir.js +12 -1
- package/dist/shared/channel/schema.d.ts +19 -0
- package/dist/shared/channel/schema.js +11 -0
- package/dist/shared/channel/secrets.d.ts +28 -0
- package/dist/shared/channel/secrets.js +44 -0
- package/dist/shared/vpn/download.d.ts +13 -0
- package/dist/shared/vpn/download.js +14 -0
- package/dist/shared/vpn/kill-switch.d.ts +18 -0
- package/dist/shared/vpn/kill-switch.js +81 -0
- package/dist/shared/vpn/ovpn.d.ts +23 -0
- package/dist/shared/vpn/ovpn.js +244 -0
- package/dist/shared/web/index.d.ts +1 -0
- package/dist/shared/web/index.js +1 -0
- package/dist/shared/web/protocol.d.ts +61 -0
- package/dist/shared/web/protocol.js +35 -0
- package/dist/sync/sync-cycle.js +5 -1
- package/dist/sync/sync-state.d.ts +1 -0
- package/dist/sync/sync-state.js +1 -0
- package/dist/sync/watcher.js +10 -0
- package/dist/web/index.html +13 -0
- package/dist/web/main-CXESL347.js +45 -0
- package/dist/web/main-VLR7DVQ4.css +1 -0
- package/native/csharp/FolderSecurity.cs +29 -0
- package/native/scripts/protect-data.ps1 +29 -0
- package/native/scripts/read-folder-sddl.ps1 +10 -0
- package/native/scripts/set-folder-access.ps1 +49 -7
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +530 -0
- package/node_modules/ws/lib/receiver.js +743 -0
- package/node_modules/ws/lib/sender.js +607 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +562 -0
- package/node_modules/ws/lib/websocket.js +1421 -0
- package/node_modules/ws/package.json +74 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/package.json +15 -5
- package/sandbox/csharp/EnvBroadcast.cs +33 -0
- package/sandbox/csharp/PathSync.cs +90 -0
- package/sandbox/scripts/install-shell-path.ps1 +73 -0
- package/sandbox/scripts/set-machine-environment.ps1 +37 -4
- package/schemas/config.schema.json +79 -4
- package/schemas/sandbox-config.schema.json +14 -1
- package/dist/cli/commands/repair/checks.d.ts +0 -2
- package/dist/cli/commands/repair/checks.js +0 -246
- package/dist/cli/menu/pages/start-page.d.ts +0 -43
- package/dist/cli/menu/pages/start-page.js +0 -192
- package/dist/cli/tui/tab-bar.d.ts +0 -9
- package/dist/cli/tui/tab-bar.js +0 -33
|
@@ -57,7 +57,8 @@ export function mergeMcpServers(hostFile, guestFile, hostWorkspace, guestWorkspa
|
|
|
57
57
|
writeJsonAtomic(guestFile, merged);
|
|
58
58
|
return Object.keys(host.mcpServers ?? {}).length + Object.keys(hostProject).length;
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
/** `credentials`: the text of this PC's .credentials.json, handed over in the one-shot secrets file. */
|
|
61
|
+
export function syncClaudeProfile(credentials) {
|
|
61
62
|
const profile = session.claudeProfile;
|
|
62
63
|
if (profile.items.length === 0) {
|
|
63
64
|
log('No Claude settings were staged from this PC.');
|
|
@@ -68,6 +69,16 @@ export function syncClaudeProfile() {
|
|
|
68
69
|
for (const name of profile.items) {
|
|
69
70
|
const from = path.join(source, name);
|
|
70
71
|
const to = path.join(claudeDir, name);
|
|
72
|
+
if (name === '.credentials.json') {
|
|
73
|
+
// Never staged in the launch folder: it came in the secrets file.
|
|
74
|
+
if (credentials && !exists(to)) {
|
|
75
|
+
fs.writeFileSync(to, credentials, 'utf8');
|
|
76
|
+
log('Claude settings: copied ' + name + ' from this PC');
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
log("Claude settings: kept the sandbox's " + name);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
71
82
|
if (!exists(from))
|
|
72
83
|
continue;
|
|
73
84
|
if ((KEEP_SANDBOX_COPY.includes(name) || !profile.overwrite) && exists(to)) {
|
|
@@ -14,3 +14,9 @@ export declare function persistEnvironment(env: NodeJS.ProcessEnv, toolVars: str
|
|
|
14
14
|
* the boot project's terminals see never changes; the project's own sessions get its full environment anyway.
|
|
15
15
|
*/
|
|
16
16
|
export declare function addMachineEnvironment(vars: Record<string, string>, dirs: string[], additive?: boolean): void;
|
|
17
|
+
/**
|
|
18
|
+
* The environment for a program the daemon starts (an agent session, `sandbox exec`): its own PATH, plus every machine
|
|
19
|
+
* and user PATH folder added since the daemon started (python, cargo, other agents ...) at the end. Never changes
|
|
20
|
+
* `env`; without Windows it is returned as it is.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withRegistryPath(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { claudeDir, session, stateDir, workspace } from '../runtime/session.js';
|
|
4
4
|
import { dataDir, dryRun, isWin, testMode, toolsDir } from '../runtime/paths.js';
|
|
5
|
-
import { mkdirp } from '../runtime/fs.js';
|
|
6
|
-
import { runScriptWithJson } from '../runtime/process.js';
|
|
5
|
+
import { exists, mkdirp } from '../runtime/fs.js';
|
|
6
|
+
import { execOutput, runScriptWithJson } from '../runtime/process.js';
|
|
7
|
+
import { SYSTEM } from '../../shared/system.js';
|
|
7
8
|
import { warn } from '../runtime/log.js';
|
|
8
9
|
import { errMsg } from '../../core/util/errors.js';
|
|
10
|
+
import { localStateVars } from '../agents/local-state.js';
|
|
11
|
+
import { agentPaths } from '../agents/agent-files.js';
|
|
12
|
+
import { appendMissing, expandVars, isPinnedNodeDir, parseRegPath, pathKey, planPath } from './path-plan.js';
|
|
9
13
|
/** Older launch folders have no agentHomes: these two were always set. */
|
|
10
14
|
const LEGACY_HOMES = { CODEX_HOME: 'codex', GEMINI_CLI_HOME: 'gemini' };
|
|
11
15
|
/** Every agent's home variable (CODEX_HOME ...) with the folder it points to in the persistent state. */
|
|
@@ -31,6 +35,7 @@ export function agentEnv(tools, target = { workspace, project: session.project,
|
|
|
31
35
|
...tools.vars,
|
|
32
36
|
CLAUDE_CONFIG_DIR: claudeDir,
|
|
33
37
|
...homes,
|
|
38
|
+
...localStateVars(),
|
|
34
39
|
npm_config_cache: path.join(toolsDir, 'npm-cache'),
|
|
35
40
|
npm_config_update_notifier: 'false',
|
|
36
41
|
AGENT_WORKSPACE: target.workspace,
|
|
@@ -41,7 +46,13 @@ export function agentEnv(tools, target = { workspace, project: session.project,
|
|
|
41
46
|
AGENT_SANDBOX: '1',
|
|
42
47
|
AGENT_SANDBOX_WORKSPACE: target.workspace,
|
|
43
48
|
};
|
|
44
|
-
|
|
49
|
+
const local = Object.keys(localStateVars());
|
|
50
|
+
for (const dir of [
|
|
51
|
+
env.CLAUDE_CONFIG_DIR,
|
|
52
|
+
...Object.keys(homes).map((n) => env[n]),
|
|
53
|
+
env.npm_config_cache,
|
|
54
|
+
...local.map((n) => env[n]),
|
|
55
|
+
])
|
|
45
56
|
mkdirp(dir);
|
|
46
57
|
// The runner's own Node (bootstrap.ps1 puts it first on PATH) is not exposed: `node` is whatever nvm selected.
|
|
47
58
|
const runners = [path.join(toolsDir, 'node'), path.dirname(process.execPath)].map((d) => d.toLowerCase());
|
|
@@ -53,13 +64,17 @@ export function agentEnv(tools, target = { workspace, project: session.project,
|
|
|
53
64
|
export function persistEnvironment(env, toolVars) {
|
|
54
65
|
if (!isWin || dryRun || testMode)
|
|
55
66
|
return;
|
|
56
|
-
const vars = Object.fromEntries([...toolVars, ...AGENT_VARS, ...Object.keys(agentHomes())]
|
|
67
|
+
const vars = Object.fromEntries([...toolVars, ...AGENT_VARS, ...Object.keys(agentHomes()), ...Object.keys(localStateVars())]
|
|
57
68
|
.filter((n) => env[n] !== undefined)
|
|
58
69
|
.map((n) => [n, env[n]]));
|
|
59
70
|
try {
|
|
60
71
|
runScriptWithJson('set-machine-environment.ps1', 'SettingsFile', {
|
|
61
72
|
vars,
|
|
62
|
-
path:
|
|
73
|
+
path: planPath({
|
|
74
|
+
entries: [env.PATH ?? '', ...installedAgentDirs()],
|
|
75
|
+
exists,
|
|
76
|
+
drop: (dir) => isPinnedNodeDir(dir, nvmHome()),
|
|
77
|
+
}).join(path.delimiter),
|
|
63
78
|
add: [],
|
|
64
79
|
additive: false,
|
|
65
80
|
});
|
|
@@ -76,10 +91,45 @@ export function persistEnvironment(env, toolVars) {
|
|
|
76
91
|
export function addMachineEnvironment(vars, dirs, additive = false) {
|
|
77
92
|
if (!isWin || dryRun || testMode)
|
|
78
93
|
return;
|
|
94
|
+
// Folders that do not exist stay out, and so does a pinned Node version (`nvm use` could not move it).
|
|
95
|
+
const planned = planPath({ entries: dirs, exists, drop: (dir) => isPinnedNodeDir(dir, nvmHome()) });
|
|
79
96
|
try {
|
|
80
|
-
runScriptWithJson('set-machine-environment.ps1', 'SettingsFile', { vars, path: null, add:
|
|
97
|
+
runScriptWithJson('set-machine-environment.ps1', 'SettingsFile', { vars, path: null, add: planned, additive });
|
|
81
98
|
}
|
|
82
99
|
catch (e) {
|
|
83
100
|
warn('Could not add to the environment: ' + errMsg(e));
|
|
84
101
|
}
|
|
85
102
|
}
|
|
103
|
+
const nvmHome = () => path.join(toolsDir, 'nvm');
|
|
104
|
+
/** The folder of every configured agent that is installed: its command works in any terminal. */
|
|
105
|
+
function installedAgentDirs() {
|
|
106
|
+
return Object.entries(session.agents ?? {}).flatMap(([name, harness]) => {
|
|
107
|
+
const p = agentPaths(toolsDir, name, harness);
|
|
108
|
+
return (p.exe && exists(p.exe)) || (p.cmd && exists(p.cmd)) ? [p.dir] : [];
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const MACHINE_ENVIRONMENT_KEY = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment';
|
|
112
|
+
const USER_ENVIRONMENT_KEY = 'HKCU\\Environment';
|
|
113
|
+
const registryPath = (key) => parseRegPath(execOutput(SYSTEM.reg(), ['query', key, '/v', 'Path']));
|
|
114
|
+
/**
|
|
115
|
+
* The environment for a program the daemon starts (an agent session, `sandbox exec`): its own PATH, plus every machine
|
|
116
|
+
* and user PATH folder added since the daemon started (python, cargo, other agents ...) at the end. Never changes
|
|
117
|
+
* `env`; without Windows it is returned as it is.
|
|
118
|
+
*/
|
|
119
|
+
export function withRegistryPath(env) {
|
|
120
|
+
if (!isWin || dryRun || testMode)
|
|
121
|
+
return env;
|
|
122
|
+
try {
|
|
123
|
+
const registry = expandVars(registryPath(MACHINE_ENVIRONMENT_KEY) + ';' + registryPath(USER_ENVIRONMENT_KEY), env);
|
|
124
|
+
const runner = pathKey(path.join(toolsDir, 'node'));
|
|
125
|
+
const pathName = Object.keys(env).find((k) => k.toLowerCase() === 'path') ?? 'PATH';
|
|
126
|
+
return {
|
|
127
|
+
...env,
|
|
128
|
+
[pathName]: appendMissing(env[pathName] ?? '', registry, (dir) => pathKey(dir) === runner || isPinnedNodeDir(dir, nvmHome())),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
warn('Could not read the machine PATH (the session keeps the one it has): ' + errMsg(e));
|
|
133
|
+
return env;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A folder as a comparison key: one slash style, no trailing slash (a drive root keeps its one), lower case. */
|
|
2
|
+
export declare function pathKey(folder: string): string;
|
|
3
|
+
/** The folders of a PATH string: no empty parts, no quotes around a folder. */
|
|
4
|
+
export declare function splitPath(value: string): string[];
|
|
5
|
+
/** Replaces %NAME% with the variable's value (any case); a name that is not set stays as written. */
|
|
6
|
+
export declare function expandVars(value: string, env: Readonly<Record<string, string | undefined>>): string;
|
|
7
|
+
export interface PathPlanInput {
|
|
8
|
+
/** In the order they should have: earlier entries win. */
|
|
9
|
+
readonly entries: readonly string[];
|
|
10
|
+
readonly exists: (folder: string) => boolean;
|
|
11
|
+
/** Entries to leave out (a folder that must not be machine-wide, matched like pathKey). */
|
|
12
|
+
readonly drop?: (folder: string) => boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Ordered, de-duplicated (case, trailing slash) PATH folders; missing and dropped folders are left out. */
|
|
15
|
+
export declare function planPath(input: PathPlanInput): string[];
|
|
16
|
+
/** `base` with the folders of `extra` it lacks added at the end (a process keeps its own order and entries). */
|
|
17
|
+
export declare function appendMissing(base: string, extra: string, skip?: (folder: string) => boolean): string;
|
|
18
|
+
/**
|
|
19
|
+
* What a new shell gets: folders only this process has first (a parent put them there on purpose), then the machine
|
|
20
|
+
* and the user PATH with their %VARIABLES% expanded; no duplicates. `skip` leaves folders out (the runner's own Node).
|
|
21
|
+
*/
|
|
22
|
+
export declare function rebuildPath(o: {
|
|
23
|
+
readonly machine: string;
|
|
24
|
+
readonly user: string;
|
|
25
|
+
readonly process: string;
|
|
26
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
27
|
+
readonly skip?: (folder: string) => boolean;
|
|
28
|
+
}): string;
|
|
29
|
+
/** The value of `Path` in the output of `reg query <key> /v Path` ('' when there is none). */
|
|
30
|
+
export declare function parseRegPath(output: string): string;
|
|
31
|
+
/** A folder of one pinned Node version (nvm's `v24.22.0`): never machine-wide, `nvm use` would not move it. */
|
|
32
|
+
export declare function isPinnedNodeDir(folder: string, nvmHome: string): boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The one place that decides which folders are on the machine PATH, in what order, and how a process's PATH is brought
|
|
2
|
+
// up to date with the registry. Pure (no side effects): the self-test imports this file. The shells' own start-up code
|
|
3
|
+
// (sandbox\csharp\PathSync.cs) follows the same rules.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
/** A folder as a comparison key: one slash style, no trailing slash (a drive root keeps its one), lower case. */
|
|
6
|
+
export function pathKey(folder) {
|
|
7
|
+
const slashes = folder.trim().replace(/^"|"$/g, '').replace(/\//g, '\\');
|
|
8
|
+
return (slashes.length > 3 ? slashes.replace(/\\+$/, '') : slashes).toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
/** The folders of a PATH string: no empty parts, no quotes around a folder. */
|
|
11
|
+
export function splitPath(value) {
|
|
12
|
+
return value
|
|
13
|
+
.split(';')
|
|
14
|
+
.map((part) => part.trim().replace(/^"|"$/g, ''))
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
/** Replaces %NAME% with the variable's value (any case); a name that is not set stays as written. */
|
|
18
|
+
export function expandVars(value, env) {
|
|
19
|
+
const lower = new Map(Object.entries(env).map(([k, v]) => [k.toLowerCase(), v]));
|
|
20
|
+
return value.replace(/%([^%;\\]+)%/g, (whole, name) => lower.get(name.toLowerCase()) ?? whole);
|
|
21
|
+
}
|
|
22
|
+
/** Ordered, de-duplicated (case, trailing slash) PATH folders; missing and dropped folders are left out. */
|
|
23
|
+
export function planPath(input) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const result = [];
|
|
26
|
+
for (const entry of input.entries.flatMap(splitPath)) {
|
|
27
|
+
const key = pathKey(entry);
|
|
28
|
+
if (seen.has(key) || input.drop?.(entry) || !input.exists(entry))
|
|
29
|
+
continue;
|
|
30
|
+
seen.add(key);
|
|
31
|
+
result.push(entry);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/** `base` with the folders of `extra` it lacks added at the end (a process keeps its own order and entries). */
|
|
36
|
+
export function appendMissing(base, extra, skip = () => false) {
|
|
37
|
+
const seen = new Set(splitPath(base).map(pathKey));
|
|
38
|
+
const added = splitPath(extra).filter((folder) => {
|
|
39
|
+
const key = pathKey(folder);
|
|
40
|
+
if (seen.has(key) || skip(folder))
|
|
41
|
+
return false;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
return [...splitPath(base), ...added].join(';');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* What a new shell gets: folders only this process has first (a parent put them there on purpose), then the machine
|
|
49
|
+
* and the user PATH with their %VARIABLES% expanded; no duplicates. `skip` leaves folders out (the runner's own Node).
|
|
50
|
+
*/
|
|
51
|
+
export function rebuildPath(o) {
|
|
52
|
+
const registry = splitPath(expandVars(o.machine, o.env) + ';' + expandVars(o.user, o.env));
|
|
53
|
+
const known = new Set(registry.map(pathKey));
|
|
54
|
+
const only = splitPath(o.process).filter((f) => !known.has(pathKey(f)) && !o.skip?.(f));
|
|
55
|
+
return planPath({ entries: [...only, ...registry], exists: () => true }).join(';');
|
|
56
|
+
}
|
|
57
|
+
/** The value of `Path` in the output of `reg query <key> /v Path` ('' when there is none). */
|
|
58
|
+
export function parseRegPath(output) {
|
|
59
|
+
const line = output.split(/\r?\n/).find((l) => /^\s+Path\s+REG_(EXPAND_)?SZ\s/i.test(l));
|
|
60
|
+
return line ? line.replace(/^\s+Path\s+REG_(EXPAND_)?SZ\s+/i, '').trim() : '';
|
|
61
|
+
}
|
|
62
|
+
/** A folder of one pinned Node version (nvm's `v24.22.0`): never machine-wide, `nvm use` would not move it. */
|
|
63
|
+
export function isPinnedNodeDir(folder, nvmHome) {
|
|
64
|
+
const key = pathKey(folder);
|
|
65
|
+
const home = pathKey(nvmHome);
|
|
66
|
+
return key.startsWith(home + '\\') && /^v\d/.test(path.win32.basename(key));
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installShellPath(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// New shells (PowerShell, PowerShell 7, cmd, Git Bash) start with the current machine PATH, whatever their parent had:
|
|
2
|
+
// pathsync.exe is built and the shells' start-up files call it (sandbox\scripts\install-shell-path.ps1).
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { dryRun, isWin, localDir, testMode, toolsDir } from '../runtime/paths.js';
|
|
6
|
+
import { mkdirp } from '../runtime/fs.js';
|
|
7
|
+
import { ensureCompiled } from '../runtime/csc.js';
|
|
8
|
+
import { runScript } from '../runtime/process.js';
|
|
9
|
+
export function installShellPath() {
|
|
10
|
+
if (!isWin || dryRun || testMode)
|
|
11
|
+
return;
|
|
12
|
+
const helper = path.join(mkdirp(path.join(localDir, 'bin')), 'pathsync.exe');
|
|
13
|
+
fs.copyFileSync(ensureCompiled('pathsync'), helper);
|
|
14
|
+
runScript('install-shell-path.ps1', [
|
|
15
|
+
'-Helper',
|
|
16
|
+
helper,
|
|
17
|
+
'-PwshDir',
|
|
18
|
+
path.join(toolsDir, 'pwsh'),
|
|
19
|
+
'-GitRoot',
|
|
20
|
+
path.join(toolsDir, 'git'),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
@@ -77,7 +77,7 @@ export async function setupPython(env, config, versions) {
|
|
|
77
77
|
const py3 = await installPython('python', versions.python.spec);
|
|
78
78
|
const shims = shimDir(py3);
|
|
79
79
|
shimPair(shims, '3', py3);
|
|
80
|
-
env.prepend.push(py3, path.join(py3, 'Scripts'), shims);
|
|
80
|
+
env.prepend.push(py3, mkdirp(path.join(py3, 'Scripts')), shims);
|
|
81
81
|
env.vars.PYTHONIOENCODING = 'utf-8';
|
|
82
82
|
env.summary.push('python: ' + path.basename(py3).replace('python-', '') + ' (from ' + versions.python.source + '; py works too)');
|
|
83
83
|
if (!config.python2) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Bun, uv and Deno: single-binary tools from GitHub releases (claude-mem needs Bun and uv).
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { dryRun, localDir } from '../runtime/paths.js';
|
|
4
|
-
import { exists } from '../runtime/fs.js';
|
|
4
|
+
import { exists, mkdirp } from '../runtime/fs.js';
|
|
5
5
|
import { log } from '../runtime/log.js';
|
|
6
6
|
import { arch, tool, versionOf } from './tool-env.js';
|
|
7
7
|
import { installFromGithub } from './install.js';
|
|
@@ -52,5 +52,5 @@ export function uvVars() {
|
|
|
52
52
|
export async function setupUv(env) {
|
|
53
53
|
await setupSingleExe(env, 'uv', 'astral-sh/uv', arch === 'arm64' ? /^uv-aarch64-pc-windows-msvc\.zip$/ : /^uv-x86_64-pc-windows-msvc\.zip$/, 'uv.exe', tool('uv'), { size: '20 MB' });
|
|
54
54
|
Object.assign(env.vars, uvVars());
|
|
55
|
-
env.prepend.push(tool('uv-bin'));
|
|
55
|
+
env.prepend.push(mkdirp(tool('uv-bin')));
|
|
56
56
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { setupVpn, setVpnStatus } from './vpn.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { setupVpn, setVpnStatus } from './vpn.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Where the MSI puts the program. */
|
|
2
|
+
export declare function openvpnExe(env?: NodeJS.ProcessEnv): string;
|
|
3
|
+
export declare function openvpnDownload(arch?: string): {
|
|
4
|
+
file: string;
|
|
5
|
+
sha256: string;
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
/** Installs OpenVPN unless it is there; returns the path of openvpn.exe. Throws with the reason when it cannot. */
|
|
9
|
+
export declare function ensureOpenVpn(): Promise<string>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// OpenVPN Community for the sandbox: one checksummed MSI from the publisher, installed silently (it brings the driver).
|
|
2
|
+
// The sandbox is new at every boot, so the install runs each time; the MSI itself is cached in the persistent tools
|
|
3
|
+
// folder. downloads.requireChecksum is honoured by the downloader (this entry has a checksum, so it always passes).
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { downloader, spawnAsync } from '../../shared/download/index.js';
|
|
8
|
+
import { SYSTEM } from '../../shared/system.js';
|
|
9
|
+
import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
|
|
10
|
+
import { exists, mkdirp } from '../runtime/fs.js';
|
|
11
|
+
import { log } from '../runtime/log.js';
|
|
12
|
+
import { OPENVPN_MSI } from '../../shared/vpn/download.js';
|
|
13
|
+
/** Where the MSI puts the program. */
|
|
14
|
+
export function openvpnExe(env = process.env) {
|
|
15
|
+
return path.join(env.ProgramFiles ?? 'C:\\Program Files', 'OpenVPN', 'bin', 'openvpn.exe');
|
|
16
|
+
}
|
|
17
|
+
export function openvpnDownload(arch = process.arch) {
|
|
18
|
+
const entry = arch === 'arm64' ? OPENVPN_MSI.arm64 : OPENVPN_MSI.x64;
|
|
19
|
+
return { ...entry, url: OPENVPN_MSI.base + entry.file };
|
|
20
|
+
}
|
|
21
|
+
const hasHash = (file, sha256) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') === sha256;
|
|
22
|
+
/** Installs OpenVPN unless it is there; returns the path of openvpn.exe. Throws with the reason when it cannot. */
|
|
23
|
+
export async function ensureOpenVpn() {
|
|
24
|
+
const exe = openvpnExe();
|
|
25
|
+
if (!isWin || dryRun)
|
|
26
|
+
return exe;
|
|
27
|
+
if (exists(exe))
|
|
28
|
+
return exe;
|
|
29
|
+
const dl = openvpnDownload();
|
|
30
|
+
const msi = path.join(mkdirp(path.join(toolsDir, 'downloads')), dl.file);
|
|
31
|
+
// The tools folder lasts between sandboxes and the agent can write it: a cached file is trusted only by its hash.
|
|
32
|
+
if (!exists(msi) || !hasHash(msi, dl.sha256)) {
|
|
33
|
+
log('VPN: downloading OpenVPN ' + OPENVPN_MSI.version + ' (about 6 MB)');
|
|
34
|
+
await downloader.file(dl.url, msi, dl.sha256);
|
|
35
|
+
}
|
|
36
|
+
log('VPN: installing OpenVPN');
|
|
37
|
+
const logFile = path.join(toolsDir, 'downloads', 'openvpn-install.log');
|
|
38
|
+
const code = await spawnAsync(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart', '/l*v', logFile], {
|
|
39
|
+
stdio: 'ignore',
|
|
40
|
+
});
|
|
41
|
+
// 3010: installed, a restart is wanted (nothing here needs one).
|
|
42
|
+
if (code !== 0 && code !== 3010)
|
|
43
|
+
throw new Error('The OpenVPN installer failed (exit ' + code + '). Its log: ' + logFile);
|
|
44
|
+
if (!exists(exe))
|
|
45
|
+
throw new Error('OpenVPN was installed but ' + exe + ' is missing. The installer log: ' + logFile);
|
|
46
|
+
return exe;
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TunnelOutcome {
|
|
2
|
+
readonly ok: boolean;
|
|
3
|
+
readonly message: string;
|
|
4
|
+
/** The tunnel's own address (what the kill switch lets through). */
|
|
5
|
+
readonly tunnelIp?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TunnelOptions {
|
|
8
|
+
readonly exe: string;
|
|
9
|
+
readonly profile: string;
|
|
10
|
+
readonly username: string;
|
|
11
|
+
readonly password: string;
|
|
12
|
+
readonly timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Why the log says OpenVPN stopped, in a short plain sentence (or null when it does not say). */
|
|
15
|
+
export declare function failureReason(text: string): string | null;
|
|
16
|
+
/** Connects. Never leaves the login on disk; kills OpenVPN when the tunnel does not come up in time. */
|
|
17
|
+
export declare function startTunnel(o: TunnelOptions): Promise<TunnelOutcome>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Starts OpenVPN with the login in a file that exists only while it connects, waits for the tunnel, and reports. The
|
|
2
|
+
// file is deleted as soon as the tunnel is up (or has failed). OpenVPN reads it again when it must log in again, so a
|
|
3
|
+
// tunnel that drops does not come back on its own (the kill switch then keeps the sandbox offline): the sandbox's
|
|
4
|
+
// login is not left on disk for that.
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { tunnelAddressFromLog } from '../../shared/vpn/kill-switch.js';
|
|
10
|
+
import { logsDir } from '../runtime/paths.js';
|
|
11
|
+
import { log } from '../runtime/log.js';
|
|
12
|
+
const DONE = 'Initialization Sequence Completed';
|
|
13
|
+
/** Why the log says OpenVPN stopped, in a short plain sentence (or null when it does not say). */
|
|
14
|
+
export function failureReason(text) {
|
|
15
|
+
if (/AUTH_FAILED/i.test(text))
|
|
16
|
+
return 'the VPN refused the login (for NordVPN use the service credentials, not your account password)';
|
|
17
|
+
if (/TLS Error: TLS key negotiation failed|TLS handshake failed/i.test(text))
|
|
18
|
+
return 'it could not reach the VPN server (the network blocks it, or the server is down)';
|
|
19
|
+
if (/All wintun adapters on this system are in use|Cannot open TUN\/TAP|Could not create Wintun|ovpn-dco.*(error|failed)/i.test(text))
|
|
20
|
+
return 'the network driver for the tunnel could not be used';
|
|
21
|
+
if (/Exiting due to fatal error/i.test(text))
|
|
22
|
+
return 'OpenVPN stopped with an error';
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const tail = (text, lines = 12) => text.trim().split(/\r?\n/).slice(-lines).join('\n');
|
|
26
|
+
/** Connects. Never leaves the login on disk; kills OpenVPN when the tunnel does not come up in time. */
|
|
27
|
+
export async function startTunnel(o) {
|
|
28
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sbx-vpn-'));
|
|
29
|
+
const auth = path.join(dir, 'login.txt');
|
|
30
|
+
const logFile = path.join(dir, 'openvpn.log');
|
|
31
|
+
const keepLog = () => {
|
|
32
|
+
try {
|
|
33
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
34
|
+
fs.copyFileSync(logFile, path.join(logsDir, 'openvpn.log'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// The log is only for finding out what went wrong.
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
fs.writeFileSync(auth, o.username + '\r\n' + o.password + '\r\n', 'utf8');
|
|
41
|
+
try {
|
|
42
|
+
const child = spawn(o.exe, ['--config', o.profile, '--auth-user-pass', auth, '--auth-nocache', '--verb', '3', '--log', logFile], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
43
|
+
let exited = null;
|
|
44
|
+
child.once('exit', (code) => {
|
|
45
|
+
exited = code ?? -1;
|
|
46
|
+
});
|
|
47
|
+
child.once('error', (e) => {
|
|
48
|
+
exited = -1;
|
|
49
|
+
log('VPN: could not start OpenVPN: ' + e.message);
|
|
50
|
+
});
|
|
51
|
+
child.unref();
|
|
52
|
+
const deadline = Date.now() + (o.timeoutMs ?? 90000);
|
|
53
|
+
for (;;) {
|
|
54
|
+
const text = fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf8') : '';
|
|
55
|
+
if (text.includes(DONE)) {
|
|
56
|
+
keepLog();
|
|
57
|
+
const tunnelIp = tunnelAddressFromLog(text) ?? undefined;
|
|
58
|
+
return { ok: true, message: 'the tunnel is up', ...(tunnelIp ? { tunnelIp } : {}) };
|
|
59
|
+
}
|
|
60
|
+
const reason = failureReason(text);
|
|
61
|
+
if (exited !== null || reason) {
|
|
62
|
+
if (exited === null)
|
|
63
|
+
child.kill();
|
|
64
|
+
keepLog();
|
|
65
|
+
return { ok: false, message: (reason ?? 'OpenVPN stopped') + '. ' + tail(text, 4) };
|
|
66
|
+
}
|
|
67
|
+
if (Date.now() > deadline) {
|
|
68
|
+
child.kill();
|
|
69
|
+
keepLog();
|
|
70
|
+
return { ok: false, message: 'the tunnel did not come up in time. ' + tail(text, 4) };
|
|
71
|
+
}
|
|
72
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
fs.rmSync(auth, { force: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { VpnStatus } from '../../shared/channel/schema.js';
|
|
2
|
+
import type { SecretsFile } from '../../shared/channel/secrets.js';
|
|
3
|
+
/** Writes run\vpn.json (what `sandbox vpn status` and the launch show). Never throws. */
|
|
4
|
+
export declare function setVpnStatus(state: VpnStatus['state'], message: string, extra?: Partial<VpnStatus>): void;
|
|
5
|
+
/** The whole step. Returns true when the tunnel is up. */
|
|
6
|
+
export declare function setupVpn(secrets: SecretsFile, ptyHostExe: string | null): Promise<boolean>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// The VPN boot step (after the toolchains, before the agent): install OpenVPN, block everything that does not go
|
|
2
|
+
// through the tunnel (the kill switch, applied BEFORE the tunnel starts), connect, check, delete the login, report. A
|
|
3
|
+
// tunnel that fails never stops the boot: with the kill switch the sandbox stays offline and says so; without it, it
|
|
4
|
+
// warns.
|
|
5
|
+
import dns from 'node:dns';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { now } from '../../shared/channel/run-dir.js';
|
|
10
|
+
import { writeJsonAtomic } from '../../shared/io/json.js';
|
|
11
|
+
import { assertSafeOvpn } from '../../shared/vpn/ovpn.js';
|
|
12
|
+
import { isIPv4 } from '../../shared/network/firewall-rules.js';
|
|
13
|
+
import { killSwitchCommands, tunnelCommands } from '../../shared/vpn/kill-switch.js';
|
|
14
|
+
import { errMsg } from '../../core/util/errors.js';
|
|
15
|
+
import { run as netsh } from '../network/firewall.js';
|
|
16
|
+
import { dryRun, isWin, launchDir, run } from '../runtime/paths.js';
|
|
17
|
+
import { log, warn } from '../runtime/log.js';
|
|
18
|
+
import { session } from '../runtime/session.js';
|
|
19
|
+
import { ensureOpenVpn } from './openvpn.js';
|
|
20
|
+
import { startTunnel } from './tunnel.js';
|
|
21
|
+
/** Writes run\vpn.json (what `sandbox vpn status` and the launch show). Never throws. */
|
|
22
|
+
export function setVpnStatus(state, message, extra = {}) {
|
|
23
|
+
try {
|
|
24
|
+
writeJsonAtomic(run.vpnFile, {
|
|
25
|
+
state,
|
|
26
|
+
message: message.slice(0, 1900),
|
|
27
|
+
killSwitch: session.vpn?.killSwitch ?? false,
|
|
28
|
+
updatedAt: now(),
|
|
29
|
+
...(session.vpn ? { server: session.vpn.server.slice(0, 120) } : {}),
|
|
30
|
+
...extra,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
warn('Could not write the VPN status: ' + errMsg(e));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** IPv4 addresses of the servers the profile names: names are looked up now, before the kill switch closes DNS. */
|
|
38
|
+
async function serverAddresses(remotes) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const r of remotes) {
|
|
41
|
+
const ips = isIPv4(r.host)
|
|
42
|
+
? [r.host]
|
|
43
|
+
: await dns.promises.lookup(r.host, { all: true, family: 4 }).then((all) => all.map((a) => a.address), () => []);
|
|
44
|
+
for (const ip of ips)
|
|
45
|
+
out.push({ ip, port: r.port, proto: r.proto });
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/** What the sandbox looks like from outside, through the tunnel (null when it cannot be told quickly). */
|
|
50
|
+
async function publicAddress() {
|
|
51
|
+
try {
|
|
52
|
+
const text = (await (await fetch('https://api.ipify.org', { signal: AbortSignal.timeout(10000) })).text()).trim();
|
|
53
|
+
return /^[0-9a-fA-F.:]{3,45}$/.test(text) ? text : null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const hasAdapter = (ip) => Object.values(os.networkInterfaces()).some((list) => (list ?? []).some((i) => i.address === ip));
|
|
60
|
+
/** The whole step. Returns true when the tunnel is up. */
|
|
61
|
+
export async function setupVpn(secrets, ptyHostExe) {
|
|
62
|
+
const vpn = session.vpn;
|
|
63
|
+
if (!vpn)
|
|
64
|
+
return true;
|
|
65
|
+
const killSwitch = vpn.killSwitch;
|
|
66
|
+
let blocked = false;
|
|
67
|
+
const block = (servers) => {
|
|
68
|
+
if (isWin && !dryRun)
|
|
69
|
+
netsh(killSwitchCommands(servers, ptyHostExe));
|
|
70
|
+
blocked = true;
|
|
71
|
+
log('VPN: kill switch on: nothing leaves the sandbox except through the tunnel.');
|
|
72
|
+
};
|
|
73
|
+
setVpnStatus('starting', 'Starting the VPN');
|
|
74
|
+
try {
|
|
75
|
+
if (!session.networking)
|
|
76
|
+
throw new Error('networking is off for this sandbox');
|
|
77
|
+
const credentials = secrets.vpn;
|
|
78
|
+
if (!credentials)
|
|
79
|
+
throw new Error('the VPN login did not arrive (start the sandbox again)');
|
|
80
|
+
// The profile came from the host, but it is data like any other: checked again here before OpenVPN reads it.
|
|
81
|
+
const profile = path.join(launchDir, 'vpn', 'server.ovpn');
|
|
82
|
+
const remotes = assertSafeOvpn(fs.readFileSync(profile, 'utf8'), vpn.server);
|
|
83
|
+
const exe = await ensureOpenVpn();
|
|
84
|
+
const servers = await serverAddresses(remotes);
|
|
85
|
+
if (servers.length === 0)
|
|
86
|
+
throw new Error('the VPN server addresses could not be found');
|
|
87
|
+
if (killSwitch)
|
|
88
|
+
block(servers);
|
|
89
|
+
if (dryRun || !isWin) {
|
|
90
|
+
log('(dry run) would start the VPN tunnel to ' + vpn.server);
|
|
91
|
+
setVpnStatus('off', 'dry run');
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
log('VPN: connecting to ' + vpn.server);
|
|
95
|
+
const result = await startTunnel({ exe, profile, username: credentials.username, password: credentials.password });
|
|
96
|
+
if (!result.ok)
|
|
97
|
+
throw new Error(result.message);
|
|
98
|
+
if (!result.tunnelIp || !hasAdapter(result.tunnelIp))
|
|
99
|
+
throw new Error('the tunnel reported it was up, but no network adapter with its address appeared');
|
|
100
|
+
if (killSwitch)
|
|
101
|
+
netsh(tunnelCommands(result.tunnelIp));
|
|
102
|
+
const publicIp = await publicAddress();
|
|
103
|
+
log('VPN: up' + (publicIp ? ' (the sandbox appears as ' + publicIp + ')' : '') + '.');
|
|
104
|
+
setVpnStatus('up', 'The tunnel is up', publicIp ? { publicIp } : {});
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
const reason = errMsg(e);
|
|
109
|
+
// Without the kill switch the sandbox keeps its normal connection: the rest of the boot goes on, with a warning.
|
|
110
|
+
try {
|
|
111
|
+
if (killSwitch && !blocked)
|
|
112
|
+
block([]);
|
|
113
|
+
}
|
|
114
|
+
catch (blockError) {
|
|
115
|
+
warn('VPN: could not close the network after the failure: ' + errMsg(blockError));
|
|
116
|
+
}
|
|
117
|
+
const text = 'VPN failed: ' +
|
|
118
|
+
reason +
|
|
119
|
+
(killSwitch
|
|
120
|
+
? ' The kill switch is on, so this sandbox has NO internet access.'
|
|
121
|
+
: ' The kill switch is off, so this sandbox is using your normal connection.');
|
|
122
|
+
warn(text);
|
|
123
|
+
setVpnStatus('failed', reason);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import type { SessionStatus } from '../../core/types.js';
|
|
3
|
-
import { type Viewers } from '../../shared/pty/index.js';
|
|
3
|
+
import { type Endpoint, type Viewers } from '../../shared/pty/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* True only for IPv4 addresses inside the subnet of one of this PC's Hyper-V adapters ("vEthernet ..."), which is
|
|
6
6
|
* where Windows Sandbox lives. Stops a compromised sandbox from pointing the host (and your keystrokes) anywhere else.
|
|
7
7
|
*/
|
|
8
8
|
export declare function isSandboxAddress(ip: string, interfaces?: NodeJS.Dict<os.NetworkInterfaceInfo[]>): boolean;
|
|
9
|
+
/** Where this PC may connect for a session: only addresses on its own Hyper-V network (null if there are none). */
|
|
10
|
+
export declare const hostEndpoint: (session: SessionStatus) => Endpoint | null;
|
|
9
11
|
/** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
|
|
10
12
|
export declare function sessionViewers(session: SessionStatus): Promise<Viewers | null>;
|
|
11
13
|
/**
|
|
@@ -25,7 +25,7 @@ export function isSandboxAddress(ip, interfaces = os.networkInterfaces()) {
|
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
27
|
/** Where this PC may connect for a session: only addresses on its own Hyper-V network (null if there are none). */
|
|
28
|
-
const hostEndpoint = (session) => {
|
|
28
|
+
export const hostEndpoint = (session) => {
|
|
29
29
|
// Inside the sandbox every session is on loopback; on the PC only addresses on its Hyper-V network are trusted.
|
|
30
30
|
const addresses = insideSandbox()
|
|
31
31
|
? ['127.0.0.1']
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { GuestRequest, GuestResponse, Harness, SandboxStatus, SessionStatus } from '../../core/types.js';
|
|
2
2
|
import type { ProjectSpec } from '../../shared/channel/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The boot script writes its first status once Windows has booted the sandbox and logged on (a minute or two, longer on
|
|
5
|
+
* a slow PC or the very first start); none after this long means it never got going.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FIRST_STATUS_MS: number;
|
|
8
|
+
/** True when a boot is silent: no status of this launch yet, and the sandbox had its time to write one. */
|
|
9
|
+
export declare function bootSilent(elapsedMs: number, seenStatus: boolean, limitMs?: number): boolean;
|
|
3
10
|
/** Most sessions the host will ever contact for one listing: a sandbox can write as many session files as it likes. */
|
|
4
11
|
export declare const MAX_QUERIED_SESSIONS = 32;
|
|
5
12
|
/** Where progress lines go: printed by default; a spinner passes its own. Text from the sandbox, made safe first. */
|