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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Several Claude logins for the sandbox. Each account is a saved copy of Claude's .credentials.json plus the
|
|
2
|
-
// `oauthAccount` block of .claude.json, kept in host-only storage (<storage>\accounts, never mapped into a sandbox)
|
|
2
|
+
// `oauthAccount` block of .claude.json, kept in host-only storage (<storage>\accounts, never mapped into a sandbox)
|
|
3
|
+
// and encrypted with Windows DPAPI (host/secrets): only the name, e-mail and date are readable in the file. Accounts
|
|
4
|
+
// saved before that were plain; they are encrypted the first time they are read.
|
|
3
5
|
// Switching writes them into the sandbox's persistent Claude state; when the sandbox is running the agent is then
|
|
4
6
|
// restarted in place with --continue so the conversation carries on.
|
|
5
7
|
import fs from 'node:fs';
|
|
@@ -8,8 +10,10 @@ import { z } from 'zod';
|
|
|
8
10
|
import { isSafeFileName } from '../../shared/paths/index.js';
|
|
9
11
|
import { JsonFile } from '../../shared/io/index.js';
|
|
10
12
|
import { storageOf } from '../storage/index.js';
|
|
11
|
-
import { fail, fileExists } from '../../core/util/index.js';
|
|
12
|
-
import {
|
|
13
|
+
import { errMsg, fail, fileExists } from '../../core/util/index.js';
|
|
14
|
+
import { SecretStore } from '../secrets/index.js';
|
|
15
|
+
import { PluginShare } from './plugin-share.js';
|
|
16
|
+
import { clearAccountCaches, emailOf, MAX_STATE_BYTES, readLogin, sandboxClaudeDir, readUntrusted, sandboxLogin, writeSandboxJson, } from './claude-login.js';
|
|
13
17
|
const NAME = /^[A-Za-z0-9_-]{1,40}$/;
|
|
14
18
|
const AccountSchema = z.object({
|
|
15
19
|
name: z.string().regex(NAME),
|
|
@@ -22,6 +26,15 @@ const IndexSchema = z.object({
|
|
|
22
26
|
default: z.string().regex(NAME).optional(),
|
|
23
27
|
active: z.record(z.string().max(200), z.string().regex(NAME)).optional(),
|
|
24
28
|
});
|
|
29
|
+
/** On disk: the readable details and the sealed login ({ credentials, oauthAccount }). */
|
|
30
|
+
const SealedSchema = z.object({
|
|
31
|
+
name: z.string().regex(NAME),
|
|
32
|
+
savedAt: z.string().max(64),
|
|
33
|
+
email: z.string().max(200).nullable(),
|
|
34
|
+
sealed: z.string().max(200000),
|
|
35
|
+
});
|
|
36
|
+
const StoredSchema = z.union([SealedSchema, AccountSchema]);
|
|
37
|
+
const isSealed = (s) => 'sealed' in s;
|
|
25
38
|
export function accountsDir(storageRoot) {
|
|
26
39
|
return path.join(storageRoot, 'accounts');
|
|
27
40
|
}
|
|
@@ -51,19 +64,87 @@ function assertName(name) {
|
|
|
51
64
|
/** The saved Claude logins under <storage>\accounts, and which one each sandbox data folder uses. */
|
|
52
65
|
export class AccountStore {
|
|
53
66
|
storageRoot;
|
|
54
|
-
|
|
67
|
+
sharePlugins;
|
|
68
|
+
secrets;
|
|
69
|
+
/** What the last activate() did with the shared plugins (a problem there never stops the switch). */
|
|
70
|
+
pluginNote = null;
|
|
71
|
+
/** @param sharePlugins claudeProfile.sharePluginsAcrossAccounts: carry Claude plugins to the account switched to */
|
|
72
|
+
constructor(storageRoot, sharePlugins = false, secrets = SecretStore.standard()) {
|
|
55
73
|
this.storageRoot = storageRoot;
|
|
74
|
+
this.sharePlugins = sharePlugins;
|
|
75
|
+
this.secrets = secrets;
|
|
76
|
+
}
|
|
77
|
+
/** With the setting on: refreshes the shared plugin set from this data set, or fills this data set from it. */
|
|
78
|
+
syncPlugins(dataPath) {
|
|
79
|
+
this.shareStep(dataPath, sandboxClaudeDir(dataPath));
|
|
80
|
+
}
|
|
81
|
+
shareStep(dataPath, claudeDir) {
|
|
82
|
+
this.pluginNote = null;
|
|
83
|
+
if (!this.sharePlugins)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const result = PluginShare.in(this.storageRoot).sync(dataPath, claudeDir);
|
|
87
|
+
if (result === 'restored')
|
|
88
|
+
this.pluginNote = 'Claude plugins were copied in from your other accounts.';
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
this.pluginNote = 'Claude plugins were not copied: ' + errMsg(e);
|
|
92
|
+
}
|
|
56
93
|
}
|
|
57
94
|
file(name) {
|
|
58
|
-
return new JsonFile(
|
|
95
|
+
return new JsonFile(StoredSchema, path.join(accountsDir(this.storageRoot), name + '.json'));
|
|
96
|
+
}
|
|
97
|
+
seal(account) {
|
|
98
|
+
const { name, savedAt, email, credentials, oauthAccount } = account;
|
|
99
|
+
return { name, savedAt, email, sealed: this.secrets.sealJson({ credentials, oauthAccount }) };
|
|
59
100
|
}
|
|
60
101
|
store(name, login) {
|
|
61
102
|
const account = { name, savedAt: new Date().toISOString(), email: emailOf(login.oauthAccount), ...login };
|
|
62
|
-
this.file(name).write(account);
|
|
103
|
+
this.file(name).write(this.seal(account));
|
|
63
104
|
return account;
|
|
64
105
|
}
|
|
106
|
+
/** The saved account's name, date and e-mail (nothing is decrypted). */
|
|
107
|
+
meta(name) {
|
|
108
|
+
const stored = validName(name) ? this.file(name).read() : null;
|
|
109
|
+
return stored ? { name: stored.name, savedAt: stored.savedAt, email: stored.email } : null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The saved account with its login decrypted. A plain file from an older version is encrypted on the spot (it stays
|
|
113
|
+
* readable when that fails).
|
|
114
|
+
*/
|
|
65
115
|
read(name) {
|
|
66
|
-
|
|
116
|
+
const stored = validName(name) ? this.file(name).read() : null;
|
|
117
|
+
if (!stored)
|
|
118
|
+
return null;
|
|
119
|
+
if (isSealed(stored)) {
|
|
120
|
+
const login = this.secrets.openJson(stored.sealed);
|
|
121
|
+
return { name: stored.name, savedAt: stored.savedAt, email: stored.email, ...login };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
this.file(name).write(this.seal(stored));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Could not encrypt now (DPAPI unavailable): the old plain file stays and is tried again next time.
|
|
128
|
+
}
|
|
129
|
+
return stored;
|
|
130
|
+
}
|
|
131
|
+
/** Encrypts every account still saved as plain text (older versions); returns how many it did. */
|
|
132
|
+
sealLegacy() {
|
|
133
|
+
const dir = accountsDir(this.storageRoot);
|
|
134
|
+
if (!fs.existsSync(dir))
|
|
135
|
+
return 0;
|
|
136
|
+
let done = 0;
|
|
137
|
+
for (const f of fs.readdirSync(dir).filter((x) => x.endsWith('.json') && x !== 'index.json')) {
|
|
138
|
+
const name = f.slice(0, -5);
|
|
139
|
+
const stored = validName(name) ? this.file(name).read() : null;
|
|
140
|
+
if (!stored || isSealed(stored))
|
|
141
|
+
continue;
|
|
142
|
+
this.read(name);
|
|
143
|
+
const after = this.file(name).read();
|
|
144
|
+
if (after && isSealed(after))
|
|
145
|
+
done++;
|
|
146
|
+
}
|
|
147
|
+
return done;
|
|
67
148
|
}
|
|
68
149
|
defaultName() {
|
|
69
150
|
return readIndex(this.storageRoot).default ?? null;
|
|
@@ -76,12 +157,13 @@ export class AccountStore {
|
|
|
76
157
|
const dir = accountsDir(this.storageRoot);
|
|
77
158
|
if (!fs.existsSync(dir))
|
|
78
159
|
return [];
|
|
160
|
+
this.sealLegacy();
|
|
79
161
|
const def = this.defaultName();
|
|
80
162
|
const active = this.activeFor(dataPath);
|
|
81
163
|
return fs
|
|
82
164
|
.readdirSync(dir)
|
|
83
165
|
.filter((f) => f.endsWith('.json') && f !== 'index.json')
|
|
84
|
-
.map((f) => this.
|
|
166
|
+
.map((f) => this.meta(f.slice(0, -5)))
|
|
85
167
|
.filter((a) => a !== null)
|
|
86
168
|
.map((a) => ({
|
|
87
169
|
name: a.name,
|
|
@@ -114,7 +196,7 @@ export class AccountStore {
|
|
|
114
196
|
.join(', ') || 'none yet; save one with sandbox account save <name>'));
|
|
115
197
|
const { claudeDir, globalStateFile } = sandboxLogin(dataPath);
|
|
116
198
|
const outgoing = this.activeFor(dataPath);
|
|
117
|
-
if (outgoing && outgoing !== name && this.
|
|
199
|
+
if (outgoing && outgoing !== name && this.meta(outgoing) && fileExists(path.join(claudeDir, '.credentials.json'))) {
|
|
118
200
|
try {
|
|
119
201
|
this.store(outgoing, readLogin(claudeDir, globalStateFile));
|
|
120
202
|
}
|
|
@@ -128,20 +210,21 @@ export class AccountStore {
|
|
|
128
210
|
const state = readUntrusted(globalStateFile, MAX_STATE_BYTES);
|
|
129
211
|
if (account.oauthAccount && (state || !fileExists(globalStateFile)))
|
|
130
212
|
writeSandboxJson(globalStateFile, { ...(state ?? {}), oauthAccount: account.oauthAccount });
|
|
213
|
+
this.shareStep(dataPath, claudeDir);
|
|
131
214
|
setActive(dataPath, name);
|
|
132
215
|
return account;
|
|
133
216
|
}
|
|
134
217
|
setDefault(name) {
|
|
135
|
-
if (name !== null && !this.
|
|
218
|
+
if (name !== null && !this.meta(name))
|
|
136
219
|
fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
|
|
137
220
|
updateIndex(this.storageRoot, (i) => ({ ...i, default: name ?? undefined }));
|
|
138
221
|
}
|
|
139
222
|
rename(name, to) {
|
|
140
223
|
assertName(to);
|
|
141
224
|
const account = this.read(name) ?? fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
|
|
142
|
-
if (this.
|
|
225
|
+
if (this.meta(to))
|
|
143
226
|
fail('There is an account "' + to + '" already.', 'Pick another name.');
|
|
144
|
-
this.file(to).write({ ...account, name: to });
|
|
227
|
+
this.file(to).write(this.seal({ ...account, name: to }));
|
|
145
228
|
fs.rmSync(this.file(name).file);
|
|
146
229
|
updateIndex(this.storageRoot, (i) => ({
|
|
147
230
|
...i,
|
|
@@ -150,7 +233,7 @@ export class AccountStore {
|
|
|
150
233
|
}));
|
|
151
234
|
}
|
|
152
235
|
remove(name) {
|
|
153
|
-
if (!this.
|
|
236
|
+
if (!this.meta(name))
|
|
154
237
|
fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
|
|
155
238
|
fs.rmSync(this.file(name).file);
|
|
156
239
|
if (this.defaultName() === name)
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { SecretStore } from '../secrets/index.js';
|
|
1
2
|
import { type AccountRow } from './accounts.js';
|
|
2
3
|
export declare class AgentAccountStore {
|
|
3
4
|
private readonly storageRoot;
|
|
4
5
|
private readonly agent;
|
|
5
6
|
private readonly loginFiles;
|
|
7
|
+
private readonly secrets;
|
|
6
8
|
/**
|
|
7
9
|
* @param loginFiles files of the login, relative to the folder they are read from and written to
|
|
8
10
|
*/
|
|
9
|
-
constructor(storageRoot: string, agent: string, loginFiles: readonly string[]);
|
|
11
|
+
constructor(storageRoot: string, agent: string, loginFiles: readonly string[], secrets?: SecretStore);
|
|
12
|
+
/** Encrypts the plain login files of every account saved by an older version. */
|
|
13
|
+
sealLegacy(): void;
|
|
10
14
|
private get dir();
|
|
11
15
|
private folder;
|
|
12
16
|
private index;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// Saved logins for agents other than Claude: <storage>\accounts\<agent>\<name>\ holds a copy of the agent's login files
|
|
2
|
-
// (the harness `home.loginFiles
|
|
3
|
-
// files into the sandbox's persistent state.
|
|
2
|
+
// (the harness `home.loginFiles`, each one encrypted with Windows DPAPI as <file>.sealed) and meta.json. Host-only
|
|
3
|
+
// storage, never mapped into a sandbox; switching decrypts the files into the sandbox's persistent state. Copies saved
|
|
4
|
+
// by older versions were plain: they are encrypted the next time the accounts are listed or one is switched to.
|
|
5
|
+
// Claude keeps its own older layout (accounts.ts).
|
|
4
6
|
import fs from 'node:fs';
|
|
5
7
|
import path from 'node:path';
|
|
6
8
|
import { z } from 'zod';
|
|
7
9
|
import { JsonFile } from '../../shared/io/index.js';
|
|
8
10
|
import { isSafeFileName } from '../../shared/paths/index.js';
|
|
11
|
+
import { SecretStore } from '../secrets/index.js';
|
|
9
12
|
import { assertNoLinks, ensureDir, fail, isLink } from '../../core/util/index.js';
|
|
10
13
|
import { accountsDir } from './accounts.js';
|
|
11
14
|
const NAME = /^[A-Za-z0-9_-]{1,40}$/;
|
|
@@ -27,13 +30,36 @@ export class AgentAccountStore {
|
|
|
27
30
|
storageRoot;
|
|
28
31
|
agent;
|
|
29
32
|
loginFiles;
|
|
33
|
+
secrets;
|
|
30
34
|
/**
|
|
31
35
|
* @param loginFiles files of the login, relative to the folder they are read from and written to
|
|
32
36
|
*/
|
|
33
|
-
constructor(storageRoot, agent, loginFiles) {
|
|
37
|
+
constructor(storageRoot, agent, loginFiles, secrets = SecretStore.standard()) {
|
|
34
38
|
this.storageRoot = storageRoot;
|
|
35
39
|
this.agent = agent;
|
|
36
40
|
this.loginFiles = loginFiles;
|
|
41
|
+
this.secrets = secrets;
|
|
42
|
+
}
|
|
43
|
+
/** Encrypts the plain login files of every account saved by an older version. */
|
|
44
|
+
sealLegacy() {
|
|
45
|
+
if (!fs.existsSync(this.dir))
|
|
46
|
+
return;
|
|
47
|
+
for (const d of fs.readdirSync(this.dir, { withFileTypes: true })) {
|
|
48
|
+
if (!d.isDirectory() || d.isSymbolicLink() || !validName(d.name))
|
|
49
|
+
continue;
|
|
50
|
+
for (const f of this.loginFiles) {
|
|
51
|
+
const plain = path.join(this.folder(d.name), ...f.split(/[\\/]/));
|
|
52
|
+
if (!fs.existsSync(plain) || isLink(plain))
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(plain + '.sealed', this.secrets.sealBytes(fs.readFileSync(plain)));
|
|
56
|
+
fs.rmSync(plain);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Could not encrypt now: the plain copy stays readable and is tried again next time.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
get dir() {
|
|
39
65
|
return path.join(accountsDir(this.storageRoot), this.agent);
|
|
@@ -66,6 +92,7 @@ export class AgentAccountStore {
|
|
|
66
92
|
list(dataPath) {
|
|
67
93
|
if (!fs.existsSync(this.dir))
|
|
68
94
|
return [];
|
|
95
|
+
this.sealLegacy();
|
|
69
96
|
const def = this.defaultName();
|
|
70
97
|
const active = this.activeFor(dataPath);
|
|
71
98
|
return fs
|
|
@@ -95,7 +122,7 @@ export class AgentAccountStore {
|
|
|
95
122
|
for (const f of found) {
|
|
96
123
|
const dest = path.join(target, ...f.split(/[\\/]/));
|
|
97
124
|
ensureDir(path.dirname(dest));
|
|
98
|
-
fs.
|
|
125
|
+
fs.writeFileSync(dest + '.sealed', this.secrets.sealBytes(fs.readFileSync(path.join(from, f))));
|
|
99
126
|
}
|
|
100
127
|
new JsonFile(MetaSchema, path.join(target, 'meta.json')).write({ name, savedAt: new Date().toISOString() });
|
|
101
128
|
}
|
|
@@ -115,6 +142,7 @@ export class AgentAccountStore {
|
|
|
115
142
|
(this.list(dataPath)
|
|
116
143
|
.map((a) => a.name)
|
|
117
144
|
.join(', ') || 'none yet'));
|
|
145
|
+
this.sealLegacy();
|
|
118
146
|
// Tokens rotate while the agent runs: the outgoing account's current files are saved back first.
|
|
119
147
|
const outgoing = this.activeFor(dataPath);
|
|
120
148
|
if (outgoing && outgoing !== name && this.meta(outgoing)) {
|
|
@@ -133,7 +161,9 @@ export class AgentAccountStore {
|
|
|
133
161
|
if (isLink(dest))
|
|
134
162
|
fs.rmSync(dest);
|
|
135
163
|
const saved = path.join(this.folder(name), ...f.split(/[\\/]/));
|
|
136
|
-
if (fs.existsSync(saved))
|
|
164
|
+
if (fs.existsSync(saved + '.sealed'))
|
|
165
|
+
fs.writeFileSync(dest, this.secrets.openBytes(fs.readFileSync(saved + '.sealed', 'utf8')));
|
|
166
|
+
else if (fs.existsSync(saved))
|
|
137
167
|
fs.copyFileSync(saved, dest);
|
|
138
168
|
else
|
|
139
169
|
fs.rmSync(dest, { force: true });
|
|
@@ -3,6 +3,7 @@ export { findSecretFiles } from './secret-files.js';
|
|
|
3
3
|
export { hostClaudeDir, hostGlobalStateFile, sandboxGlobalState, stageClaudeProfile } from './claude-profile.js';
|
|
4
4
|
export { capturePersonalization } from './personalize.js';
|
|
5
5
|
export { toRegFile, type RegValue } from './reg-file.js';
|
|
6
|
-
export { AccountStore, accountsDir, type Account, type AccountRow } from './accounts.js';
|
|
6
|
+
export { AccountStore, accountsDir, type Account, type AccountMeta, type AccountRow } from './accounts.js';
|
|
7
|
+
export { PluginShare, PLUGIN_SETTING_KEYS, MAX_PLUGIN_FILES, MAX_PLUGIN_BYTES } from './plugin-share.js';
|
|
7
8
|
export { AgentAccountStore } from './agent-accounts.js';
|
|
8
9
|
export { sandboxClaudeDir } from './claude-login.js';
|
|
@@ -4,5 +4,6 @@ export { hostClaudeDir, hostGlobalStateFile, sandboxGlobalState, stageClaudeProf
|
|
|
4
4
|
export { capturePersonalization } from './personalize.js';
|
|
5
5
|
export { toRegFile } from './reg-file.js';
|
|
6
6
|
export { AccountStore, accountsDir } from './accounts.js';
|
|
7
|
+
export { PluginShare, PLUGIN_SETTING_KEYS, MAX_PLUGIN_FILES, MAX_PLUGIN_BYTES } from './plugin-share.js';
|
|
7
8
|
export { AgentAccountStore } from './agent-accounts.js';
|
|
8
9
|
export { sandboxClaudeDir } from './claude-login.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The settings.json keys that belong to plugins; nothing else of settings.json is copied. */
|
|
2
|
+
export declare const PLUGIN_SETTING_KEYS: readonly ["enabledPlugins", "extraKnownMarketplaces"];
|
|
3
|
+
export declare const MAX_PLUGIN_FILES = 20000;
|
|
4
|
+
export declare const MAX_PLUGIN_BYTES: number;
|
|
5
|
+
export type PluginShareResult = 'saved' | 'restored' | 'nothing';
|
|
6
|
+
/** Copies a plugins folder: every entry a plain file or folder with a safe name, within the file and size caps. */
|
|
7
|
+
export declare function copyPluginTree(from: string, to: string): void;
|
|
8
|
+
export declare class PluginShare {
|
|
9
|
+
private readonly dir;
|
|
10
|
+
constructor(dir: string);
|
|
11
|
+
/** The shared set kept next to the saved accounts of a storage folder. */
|
|
12
|
+
static in(storageRoot: string): PluginShare;
|
|
13
|
+
/** When a sandbox ends: its plugins refresh the shared set. Never throws; returns the problem, if any. */
|
|
14
|
+
saveQuietly(dataPath: string, claudeDir: string): string | null;
|
|
15
|
+
private get pluginsDir();
|
|
16
|
+
private get settingsFile();
|
|
17
|
+
hasShared(): boolean;
|
|
18
|
+
/** Whether a Claude folder (the sandbox's) has plugins installed. */
|
|
19
|
+
static hasPlugins(claudeDir: string): boolean;
|
|
20
|
+
/** The plugins of a data set's Claude folder become the shared set (replacing the old one). */
|
|
21
|
+
save(dataPath: string, claudeDir: string): void;
|
|
22
|
+
/** The shared set goes into a data set's Claude folder; its own settings win, its other settings are untouched. */
|
|
23
|
+
restore(dataPath: string, claudeDir: string): void;
|
|
24
|
+
/** One step when an account is switched to. */
|
|
25
|
+
sync(dataPath: string, claudeDir: string): PluginShareResult;
|
|
26
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Claude plugins carried between sandbox data sets when the account changes (claudeProfile.sharePluginsAcrossAccounts).
|
|
2
|
+
// One plugin set is kept in host-only storage (<storage>\accounts\shared-plugins): the plugins folder plus the plugin
|
|
3
|
+
// keys of settings.json. A data set that has plugins refreshes it; one that has none (a fresh data set) receives it.
|
|
4
|
+
// The sandbox wrote everything it holds: no links, bounded file count and size, nothing is executed, and logins
|
|
5
|
+
// (.credentials.json, .claude.json) never travel with it.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { isSafeFileName } from '../../shared/paths/index.js';
|
|
9
|
+
import { safeRemove } from '../../shared/io/index.js';
|
|
10
|
+
import { assertNoLinks, ensureDir, errMsg, fail, fileExists, isLink, removeLink } from '../../core/util/index.js';
|
|
11
|
+
import { assertTestWritable } from '../test-guard.js';
|
|
12
|
+
import { readUntrusted, writeSandboxJson } from './claude-login.js';
|
|
13
|
+
/** The settings.json keys that belong to plugins; nothing else of settings.json is copied. */
|
|
14
|
+
export const PLUGIN_SETTING_KEYS = ['enabledPlugins', 'extraKnownMarketplaces'];
|
|
15
|
+
export const MAX_PLUGIN_FILES = 20000;
|
|
16
|
+
export const MAX_PLUGIN_BYTES = 1024 * 1024 * 1024;
|
|
17
|
+
const MAX_SETTINGS_BYTES = 4 * 1024 * 1024;
|
|
18
|
+
const MAX_DEPTH = 24;
|
|
19
|
+
const INSTALLED = 'installed_plugins.json';
|
|
20
|
+
function isObject(value) {
|
|
21
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
/** Copies a plugins folder: every entry a plain file or folder with a safe name, within the file and size caps. */
|
|
24
|
+
export function copyPluginTree(from, to) {
|
|
25
|
+
let files = 0;
|
|
26
|
+
let bytes = 0;
|
|
27
|
+
const walk = (src, dest, depth) => {
|
|
28
|
+
if (depth > MAX_DEPTH)
|
|
29
|
+
fail('The plugins folder is nested too deeply (' + from + ').', 'Remove the odd folders.');
|
|
30
|
+
ensureDir(dest);
|
|
31
|
+
for (const entry of fs.readdirSync(src)) {
|
|
32
|
+
const file = path.join(src, entry);
|
|
33
|
+
if (!isSafeFileName(entry))
|
|
34
|
+
fail('Plugin file name refused: ' + entry, 'Remove it from ' + from + '.');
|
|
35
|
+
if (isLink(file))
|
|
36
|
+
fail('Refusing to copy plugins: ' + file + ' is a link.', 'Remove the link and try again.');
|
|
37
|
+
const stat = fs.statSync(file);
|
|
38
|
+
if (stat.isDirectory())
|
|
39
|
+
walk(file, path.join(dest, entry), depth + 1);
|
|
40
|
+
else if (stat.isFile()) {
|
|
41
|
+
files++;
|
|
42
|
+
bytes += stat.size;
|
|
43
|
+
if (files > MAX_PLUGIN_FILES || bytes > MAX_PLUGIN_BYTES)
|
|
44
|
+
fail('The plugins are too big to copy between accounts.', 'Remove unused plugins and try again.');
|
|
45
|
+
fs.copyFileSync(file, path.join(dest, entry));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
walk(from, to, 0);
|
|
50
|
+
}
|
|
51
|
+
function pluginSettings(claudeDir) {
|
|
52
|
+
const settings = readUntrusted(path.join(claudeDir, 'settings.json'), MAX_SETTINGS_BYTES);
|
|
53
|
+
const out = {};
|
|
54
|
+
if (isObject(settings))
|
|
55
|
+
for (const key of PLUGIN_SETTING_KEYS)
|
|
56
|
+
if (isObject(settings[key]))
|
|
57
|
+
out[key] = settings[key];
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
export class PluginShare {
|
|
61
|
+
dir;
|
|
62
|
+
constructor(dir) {
|
|
63
|
+
this.dir = dir;
|
|
64
|
+
}
|
|
65
|
+
/** The shared set kept next to the saved accounts of a storage folder. */
|
|
66
|
+
static in(storageRoot) {
|
|
67
|
+
return new PluginShare(path.join(storageRoot, 'accounts', 'shared-plugins'));
|
|
68
|
+
}
|
|
69
|
+
/** When a sandbox ends: its plugins refresh the shared set. Never throws; returns the problem, if any. */
|
|
70
|
+
saveQuietly(dataPath, claudeDir) {
|
|
71
|
+
try {
|
|
72
|
+
if (PluginShare.hasPlugins(claudeDir))
|
|
73
|
+
this.save(dataPath, claudeDir);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return errMsg(e);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
get pluginsDir() {
|
|
81
|
+
return path.join(this.dir, 'plugins');
|
|
82
|
+
}
|
|
83
|
+
get settingsFile() {
|
|
84
|
+
return path.join(this.dir, 'settings.json');
|
|
85
|
+
}
|
|
86
|
+
hasShared() {
|
|
87
|
+
return fileExists(path.join(this.pluginsDir, INSTALLED));
|
|
88
|
+
}
|
|
89
|
+
/** Whether a Claude folder (the sandbox's) has plugins installed. */
|
|
90
|
+
static hasPlugins(claudeDir) {
|
|
91
|
+
const file = path.join(claudeDir, 'plugins', INSTALLED);
|
|
92
|
+
return !isLink(path.join(claudeDir, 'plugins')) && !isLink(file) && fileExists(file);
|
|
93
|
+
}
|
|
94
|
+
/** The plugins of a data set's Claude folder become the shared set (replacing the old one). */
|
|
95
|
+
save(dataPath, claudeDir) {
|
|
96
|
+
assertNoLinks(dataPath, path.join(claudeDir, 'plugins'));
|
|
97
|
+
assertTestWritable(this.dir);
|
|
98
|
+
const tmp = this.pluginsDir + '.new';
|
|
99
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
100
|
+
try {
|
|
101
|
+
copyPluginTree(path.join(claudeDir, 'plugins'), tmp);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
105
|
+
throw e;
|
|
106
|
+
}
|
|
107
|
+
fs.rmSync(this.pluginsDir, { recursive: true, force: true });
|
|
108
|
+
fs.renameSync(tmp, this.pluginsDir);
|
|
109
|
+
writeSandboxJson(this.settingsFile, pluginSettings(claudeDir));
|
|
110
|
+
}
|
|
111
|
+
/** The shared set goes into a data set's Claude folder; its own settings win, its other settings are untouched. */
|
|
112
|
+
restore(dataPath, claudeDir) {
|
|
113
|
+
assertNoLinks(dataPath, claudeDir);
|
|
114
|
+
const target = path.join(claudeDir, 'plugins');
|
|
115
|
+
ensureDir(claudeDir);
|
|
116
|
+
if (isLink(target))
|
|
117
|
+
removeLink(target);
|
|
118
|
+
else
|
|
119
|
+
safeRemove(target, claudeDir);
|
|
120
|
+
copyPluginTree(this.pluginsDir, target);
|
|
121
|
+
const shared = readUntrusted(this.settingsFile, MAX_SETTINGS_BYTES);
|
|
122
|
+
if (!isObject(shared))
|
|
123
|
+
return;
|
|
124
|
+
const file = path.join(claudeDir, 'settings.json');
|
|
125
|
+
const own = readUntrusted(file, MAX_SETTINGS_BYTES);
|
|
126
|
+
if (own !== null && !isObject(own))
|
|
127
|
+
return;
|
|
128
|
+
const merged = { ...(own ?? {}) };
|
|
129
|
+
for (const key of PLUGIN_SETTING_KEYS)
|
|
130
|
+
if (isObject(shared[key]))
|
|
131
|
+
merged[key] = { ...shared[key], ...(isObject(merged[key]) ? merged[key] : {}) };
|
|
132
|
+
writeSandboxJson(file, merged);
|
|
133
|
+
}
|
|
134
|
+
/** One step when an account is switched to. */
|
|
135
|
+
sync(dataPath, claudeDir) {
|
|
136
|
+
if (PluginShare.hasPlugins(claudeDir)) {
|
|
137
|
+
this.save(dataPath, claudeDir);
|
|
138
|
+
return 'saved';
|
|
139
|
+
}
|
|
140
|
+
if (this.hasShared()) {
|
|
141
|
+
this.restore(dataPath, claudeDir);
|
|
142
|
+
return 'restored';
|
|
143
|
+
}
|
|
144
|
+
return 'nothing';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { ResolvedRun } from '../../core/types.js';
|
|
2
2
|
import { type Missing } from '../../core/local-config/index.js';
|
|
3
3
|
import { type ProjectSpec } from '../../shared/channel/index.js';
|
|
4
|
-
import { type RegistryEntry } from './registry.js';
|
|
4
|
+
import { type Registry, type RegistryEntry } from './registry.js';
|
|
5
5
|
import { type ProjectMapper } from './mapper.js';
|
|
6
|
+
/**
|
|
7
|
+
* The running sandbox's registry. When run\projects.json is missing, cannot be read or belongs to an earlier sandbox
|
|
8
|
+
* (another copy or version of this tool started this one), it is rebuilt from the launch folder, so a folder in one of
|
|
9
|
+
* the sandbox's roots can still join. null: no running sandbox reported a launch, or nothing to rebuild it from.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runningRegistry(storageRoot: string, dataPath: string, protections: Registry['protections']): Registry | null;
|
|
6
12
|
/** The launch folder of the running sandbox (host-only storage; its id comes from the guest, so it is checked). */
|
|
7
13
|
export declare function runningLaunchPath(storageRoot: string, dataPath: string): string | null;
|
|
8
14
|
/** What a session or command from this folder needs: where it is inside, and (for another project) its spec. */
|
|
@@ -17,12 +17,29 @@ import { RepoGuardStore } from '../guard/index.js';
|
|
|
17
17
|
import { ProjectRegistry } from './registry.js';
|
|
18
18
|
import { NeedsRestart, projectMapper } from './mapper.js';
|
|
19
19
|
import { KnownProjects } from './known.js';
|
|
20
|
+
/** A launch id as the launch writes it (it comes back from the guest, so it is checked before it names a folder). */
|
|
21
|
+
const LAUNCH_ID = /^\d+-[0-9a-f]{6}$/;
|
|
22
|
+
/**
|
|
23
|
+
* The running sandbox's registry. When run\projects.json is missing, cannot be read or belongs to an earlier sandbox
|
|
24
|
+
* (another copy or version of this tool started this one), it is rebuilt from the launch folder, so a folder in one of
|
|
25
|
+
* the sandbox's roots can still join. null: no running sandbox reported a launch, or nothing to rebuild it from.
|
|
26
|
+
*/
|
|
27
|
+
export function runningRegistry(storageRoot, dataPath, protections) {
|
|
28
|
+
const file = new ProjectRegistry(storageRoot);
|
|
29
|
+
const current = file.read();
|
|
30
|
+
const launchId = new RunDir(dataPath).readSandboxStatus()?.launchId;
|
|
31
|
+
if (!launchId || !LAUNCH_ID.test(launchId))
|
|
32
|
+
return current;
|
|
33
|
+
if (current?.launchId === launchId)
|
|
34
|
+
return current;
|
|
35
|
+
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
36
|
+
assertNoLinks(storageRoot, launchPath);
|
|
37
|
+
return file.rebuild(launchPath, launchId, protections);
|
|
38
|
+
}
|
|
20
39
|
/** The launch folder of the running sandbox (host-only storage; its id comes from the guest, so it is checked). */
|
|
21
40
|
export function runningLaunchPath(storageRoot, dataPath) {
|
|
22
41
|
const launchId = new RunDir(dataPath).readSandboxStatus()?.launchId;
|
|
23
|
-
if (!launchId ||
|
|
24
|
-
!/^\d+-[0-9a-f]{6}$/.test(launchId) ||
|
|
25
|
-
new ProjectRegistry(storageRoot).read()?.launchId !== launchId)
|
|
42
|
+
if (!launchId || !LAUNCH_ID.test(launchId) || new ProjectRegistry(storageRoot).read()?.launchId !== launchId)
|
|
26
43
|
return null;
|
|
27
44
|
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
28
45
|
assertNoLinks(storageRoot, launchPath);
|
|
@@ -54,7 +71,7 @@ export async function joinRunning(o) {
|
|
|
54
71
|
if (here?.project.primary)
|
|
55
72
|
return { ...here, spec: null, run: null, notes: [], missing: null };
|
|
56
73
|
const registry = registryFile.read() ??
|
|
57
|
-
fail(
|
|
74
|
+
fail("The record of the running sandbox's projects is missing, and its launch folder could not replace it.", 'Run sandbox doctor (it shows which copy of sandbox runs), or stop the sandbox (sandbox stop) and start it again.');
|
|
58
75
|
// A folder below a project already in the sandbox uses that project; anything else becomes a project of its own.
|
|
59
76
|
const root = here?.project.host ?? path.resolve(o.hostDir);
|
|
60
77
|
const run = await o.resolve(root);
|
|
@@ -9,44 +9,44 @@ declare const RegistryEntrySchema: z.ZodObject<{
|
|
|
9
9
|
host: z.ZodString;
|
|
10
10
|
guest: z.ZodString;
|
|
11
11
|
primary: z.ZodBoolean;
|
|
12
|
-
mechanism: z.ZodEnum<{
|
|
12
|
+
mechanism: z.ZodCatch<z.ZodEnum<{
|
|
13
13
|
root: "root";
|
|
14
14
|
launch: "launch";
|
|
15
15
|
boot: "boot";
|
|
16
16
|
wsb: "wsb";
|
|
17
|
-
}
|
|
17
|
+
}>>;
|
|
18
18
|
registeredAt: z.ZodString;
|
|
19
|
-
}, z.core.$
|
|
19
|
+
}, z.core.$strip>;
|
|
20
20
|
declare const RegistrySchema: z.ZodObject<{
|
|
21
21
|
launchId: z.ZodString;
|
|
22
22
|
protections: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
|
|
23
23
|
roots: z.ZodArray<z.ZodObject<{
|
|
24
24
|
host: z.ZodString;
|
|
25
25
|
guest: z.ZodString;
|
|
26
|
-
}, z.core.$
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
27
|
projects: z.ZodArray<z.ZodObject<{
|
|
28
28
|
name: z.ZodString;
|
|
29
29
|
host: z.ZodString;
|
|
30
30
|
guest: z.ZodString;
|
|
31
31
|
primary: z.ZodBoolean;
|
|
32
|
-
mechanism: z.ZodEnum<{
|
|
32
|
+
mechanism: z.ZodCatch<z.ZodEnum<{
|
|
33
33
|
root: "root";
|
|
34
34
|
launch: "launch";
|
|
35
35
|
boot: "boot";
|
|
36
36
|
wsb: "wsb";
|
|
37
|
-
}
|
|
37
|
+
}>>;
|
|
38
38
|
registeredAt: z.ZodString;
|
|
39
|
-
}, z.core.$
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
40
|
atBoot: z.ZodOptional<z.ZodObject<{
|
|
41
41
|
memoryMb: z.ZodNullable<z.ZodNumber>;
|
|
42
42
|
mappings: z.ZodArray<z.ZodObject<{
|
|
43
43
|
host: z.ZodString;
|
|
44
44
|
guest: z.ZodString;
|
|
45
45
|
readOnly: z.ZodBoolean;
|
|
46
|
-
}, z.core.$
|
|
46
|
+
}, z.core.$strip>>;
|
|
47
47
|
widened: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
48
|
-
}, z.core.$
|
|
49
|
-
}, z.core.$
|
|
48
|
+
}, z.core.$strip>>;
|
|
49
|
+
}, z.core.$strip>;
|
|
50
50
|
export type AtBoot = NonNullable<z.output<typeof RegistrySchema>['atBoot']>;
|
|
51
51
|
export type RegistryEntry = z.output<typeof RegistryEntrySchema>;
|
|
52
52
|
export type Registry = z.output<typeof RegistrySchema>;
|
|
@@ -79,6 +79,12 @@ export declare class ProjectRegistry {
|
|
|
79
79
|
here(hostDir: string): ProjectHere | null;
|
|
80
80
|
/** The running sandbox's root containing this folder, if any. */
|
|
81
81
|
rootHere(hostDir: string): RootMapping | null;
|
|
82
|
+
/**
|
|
83
|
+
* The record rebuilt from the running sandbox's launch folder (host-only storage), for when run\projects.json is
|
|
84
|
+
* missing, cannot be read or belongs to an earlier sandbox: the launch folder, and every folder mapped for other
|
|
85
|
+
* projects at boot as a root. Saved and returned; null when the launch folder does not have what it takes.
|
|
86
|
+
*/
|
|
87
|
+
rebuild(launchPath: string, launchId: string, protections: Registry['protections']): Registry | null;
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
90
|
* The projects of a sandbox another user started and shares: what that user's record lists, in the shape of a registry.
|