windows-development-sandbox 0.0.0-stage → 1.1.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/.sandbox-config.example.json +14 -0
- package/LICENSE +21 -0
- package/README.md +94 -2
- package/config.example.json +349 -0
- package/dist/api.d.ts +31 -0
- package/dist/api.js +31 -0
- package/dist/bin/agent-sandbox.d.ts +2 -0
- package/dist/bin/agent-sandbox.js +3 -0
- package/dist/bin/claude-host.d.ts +2 -0
- package/dist/bin/claude-host.js +5 -0
- package/dist/bin/claude-sbx.d.ts +2 -0
- package/dist/bin/claude-sbx.js +3 -0
- package/dist/bin/codex-sbx.d.ts +2 -0
- package/dist/bin/codex-sbx.js +3 -0
- package/dist/bin/gemini-sbx.d.ts +2 -0
- package/dist/bin/gemini-sbx.js +3 -0
- package/dist/bin/sandbox.d.ts +2 -0
- package/dist/bin/sandbox.js +4 -0
- package/dist/bin/sbx-hide.d.ts +2 -0
- package/dist/bin/sbx-hide.js +3 -0
- package/dist/bin/sbx-show.d.ts +2 -0
- package/dist/bin/sbx-show.js +3 -0
- package/dist/cli/args.d.ts +52 -0
- package/dist/cli/args.js +143 -0
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.d.ts +2 -0
- package/dist/cli/commands/accounts.js +52 -0
- package/dist/cli/commands/agent-commands.d.ts +6 -0
- package/dist/cli/commands/agent-commands.js +61 -0
- package/dist/cli/commands/context.d.ts +23 -0
- package/dist/cli/commands/context.js +57 -0
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +101 -0
- package/dist/cli/commands/data/collect.d.ts +11 -0
- package/dist/cli/commands/data/collect.js +54 -0
- package/dist/cli/commands/data/index.d.ts +4 -0
- package/dist/cli/commands/data/index.js +45 -0
- package/dist/cli/commands/data/remove.d.ts +2 -0
- package/dist/cli/commands/data/remove.js +30 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/launch.d.ts +29 -0
- package/dist/cli/commands/launch.js +333 -0
- package/dist/cli/commands/maintenance.d.ts +8 -0
- package/dist/cli/commands/maintenance.js +154 -0
- package/dist/cli/commands/project.d.ts +29 -0
- package/dist/cli/commands/project.js +63 -0
- package/dist/cli/commands/projects.d.ts +14 -0
- package/dist/cli/commands/projects.js +146 -0
- package/dist/cli/commands/repair/checks.d.ts +2 -0
- package/dist/cli/commands/repair/checks.js +170 -0
- package/dist/cli/commands/repair/finders.d.ts +30 -0
- package/dist/cli/commands/repair/finders.js +108 -0
- package/dist/cli/commands/repair/index.d.ts +12 -0
- package/dist/cli/commands/repair/index.js +61 -0
- package/dist/cli/commands/repair/types.d.ts +26 -0
- package/dist/cli/commands/repair/types.js +3 -0
- package/dist/cli/commands/restart.d.ts +6 -0
- package/dist/cli/commands/restart.js +85 -0
- package/dist/cli/commands/sessions.d.ts +12 -0
- package/dist/cli/commands/sessions.js +257 -0
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/setup/facts.d.ts +31 -0
- package/dist/cli/commands/setup/facts.js +60 -0
- package/dist/cli/commands/setup/index.d.ts +2 -0
- package/dist/cli/commands/setup/index.js +43 -0
- package/dist/cli/commands/user-config.d.ts +8 -0
- package/dist/cli/commands/user-config.js +41 -0
- package/dist/cli/history.d.ts +11 -0
- package/dist/cli/history.js +21 -0
- package/dist/cli/index.d.ts +20 -0
- package/dist/cli/index.js +121 -0
- package/dist/cli/manifest.d.ts +103 -0
- package/dist/cli/manifest.js +107 -0
- package/dist/cli/menu-multi.d.ts +12 -0
- package/dist/cli/menu-multi.js +82 -0
- package/dist/cli/menu.d.ts +14 -0
- package/dist/cli/menu.js +290 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/settings/edit.d.ts +13 -0
- package/dist/cli/settings/edit.js +19 -0
- package/dist/cli/settings/index.d.ts +8 -0
- package/dist/cli/settings/index.js +131 -0
- package/dist/cli/settings/prompts.d.ts +7 -0
- package/dist/cli/settings/prompts.js +48 -0
- package/dist/cli/settings/schema.d.ts +33 -0
- package/dist/cli/settings/schema.js +29 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +106 -0
- package/dist/core/detect-databases.d.ts +15 -0
- package/dist/core/detect-databases.js +147 -0
- package/dist/core/detect-pwsh.d.ts +18 -0
- package/dist/core/detect-pwsh.js +111 -0
- package/dist/core/detect-toolchains.d.ts +30 -0
- package/dist/core/detect-toolchains.js +58 -0
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +20 -0
- package/dist/core/local-config.js +77 -0
- package/dist/core/project-policy.d.ts +15 -0
- package/dist/core/project-policy.js +35 -0
- package/dist/core/prompt.d.ts +4 -0
- package/dist/core/prompt.js +15 -0
- package/dist/core/safe-paths.d.ts +4 -0
- package/dist/core/safe-paths.js +37 -0
- package/dist/core/schema/common.d.ts +17 -0
- package/dist/core/schema/common.js +15 -0
- package/dist/core/schema/config.d.ts +594 -0
- package/dist/core/schema/config.js +63 -0
- package/dist/core/schema/databases.d.ts +33 -0
- package/dist/core/schema/databases.js +35 -0
- package/dist/core/schema/harness.d.ts +12 -0
- package/dist/core/schema/harness.js +17 -0
- package/dist/core/schema/json-schema.d.ts +5 -0
- package/dist/core/schema/json-schema.js +25 -0
- package/dist/core/schema/profile.d.ts +27 -0
- package/dist/core/schema/profile.js +29 -0
- package/dist/core/schema/projects.d.ts +9 -0
- package/dist/core/schema/projects.js +11 -0
- package/dist/core/schema/sandbox.d.ts +50 -0
- package/dist/core/schema/sandbox.js +36 -0
- package/dist/core/schema/toolchains.d.ts +93 -0
- package/dist/core/schema/toolchains.js +83 -0
- package/dist/core/schema.d.ts +6 -0
- package/dist/core/schema.js +8 -0
- package/dist/core/types.d.ts +190 -0
- package/dist/core/types.js +1 -0
- package/dist/core/util.d.ts +60 -0
- package/dist/core/util.js +154 -0
- package/dist/core/workspace.d.ts +18 -0
- package/dist/core/workspace.js +128 -0
- package/dist/guest/boot-env.d.ts +4 -0
- package/dist/guest/boot-env.js +25 -0
- package/dist/guest/cmdline.d.ts +12 -0
- package/dist/guest/cmdline.js +23 -0
- package/dist/guest/daemon/exec.d.ts +5 -0
- package/dist/guest/daemon/exec.js +32 -0
- package/dist/guest/daemon/project.d.ts +14 -0
- package/dist/guest/daemon/project.js +132 -0
- package/dist/guest/daemon/session.d.ts +13 -0
- package/dist/guest/daemon/session.js +139 -0
- package/dist/guest/daemon/shutdown.d.ts +1 -0
- package/dist/guest/daemon/shutdown.js +21 -0
- package/dist/guest/daemon/state.d.ts +17 -0
- package/dist/guest/daemon/state.js +31 -0
- package/dist/guest/daemon.d.ts +1 -0
- package/dist/guest/daemon.js +53 -0
- package/dist/guest/db.d.ts +1 -0
- package/dist/guest/db.js +75 -0
- package/dist/guest/env.d.ts +51 -0
- package/dist/guest/env.js +101 -0
- package/dist/guest/gitguard.d.ts +7 -0
- package/dist/guest/gitguard.js +60 -0
- package/dist/guest/gitrules.d.ts +2 -0
- package/dist/guest/gitrules.js +11 -0
- package/dist/guest/guide.d.ts +16 -0
- package/dist/guest/guide.js +69 -0
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.d.ts +1 -0
- package/dist/guest/main.js +130 -0
- package/dist/guest/ports.d.ts +6 -0
- package/dist/guest/ports.js +22 -0
- package/dist/guest/project-context.d.ts +7 -0
- package/dist/guest/project-context.js +10 -0
- package/dist/guest/project-env.d.ts +20 -0
- package/dist/guest/project-env.js +20 -0
- package/dist/guest/project-status.d.ts +5 -0
- package/dist/guest/project-status.js +17 -0
- package/dist/guest/ptyhost.d.ts +22 -0
- package/dist/guest/ptyhost.js +36 -0
- package/dist/guest/setup/agents.d.ts +11 -0
- package/dist/guest/setup/agents.js +87 -0
- package/dist/guest/setup/claude.d.ts +21 -0
- package/dist/guest/setup/claude.js +146 -0
- package/dist/guest/setup/environment.d.ts +16 -0
- package/dist/guest/setup/environment.js +69 -0
- package/dist/guest/setup/hooks.d.ts +9 -0
- package/dist/guest/setup/hooks.js +50 -0
- package/dist/guest/setup/network.d.ts +10 -0
- package/dist/guest/setup/network.js +99 -0
- package/dist/guest/setup/policy.d.ts +3 -0
- package/dist/guest/setup/policy.js +18 -0
- package/dist/guest/setup/user.d.ts +7 -0
- package/dist/guest/setup/user.js +47 -0
- package/dist/guest/toolchains/catalogue.d.ts +55 -0
- package/dist/guest/toolchains/catalogue.js +31 -0
- package/dist/guest/toolchains/cli.d.ts +10 -0
- package/dist/guest/toolchains/cli.js +72 -0
- package/dist/guest/toolchains/common.d.ts +54 -0
- package/dist/guest/toolchains/common.js +147 -0
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/databases/index.d.ts +6 -0
- package/dist/guest/toolchains/databases/index.js +62 -0
- package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
- package/dist/guest/toolchains/databases/mongodb.js +31 -0
- package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
- package/dist/guest/toolchains/databases/mysql.js +38 -0
- package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
- package/dist/guest/toolchains/databases/postgres.js +38 -0
- package/dist/guest/toolchains/databases/redis.d.ts +3 -0
- package/dist/guest/toolchains/databases/redis.js +16 -0
- package/dist/guest/toolchains/databases/server.d.ts +14 -0
- package/dist/guest/toolchains/databases/server.js +52 -0
- package/dist/guest/toolchains/git.d.ts +3 -0
- package/dist/guest/toolchains/git.js +121 -0
- package/dist/guest/toolchains/go.d.ts +3 -0
- package/dist/guest/toolchains/go.js +33 -0
- package/dist/guest/toolchains/index.d.ts +17 -0
- package/dist/guest/toolchains/index.js +80 -0
- package/dist/guest/toolchains/java.d.ts +8 -0
- package/dist/guest/toolchains/java.js +96 -0
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.d.ts +16 -0
- package/dist/guest/toolchains/managers.js +84 -0
- package/dist/guest/toolchains/native/binstall.d.ts +7 -0
- package/dist/guest/toolchains/native/binstall.js +52 -0
- package/dist/guest/toolchains/native/cpp.d.ts +4 -0
- package/dist/guest/toolchains/native/cpp.js +38 -0
- package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
- package/dist/guest/toolchains/native/dotnet.js +103 -0
- package/dist/guest/toolchains/native/mesa.d.ts +7 -0
- package/dist/guest/toolchains/native/mesa.js +46 -0
- package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
- package/dist/guest/toolchains/native/msvc/environment.js +57 -0
- package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/index.js +48 -0
- package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/packages.js +156 -0
- package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
- package/dist/guest/toolchains/native/msvc/tree.js +21 -0
- package/dist/guest/toolchains/native/rust.d.ts +3 -0
- package/dist/guest/toolchains/native/rust.js +45 -0
- package/dist/guest/toolchains/node.d.ts +3 -0
- package/dist/guest/toolchains/node.js +152 -0
- package/dist/guest/toolchains/plan.d.ts +22 -0
- package/dist/guest/toolchains/plan.js +96 -0
- package/dist/guest/toolchains/pwsh.d.ts +4 -0
- package/dist/guest/toolchains/pwsh.js +14 -0
- package/dist/guest/toolchains/python.d.ts +3 -0
- package/dist/guest/toolchains/python.js +54 -0
- package/dist/guest/toolchains/runtimes.d.ts +19 -0
- package/dist/guest/toolchains/runtimes.js +38 -0
- package/dist/guest/toolchains/scripting/php.d.ts +3 -0
- package/dist/guest/toolchains/scripting/php.js +71 -0
- package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
- package/dist/guest/toolchains/scripting/ruby.js +47 -0
- package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
- package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
- package/dist/guest/toolchains/versions.d.ts +47 -0
- package/dist/guest/toolchains/versions.js +77 -0
- package/dist/guest/toolchains/vscode.d.ts +2 -0
- package/dist/guest/toolchains/vscode.js +53 -0
- package/dist/guest/view.d.ts +1 -0
- package/dist/guest/view.js +42 -0
- package/dist/host/accounts.d.ts +32 -0
- package/dist/host/accounts.js +128 -0
- package/dist/host/agent-commands.d.ts +25 -0
- package/dist/host/agent-commands.js +145 -0
- package/dist/host/attach.d.ts +17 -0
- package/dist/host/attach.js +69 -0
- package/dist/host/channel.d.ts +38 -0
- package/dist/host/channel.js +162 -0
- package/dist/host/claude-profile.d.ts +15 -0
- package/dist/host/claude-profile.js +107 -0
- package/dist/host/cleanup.d.ts +23 -0
- package/dist/host/cleanup.js +57 -0
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +7 -0
- package/dist/host/generate.js +98 -0
- package/dist/host/join.d.ts +28 -0
- package/dist/host/join.js +76 -0
- package/dist/host/launch/mappings.d.ts +3 -0
- package/dist/host/launch/mappings.js +50 -0
- package/dist/host/launch/projects.d.ts +15 -0
- package/dist/host/launch/projects.js +56 -0
- package/dist/host/launch/stage.d.ts +11 -0
- package/dist/host/launch/stage.js +66 -0
- package/dist/host/launch/wsb.d.ts +10 -0
- package/dist/host/launch/wsb.js +34 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/personalize.d.ts +10 -0
- package/dist/host/personalize.js +97 -0
- package/dist/host/platform.d.ts +19 -0
- package/dist/host/platform.js +126 -0
- package/dist/host/project-scan.d.ts +7 -0
- package/dist/host/project-scan.js +89 -0
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +36 -0
- package/dist/host/repo-guard.js +219 -0
- package/dist/host/secrets.d.ts +14 -0
- package/dist/host/secrets.js +133 -0
- package/dist/host/terminal.d.ts +32 -0
- package/dist/host/terminal.js +130 -0
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.d.ts +4 -0
- package/dist/host/window.js +72 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +28494 -0
- package/dist/runtime/db.js +26387 -0
- package/dist/runtime/gitguard.js +84 -0
- package/dist/runtime/main.js +28622 -0
- package/dist/runtime/view.js +25841 -0
- package/dist/selftest/accounts.test.d.ts +2 -0
- package/dist/selftest/accounts.test.js +44 -0
- package/dist/selftest/agent-commands.test.d.ts +2 -0
- package/dist/selftest/agent-commands.test.js +47 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +37 -0
- package/dist/selftest/cli.test.d.ts +2 -0
- package/dist/selftest/cli.test.js +118 -0
- package/dist/selftest/cmdline.test.d.ts +2 -0
- package/dist/selftest/cmdline.test.js +45 -0
- package/dist/selftest/config.test.d.ts +2 -0
- package/dist/selftest/config.test.js +38 -0
- package/dist/selftest/data.test.d.ts +2 -0
- package/dist/selftest/data.test.js +80 -0
- package/dist/selftest/detect.test.d.ts +2 -0
- package/dist/selftest/detect.test.js +58 -0
- package/dist/selftest/fixtures.d.ts +272 -0
- package/dist/selftest/fixtures.js +80 -0
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +63 -0
- package/dist/selftest/git-access.test.d.ts +2 -0
- package/dist/selftest/git-access.test.js +26 -0
- package/dist/selftest/guest-boot.test.d.ts +2 -0
- package/dist/selftest/guest-boot.test.js +76 -0
- package/dist/selftest/harness.d.ts +51 -0
- package/dist/selftest/harness.js +77 -0
- package/dist/selftest/host-sim.test.d.ts +2 -0
- package/dist/selftest/host-sim.test.js +93 -0
- package/dist/selftest/hostile-input.test.d.ts +2 -0
- package/dist/selftest/hostile-input.test.js +54 -0
- package/dist/selftest/index.d.ts +8 -0
- package/dist/selftest/index.js +155 -0
- package/dist/selftest/links.test.d.ts +2 -0
- package/dist/selftest/links.test.js +50 -0
- package/dist/selftest/live-daemon.test.d.ts +2 -0
- package/dist/selftest/live-daemon.test.js +91 -0
- package/dist/selftest/live-git.test.d.ts +2 -0
- package/dist/selftest/live-git.test.js +60 -0
- package/dist/selftest/live-projects.test.d.ts +2 -0
- package/dist/selftest/live-projects.test.js +127 -0
- package/dist/selftest/live.d.ts +65 -0
- package/dist/selftest/live.js +109 -0
- package/dist/selftest/local-config.test.d.ts +2 -0
- package/dist/selftest/local-config.test.js +107 -0
- package/dist/selftest/mirror.test.d.ts +2 -0
- package/dist/selftest/mirror.test.js +101 -0
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +28 -0
- package/dist/selftest/personalization.test.d.ts +2 -0
- package/dist/selftest/personalization.test.js +53 -0
- package/dist/selftest/projects.test.d.ts +2 -0
- package/dist/selftest/projects.test.js +254 -0
- package/dist/selftest/repair.test.d.ts +2 -0
- package/dist/selftest/repair.test.js +42 -0
- package/dist/selftest/repo-guard.test.d.ts +2 -0
- package/dist/selftest/repo-guard.test.js +63 -0
- package/dist/selftest/secrets.test.d.ts +2 -0
- package/dist/selftest/secrets.test.js +71 -0
- package/dist/selftest/setup.test.d.ts +2 -0
- package/dist/selftest/setup.test.js +46 -0
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -0
- package/dist/selftest/sync.test.js +94 -0
- package/dist/selftest/test-guard.test.d.ts +2 -0
- package/dist/selftest/test-guard.test.js +49 -0
- package/dist/selftest/toolchains.test.d.ts +2 -0
- package/dist/selftest/toolchains.test.js +103 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/download.d.ts +60 -0
- package/dist/shared/download.js +215 -0
- package/dist/shared/guest-paths.d.ts +30 -0
- package/dist/shared/guest-paths.js +61 -0
- package/dist/shared/netrules.d.ts +18 -0
- package/dist/shared/netrules.js +98 -0
- package/dist/shared/paths.d.ts +14 -0
- package/dist/shared/paths.js +28 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/pty-client.d.ts +86 -0
- package/dist/shared/pty-client.js +227 -0
- package/dist/shared/rundir.d.ts +39 -0
- package/dist/shared/rundir.js +94 -0
- package/dist/shared/safe-remove.d.ts +10 -0
- package/dist/shared/safe-remove.js +66 -0
- package/dist/shared/schema.d.ts +527 -0
- package/dist/shared/schema.js +90 -0
- package/dist/shared/system.d.ts +15 -0
- package/dist/shared/system.js +19 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.d.ts +8 -0
- package/dist/sync/claude-mem.js +79 -0
- package/dist/sync/history.d.ts +41 -0
- package/dist/sync/history.js +161 -0
- package/dist/sync/watcher.d.ts +21 -0
- package/dist/sync/watcher.js +135 -0
- package/hooks/post-agent/README.txt +1 -0
- package/hooks/pre-provision/README.txt +1 -0
- package/package.json +91 -4
- package/sandbox/bootstrap.ps1 +258 -0
- package/sandbox/cpucap.ps1 +51 -0
- package/sandbox/gitshim.cs +35 -0
- package/sandbox/logon.cmd +18 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/ptyhost.cs +614 -0
- package/sandbox/trust.cmd +35 -0
- package/sandbox/vswhere.cs +83 -0
- package/schemas/config.schema.json +1161 -0
- package/schemas/sandbox-config.schema.json +968 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { Config, HostProbe, ResolvedRun, RunRequest } from '../core/types.js';
|
|
2
|
+
import { generateSandbox } from '../host/generate.js';
|
|
3
|
+
import type { Harness } from './harness.js';
|
|
4
|
+
export declare const HOST_NODE_VERSION = "v22.3.0";
|
|
5
|
+
/** Colour codes are removed from captured output, so tests can match plain text. */
|
|
6
|
+
export declare const stripAnsi: (text: string) => string;
|
|
7
|
+
export interface NodeRun {
|
|
8
|
+
status: number | null;
|
|
9
|
+
out: string;
|
|
10
|
+
timedOut: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface NodeRunOptions {
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Runs `node <args>` and collects stdout + stderr (colours removed). Never throws; a hung process is killed at the timeout. */
|
|
18
|
+
export declare const runNode: (args: string[], options?: NodeRunOptions) => Promise<NodeRun>;
|
|
19
|
+
export declare const createFixtures: (t: Harness) => {
|
|
20
|
+
at: (...parts: string[]) => string;
|
|
21
|
+
hostNode: string;
|
|
22
|
+
hostClaude: string;
|
|
23
|
+
hostProfile: string;
|
|
24
|
+
hostGlobalState: string;
|
|
25
|
+
install: string;
|
|
26
|
+
workA: string;
|
|
27
|
+
workB: string;
|
|
28
|
+
shared: string;
|
|
29
|
+
makeConfig: (copyFromHost?: boolean) => Config;
|
|
30
|
+
baseConfig: {
|
|
31
|
+
defaultHarness: string;
|
|
32
|
+
guestWorkspace: string;
|
|
33
|
+
readOnly: boolean;
|
|
34
|
+
mappings: {
|
|
35
|
+
host: string;
|
|
36
|
+
guest: string;
|
|
37
|
+
readOnly: boolean;
|
|
38
|
+
optional?: boolean | undefined;
|
|
39
|
+
}[];
|
|
40
|
+
sandbox: {
|
|
41
|
+
memoryMb: number | "auto";
|
|
42
|
+
maxMemoryMb: number;
|
|
43
|
+
maxMemoryPercent: number;
|
|
44
|
+
cpuLimit: number | "none";
|
|
45
|
+
reserveHostMb: number;
|
|
46
|
+
vGpu: boolean;
|
|
47
|
+
networking: boolean;
|
|
48
|
+
clipboard: boolean;
|
|
49
|
+
audioInput: boolean;
|
|
50
|
+
videoInput: boolean;
|
|
51
|
+
printerRedirection: boolean;
|
|
52
|
+
protectedClient: boolean;
|
|
53
|
+
headless: boolean;
|
|
54
|
+
};
|
|
55
|
+
storage: {
|
|
56
|
+
root: string;
|
|
57
|
+
persistAgentState: boolean;
|
|
58
|
+
dataName: string;
|
|
59
|
+
};
|
|
60
|
+
network: {
|
|
61
|
+
blockPrivateRanges: boolean;
|
|
62
|
+
};
|
|
63
|
+
provisioning: {
|
|
64
|
+
copyFromHost: boolean;
|
|
65
|
+
fallbackToInstall: boolean;
|
|
66
|
+
refresh: boolean;
|
|
67
|
+
git: {
|
|
68
|
+
enabled: boolean;
|
|
69
|
+
access: "readonly" | "full" | "none";
|
|
70
|
+
};
|
|
71
|
+
terminal: {
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
downloads: {
|
|
76
|
+
requireChecksum: boolean;
|
|
77
|
+
};
|
|
78
|
+
toolchains: {
|
|
79
|
+
node: {
|
|
80
|
+
version: string;
|
|
81
|
+
extraVersions: string[];
|
|
82
|
+
corepack: boolean;
|
|
83
|
+
volta: boolean | "auto";
|
|
84
|
+
globalPackages: string[];
|
|
85
|
+
};
|
|
86
|
+
python: {
|
|
87
|
+
enabled: boolean | "auto";
|
|
88
|
+
version: string;
|
|
89
|
+
python2: boolean;
|
|
90
|
+
pyenv: boolean | "auto";
|
|
91
|
+
tools: string[];
|
|
92
|
+
};
|
|
93
|
+
rust: {
|
|
94
|
+
enabled: boolean | "auto";
|
|
95
|
+
toolchain: string;
|
|
96
|
+
host: string;
|
|
97
|
+
components: string[];
|
|
98
|
+
targets: string[];
|
|
99
|
+
crates: string[];
|
|
100
|
+
targetDir: string;
|
|
101
|
+
};
|
|
102
|
+
msvc: {
|
|
103
|
+
enabled: boolean | "auto";
|
|
104
|
+
acceptLicense: boolean;
|
|
105
|
+
msvcVersion: string;
|
|
106
|
+
sdkVersion: string;
|
|
107
|
+
};
|
|
108
|
+
cpp: {
|
|
109
|
+
enabled: boolean | "auto";
|
|
110
|
+
flexBison: boolean;
|
|
111
|
+
};
|
|
112
|
+
dotnet: {
|
|
113
|
+
enabled: boolean | "auto";
|
|
114
|
+
channels: string[];
|
|
115
|
+
tools: string[];
|
|
116
|
+
workloads: string[];
|
|
117
|
+
frameworkTargets: string[];
|
|
118
|
+
netfx35: boolean;
|
|
119
|
+
};
|
|
120
|
+
go: {
|
|
121
|
+
enabled: boolean | "auto";
|
|
122
|
+
version: string;
|
|
123
|
+
tools: string[];
|
|
124
|
+
};
|
|
125
|
+
java: {
|
|
126
|
+
enabled: boolean | "auto";
|
|
127
|
+
version: string;
|
|
128
|
+
maven: boolean;
|
|
129
|
+
gradle: boolean;
|
|
130
|
+
};
|
|
131
|
+
ruby: {
|
|
132
|
+
enabled: boolean | "auto";
|
|
133
|
+
version: string;
|
|
134
|
+
gems: string[];
|
|
135
|
+
};
|
|
136
|
+
php: {
|
|
137
|
+
enabled: boolean | "auto";
|
|
138
|
+
version: string;
|
|
139
|
+
composer: boolean;
|
|
140
|
+
};
|
|
141
|
+
deno: {
|
|
142
|
+
enabled: boolean | "auto";
|
|
143
|
+
};
|
|
144
|
+
pwsh: {
|
|
145
|
+
enabled: boolean | "auto";
|
|
146
|
+
};
|
|
147
|
+
cli: {
|
|
148
|
+
gh: boolean | "auto";
|
|
149
|
+
zstd: boolean | "auto";
|
|
150
|
+
openssl: boolean | "auto";
|
|
151
|
+
};
|
|
152
|
+
bun: {
|
|
153
|
+
enabled: boolean;
|
|
154
|
+
};
|
|
155
|
+
uv: {
|
|
156
|
+
enabled: boolean;
|
|
157
|
+
};
|
|
158
|
+
vcRuntime: {
|
|
159
|
+
enabled: boolean;
|
|
160
|
+
};
|
|
161
|
+
mesa: {
|
|
162
|
+
enabled: boolean;
|
|
163
|
+
vulkan: boolean;
|
|
164
|
+
};
|
|
165
|
+
vscode: {
|
|
166
|
+
enabled: boolean;
|
|
167
|
+
};
|
|
168
|
+
environment: Record<string, string>;
|
|
169
|
+
};
|
|
170
|
+
databases: {
|
|
171
|
+
postgres: {
|
|
172
|
+
enabled: boolean | "auto";
|
|
173
|
+
version: string;
|
|
174
|
+
port: number;
|
|
175
|
+
};
|
|
176
|
+
mysql: {
|
|
177
|
+
enabled: boolean | "auto";
|
|
178
|
+
version: string;
|
|
179
|
+
port: number;
|
|
180
|
+
};
|
|
181
|
+
mongodb: {
|
|
182
|
+
enabled: boolean | "auto";
|
|
183
|
+
version: string;
|
|
184
|
+
port: number;
|
|
185
|
+
};
|
|
186
|
+
redis: {
|
|
187
|
+
enabled: boolean | "auto";
|
|
188
|
+
port: number;
|
|
189
|
+
};
|
|
190
|
+
dynamodb: {
|
|
191
|
+
enabled: boolean | "auto";
|
|
192
|
+
port: number;
|
|
193
|
+
adminPort: number;
|
|
194
|
+
admin: boolean;
|
|
195
|
+
};
|
|
196
|
+
sqlserver: {
|
|
197
|
+
enabled: boolean | "auto";
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
attach: {
|
|
201
|
+
enabled: boolean;
|
|
202
|
+
reuseSession: boolean;
|
|
203
|
+
auto: boolean;
|
|
204
|
+
autoHide: boolean;
|
|
205
|
+
};
|
|
206
|
+
agentCommands: {
|
|
207
|
+
enabled: boolean;
|
|
208
|
+
agents: string[] | "auto";
|
|
209
|
+
};
|
|
210
|
+
secrets: {
|
|
211
|
+
strip: boolean;
|
|
212
|
+
allow: string[];
|
|
213
|
+
patterns: string[];
|
|
214
|
+
warnFiles: boolean;
|
|
215
|
+
};
|
|
216
|
+
personalization: {
|
|
217
|
+
enabled: boolean;
|
|
218
|
+
};
|
|
219
|
+
claudeProfile: {
|
|
220
|
+
enabled: boolean;
|
|
221
|
+
hostDir: string;
|
|
222
|
+
include: string[];
|
|
223
|
+
globalStateFile: string;
|
|
224
|
+
copyCredentials: boolean;
|
|
225
|
+
overwrite: boolean;
|
|
226
|
+
syncHistory: boolean;
|
|
227
|
+
syncClaudeMem: boolean;
|
|
228
|
+
};
|
|
229
|
+
harnesses: Record<string, {
|
|
230
|
+
arguments: string[];
|
|
231
|
+
environment: Record<string, string>;
|
|
232
|
+
hostExecutable?: string | undefined;
|
|
233
|
+
hostDir?: string | undefined;
|
|
234
|
+
npmPackage?: string | undefined;
|
|
235
|
+
npmBin?: string | undefined;
|
|
236
|
+
uvPackage?: string | undefined;
|
|
237
|
+
uvBin?: string | undefined;
|
|
238
|
+
resumeArguments?: string[] | undefined;
|
|
239
|
+
}>;
|
|
240
|
+
hooks: {
|
|
241
|
+
failFast: boolean;
|
|
242
|
+
};
|
|
243
|
+
projects: {
|
|
244
|
+
all: boolean;
|
|
245
|
+
exclude: string[];
|
|
246
|
+
roots: string[];
|
|
247
|
+
askToAddRoot: boolean;
|
|
248
|
+
declinedRoots: string[];
|
|
249
|
+
wsbShare: boolean;
|
|
250
|
+
};
|
|
251
|
+
$schema?: string | undefined;
|
|
252
|
+
};
|
|
253
|
+
probe: HostProbe;
|
|
254
|
+
run: (over?: Partial<RunRequest>, config?: Config) => Promise<ResolvedRun>;
|
|
255
|
+
generate: (r: ResolvedRun, args?: string[], p?: HostProbe) => ReturnType<typeof generateSandbox>;
|
|
256
|
+
launch: (over?: Partial<RunRequest>, options?: {
|
|
257
|
+
config?: Config;
|
|
258
|
+
args?: string[];
|
|
259
|
+
probe?: HostProbe;
|
|
260
|
+
}) => Promise<ReturnType<typeof generateSandbox>>;
|
|
261
|
+
writeLocal: (dir: string, content: unknown) => void;
|
|
262
|
+
historySides: (name: string, hostWorkspace: string, guestWorkspace: string) => {
|
|
263
|
+
hostClaudeDir: string;
|
|
264
|
+
guestClaudeDir: string;
|
|
265
|
+
hostWorkspace: string;
|
|
266
|
+
guestWorkspace: string;
|
|
267
|
+
};
|
|
268
|
+
runGuest: (launchPath: string, data: string, workspace: string, extra?: Record<string, string>) => Promise<NodeRun>;
|
|
269
|
+
runBin: (name: string, args: string[], options?: NodeRunOptions) => Promise<NodeRun>;
|
|
270
|
+
readJson: <T = any>(file: string) => T;
|
|
271
|
+
};
|
|
272
|
+
export type Fixtures = ReturnType<typeof createFixtures>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// A fake "host PC" inside the temp folder: Node, claude.exe, a ~/.claude profile, workspaces, and helpers to resolve a
|
|
2
|
+
// run, generate the sandbox for it, run the guest against it and start the CLI. Shared by all suites.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { DEFAULTS } from '../core/config.js';
|
|
6
|
+
import { generateSandbox } from '../host/generate.js';
|
|
7
|
+
import { resolveRun } from '../core/workspace.js';
|
|
8
|
+
export const HOST_NODE_VERSION = 'v22.3.0';
|
|
9
|
+
/** Colour codes are removed from captured output, so tests can match plain text. */
|
|
10
|
+
export const stripAnsi = (text) => text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
11
|
+
/** Runs `node <args>` and collects stdout + stderr (colours removed). Never throws; a hung process is killed at the timeout. */
|
|
12
|
+
export const runNode = (args, options = {}) => new Promise((resolve) => {
|
|
13
|
+
const child = spawn(process.execPath, args, { cwd: options.cwd, env: options.env ?? process.env, windowsHide: true });
|
|
14
|
+
let out = '';
|
|
15
|
+
let timedOut = false;
|
|
16
|
+
const timer = setTimeout(() => { timedOut = true; child.kill(); }, options.timeoutMs ?? 120000);
|
|
17
|
+
child.stdout.on('data', (c) => { out += c.toString(); });
|
|
18
|
+
child.stderr.on('data', (c) => { out += c.toString(); });
|
|
19
|
+
child.on('error', (e) => { out += String(e); });
|
|
20
|
+
child.on('close', (status) => { clearTimeout(timer); resolve({ status: timedOut ? null : status, out: stripAnsi(out), timedOut }); });
|
|
21
|
+
});
|
|
22
|
+
export const createFixtures = (t) => {
|
|
23
|
+
const { at, write } = t;
|
|
24
|
+
// The PC: Node with npm, Claude Code, a ~/.claude profile (with a login that must never be copied) and ~/.claude.json.
|
|
25
|
+
const hostNode = at('host-node');
|
|
26
|
+
write(path.join(hostNode, 'node.exe'), 'x');
|
|
27
|
+
write(path.join(hostNode, 'node_modules', 'npm', 'bin', 'npm-cli.js'), '');
|
|
28
|
+
const hostClaude = at('host-claude');
|
|
29
|
+
write(path.join(hostClaude, 'claude.exe'), 'fake-claude');
|
|
30
|
+
const hostProfile = at('host-dot-claude');
|
|
31
|
+
write(path.join(hostProfile, 'settings.json'), '{ "theme": "dark" }');
|
|
32
|
+
write(path.join(hostProfile, 'CLAUDE.md'), 'my rules');
|
|
33
|
+
write(path.join(hostProfile, 'commands', 'review.md'), 'review cmd');
|
|
34
|
+
write(path.join(hostProfile, 'plugins', 'installed_plugins.json'), JSON.stringify({ path: hostProfile + '\\plugins\\cache\\claude-mem' }));
|
|
35
|
+
write(path.join(hostProfile, '.credentials.json'), '{ "token": "secret" }');
|
|
36
|
+
const hostGlobalState = t.writeJson(at('host-claude.json'), { hasCompletedOnboarding: true, mcpServers: { github: { command: 'npx' } } });
|
|
37
|
+
// The install folder, two workspaces (B has a setup script), a folder to share, the storage root, and tools "from this PC".
|
|
38
|
+
const install = at('install');
|
|
39
|
+
t.dir('install', 'sub');
|
|
40
|
+
const workA = t.dir('work-a');
|
|
41
|
+
const workB = at('work-b');
|
|
42
|
+
t.dir('work-b', 'assets');
|
|
43
|
+
const shared = t.dir('shared');
|
|
44
|
+
t.dir('storage');
|
|
45
|
+
write(path.join(workB, 'setup.mjs'), "import fs from 'node:fs'; import path from 'node:path'; fs.writeFileSync(path.join(process.env.AGENT_DATA, 'hook-ran.txt'), process.env.AGENT_WORKSPACE);\n");
|
|
46
|
+
write(at('host-tools', 'agents', 'claude', 'claude.exe'), 'fake-claude');
|
|
47
|
+
/** The default config, pointed at the fake PC. `copyFromHost = false` makes the sandbox download everything instead. */
|
|
48
|
+
const makeConfig = (copyFromHost = true) => {
|
|
49
|
+
const c = structuredClone(DEFAULTS);
|
|
50
|
+
c.storage.root = at('storage');
|
|
51
|
+
c.storage.dataName = 'testdata';
|
|
52
|
+
c.sandbox.memoryMb = 8192;
|
|
53
|
+
c.provisioning.copyFromHost = copyFromHost;
|
|
54
|
+
c.provisioning.terminal.enabled = false;
|
|
55
|
+
c.harnesses.claude.hostDir = hostClaude;
|
|
56
|
+
c.claudeProfile = { ...c.claudeProfile, hostDir: hostProfile, globalStateFile: hostGlobalState };
|
|
57
|
+
c.personalization.enabled = false;
|
|
58
|
+
c.mappings = [{ host: at('missing-optional'), guest: 'C:\\GameSources\\Missing', readOnly: true, optional: true }];
|
|
59
|
+
return c;
|
|
60
|
+
};
|
|
61
|
+
const baseConfig = makeConfig();
|
|
62
|
+
const probe = { nodeDir: hostNode, harnessExe: { dir: hostClaude, file: 'claude.exe' }, gitDir: null, nvmDir: null, hostNodeVersion: HOST_NODE_VERSION };
|
|
63
|
+
/** Resolves a run for `workA` (or whatever `over` says). */
|
|
64
|
+
const run = (over = {}, config = baseConfig) => resolveRun({ packageRoot: install, loaded: { config, configPath: null, base: t.temp }, cwd: workA, noAgent: false, noLocalConfig: false, trustConfig: false, scripts: [], ...over }, HOST_NODE_VERSION);
|
|
65
|
+
const generate = (r, args = [], p = probe) => generateSandbox({ packageRoot: t.packageRoot, run: r, harnessArgs: args, probe: p });
|
|
66
|
+
/** Resolves a run and generates its sandbox (.wsb + launch folder + session.json) in one step. */
|
|
67
|
+
const launch = async (over = {}, options = {}) => generate(await run(over, options.config ?? baseConfig), options.args, options.probe);
|
|
68
|
+
/** Writes a project's `.sandbox-config.json` (objects are serialised; strings are written as they are). */
|
|
69
|
+
const writeLocal = (dir, content) => { write(path.join(dir, '.sandbox-config.json'), typeof content === 'string' ? content : JSON.stringify(content)); };
|
|
70
|
+
/** The four folders history sync works with, for a workspace on the PC and its place in the sandbox. */
|
|
71
|
+
const historySides = (name, hostWorkspace, guestWorkspace) => ({ hostClaudeDir: at(name + '-host'), guestClaudeDir: at(name + '-guest'), hostWorkspace, guestWorkspace });
|
|
72
|
+
/** Runs the guest's bundled main.js in dry-run mode against fake sandbox folders. */
|
|
73
|
+
const runGuest = async (launchPath, data, workspace, extra = {}) => runNode([path.join(launchPath, 'runtime', 'main.js')], {
|
|
74
|
+
env: { ...process.env, AGENT_LAUNCH_DIR: launchPath, AGENT_DATA_DIR: data, AGENT_HOST_TOOLS_DIR: at('host-tools'), AGENT_WORKSPACE: workspace, AGENT_LOCAL_DIR: at('local'), AGENT_MANAGED_DIR: at('managed'), AGENT_DRY_RUN: '1', ...extra },
|
|
75
|
+
});
|
|
76
|
+
/** Runs a built command (dist/bin/<name>.js) as its own process. */
|
|
77
|
+
const runBin = (name, args, options = {}) => runNode([path.join(t.packageRoot, 'dist', 'bin', name + '.js'), ...args], options);
|
|
78
|
+
const readJson = t.readJson;
|
|
79
|
+
return { at, hostNode, hostClaude, hostProfile, hostGlobalState, install, workA, workB, shared, makeConfig, baseConfig, probe, run, generate, launch, writeLocal, historySides, runGuest, runBin, readJson };
|
|
80
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// What the host generates for a sandbox: the .wsb file, the launch folder (runtime, C# sources, boot scripts),
|
|
2
|
+
// session.json, the staged Claude profile, and the values read from this PC (git identity, free RAM).
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { LocalOverrides } from '../core/schema.js';
|
|
6
|
+
import { gitIdentity } from '../host/launch/stage.js';
|
|
7
|
+
import { buildWsb, resolveMemoryMb } from '../host/launch/wsb.js';
|
|
8
|
+
export const generateSuite = {
|
|
9
|
+
name: 'generate',
|
|
10
|
+
title: 'generated .wsb, launch folder and session.json',
|
|
11
|
+
run: async (t, f) => {
|
|
12
|
+
t.section('the .wsb file');
|
|
13
|
+
const gen = await f.launch({}, { args: ['--version'], probe: { ...f.probe, gitDir: f.shared, nvmDir: f.workB } });
|
|
14
|
+
const wsb = fs.readFileSync(gen.wsbPath, 'utf8');
|
|
15
|
+
t.includes(wsb, '<MemoryInMB>8192</MemoryInMB>', 'wsb: the configured memory');
|
|
16
|
+
t.includes(wsb, '<SandboxFolder>C:\\Workspace\\work-a</SandboxFolder>', 'wsb: the workspace is mapped to its guest folder');
|
|
17
|
+
t.includes(wsb, 'C:\\HostTools\\git', 'wsb: this PC\'s git is mapped');
|
|
18
|
+
t.includes(wsb, 'C:\\HostTools\\nvm', 'wsb: this PC\'s nvm is mapped');
|
|
19
|
+
t.excludes(wsb, 'Missing', 'wsb: an optional mapping whose folder is missing is skipped');
|
|
20
|
+
t.includes(wsb, 'logon.cmd</Command>', 'wsb: the logon command');
|
|
21
|
+
t.includes(wsb, '<vGPU>Disable</vGPU>', 'wsb: vGPU is off');
|
|
22
|
+
t.includes(buildWsb(f.baseConfig, 4096, [{ host: 'C:\\a&b', guest: 'C:\\X', readOnly: true }]), 'C:\\a&b', 'wsb values are XML-escaped');
|
|
23
|
+
t.section('the launch folder');
|
|
24
|
+
const expectedFiles = ['package.json', 'runtime/main.js', 'runtime/daemon.js', 'runtime/view.js', 'runtime/gitguard.js', 'sandbox/bootstrap.ps1', 'sandbox/logon.cmd', 'sandbox/trust.cmd', 'sandbox/ptyhost.cs', 'sandbox/gitshim.cs'];
|
|
25
|
+
const missing = expectedFiles.filter((file) => !fs.existsSync(path.join(gen.launchPath, file)));
|
|
26
|
+
t.equal(missing, [], 'the launch folder has the runtime, the C# sources and the boot scripts');
|
|
27
|
+
t.equal(fs.readdirSync(path.join(gen.launchPath, 'runtime')).length, 5, 'the runtime is 5 self-contained files');
|
|
28
|
+
const logon = fs.readFileSync(path.join(gen.launchPath, 'sandbox', 'logon.cmd'), 'utf8');
|
|
29
|
+
t.check(logon.indexOf('trust.cmd') < logon.indexOf('wt.exe'), 'logon.cmd applies the sandbox trust settings before opening Windows Terminal');
|
|
30
|
+
t.includes(logon, 'tools\\terminal\\wt.exe', 'logon.cmd opens Windows Terminal from the tools folder');
|
|
31
|
+
t.includes(logon, '-NoExit', 'logon.cmd falls back to a PowerShell window');
|
|
32
|
+
t.section('session.json');
|
|
33
|
+
const session = f.readJson(gen.sessionPath);
|
|
34
|
+
t.equal({
|
|
35
|
+
launchId: session.launchId === gen.launchId,
|
|
36
|
+
workspace: session.workspace,
|
|
37
|
+
workspaceHost: session.workspaceHost,
|
|
38
|
+
firstArgument: session.harnessArguments[0],
|
|
39
|
+
gitFromHost: Boolean(session.hostTools.git),
|
|
40
|
+
nvmFromHost: Boolean(session.hostTools.nvm),
|
|
41
|
+
nodeSpec: session.versions.node.spec,
|
|
42
|
+
gitAccess: session.provisioning.git.access,
|
|
43
|
+
}, { launchId: true, workspace: 'C:\\Workspace\\work-a', workspaceHost: f.workA, firstArgument: '--version', gitFromHost: true, nvmFromHost: true, nodeSpec: '22.3.0', gitAccess: 'full' }, 'session.json carries the id, paths, agent arguments, host tools, versions and git access');
|
|
44
|
+
t.matches(session.hostTools.nodeVersion ?? '', /^\d+\.\d+\.\d+$/, 'session.json: the host Node version is a plain version');
|
|
45
|
+
t.equal(session.cpuLimitPercent, 80, 'session.json carries the CPU cap for cpucap.ps1 (default 80 %)');
|
|
46
|
+
t.check(!LocalOverrides.safeParse({ sandbox: { cpuLimit: 'none' } }).success, "a project's .sandbox-config.json cannot lift your CPU cap (sandbox.cpuLimit is yours only)");
|
|
47
|
+
t.section('what is copied from this PC');
|
|
48
|
+
const staged = path.join(gen.launchPath, 'claude-profile');
|
|
49
|
+
const stagedMissing = ['settings.json', 'CLAUDE.md', '.claude.json', 'plugins'].filter((name) => !fs.existsSync(path.join(staged, name)));
|
|
50
|
+
t.equal(stagedMissing, [], 'Claude settings, instructions, global state and plugins are staged');
|
|
51
|
+
t.check(!fs.existsSync(path.join(staged, '.credentials.json')), 'the Claude login is not staged');
|
|
52
|
+
const gitconfig = '[user]\n\tname = Josh\n\temail = "j@x.io"\n[credential]\n\thelper = store\n[url "https://TOKEN@github.com/"]\n\tinsteadOf = https://github.com/\n';
|
|
53
|
+
t.equal(gitIdentity(gitconfig), '[user]\n\tname = Josh\n\temail = j@x.io\n', 'only name/email from ~/.gitconfig reach the sandbox (no credential helpers or token URLs)');
|
|
54
|
+
t.equal(gitIdentity('[core]\n\tname = x\n'), '', 'a name outside [user] is not an identity');
|
|
55
|
+
const auto = { ...f.baseConfig.sandbox, memoryMb: 'auto' };
|
|
56
|
+
const MB = 1048576;
|
|
57
|
+
const pcRam = 64 * 1024 * MB;
|
|
58
|
+
t.equal(resolveMemoryMb(auto, 40000 * MB, pcRam), 31744, "memory 'auto' follows free RAM minus the reserve");
|
|
59
|
+
await t.throws(() => resolveMemoryMb(auto, 9000 * MB, pcRam), 'Not enough free RAM', "memory 'auto' refuses when too little RAM is free");
|
|
60
|
+
t.equal(resolveMemoryMb({ ...auto, maxMemoryPercent: 80 }, 30000 * MB, 32 * 1024 * MB), 21504, "memory 'auto' never takes more than maxMemoryPercent of the PC's RAM");
|
|
61
|
+
t.equal(resolveMemoryMb({ ...auto, maxMemoryPercent: 50 }, 60000 * MB, pcRam), 32768, "memory 'auto' is capped at half the PC's RAM with maxMemoryPercent 50 (free RAM and maxMemoryMb still apply)");
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Git access modes ("full", "readonly", "none"): which git commands the guard allows and the matching Claude deny rules.
|
|
2
|
+
import { gitDecision } from '../guest/gitguard.js';
|
|
3
|
+
import { gitDenyRules } from '../guest/gitrules.js';
|
|
4
|
+
export const gitAccessSuite = {
|
|
5
|
+
name: 'git-access',
|
|
6
|
+
title: 'git access modes and Claude deny rules',
|
|
7
|
+
run: (t) => {
|
|
8
|
+
t.section('readonly');
|
|
9
|
+
const allowed = (args, mode = 'readonly') => gitDecision(args, mode).allow;
|
|
10
|
+
const reading = ['status', 'log --oneline', 'diff HEAD~1', 'show HEAD', 'blame a.ts', 'branch', 'branch -a', 'branch --contains HEAD', 'tag', 'tag -l v*', 'remote -v', 'config --get user.name', 'stash list', '-C sub status', '--no-pager log'];
|
|
11
|
+
t.equal(reading.filter((command) => !allowed(command.split(' '))), [], 'readonly: reading commands are allowed');
|
|
12
|
+
const writing = ['commit -m x', 'push', 'add .', 'checkout main', 'switch -c x', 'reset --hard', 'stash', 'stash pop', 'branch new', 'branch -D x', 'tag v1', 'merge x', 'rebase main', 'fetch', 'pull', 'config user.name x', 'remote add o u', '-c core.hooksPath=x status', '--git-dir=x status', 'clean -fd', 'apply p.diff'];
|
|
13
|
+
t.equal(writing.filter((command) => allowed(command.split(' '))), [], 'readonly: anything that writes (or overrides config) is refused');
|
|
14
|
+
t.section('none and full');
|
|
15
|
+
t.check(!allowed(['status'], 'none') && !allowed(['--version'], 'none'), 'none: every git call is refused');
|
|
16
|
+
t.check(allowed(['commit'], 'full'), 'full: everything is allowed');
|
|
17
|
+
t.section('Claude managed deny rules');
|
|
18
|
+
const readonlyRules = gitDenyRules('readonly');
|
|
19
|
+
const noneRules = gitDenyRules('none');
|
|
20
|
+
t.equal(gitDenyRules('full').length, 0, 'full: no deny rules');
|
|
21
|
+
t.includes(readonlyRules.join('\n'), 'Bash(git push:*)', 'readonly: pushing is denied');
|
|
22
|
+
t.check(!readonlyRules.some((r) => r.startsWith('Read(')), 'readonly: reading .git is not denied');
|
|
23
|
+
t.check(noneRules.includes('Bash(git:*)'), 'none: every git command is denied');
|
|
24
|
+
t.check(noneRules.includes('Read(**/.git/**)'), 'none: reading .git is denied too');
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The guest's boot script (runtime/main.js) run in dry-run mode against fake sandbox folders: what it provisions,
|
|
2
|
+
// what it copies in, and what it refuses to.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readJsonFile } from '../shared/rundir.js';
|
|
6
|
+
export const guestBootSuite = {
|
|
7
|
+
name: 'guest-boot',
|
|
8
|
+
title: 'guest boot in dry-run mode (provisioning, Claude state, git policy)',
|
|
9
|
+
run: async (t, f) => {
|
|
10
|
+
const gen = await f.launch({}, { args: ['--version'], probe: { ...f.probe, gitDir: f.shared, nvmDir: f.workB } });
|
|
11
|
+
t.section('first boot');
|
|
12
|
+
const data1 = t.at('data1');
|
|
13
|
+
const boot = await f.runGuest(gen.launchPath, data1, f.workA, { AGENT_TEST_EXEMPT: '172.28.0.1' });
|
|
14
|
+
t.check(boot.status === 0, 'the guest boots and exits cleanly', 'exit code ' + boot.status + '\n' + boot.out);
|
|
15
|
+
t.includes(boot.out, 'would run:', 'the guest starts the agent...');
|
|
16
|
+
t.includes(boot.out, '--version', '... with its arguments...');
|
|
17
|
+
t.includes(boot.out, 'cwd=' + f.workA, '... in the workspace');
|
|
18
|
+
for (const step of ['would provision nvm and Node 22.3.0 (from this PC)', 'would provision Python 3', 'would provision MinGW-w64 GCC', 'would provision Rust', '.NET SDK', 'would provision bun', 'would provision uv', 'would provision Git for Windows from this PC']) {
|
|
19
|
+
t.includes(boot.out, step, 'the guest provisions: ' + step);
|
|
20
|
+
}
|
|
21
|
+
t.includes(boot.out, 'AgentSandbox-Block-Private', 'the firewall plan blocks the LAN...');
|
|
22
|
+
t.includes(boot.out, 'remoteport=1-52,54-65535', '... but keeps DNS to the gateway');
|
|
23
|
+
t.equal(readJsonFile(path.join(data1, 'run', 'sandbox.json'))?.phase, 'ready', 'the guest reports "ready" in run/sandbox.json');
|
|
24
|
+
t.section('Claude state copied in');
|
|
25
|
+
const claudeState = path.join(data1, 'state', 'claude');
|
|
26
|
+
t.includes(fs.readFileSync(path.join(claudeState, 'settings.json'), 'utf8'), 'dark', 'the guest applied this PC\'s Claude settings');
|
|
27
|
+
t.check(fs.existsSync(path.join(claudeState, 'commands', 'review.md')), 'custom commands are copied');
|
|
28
|
+
t.check(!fs.existsSync(path.join(claudeState, '.credentials.json')), 'the login is not copied');
|
|
29
|
+
const plugins = fs.readFileSync(path.join(claudeState, 'plugins', 'installed_plugins.json'), 'utf8');
|
|
30
|
+
t.includes(plugins, JSON.stringify(claudeState).slice(1, -1), 'plugin manifests are re-pointed at the sandbox Claude folder');
|
|
31
|
+
const guestState = f.readJson(path.join(claudeState, '.claude.json'));
|
|
32
|
+
t.equal(guestState.mcpServers?.github?.command, 'npx', 'MCP servers from this PC are in the sandbox');
|
|
33
|
+
const manifest = fs.readFileSync(f.at('managed', 'CLAUDE.md'), 'utf8');
|
|
34
|
+
t.includes(manifest, 'DISPOSABLE Windows Sandbox', 'the agent manifest goes to the managed CLAUDE.md...');
|
|
35
|
+
t.includes(manifest, f.workA, '... naming the workspace');
|
|
36
|
+
t.check(manifest.split('\n').length < 16, 'the managed manifest stays tiny', manifest.split('\n').length + ' lines');
|
|
37
|
+
t.equal(fs.readFileSync(path.join(claudeState, 'CLAUDE.md'), 'utf8'), 'my rules', 'the user\'s own CLAUDE.md is untouched');
|
|
38
|
+
t.check(fs.existsSync(path.join(data1, 'SANDBOX.md')), 'SANDBOX.md is written to the data folder');
|
|
39
|
+
t.check(!fs.existsSync(f.at('managed', 'managed-settings.json')), 'git access full: no managed deny rules');
|
|
40
|
+
t.section('second boot');
|
|
41
|
+
// The host changed its MCP servers: the sandbox keeps its own login and .claude.json but gets the new servers.
|
|
42
|
+
fs.writeFileSync(path.join(claudeState, '.claude.json'), JSON.stringify({ ...guestState, oauthAccount: { emailAddress: 'sandbox@x' }, mcpServers: { local: { command: 'x' } } }));
|
|
43
|
+
t.writeJson(path.join(gen.launchPath, 'claude-profile', '.claude.json'), { mcpServers: { github: { command: 'npx2' } }, projects: { [f.workA.replace(/\\/g, '/')]: { mcpServers: { db: { command: 'pg' } } } } });
|
|
44
|
+
await f.runGuest(gen.launchPath, data1, f.workA);
|
|
45
|
+
const merged = f.readJson(path.join(claudeState, '.claude.json'));
|
|
46
|
+
t.equal(merged.oauthAccount.emailAddress, 'sandbox@x', 'MCP re-sync leaves the sandbox login alone');
|
|
47
|
+
t.equal(merged.mcpServers.github.command, 'npx2', 'MCP re-sync updates a server changed on this PC');
|
|
48
|
+
t.equal(merged.mcpServers.local.command, 'x', 'MCP re-sync keeps a server added in the sandbox');
|
|
49
|
+
const projectServers = Object.values(merged.projects);
|
|
50
|
+
t.check(projectServers.some((p) => p.mcpServers?.db), 'MCP re-sync brings in project-scope servers', JSON.stringify(merged.projects));
|
|
51
|
+
t.section('other modes');
|
|
52
|
+
const readonlyConfig = f.makeConfig();
|
|
53
|
+
readonlyConfig.provisioning.git.access = 'readonly';
|
|
54
|
+
const genRo = await f.launch({}, { config: readonlyConfig });
|
|
55
|
+
await f.runGuest(genRo.launchPath, t.at('data-ro'), f.workA, { AGENT_MANAGED_DIR: t.at('managed-ro') });
|
|
56
|
+
const deny = f.readJson(t.at('managed-ro', 'managed-settings.json')).permissions.deny;
|
|
57
|
+
t.check(deny.includes('Bash(git commit:*)') && deny.includes('PowerShell(git push:*)'), 'git readonly: Claude managed settings deny writing git commands', JSON.stringify(deny));
|
|
58
|
+
t.check(deny.includes('Edit(**/.git/**)'), 'git readonly: .git edits are denied');
|
|
59
|
+
t.check(!deny.includes('Bash(git status:*)'), 'git readonly: reading git commands stay allowed');
|
|
60
|
+
f.writeLocal(f.workB, { scripts: ['./setup.mjs'] });
|
|
61
|
+
const genHook = await f.launch({ cwd: f.workB, harness: 'claude', trustConfig: true });
|
|
62
|
+
const hook = await f.runGuest(genHook.launchPath, t.at('data-hook'), f.workB);
|
|
63
|
+
t.check(hook.status === 0, 'a project with "scripts" boots', hook.out);
|
|
64
|
+
t.equal(fs.readFileSync(t.at('data-hook', 'hook-ran.txt'), 'utf8'), f.workB, 'local "scripts" run inside the sandbox with the workspace set');
|
|
65
|
+
const genInstall = await f.launch({}, { config: f.makeConfig(false), probe: { ...f.probe, nodeDir: null, harnessExe: null } });
|
|
66
|
+
const install = await f.runGuest(genInstall.launchPath, t.at('data3'), f.workA, { AGENT_HOST_TOOLS_DIR: t.at('empty') });
|
|
67
|
+
t.check(install.status === 0, 'install mode boots', install.out);
|
|
68
|
+
t.includes(install.out, 'npm-cli.js', 'install mode: npm is used...');
|
|
69
|
+
t.includes(install.out, '@anthropic-ai/claude-code', '... to install the agent');
|
|
70
|
+
t.excludes(fs.readFileSync(genInstall.wsbPath, 'utf8'), 'HostTools', 'install mode: nothing is mapped from this PC');
|
|
71
|
+
const genNoAgent = await f.launch({ noAgent: true });
|
|
72
|
+
const noAgent = await f.runGuest(genNoAgent.launchPath, t.at('data5'), f.workA);
|
|
73
|
+
t.check(noAgent.status === 0, '--no-agent boots', noAgent.out);
|
|
74
|
+
t.includes(noAgent.out, 'no agent requested', '--no-agent provisions without an agent');
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Text a failure message must contain (string) or match (RegExp). */
|
|
2
|
+
export type Expected = string | RegExp;
|
|
3
|
+
export interface Harness {
|
|
4
|
+
packageRoot: string;
|
|
5
|
+
/** The folder every test works in; removed at the end. */
|
|
6
|
+
temp: string;
|
|
7
|
+
section: (title: string) => void;
|
|
8
|
+
/** Prints a SKIP line (not a check). */
|
|
9
|
+
skip: (what: string) => void;
|
|
10
|
+
/** The basic assertion. `detail` is printed under a failing check, so say what was found. */
|
|
11
|
+
check: (ok: boolean, name: string, detail?: string) => void;
|
|
12
|
+
/** Deep equality; on failure both values are shown. */
|
|
13
|
+
equal: <T>(actual: T, expected: T, name: string) => void;
|
|
14
|
+
/** `text` contains `needle`; on failure the text is shown. */
|
|
15
|
+
includes: (text: string, needle: string, name: string) => void;
|
|
16
|
+
/** `text` does not contain `needle`. */
|
|
17
|
+
excludes: (text: string, needle: string, name: string) => void;
|
|
18
|
+
/** `text` matches the pattern. */
|
|
19
|
+
matches: (text: string, pattern: RegExp, name: string) => void;
|
|
20
|
+
/** `fn` (sync or async) throws an error whose message (or `fix` hint) contains/matches `expected`. */
|
|
21
|
+
throws: (fn: () => unknown, expected: Expected, name: string) => Promise<void>;
|
|
22
|
+
/** Runs `fn` and returns the message of the error it throws ('' when it does not throw). */
|
|
23
|
+
thrownMessage: (fn: () => unknown) => Promise<string>;
|
|
24
|
+
/** Path below the temp folder (nothing is created). */
|
|
25
|
+
at: (...parts: string[]) => string;
|
|
26
|
+
/** Creates (if needed) and returns a folder below the temp folder. */
|
|
27
|
+
dir: (...parts: string[]) => string;
|
|
28
|
+
/** Writes a text file below anywhere in the temp folder (parents are created) and returns its path. */
|
|
29
|
+
write: (file: string, content: string) => string;
|
|
30
|
+
/** Writes `value` as JSON and returns the path. */
|
|
31
|
+
writeJson: (file: string, value: unknown) => string;
|
|
32
|
+
readJson: <T = any>(file: string) => T;
|
|
33
|
+
/**
|
|
34
|
+
* Sets a file's modified time to `secondsFromNow` (default: 10 s ahead). Sync decides what changed by comparing
|
|
35
|
+
* modified times, so tests make a change clearly newer this way instead of sleeping.
|
|
36
|
+
*/
|
|
37
|
+
touch: (file: string, secondsFromNow?: number) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Polls `read` every `everyMs` until it returns something truthy and returns that, or returns null after `ms`.
|
|
40
|
+
* Use this instead of fixed sleeps: it is fast when the machine is idle and patient when it is busy.
|
|
41
|
+
*/
|
|
42
|
+
waitFor: <T>(read: () => T | null | undefined | false | Promise<T | null | undefined | false>, ms?: number, everyMs?: number) => Promise<T | null>;
|
|
43
|
+
}
|
|
44
|
+
export interface RunningHarness extends Harness {
|
|
45
|
+
/** Number of checks that failed so far. */
|
|
46
|
+
failures: () => number;
|
|
47
|
+
/** Number of checks that ran so far (passed or failed). */
|
|
48
|
+
checks: () => number;
|
|
49
|
+
dispose: () => void;
|
|
50
|
+
}
|
|
51
|
+
export declare const createHarness: (packageRoot: string) => RunningHarness;
|