windows-development-sandbox 1.0.5 → 1.4.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/LICENSE +21 -0
- package/README.md +25 -8
- package/config.example.json +55 -5
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.js +38 -4
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.js +2 -2
- 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 +2 -0
- package/dist/cli/commands/context.js +5 -1
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +106 -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 +6 -4
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +256 -42
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +67 -5
- 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.js → repair/checks.js} +19 -181
- package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
- 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 +2 -1
- package/dist/cli/commands/sessions.js +189 -45
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
- 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 +7 -1
- package/dist/cli/index.js +42 -15
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +40 -15
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/menu/several.d.ts +20 -0
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +12 -6
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +23 -0
- package/dist/core/local-config.js +79 -7
- 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/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -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 +659 -0
- package/dist/core/schema/config.js +75 -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 +32 -0
- package/dist/core/schema/profile.js +31 -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 +78 -0
- package/dist/core/schema/sandbox.js +64 -0
- package/dist/core/schema/toolchains.d.ts +96 -0
- package/dist/core/schema/toolchains.js +88 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +35 -1
- package/dist/core/util.d.ts +9 -0
- package/dist/core/util.js +14 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +33 -50
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -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/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -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.js +12 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +3 -0
- package/dist/guest/env.js +7 -3
- package/dist/guest/guide.d.ts +6 -2
- package/dist/guest/guide.js +5 -4
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.js +31 -8
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -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 +0 -18
- package/dist/guest/ptyhost.js +17 -65
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +59 -5
- package/dist/guest/setup/environment.d.ts +13 -3
- package/dist/guest/setup/environment.js +14 -10
- package/dist/guest/setup/hooks.d.ts +9 -1
- package/dist/guest/setup/hooks.js +28 -14
- package/dist/guest/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- package/dist/guest/toolchains/catalogue.d.ts +4 -0
- package/dist/guest/toolchains/catalogue.js +2 -2
- package/dist/guest/toolchains/cli.js +25 -17
- package/dist/guest/toolchains/common.d.ts +18 -1
- package/dist/guest/toolchains/common.js +34 -11
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
- 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.js +25 -16
- package/dist/guest/toolchains/go.js +1 -1
- package/dist/guest/toolchains/index.d.ts +13 -9
- package/dist/guest/toolchains/index.js +40 -82
- package/dist/guest/toolchains/java.js +5 -5
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.js +12 -10
- package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
- 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 +56 -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.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +24 -0
- package/dist/guest/toolchains/plan.js +140 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +43 -12
- package/dist/guest/toolchains/runtimes.d.ts +2 -0
- package/dist/guest/toolchains/runtimes.js +6 -8
- 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/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- package/dist/guest/view.js +21 -1
- package/dist/host/accounts.js +14 -5
- 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 +70 -0
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -0
- package/dist/host/channel.d.ts +23 -22
- package/dist/host/channel.js +66 -84
- package/dist/host/claude-profile.js +7 -3
- package/dist/host/cleanup.js +2 -11
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +10 -12
- package/dist/host/generate.js +58 -135
- 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 +54 -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/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/platform.d.ts +2 -0
- package/dist/host/platform.js +16 -1
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- package/dist/host/project-scan.d.ts +1 -17
- package/dist/host/project-scan.js +2 -86
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +25 -5
- package/dist/host/repo-guard.js +242 -23
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +48 -1
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.js +7 -3
- package/dist/host/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +11791 -2821
- package/dist/runtime/db.js +6585 -466
- package/dist/runtime/main.js +10238 -1724
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +6183 -59
- 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/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +44 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +145 -27
- 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 -3
- package/dist/selftest/config.test.js +36 -72
- package/dist/selftest/data.test.d.ts +2 -2
- package/dist/selftest/data.test.js +77 -70
- package/dist/selftest/detect.test.d.ts +2 -2
- package/dist/selftest/detect.test.js +54 -38
- package/dist/selftest/fixtures.d.ts +89 -4
- package/dist/selftest/fixtures.js +43 -24
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +68 -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 +44 -7
- package/dist/selftest/harness.js +62 -21
- 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 -1
- package/dist/selftest/index.js +137 -35
- 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-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -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 +120 -0
- package/dist/selftest/mirror.test.d.ts +2 -3
- package/dist/selftest/mirror.test.js +92 -65
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +40 -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 +121 -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 -2
- package/dist/selftest/setup.test.js +45 -22
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -3
- package/dist/selftest/sync.test.js +130 -78
- 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 +133 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- package/dist/shared/paths.d.ts +5 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +12 -1
- package/dist/shared/rundir.js +14 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +453 -0
- package/dist/shared/schema.js +43 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +6 -3
- package/dist/sync/history.d.ts +29 -12
- package/dist/sync/history.js +72 -25
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +99 -8
- package/package.json +7 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +20 -0
- package/sandbox/logon.cmd +24 -16
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +120 -11
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +403 -3
- package/schemas/sandbox-config.schema.json +143 -13
- package/dist/cli/commands/data.d.ts +0 -46
- package/dist/cli/commands/data.js +0 -249
- package/dist/cli/commands/setup.js +0 -152
- package/dist/cli/menu.d.ts +0 -22
- package/dist/cli/menu.js +0 -187
- package/dist/cli/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Checks what the user typed before anything starts, so a typo fails at once with the list of valid values
|
|
2
|
+
// instead of halfway into a sandbox boot. Pure helpers first, then validateOptions (the one call from the dispatcher).
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DATABASES } from '../core/detect-databases.js';
|
|
6
|
+
import { fail } from '../core/util.js';
|
|
7
|
+
import { listAccounts } from '../host/accounts.js';
|
|
8
|
+
/** Edit distance, for "did you mean". */
|
|
9
|
+
export const distance = (a, b) => {
|
|
10
|
+
let row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
11
|
+
for (let i = 1; i <= a.length; i++) {
|
|
12
|
+
const next = [i];
|
|
13
|
+
for (let j = 1; j <= b.length; j++)
|
|
14
|
+
next[j] = Math.min(row[j] + 1, next[j - 1] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
15
|
+
row = next;
|
|
16
|
+
}
|
|
17
|
+
return row[b.length];
|
|
18
|
+
};
|
|
19
|
+
/** The closest of `choices` to `input`, when it is close enough to be a typo. */
|
|
20
|
+
export const closest = (input, choices) => {
|
|
21
|
+
const best = choices.map((c) => ({ c, d: distance(input.toLowerCase(), c.toLowerCase()) })).sort((x, y) => x.d - y.d)[0];
|
|
22
|
+
return best && best.d <= Math.max(2, Math.floor(input.length / 3)) ? best.c : undefined;
|
|
23
|
+
};
|
|
24
|
+
/** "Unknown agent "cluade". Did you mean claude?" with the valid values as the fix. */
|
|
25
|
+
export const unknownValue = (what, input, choices, howToList) => {
|
|
26
|
+
const hint = closest(input, choices);
|
|
27
|
+
return fail('Unknown ' + what + ' "' + input + '".' + (hint ? ' Did you mean ' + hint + '?' : ''), (choices.length ? 'Choose one of: ' + choices.join(', ') + '. ' : '') + howToList);
|
|
28
|
+
};
|
|
29
|
+
/** Node: 22, 22.1, v22.1.0, lts, lts/*, lts/iron, latest, current, node. */
|
|
30
|
+
export const validNodeSpec = (spec) => /^(v?\d+(\.\d+){0,2}|node|latest|current|stable|lts(\/(\*|[a-z]+))?)$/i.test(spec.trim());
|
|
31
|
+
/** Python 3: 3, 3.12, 3.12.4. */
|
|
32
|
+
export const validPythonSpec = (spec) => /^v?3(\.\d+){0,2}$/.test(spec.trim());
|
|
33
|
+
export const checkHarness = (name, harnesses) => {
|
|
34
|
+
if (name !== undefined && !harnesses.includes(name))
|
|
35
|
+
unknownValue('agent', name, harnesses, 'Add your own under "harnesses" in your settings (sandbox config).');
|
|
36
|
+
};
|
|
37
|
+
/** Commands that read these options; checked only for them (restart/doctor take --harness too). */
|
|
38
|
+
const LAUNCHING = new Set(['launch', 'generate', 'host-claude']);
|
|
39
|
+
export const PROJECT_ACTIONS = ['list', 'choose', 'add', 'remove', 'include', 'exclude', 'all', 'roots'];
|
|
40
|
+
/** `sandbox projects <action> [value] [folder]`: the action, then what it needs. Pure (no file checks). */
|
|
41
|
+
export const validateProjectsArgs = (args) => {
|
|
42
|
+
const [action = 'list', value, folder] = args;
|
|
43
|
+
if (!PROJECT_ACTIONS.includes(action))
|
|
44
|
+
unknownValue('projects action', action, PROJECT_ACTIONS, 'sandbox projects with no action lists your projects.');
|
|
45
|
+
if (['add', 'remove', 'include', 'exclude'].includes(action) && !value)
|
|
46
|
+
fail('Which folder?', 'Usage: sandbox projects ' + action + ' <folder> (. = this folder)');
|
|
47
|
+
if (action === 'all' && value !== undefined && !['on', 'off'].includes(value))
|
|
48
|
+
unknownValue('value for projects all', value, ['on', 'off'], 'sandbox projects all on|off');
|
|
49
|
+
if (action === 'roots' && value !== undefined && !['list', 'add', 'remove'].includes(value))
|
|
50
|
+
unknownValue('projects roots action', value, ['list', 'add', 'remove'], 'sandbox projects roots add|remove <folder>');
|
|
51
|
+
if (action === 'roots' && (value === 'add' || value === 'remove') && !folder)
|
|
52
|
+
fail('Which folder?', 'Usage: sandbox projects roots ' + value + ' <folder>');
|
|
53
|
+
};
|
|
54
|
+
/** Fails with what is wrong and what is valid, before anything is started. */
|
|
55
|
+
export const validateOptions = (ctx) => {
|
|
56
|
+
const o = ctx.options;
|
|
57
|
+
const harnesses = Object.keys(ctx.loaded.config.harnesses);
|
|
58
|
+
if (['launch', 'generate', 'restart', 'doctor'].includes(o.command))
|
|
59
|
+
checkHarness(o.harness, harnesses);
|
|
60
|
+
if (o.command === 'projects')
|
|
61
|
+
validateProjectsArgs(o.positionals);
|
|
62
|
+
if (!LAUNCHING.has(o.command))
|
|
63
|
+
return;
|
|
64
|
+
if (o.node !== undefined && !validNodeSpec(o.node))
|
|
65
|
+
fail('"' + o.node + '" is not a Node version.', 'Use a number like 22 or 22.11.0, or lts, latest.');
|
|
66
|
+
if (o.python !== undefined && !validPythonSpec(o.python))
|
|
67
|
+
fail('"' + o.python + '" is not a Python 3 version.', 'Use 3, 3.12 or 3.12.4.');
|
|
68
|
+
for (const db of o.databases.flatMap((d) => d.split(',')).map((d) => d.trim()).filter(Boolean)) {
|
|
69
|
+
if (!DATABASES.includes(db))
|
|
70
|
+
unknownValue('database', db, DATABASES, 'Repeat --database for several.');
|
|
71
|
+
}
|
|
72
|
+
if (o.account !== undefined) {
|
|
73
|
+
const names = listAccounts(ctx.storageRoot, ctx.dataPath).map((a) => a.name);
|
|
74
|
+
if (!names.includes(o.account))
|
|
75
|
+
unknownValue('account', o.account, names, names.length ? 'See them with: sandbox account list' : 'None saved yet: sign in inside the sandbox, then run sandbox account save <name>.');
|
|
76
|
+
}
|
|
77
|
+
for (const s of o.sessions) {
|
|
78
|
+
checkHarness(s.harness, harnesses);
|
|
79
|
+
if (!fs.existsSync(s.folder) || !fs.statSync(s.folder).isDirectory())
|
|
80
|
+
fail('The folder ' + s.folder + ' (--session ' + s.harness + '@...) does not exist.', 'Check the path after the @.');
|
|
81
|
+
}
|
|
82
|
+
if (o.sessions.length && (o.harness !== undefined || o.workspace !== undefined))
|
|
83
|
+
fail('--session names the agent and folder of each session, so --harness and --workspace do not apply.', 'Use only --session <agent>@<folder> (repeatable).');
|
|
84
|
+
if (o.workspace !== undefined && !fs.existsSync(path.resolve(o.workspace)))
|
|
85
|
+
fail('The folder ' + path.resolve(o.workspace) + ' does not exist.', 'Check the --workspace path (or leave it out to use the current folder).');
|
|
86
|
+
for (const script of o.scripts)
|
|
87
|
+
if (!fs.existsSync(path.resolve(script)))
|
|
88
|
+
fail('The script ' + path.resolve(script) + ' does not exist.', 'Check the --script path.');
|
|
89
|
+
};
|
package/dist/core/config.js
CHANGED
|
@@ -8,11 +8,14 @@ export const DEFAULTS = {
|
|
|
8
8
|
defaultHarness: 'claude',
|
|
9
9
|
guestWorkspace: 'auto',
|
|
10
10
|
readOnly: false,
|
|
11
|
+
workspaceFilter: { enabled: false, exclude: [], include: [] },
|
|
12
|
+
audit: { enabled: false, days: 14, maxMb: 100, processes: true, files: true },
|
|
11
13
|
mappings: [],
|
|
12
|
-
sandbox: { memoryMb: 'auto', maxMemoryMb: 32768, reserveHostMb: 8192, vGpu: false, networking: true, clipboard: true, audioInput: false, videoInput: false, printerRedirection: false, protectedClient: true, headless: false },
|
|
13
|
-
storage: { root: '%LOCALAPPDATA%\\AgentSandbox', persistAgentState: true, dataName: 'default' },
|
|
14
|
-
network: { blockPrivateRanges: true },
|
|
15
|
-
provisioning: { copyFromHost: true, fallbackToInstall: true, refresh: false, git: { enabled: true, access: 'full' }, terminal: { enabled: true } },
|
|
14
|
+
sandbox: { memoryMb: 'auto', maxMemoryMb: 32768, maxMemoryPercent: 80, cpuLimit: 80, pageFileMb: 16384, reserveHostMb: 8192, vGpu: false, networking: true, clipboard: true, audioInput: false, videoInput: false, printerRedirection: false, protectedClient: true, headless: false, keepAwake: true },
|
|
15
|
+
storage: { root: '%LOCALAPPDATA%\\AgentSandbox', persistAgentState: true, dataName: 'default', markSandboxFiles: true, warnDataGb: 50 },
|
|
16
|
+
network: { blockPrivateRanges: true, allowOnly: [], blockHosts: [], forwardPorts: [], dns: [], proxy: '' },
|
|
17
|
+
provisioning: { copyFromHost: true, fallbackToInstall: true, refresh: false, git: { enabled: true, access: 'full' }, terminal: { enabled: true }, conpty: { enabled: true } },
|
|
18
|
+
downloads: { requireChecksum: false },
|
|
16
19
|
toolchains: {
|
|
17
20
|
node: { version: 'host', extraVersions: [], corepack: true, volta: 'auto', globalPackages: [] },
|
|
18
21
|
python: { enabled: 'auto', version: '3', python2: true, pyenv: 'auto', tools: [] },
|
|
@@ -32,7 +35,7 @@ export const DEFAULTS = {
|
|
|
32
35
|
uv: { enabled: true },
|
|
33
36
|
vcRuntime: { enabled: false },
|
|
34
37
|
mesa: { enabled: false, vulkan: false },
|
|
35
|
-
vscode: { enabled: true },
|
|
38
|
+
vscode: { enabled: true, setup: true, recommended: true, extensions: [] },
|
|
36
39
|
environment: {},
|
|
37
40
|
},
|
|
38
41
|
databases: {
|
|
@@ -44,9 +47,11 @@ export const DEFAULTS = {
|
|
|
44
47
|
sqlserver: { enabled: 'auto' },
|
|
45
48
|
},
|
|
46
49
|
attach: { enabled: true, reuseSession: true, auto: false, autoHide: false },
|
|
50
|
+
agentCommands: { enabled: false, agents: 'auto' },
|
|
47
51
|
secrets: { strip: true, allow: [], patterns: [], warnFiles: true },
|
|
48
52
|
personalization: { enabled: true },
|
|
49
|
-
|
|
53
|
+
apps: { install: [], custom: {} },
|
|
54
|
+
claudeProfile: { enabled: true, hostDir: 'auto', include: ['settings.json', 'CLAUDE.md', 'commands', 'agents', 'skills', 'plugins'], globalStateFile: 'auto', copyCredentials: false, overwrite: true, syncHistory: true, historyScope: 'project', syncClaudeMem: true, syncClaudeMemBack: false },
|
|
50
55
|
harnesses: {
|
|
51
56
|
claude: { hostExecutable: 'claude.exe', hostDir: 'auto', npmPackage: '@anthropic-ai/claude-code@latest', npmBin: 'claude', arguments: [], environment: { DISABLE_AUTOUPDATER: '1' }, resumeArguments: ['--continue'] },
|
|
52
57
|
codex: { npmPackage: '@openai/codex@latest', npmBin: 'codex', arguments: [], environment: {}, resumeArguments: ['resume', '--last'] },
|
|
@@ -67,6 +72,7 @@ export const DEFAULTS = {
|
|
|
67
72
|
aider: { uvPackage: 'aider-chat', uvBin: 'aider', arguments: [], environment: {}, resumeArguments: ['--restore-chat-history'] },
|
|
68
73
|
},
|
|
69
74
|
hooks: { failFast: false },
|
|
75
|
+
projects: { all: false, exclude: [], roots: [], askToAddRoot: true, declinedRoots: [], wsbShare: true },
|
|
70
76
|
};
|
|
71
77
|
/** Per-user folder for config and hooks. Survives reinstalling the npm package. */
|
|
72
78
|
export const userDir = () => process.env.AGENT_SANDBOX_HOME ?? path.join(homeDir(), '.agent-sandbox');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const GB: number;
|
|
2
|
+
/** 1536 -> "1.5 KB". */
|
|
3
|
+
export declare const humanBytes: (bytes: number) => string;
|
|
4
|
+
/** Size on disk, never following links or junctions (a link counts as 0, so a planted junction cannot make us walk this PC's disk). */
|
|
5
|
+
export declare const sizeOf: (p: string) => number;
|
|
6
|
+
/** Free bytes on the drive that holds `dir` (which may not exist yet: the nearest existing parent is asked); null if unknown. */
|
|
7
|
+
export declare const freeBytesAt: (dir: string) => number | null;
|
|
8
|
+
export interface DiskUsage {
|
|
9
|
+
drive: string;
|
|
10
|
+
totalBytes: number;
|
|
11
|
+
freeBytes: number;
|
|
12
|
+
usedBytes: number;
|
|
13
|
+
holdsStorage: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Every local drive's size and free space, and which one holds the sandbox storage. */
|
|
16
|
+
export declare const diskUsage: (storageRoot: string) => DiskUsage[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Sizes and free space on disk, shared by `data`, `repair`, `setup` and the storage cleanup.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
export const GB = 1024 ** 3;
|
|
6
|
+
/** 1536 -> "1.5 KB". */
|
|
7
|
+
export const humanBytes = (bytes) => {
|
|
8
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
9
|
+
const i = bytes <= 0 ? 0 : Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
10
|
+
const value = bytes / 1024 ** i;
|
|
11
|
+
return (i === 0 ? String(bytes) : value.toFixed(value < 10 ? 1 : 0)) + ' ' + units[i];
|
|
12
|
+
};
|
|
13
|
+
/** Size on disk, never following links or junctions (a link counts as 0, so a planted junction cannot make us walk this PC's disk). */
|
|
14
|
+
export const sizeOf = (p) => {
|
|
15
|
+
try {
|
|
16
|
+
const stat = fs.lstatSync(p);
|
|
17
|
+
if (stat.isSymbolicLink())
|
|
18
|
+
return 0;
|
|
19
|
+
if (!stat.isDirectory())
|
|
20
|
+
return stat.size;
|
|
21
|
+
return _.sumBy(fs.readdirSync(p, { withFileTypes: true }), (d) => (d.isSymbolicLink() ? 0 : sizeOf(path.join(p, d.name))));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/** Free bytes on the drive that holds `dir` (which may not exist yet: the nearest existing parent is asked); null if unknown. */
|
|
28
|
+
export const freeBytesAt = (dir) => {
|
|
29
|
+
let at = path.resolve(dir);
|
|
30
|
+
while (!fs.existsSync(at) && path.dirname(at) !== at)
|
|
31
|
+
at = path.dirname(at);
|
|
32
|
+
try {
|
|
33
|
+
const s = fs.statfsSync(at);
|
|
34
|
+
return s.bavail * s.bsize;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** Every local drive's size and free space, and which one holds the sandbox storage. */
|
|
41
|
+
export const diskUsage = (storageRoot) => {
|
|
42
|
+
const storageDrive = path.parse(path.resolve(storageRoot)).root.toUpperCase();
|
|
43
|
+
return 'CDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((letter) => letter + ':\\').flatMap((drive) => {
|
|
44
|
+
try {
|
|
45
|
+
const s = fs.statfsSync(drive);
|
|
46
|
+
const totalBytes = s.blocks * s.bsize;
|
|
47
|
+
if (!totalBytes)
|
|
48
|
+
return [];
|
|
49
|
+
return [{ drive, totalBytes, freeBytes: s.bavail * s.bsize, usedBytes: totalBytes - s.bfree * s.bsize, holdsStorage: drive.toUpperCase() === storageDrive }];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
@@ -2,6 +2,20 @@ import type { Config, LocalConfigFile, Overrides } from './types.js';
|
|
|
2
2
|
export declare const LOCAL_CONFIG_NAME = ".sandbox-config.json";
|
|
3
3
|
/** Validates a folder's settings and resolves its relative paths against that folder. */
|
|
4
4
|
export declare const normalizeOverrides: (data: unknown, dir: string, source: string) => Overrides;
|
|
5
|
+
/**
|
|
6
|
+
* Protections a project file may only make stricter, never looser. A repo can ship the file, so it must not be able to
|
|
7
|
+
* switch off what you turned on (or turn on what you turned off). The strictest of the two values always wins.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ON_IS_SAFER: string[];
|
|
10
|
+
export declare const OFF_IS_SAFER: string[];
|
|
11
|
+
export declare const GIT_STRICTNESS: Record<string, number>;
|
|
12
|
+
export declare const NEVER_LOOSER: string[];
|
|
13
|
+
/** What a project file asks for that would loosen one of your protections (so it is not used). */
|
|
14
|
+
export declare const loosenedRequests: (config: Config, ov: Overrides) => {
|
|
15
|
+
key: string;
|
|
16
|
+
wanted: unknown;
|
|
17
|
+
yours: unknown;
|
|
18
|
+
}[];
|
|
5
19
|
/** Applies validated overrides. Mappings with the same guest folder replace the configured ones. */
|
|
6
20
|
export declare const applyOverrides: (config: Config, ov: Overrides) => Config;
|
|
7
21
|
/** Human-readable list of what a local config changes, with risky items flagged. */
|
|
@@ -10,3 +24,12 @@ export declare const readLocalConfig: (dir: string) => LocalConfigFile | null;
|
|
|
10
24
|
export declare const trustStorePath: (storageRoot: string) => string;
|
|
11
25
|
export declare const isTrusted: (store: string, file: string, hash: string) => boolean;
|
|
12
26
|
export declare const saveTrust: (store: string, file: string, hash: string) => void;
|
|
27
|
+
export declare const allowStorePath: (storageRoot: string) => string;
|
|
28
|
+
/** true = you allowed it, false = you said no (not asked again), null = not asked yet. */
|
|
29
|
+
export declare const allowedAnswer: (store: string, file: string, setting: string, value: unknown) => boolean | null;
|
|
30
|
+
export declare const saveAllowed: (store: string, file: string, setting: string, value: unknown, allow: boolean) => void;
|
|
31
|
+
/** Your config with these requests let through, so applyOverrides' "strictest wins" keeps the project's value. */
|
|
32
|
+
export declare const allowRequests: (config: Config, allowed: {
|
|
33
|
+
key: string;
|
|
34
|
+
wanted: unknown;
|
|
35
|
+
}[]) => Config;
|
|
@@ -4,9 +4,10 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import _ from 'lodash';
|
|
7
|
+
import { z } from 'zod';
|
|
7
8
|
import { mergeConfig } from './config.js';
|
|
8
9
|
import { describeIssues, LocalOverrides } from './schema.js';
|
|
9
|
-
import { expandPath, fail, fileExists, parseJsonc, tryReadJson, writeJson } from './util.js';
|
|
10
|
+
import { expandPath, fail, fileExists, isInsideOrEqual, parseJsonc, tryReadJson, writeJson } from './util.js';
|
|
10
11
|
export const LOCAL_CONFIG_NAME = '.sandbox-config.json';
|
|
11
12
|
/** Validates a folder's settings and resolves its relative paths against that folder. */
|
|
12
13
|
export const normalizeOverrides = (data, dir, source) => {
|
|
@@ -18,14 +19,62 @@ export const normalizeOverrides = (data, dir, source) => {
|
|
|
18
19
|
const missing = scripts?.filter((s) => !fileExists(s)) ?? [];
|
|
19
20
|
if (missing.length)
|
|
20
21
|
fail(source + ' is not valid:\n - scripts: file not found: ' + missing.join(', '));
|
|
21
|
-
|
|
22
|
+
const mappings = overrides.mappings?.map((m) => ({ ...m, host: expandPath(m.host, dir) }));
|
|
23
|
+
// A repo ships this file, so it must not reach the rest of this PC: its extra folders stay inside the project.
|
|
24
|
+
const outside = mappings?.filter((m) => !insideProject(dir, m.host)) ?? [];
|
|
25
|
+
if (outside.length)
|
|
26
|
+
fail(source + ' is not valid:\n - mappings: a project file may only map folders inside the project, not ' + outside.map((m) => m.host).join(', '), 'Map other folders in your own settings (sandbox config path shows where they are).');
|
|
27
|
+
return { ...overrides, scripts, mappings };
|
|
22
28
|
};
|
|
29
|
+
/** `folder` (also where it really is, through links) is inside the project folder `dir`. */
|
|
30
|
+
const insideProject = (dir, folder) => {
|
|
31
|
+
// Where a path really is: the nearest existing folder resolved (links, 8.3 short names), plus the part not created yet.
|
|
32
|
+
const real = (p) => {
|
|
33
|
+
const full = path.resolve(p);
|
|
34
|
+
try {
|
|
35
|
+
return fs.realpathSync.native(full);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return path.dirname(full) === full ? full : path.join(real(path.dirname(full)), path.basename(full));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return isInsideOrEqual(dir, folder) && isInsideOrEqual(real(dir), real(folder));
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Protections a project file may only make stricter, never looser. A repo can ship the file, so it must not be able to
|
|
45
|
+
* switch off what you turned on (or turn on what you turned off). The strictest of the two values always wins.
|
|
46
|
+
*/
|
|
47
|
+
export const ON_IS_SAFER = ['readOnly', 'workspaceFilter.enabled', 'network.blockPrivateRanges', 'sandbox.protectedClient', 'downloads.requireChecksum'];
|
|
48
|
+
export const OFF_IS_SAFER = ['sandbox.networking', 'sandbox.clipboard', 'sandbox.audioInput', 'sandbox.videoInput', 'sandbox.printerRedirection', 'sandbox.vGpu', 'claudeProfile.copyCredentials', 'claudeProfile.enabled', 'claudeProfile.syncHistory', 'claudeProfile.syncClaudeMem'];
|
|
49
|
+
export const GIT_STRICTNESS = { full: 0, readonly: 1, none: 2 };
|
|
50
|
+
export const NEVER_LOOSER = [...ON_IS_SAFER, ...OFF_IS_SAFER, 'provisioning.git.access'];
|
|
51
|
+
const keepStrictest = (yours, merged) => {
|
|
52
|
+
ON_IS_SAFER.forEach((p) => _.set(merged, p, Boolean(_.get(yours, p)) || Boolean(_.get(merged, p))));
|
|
53
|
+
OFF_IS_SAFER.forEach((p) => _.set(merged, p, Boolean(_.get(yours, p)) && Boolean(_.get(merged, p))));
|
|
54
|
+
if (GIT_STRICTNESS[yours.provisioning.git.access] > GIT_STRICTNESS[merged.provisioning.git.access])
|
|
55
|
+
merged.provisioning.git.access = yours.provisioning.git.access;
|
|
56
|
+
};
|
|
57
|
+
/** What a project file asks for that would loosen one of your protections (so it is not used). */
|
|
58
|
+
export const loosenedRequests = (config, ov) => [
|
|
59
|
+
...ON_IS_SAFER.filter((p) => _.has(ov, p) && !_.get(ov, p) && _.get(config, p)),
|
|
60
|
+
...OFF_IS_SAFER.filter((p) => _.has(ov, p) && _.get(ov, p) && !_.get(config, p)),
|
|
61
|
+
...(_.has(ov, 'provisioning.git.access') && GIT_STRICTNESS[_.get(ov, 'provisioning.git.access')] < GIT_STRICTNESS[config.provisioning.git.access] ? ['provisioning.git.access'] : []),
|
|
62
|
+
].map((key) => ({ key, wanted: _.get(ov, key), yours: _.get(config, key) }));
|
|
23
63
|
/** Applies validated overrides. Mappings with the same guest folder replace the configured ones. */
|
|
24
64
|
export const applyOverrides = (config, ov) => {
|
|
25
65
|
const { harness, mappings, scripts: _scripts, $schema: _schema, ...rest } = ov;
|
|
26
66
|
const next = mergeConfig(config, rest);
|
|
67
|
+
keepStrictest(config, next);
|
|
27
68
|
if (harness)
|
|
28
69
|
next.defaultHarness = harness;
|
|
70
|
+
// A project adds catalogue apps to yours; it never removes the ones you chose.
|
|
71
|
+
if (rest.apps?.install)
|
|
72
|
+
next.apps.install = _.union(config.apps.install, rest.apps.install);
|
|
73
|
+
// Its patterns keep more out on top of yours; yours stay.
|
|
74
|
+
if (rest.network?.blockHosts)
|
|
75
|
+
next.network.blockHosts = _.union(config.network.blockHosts, rest.network.blockHosts);
|
|
76
|
+
if (rest.workspaceFilter?.exclude)
|
|
77
|
+
next.workspaceFilter.exclude = _.union(config.workspaceFilter.exclude, rest.workspaceFilter.exclude);
|
|
29
78
|
if (mappings)
|
|
30
79
|
next.mappings = [..._.reject(config.mappings, (m) => mappings.some((o) => o.guest.toLowerCase() === m.guest.toLowerCase())), ...mappings];
|
|
31
80
|
return next;
|
|
@@ -41,13 +90,11 @@ export const describeOverrides = (ov) => {
|
|
|
41
90
|
...flatten(sections),
|
|
42
91
|
];
|
|
43
92
|
const risky = _.compact([
|
|
44
|
-
ov.claudeProfile?.copyCredentials === true && 'claudeProfile.copyCredentials = true copies your Claude LOGIN into the sandbox',
|
|
45
|
-
ov.network?.blockPrivateRanges === false && 'network.blockPrivateRanges = false turns OFF the LAN block',
|
|
46
93
|
mappings.some((m) => !m.readOnly) && 'a READ/WRITE mapping lets the agent change files outside your workspace',
|
|
47
|
-
ov.provisioning?.git?.access === 'full' && 'provisioning.git.access = full lets the agent commit and push',
|
|
48
|
-
ov.sandbox?.vGpu === true && 'sandbox.vGpu = true widens the sandbox attack surface',
|
|
49
94
|
]);
|
|
50
|
-
|
|
95
|
+
// These are only used when they are stricter than your own settings, so a project can never loosen them.
|
|
96
|
+
const limited = NEVER_LOOSER.filter((p) => _.has(sections, p)).map((p) => p + ' is only used if it is stricter than your own setting');
|
|
97
|
+
return [...lines, ...risky.map((r) => '!! ' + r), ...limited.map((r) => ' ' + r)];
|
|
51
98
|
};
|
|
52
99
|
export const readLocalConfig = (dir) => {
|
|
53
100
|
const file = path.join(dir, LOCAL_CONFIG_NAME);
|
|
@@ -62,3 +109,28 @@ const loadTrust = (store) => tryReadJson(store) ?? {};
|
|
|
62
109
|
const key = (file) => path.resolve(file).toLowerCase();
|
|
63
110
|
export const isTrusted = (store, file, hash) => loadTrust(store)[key(file)] === hash;
|
|
64
111
|
export const saveTrust = (store, file, hash) => writeJson(store, { ...loadTrust(store), [key(file)]: hash });
|
|
112
|
+
// ---- looser settings you allowed for one project (host-only: <storage>\allowed-requests.json) ----------------------
|
|
113
|
+
// A project file can never loosen a protection by itself. When it asks, you are asked once (interactively); a "yes"
|
|
114
|
+
// is kept here, per project file and per "setting = value", so only that value is ever let through for that project.
|
|
115
|
+
export const allowStorePath = (storageRoot) => path.join(storageRoot, 'allowed-requests.json');
|
|
116
|
+
const loadAllowed = (store) => {
|
|
117
|
+
const parsed = z.record(z.string(), z.array(z.string().max(300)).max(50)).safeParse(tryReadJson(store) ?? {});
|
|
118
|
+
return parsed.success ? parsed.data : {};
|
|
119
|
+
};
|
|
120
|
+
const allowKey = (setting, value) => setting + ' = ' + JSON.stringify(value);
|
|
121
|
+
/** true = you allowed it, false = you said no (not asked again), null = not asked yet. */
|
|
122
|
+
export const allowedAnswer = (store, file, setting, value) => {
|
|
123
|
+
const answers = loadAllowed(store)[key(file)] ?? [];
|
|
124
|
+
return answers.includes(allowKey(setting, value)) ? true : answers.includes('no: ' + allowKey(setting, value)) ? false : null;
|
|
125
|
+
};
|
|
126
|
+
export const saveAllowed = (store, file, setting, value, allow) => {
|
|
127
|
+
const all = loadAllowed(store);
|
|
128
|
+
const entry = allowKey(setting, value);
|
|
129
|
+
writeJson(store, { ...all, [key(file)]: _.union(_.without(all[key(file)] ?? [], entry, 'no: ' + entry), [(allow ? '' : 'no: ') + entry]) });
|
|
130
|
+
};
|
|
131
|
+
/** Your config with these requests let through, so applyOverrides' "strictest wins" keeps the project's value. */
|
|
132
|
+
export const allowRequests = (config, allowed) => {
|
|
133
|
+
const next = _.cloneDeep(config);
|
|
134
|
+
allowed.forEach((a) => _.set(next, a.key, a.wanted));
|
|
135
|
+
return next;
|
|
136
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Config } from './types.js';
|
|
2
|
+
/** The protections of a running sandbox, by setting path (`sandbox.networking`, `provisioning.git.access`, ...). */
|
|
3
|
+
export type Protections = Record<string, boolean | string>;
|
|
4
|
+
export declare const protectionsOf: (config: Config) => Protections;
|
|
5
|
+
export interface JoinCheck {
|
|
6
|
+
/** Settings this project needs stricter than the running sandbox: the launch is refused. */
|
|
7
|
+
stricter: string[];
|
|
8
|
+
/** Settings it would have looser: it gets the sandbox's (stricter) value; shown as a note. */
|
|
9
|
+
looser: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Compares a project's effective protections with the running sandbox's. Hub projects are always read/write
|
|
13
|
+
* (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter.
|
|
14
|
+
*/
|
|
15
|
+
export declare const checkJoin: (running: Protections, wanted: Protections) => JoinCheck;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Joining a running sandbox from another project. Its protections (network, LAN block, git access, clipboard, the
|
|
2
|
+
// .wsb device settings, read-only) are machine-wide: they were fixed when the sandbox booted for the first project.
|
|
3
|
+
// A second project may never loosen them, and a project that needs a STRICTER setting than the running sandbox has
|
|
4
|
+
// cannot get it there without changing it for the sessions already running, so that launch is refused with a clear
|
|
5
|
+
// message. A project asking for less than the sandbox has simply runs with the sandbox's (stricter) setting.
|
|
6
|
+
// Pure: the host checks it before registering a project, the tests drive it directly.
|
|
7
|
+
import _ from 'lodash';
|
|
8
|
+
import { GIT_STRICTNESS, NEVER_LOOSER, OFF_IS_SAFER, ON_IS_SAFER } from './local-config.js';
|
|
9
|
+
export const protectionsOf = (config) => Object.fromEntries(NEVER_LOOSER.map((p) => [p, _.get(config, p)]));
|
|
10
|
+
/** 1 = `wanted` is stricter than `running`, -1 = looser, 0 = the same. */
|
|
11
|
+
const compare = (setting, running, wanted) => {
|
|
12
|
+
if (setting === 'provisioning.git.access')
|
|
13
|
+
return Math.sign((GIT_STRICTNESS[String(wanted)] ?? 0) - (GIT_STRICTNESS[String(running)] ?? 0));
|
|
14
|
+
if (ON_IS_SAFER.includes(setting))
|
|
15
|
+
return Number(Boolean(wanted)) - Number(Boolean(running));
|
|
16
|
+
if (OFF_IS_SAFER.includes(setting))
|
|
17
|
+
return Number(Boolean(running)) - Number(Boolean(wanted));
|
|
18
|
+
return 0;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Compares a project's effective protections with the running sandbox's. Hub projects are always read/write
|
|
22
|
+
* (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter.
|
|
23
|
+
*/
|
|
24
|
+
export const checkJoin = (running, wanted) => {
|
|
25
|
+
const stricter = [];
|
|
26
|
+
const looser = [];
|
|
27
|
+
for (const setting of NEVER_LOOSER) {
|
|
28
|
+
const result = compare(setting, setting === 'readOnly' ? false : running[setting], wanted[setting]);
|
|
29
|
+
if (result > 0)
|
|
30
|
+
stricter.push(setting + ' = ' + JSON.stringify(wanted[setting]) + ' (the running sandbox has ' + JSON.stringify(setting === 'readOnly' ? false : running[setting]) + ')');
|
|
31
|
+
if (result < 0)
|
|
32
|
+
looser.push(setting + ' stays ' + JSON.stringify(running[setting]));
|
|
33
|
+
}
|
|
34
|
+
return { stricter, looser };
|
|
35
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** A yes/no question (default no). */
|
|
2
|
+
export declare const askYesNo: (question: string, defaultYes?: boolean) => Promise<boolean>;
|
|
3
|
+
/** Asks the user to type `expected` (a stronger confirmation than yes/no). */
|
|
4
|
+
export declare const askTyped: (question: string, expected: string) => Promise<boolean>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Questions asked on the terminal, all through @inquirer/prompts. Without an interactive terminal the answer is always "no".
|
|
2
|
+
import { confirm, input } from '@inquirer/prompts';
|
|
3
|
+
/** A yes/no question (default no). */
|
|
4
|
+
export const askYesNo = async (question, defaultYes = false) => {
|
|
5
|
+
if (!process.stdin.isTTY)
|
|
6
|
+
return false;
|
|
7
|
+
return confirm({ message: question, default: defaultYes });
|
|
8
|
+
};
|
|
9
|
+
/** Asks the user to type `expected` (a stronger confirmation than yes/no). */
|
|
10
|
+
export const askTyped = async (question, expected) => {
|
|
11
|
+
if (!process.stdin.isTTY)
|
|
12
|
+
return false;
|
|
13
|
+
const answer = await input({ message: question + ' Type "' + expected + '" to confirm:' });
|
|
14
|
+
return answer.trim().toLowerCase() === expected;
|
|
15
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** A read/write folder mapped into the sandbox must not contain, or sit inside, any protected folder. */
|
|
2
|
+
export declare const assertSafeMapping: (folder: string, packageRoot: string, storageRoot: string) => void;
|
|
3
|
+
/** The agent gets read/write access to the workspace, so refuse folders that would let it reach the tool itself, its storage, or the OS. */
|
|
4
|
+
export declare const assertSafeWorkspace: (workspace: string, packageRoot: string, storageRoot: string) => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Which folders may be shared with the sandbox. The agent gets read/write access to what is mapped, so nothing that
|
|
2
|
+
// would let it reach this tool, its storage, your settings or the OS may be mapped.
|
|
3
|
+
import { userDir } from './config.js';
|
|
4
|
+
import { fail, isInsideOrEqual, isRootPath, isUserProfile } from './util.js';
|
|
5
|
+
/** Folders the agent must never be able to write: the tool itself, its storage and your settings for it. */
|
|
6
|
+
const protectedFolders = (packageRoot, storageRoot) => [
|
|
7
|
+
['this tool\'s install folder', packageRoot],
|
|
8
|
+
['the sandbox storage folder', storageRoot],
|
|
9
|
+
['your sandbox settings folder', userDir()],
|
|
10
|
+
];
|
|
11
|
+
/** A read/write folder mapped into the sandbox must not contain, or sit inside, any protected folder. */
|
|
12
|
+
export const assertSafeMapping = (folder, packageRoot, storageRoot) => {
|
|
13
|
+
if (isRootPath(folder) || isUserProfile(folder))
|
|
14
|
+
fail('A whole drive or user profile cannot be mapped into the sandbox: ' + folder, 'Map a specific folder instead.');
|
|
15
|
+
for (const [label, dir] of protectedFolders(packageRoot, storageRoot)) {
|
|
16
|
+
if (isInsideOrEqual(dir, folder) || isInsideOrEqual(folder, dir))
|
|
17
|
+
fail(folder + ' cannot be mapped into the sandbox: it overlaps ' + label + ' (' + dir + ').', 'Map a folder that is not inside (and does not contain) ' + dir + '.');
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** The agent gets read/write access to the workspace, so refuse folders that would let it reach the tool itself, its storage, or the OS. */
|
|
21
|
+
export const assertSafeWorkspace = (workspace, packageRoot, storageRoot) => {
|
|
22
|
+
if (isRootPath(workspace))
|
|
23
|
+
fail('A whole drive cannot be the workspace: ' + workspace, 'Run sandbox claude from a project folder (or pass --workspace <folder>).');
|
|
24
|
+
if (isUserProfile(workspace))
|
|
25
|
+
fail('Your whole user profile cannot be the workspace: ' + workspace, 'Run sandbox claude from a project folder (or pass --workspace <folder>).');
|
|
26
|
+
for (const [label, dir] of protectedFolders(packageRoot, storageRoot)) {
|
|
27
|
+
if (isInsideOrEqual(dir, workspace))
|
|
28
|
+
fail(workspace + ' cannot be the workspace: it is inside ' + label + ' (' + dir + '), so the agent could change the code that runs on this PC.', dir === packageRoot ? 'Developing windows-development-sandbox itself? Install it as a copy instead of a link: run `npm run install:global` in the repository, then sandbox claude works there.' : 'Run sandbox claude from a project folder somewhere else.');
|
|
29
|
+
if (isInsideOrEqual(workspace, dir))
|
|
30
|
+
fail(workspace + ' cannot be the workspace: it contains ' + label + ' (' + dir + '), which the agent must not be able to change.', 'Run sandbox claude from a project folder that does not contain it.');
|
|
31
|
+
}
|
|
32
|
+
for (const name of ['WINDIR', 'SystemRoot', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramData']) {
|
|
33
|
+
const dir = process.env[name];
|
|
34
|
+
if (dir && isInsideOrEqual(dir, workspace))
|
|
35
|
+
fail('A system folder cannot be the workspace: ' + workspace, 'Run sandbox claude from a project folder.');
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AppId: z.ZodEnum<{
|
|
3
|
+
[x: string]: string;
|
|
4
|
+
}>;
|
|
5
|
+
export declare const CustomAppId: z.ZodString;
|
|
6
|
+
export declare const CustomApp: z.ZodObject<{
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
github: z.ZodOptional<z.ZodString>;
|
|
9
|
+
asset: z.ZodOptional<z.ZodString>;
|
|
10
|
+
url: z.ZodOptional<z.ZodString>;
|
|
11
|
+
sha256: z.ZodOptional<z.ZodString>;
|
|
12
|
+
unpack: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
file: "file";
|
|
14
|
+
archive: "archive";
|
|
15
|
+
nsis: "nsis";
|
|
16
|
+
msi: "msi";
|
|
17
|
+
}>>;
|
|
18
|
+
exe: z.ZodString;
|
|
19
|
+
cli: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
}, z.core.$strict>;
|
|
21
|
+
export declare const Apps: z.ZodObject<{
|
|
22
|
+
install: z.ZodArray<z.ZodEnum<{
|
|
23
|
+
[x: string]: string;
|
|
24
|
+
}>>;
|
|
25
|
+
custom: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
github: z.ZodOptional<z.ZodString>;
|
|
28
|
+
asset: z.ZodOptional<z.ZodString>;
|
|
29
|
+
url: z.ZodOptional<z.ZodString>;
|
|
30
|
+
sha256: z.ZodOptional<z.ZodString>;
|
|
31
|
+
unpack: z.ZodOptional<z.ZodEnum<{
|
|
32
|
+
file: "file";
|
|
33
|
+
archive: "archive";
|
|
34
|
+
nsis: "nsis";
|
|
35
|
+
msi: "msi";
|
|
36
|
+
}>>;
|
|
37
|
+
exe: z.ZodString;
|
|
38
|
+
cli: z.ZodOptional<z.ZodBoolean>;
|
|
39
|
+
}, z.core.$strict>>;
|
|
40
|
+
}, z.core.$strict>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Apps installed in the sandbox: entries from the catalogue (shared/apps.ts) and programs of your own.
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { APPS } from '../../shared/apps.js';
|
|
4
|
+
const CATALOGUE_IDS = APPS.filter((a) => !a.setting).map((a) => a.id);
|
|
5
|
+
export const AppId = z.enum(CATALOGUE_IDS);
|
|
6
|
+
export const CustomAppId = z.string().regex(/^[a-z0-9][a-z0-9-]{0,40}$/, 'lower-case letters, digits and - only');
|
|
7
|
+
const compiles = (pattern) => { try {
|
|
8
|
+
new RegExp(pattern);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
} };
|
|
14
|
+
export const CustomApp = z.strictObject({
|
|
15
|
+
name: z.string().min(1).max(80).describe('The name on the desktop shortcut, e.g. "My Tool".'),
|
|
16
|
+
github: z.string().regex(/^[\w.-]+\/[\w.-]+$/, 'owner/repo, e.g. sharkdp/bat').optional().describe('GitHub repository (owner/repo): the file comes from its latest release.'),
|
|
17
|
+
asset: z.string().max(200).refine(compiles, 'not a valid pattern').optional().describe('With github: a pattern (regular expression) matching the release file, e.g. windows.*x64\\.zip$'),
|
|
18
|
+
url: z.string().url().startsWith('https://', 'https:// only').optional().describe('Or a direct https:// download link (zip, 7z, exe or msi).'),
|
|
19
|
+
sha256: z.string().regex(/^[0-9a-fA-F]{64}$/, '64 hex characters').optional().describe('SHA-256 of the download (GitHub releases usually publish one; then this is not needed).'),
|
|
20
|
+
unpack: z.enum(['archive', 'file', 'nsis', 'msi']).optional().describe('archive (zip, 7z, tar: the default), file (a single .exe), nsis (an installer 7-Zip can open) or msi.'),
|
|
21
|
+
exe: z.string().min(1).max(200).describe('The program to start (found anywhere in the download), e.g. mytool.exe.'),
|
|
22
|
+
cli: z.boolean().optional().describe('A command-line tool: its folder goes on PATH instead of getting a desktop shortcut.'),
|
|
23
|
+
}).refine((a) => (a.github && a.asset ? !a.url : Boolean(a.url) && !a.github && !a.asset), { message: 'give github + asset, or url (not both)' });
|
|
24
|
+
export const Apps = z.strictObject({
|
|
25
|
+
install: z.array(AppId).describe('Apps from the catalogue to install in every sandbox (the Apps screen in the menu lists them by category). Each is downloaded once and kept.'),
|
|
26
|
+
custom: z.record(CustomAppId, CustomApp).describe('Programs of your own: a GitHub release or a download link, and the program to start. Installed like the catalogue apps.'),
|
|
27
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const GuestPath: z.ZodString;
|
|
3
|
+
export declare const Name: z.ZodString;
|
|
4
|
+
export declare const Strings: z.ZodArray<z.ZodString>;
|
|
5
|
+
export declare const toggle: (description: string) => z.ZodObject<{
|
|
6
|
+
enabled: z.ZodBoolean;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export declare const SchemaRef: z.ZodString;
|
|
9
|
+
export declare const GitAccess: z.ZodEnum<{
|
|
10
|
+
readonly: "readonly";
|
|
11
|
+
full: "full";
|
|
12
|
+
none: "none";
|
|
13
|
+
}>;
|
|
14
|
+
/** true, false, or "auto": only when the project uses it (found from files like go.mod in the workspace or one level down). */
|
|
15
|
+
export declare const Enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
|
|
16
|
+
export declare const enabled: (what: string, markers: string) => z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
|
|
17
|
+
export declare const Packages: (what: string, example: string) => z.ZodArray<z.ZodString>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Building blocks shared by the settings schemas.
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { isValidGuestPath } from '../../shared/guest-paths.js';
|
|
4
|
+
// Rules (and RESERVED_GUEST) live in shared/guest-paths.ts: the host re-checks them before writing the .wsb.
|
|
5
|
+
export const GuestPath = z.string().refine(isValidGuestPath, 'reserved or invalid guest path (a folder under C:\\ other than Windows, Program Files, Users, AgentData, Mirror, ..., or on drive D: to Z:)');
|
|
6
|
+
export const Name = z.string().regex(/^[A-Za-z0-9_-]+$/, 'letters, numbers, - and _ only');
|
|
7
|
+
export const Strings = z.array(z.string());
|
|
8
|
+
export const toggle = (description) => z.strictObject({ enabled: z.boolean().describe(description) });
|
|
9
|
+
export const SchemaRef = z.string().describe('Path or URL of the JSON Schema for editor autocomplete and docs.');
|
|
10
|
+
export const GitAccess = z.enum(['full', 'readonly', 'none'])
|
|
11
|
+
.describe('full: normal git. readonly: status/log/diff/show/blame only (no commit, push, checkout, ...). none: no git at all; .git is hidden from the agent.');
|
|
12
|
+
/** true, false, or "auto": only when the project uses it (found from files like go.mod in the workspace or one level down). */
|
|
13
|
+
export const Enabled = z.union([z.boolean(), z.literal('auto')]);
|
|
14
|
+
export const enabled = (what, markers) => Enabled.describe(what + ' true, false, or "auto" = only when the project has ' + markers + '.');
|
|
15
|
+
export const Packages = (what, example) => Strings.describe(what + ', installed once and kept (e.g. ' + example + ').');
|