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,98 @@
|
|
|
1
|
+
// Writes a launch folder for one sandbox: session.json (what the guest needs to know), the bundled runtime, hooks, the
|
|
2
|
+
// staged Claude settings and the .wsb file that starts Windows Sandbox. The pieces live in launch/.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import { decideToolchains, resolveToolchains } from '../core/detect-toolchains.js';
|
|
7
|
+
import { readPwshScripts, usesPwsh } from '../core/detect-pwsh.js';
|
|
8
|
+
import { dirExists, ensureDir, fail, writeJson } from '../core/util.js';
|
|
9
|
+
import { substDrives } from '../shared/guest-paths.js';
|
|
10
|
+
import { PROJECTS_GUEST, hubName } from '../shared/projects.js';
|
|
11
|
+
import { planBootProjects } from './launch/projects.js';
|
|
12
|
+
import { hostClaudeDir, stageClaudeProfile } from './claude-profile.js';
|
|
13
|
+
import { databaseCatalog, planDatabases } from './database-plan.js';
|
|
14
|
+
import { collectMappings } from './launch/mappings.js';
|
|
15
|
+
import { copyRuntime, stageGitIdentity, stageHooks } from './launch/stage.js';
|
|
16
|
+
import { buildWsb, resolveMemoryMb } from './launch/wsb.js';
|
|
17
|
+
import { capturePersonalization } from './personalize.js';
|
|
18
|
+
import { pcLanguages } from './pc-scan.js';
|
|
19
|
+
import { msvcHint, projectEntries } from './project-scan.js';
|
|
20
|
+
import { assertTestWritable } from './test-guard.js';
|
|
21
|
+
const NO_PROFILE = { hostDir: null, staged: [], skipped: [], credentials: false, secretsRemoved: [] };
|
|
22
|
+
/** Which toolchains the project needs ("auto" settings follow the project's files and what this PC has). */
|
|
23
|
+
export const decideProjectToolchains = (o) => {
|
|
24
|
+
const { config, workspaceHost } = o.run;
|
|
25
|
+
const msvcScript = config.toolchains.msvc.enabled === 'auto' ? msvcHint(workspaceHost) : undefined;
|
|
26
|
+
const pwshScript = config.toolchains.pwsh.enabled === 'auto' ? usesPwsh(readPwshScripts(workspaceHost)) : null;
|
|
27
|
+
const hints = { ...(msvcScript ? { msvc: msvcScript } : {}), ...(pwshScript ? { pwsh: pwshScript } : {}) };
|
|
28
|
+
return decideToolchains(config.toolchains, projectEntries(workspaceHost), o.pcLanguages ?? pcLanguages(), hints);
|
|
29
|
+
};
|
|
30
|
+
/** What was found on this PC to copy instead of download. */
|
|
31
|
+
const hostToolsOf = (probe) => ({
|
|
32
|
+
node: Boolean(probe.nodeDir),
|
|
33
|
+
nodeVersion: probe.nodeDir || probe.nvmNode ? probe.hostNodeVersion.replace(/^v/, '') : null,
|
|
34
|
+
harnessExecutable: probe.harnessExe?.file ?? null,
|
|
35
|
+
git: Boolean(probe.gitDir),
|
|
36
|
+
nvm: Boolean(probe.nvmDir),
|
|
37
|
+
volta: Boolean(probe.volta),
|
|
38
|
+
pyenv: Boolean(probe.pyenv),
|
|
39
|
+
});
|
|
40
|
+
export const generateSandbox = (o) => {
|
|
41
|
+
const { config, dataPath, workspaceHost, harnessName, storageRoot } = o.run;
|
|
42
|
+
const harness = harnessName === 'none' ? null : config.harnesses[harnessName] ?? fail('Unknown agent: ' + harnessName, 'Configured agents: ' + Object.keys(config.harnesses).join(', '));
|
|
43
|
+
if (!dirExists(workspaceHost))
|
|
44
|
+
fail('The workspace folder does not exist: ' + workspaceHost, 'cd into the project first.');
|
|
45
|
+
assertTestWritable(storageRoot);
|
|
46
|
+
assertTestWritable(dataPath);
|
|
47
|
+
// Checked before anything is written, so a refusal leaves nothing behind.
|
|
48
|
+
const memoryMb = resolveMemoryMb(config.sandbox, o.freeMemoryBytes);
|
|
49
|
+
const launchId = Date.now() + '-' + crypto.randomBytes(3).toString('hex');
|
|
50
|
+
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
51
|
+
ensureDir(launchPath);
|
|
52
|
+
ensureDir(dataPath);
|
|
53
|
+
const boot = planBootProjects({ run: o.run, packageRoot: o.packageRoot, extraProjects: o.extraProjects, knownProjects: o.knownProjects });
|
|
54
|
+
const mappings = collectMappings(o, launchPath, boot);
|
|
55
|
+
const profile = harnessName === 'claude' ? stageClaudeProfile(config.claudeProfile, launchPath, workspaceHost, config.secrets) : NO_PROFILE;
|
|
56
|
+
stageGitIdentity(launchPath);
|
|
57
|
+
const personalization = config.personalization.enabled ? capturePersonalization(launchPath) : 'disabled';
|
|
58
|
+
const databases = o.databases ?? planDatabases(config.databases, workspaceHost);
|
|
59
|
+
const toolchainDecisions = decideProjectToolchains(o);
|
|
60
|
+
const session = {
|
|
61
|
+
launchId,
|
|
62
|
+
project: path.basename(workspaceHost),
|
|
63
|
+
projectId: hubName(workspaceHost, []),
|
|
64
|
+
workspace: config.guestWorkspace,
|
|
65
|
+
workspaceHost,
|
|
66
|
+
projectsDir: PROJECTS_GUEST,
|
|
67
|
+
roots: boot.roots.map((r) => r.guest),
|
|
68
|
+
...(o.autoSession === false ? { autoSession: false } : {}),
|
|
69
|
+
harnessName,
|
|
70
|
+
harness,
|
|
71
|
+
harnessArguments: o.harnessArgs,
|
|
72
|
+
persistAgentState: config.storage.persistAgentState,
|
|
73
|
+
networking: config.sandbox.networking,
|
|
74
|
+
headless: config.sandbox.headless,
|
|
75
|
+
network: config.network,
|
|
76
|
+
provisioning: config.provisioning,
|
|
77
|
+
// "auto" is decided here, so the guest and the launch table agree.
|
|
78
|
+
toolchains: resolveToolchains(config.toolchains, toolchainDecisions),
|
|
79
|
+
databases,
|
|
80
|
+
databaseCatalog: databaseCatalog(config.databases),
|
|
81
|
+
versions: o.run.versions,
|
|
82
|
+
attach: config.attach,
|
|
83
|
+
hooks: config.hooks,
|
|
84
|
+
downloads: config.downloads,
|
|
85
|
+
hostTools: hostToolsOf(o.probe),
|
|
86
|
+
claudeProfile: { overwrite: config.claudeProfile.overwrite, items: profile.staged, credentials: profile.credentials, hostConfigDir: profile.hostDir ?? hostClaudeDir(config.claudeProfile), syncClaudeMem: config.claudeProfile.syncClaudeMem },
|
|
87
|
+
personalization: fs.existsSync(path.join(launchPath, 'personalize', 'personalize.reg')),
|
|
88
|
+
substDrives: substDrives(mappings.map((m) => m.guest)),
|
|
89
|
+
cpuLimitPercent: config.sandbox.cpuLimit === 'none' ? null : config.sandbox.cpuLimit,
|
|
90
|
+
};
|
|
91
|
+
const sessionPath = path.join(launchPath, 'session.json');
|
|
92
|
+
writeJson(sessionPath, session);
|
|
93
|
+
copyRuntime(o.packageRoot, launchPath);
|
|
94
|
+
stageHooks(o.packageRoot, launchPath, o.run.scripts);
|
|
95
|
+
const wsbPath = path.join(launchPath, 'agent.wsb');
|
|
96
|
+
fs.writeFileSync(wsbPath, buildWsb(config, memoryMb, mappings), 'utf8');
|
|
97
|
+
return { wsbPath, launchPath, launchId, sessionPath, memoryMb, mappings, profile, personalization, databases, toolchains: toolchainDecisions, projects: boot };
|
|
98
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ResolvedRun } from '../core/types.js';
|
|
2
|
+
import { type ProjectSpecT } from '../shared/schema.js';
|
|
3
|
+
import { type ProjectMapper, type RegistryEntry } from './projects.js';
|
|
4
|
+
/** The launch folder of the running sandbox (host-only storage; its id comes from the guest, so it is checked). */
|
|
5
|
+
export declare const runningLaunchPath: (storageRoot: string, dataPath: string) => string | null;
|
|
6
|
+
/** What a session or command from this folder needs: where it is inside, and (for another project) its spec. */
|
|
7
|
+
export interface Joined {
|
|
8
|
+
project: RegistryEntry;
|
|
9
|
+
guestDir: string;
|
|
10
|
+
spec: ProjectSpecT | null;
|
|
11
|
+
run: ResolvedRun | null;
|
|
12
|
+
notes: string[];
|
|
13
|
+
}
|
|
14
|
+
/** The project spec the guest provisions from (scans the project folder like a launch does). */
|
|
15
|
+
export declare const projectSpec: (run: ResolvedRun, entry: RegistryEntry, hooks: boolean) => ProjectSpecT;
|
|
16
|
+
/**
|
|
17
|
+
* Makes `hostDir` usable in the running sandbox. The launch folder (or a folder below it) needs nothing; any other
|
|
18
|
+
* folder is resolved with `resolve` (the caller's resolveRun, with the approval prompt), checked, and registered.
|
|
19
|
+
*/
|
|
20
|
+
export declare const joinRunning: (o: {
|
|
21
|
+
storageRoot: string;
|
|
22
|
+
dataPath: string;
|
|
23
|
+
packageRoot: string;
|
|
24
|
+
hostDir: string;
|
|
25
|
+
wsbShare: boolean;
|
|
26
|
+
resolve: (projectRoot: string) => Promise<ResolvedRun>;
|
|
27
|
+
mapper?: ProjectMapper;
|
|
28
|
+
}) => Promise<Joined>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Joining the running sandbox from another folder. The folder's run is resolved exactly as a fresh launch would
|
|
2
|
+
// resolve it (its approved .sandbox-config.json, versions, toolchain detection, database plan), checked against the
|
|
3
|
+
// running sandbox's machine-wide protections, made visible inside (host/projects.ts: a project root, a project
|
|
4
|
+
// mapped at boot, or wsb share) and described to the guest, which provisions what is missing before its session.
|
|
5
|
+
// When none of those can make it visible, NeedsRestart reaches the caller, which offers a restart.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { checkJoin, protectionsOf } from '../core/project-policy.js';
|
|
9
|
+
import { resolveToolchains } from '../core/detect-toolchains.js';
|
|
10
|
+
import { assertNoLinks, fail } from '../core/util.js';
|
|
11
|
+
import { Id } from '../shared/schema.js';
|
|
12
|
+
import { readSandboxStatus } from '../shared/rundir.js';
|
|
13
|
+
import { syncProjectIn } from '../sync/watcher.js';
|
|
14
|
+
import { planDatabases } from './database-plan.js';
|
|
15
|
+
import { decideProjectToolchains } from './generate.js';
|
|
16
|
+
import { projectHere, projectMapper, readRegistry, rememberProject, rootHere } from './projects.js';
|
|
17
|
+
import { stageScripts } from './launch/stage.js';
|
|
18
|
+
import { addRepoSnapshot } from './repo-guard.js';
|
|
19
|
+
/** The launch folder of the running sandbox (host-only storage; its id comes from the guest, so it is checked). */
|
|
20
|
+
export const runningLaunchPath = (storageRoot, dataPath) => {
|
|
21
|
+
const launchId = readSandboxStatus(dataPath)?.launchId;
|
|
22
|
+
if (!launchId || !/^\d+-[0-9a-f]{6}$/.test(launchId) || readRegistry(storageRoot)?.launchId !== launchId)
|
|
23
|
+
return null;
|
|
24
|
+
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
25
|
+
assertNoLinks(storageRoot, launchPath);
|
|
26
|
+
return fs.existsSync(path.join(launchPath, 'session.json')) ? launchPath : null;
|
|
27
|
+
};
|
|
28
|
+
/** The project spec the guest provisions from (scans the project folder like a launch does). */
|
|
29
|
+
export const projectSpec = (run, entry, hooks) => {
|
|
30
|
+
const { config } = run;
|
|
31
|
+
const toolchains = resolveToolchains(config.toolchains, decideProjectToolchains({ run }));
|
|
32
|
+
const harness = run.harnessName === 'none' ? null : config.harnesses[run.harnessName];
|
|
33
|
+
return {
|
|
34
|
+
name: Id.parse(entry.name), workspace: entry.guest, workspaceHost: entry.host, toolchains, versions: run.versions,
|
|
35
|
+
databases: planDatabases(config.databases, run.workspaceHost).slice(0, 8),
|
|
36
|
+
harnesses: harness ? { [run.harnessName]: harness } : {},
|
|
37
|
+
hooks,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Makes `hostDir` usable in the running sandbox. The launch folder (or a folder below it) needs nothing; any other
|
|
42
|
+
* folder is resolved with `resolve` (the caller's resolveRun, with the approval prompt), checked, and registered.
|
|
43
|
+
*/
|
|
44
|
+
export const joinRunning = async (o) => {
|
|
45
|
+
const here = projectHere(o.storageRoot, o.hostDir);
|
|
46
|
+
if (here?.project.primary)
|
|
47
|
+
return { ...here, spec: null, run: null, notes: [] };
|
|
48
|
+
const registry = readRegistry(o.storageRoot) ?? fail('The running sandbox was started by an older version, so it cannot take another folder.', 'Stop it (sandbox stop) and start it again.');
|
|
49
|
+
// A folder below a project already in the sandbox uses that project; anything else becomes a project of its own.
|
|
50
|
+
const root = here?.project.host ?? path.resolve(o.hostDir);
|
|
51
|
+
const run = await o.resolve(root);
|
|
52
|
+
const check = checkJoin(registry.protections, protectionsOf(run.config));
|
|
53
|
+
if (check.stricter.length) {
|
|
54
|
+
fail('This project needs stricter protection than the running sandbox has, and those settings are machine-wide:\n ' + check.stricter.join('\n '), 'Its sessions cannot get them without changing them for the sessions already running. Stop the sandbox (sandbox stop) and start it from ' + root + '.');
|
|
55
|
+
}
|
|
56
|
+
const mapper = o.mapper ?? projectMapper({ storageRoot: o.storageRoot, packageRoot: o.packageRoot, wsbShare: o.wsbShare });
|
|
57
|
+
// Guarded from now on. A folder the sandbox could already write (inside a root, or mapped at boot) and that has no
|
|
58
|
+
// snapshot from the launch gets an empty one: what is there now may have been planted, so it counts as new.
|
|
59
|
+
// Anything else is snapshotted before it becomes visible.
|
|
60
|
+
addRepoSnapshot(o.dataPath, root, { empty: Boolean(here) || Boolean(rootHere(o.storageRoot, root)) });
|
|
61
|
+
const entry = mapper.register(root);
|
|
62
|
+
rememberProject(o.storageRoot, entry.host);
|
|
63
|
+
const launchPath = runningLaunchPath(o.storageRoot, o.dataPath);
|
|
64
|
+
// Your hooks in ~/.agent-sandbox/hooks ran at boot (once per sandbox); the project's own scripts ("scripts" in its
|
|
65
|
+
// .sandbox-config.json, --script) run before its first agent. The launch folder is read-only for the sandbox, but
|
|
66
|
+
// this side can still add them to it.
|
|
67
|
+
const hooks = Boolean(launchPath) && run.scripts.length > 0;
|
|
68
|
+
if (launchPath && hooks)
|
|
69
|
+
stageScripts(path.join(launchPath, 'projects', entry.name, 'hooks', 'pre-agent'), run.scripts);
|
|
70
|
+
try {
|
|
71
|
+
syncProjectIn(o.dataPath, { hostWorkspace: entry.host, guestWorkspace: entry.guest });
|
|
72
|
+
}
|
|
73
|
+
catch { /* history is a convenience; never block the session */ }
|
|
74
|
+
const notes = [...run.notes, ...check.looser.map((l) => 'The running sandbox is stricter: ' + l + '.')];
|
|
75
|
+
return { project: entry, guestDir: here ? here.guestDir : entry.guest, spec: projectSpec(run, entry, hooks), run, notes };
|
|
76
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { assertNoGuestOverlap } from '../../core/config.js';
|
|
2
|
+
import { assertSafeMapping } from '../../core/safe-paths.js';
|
|
3
|
+
import { dirExists, fail } from '../../core/util.js';
|
|
4
|
+
import { isValidGuestPath, sandboxFolder } from '../../shared/guest-paths.js';
|
|
5
|
+
import { GUEST } from './wsb.js';
|
|
6
|
+
export const collectMappings = (o, launchPath, boot = { roots: [], projects: [], notes: [] }) => {
|
|
7
|
+
const { config, dataPath, workspaceHost, harnessName } = o.run;
|
|
8
|
+
assertGuestPath(config.guestWorkspace, 'guestWorkspace');
|
|
9
|
+
const mappings = [
|
|
10
|
+
{ host: launchPath, guest: GUEST.launch, readOnly: true },
|
|
11
|
+
{ host: dataPath, guest: GUEST.data, readOnly: false },
|
|
12
|
+
{ host: workspaceHost, guest: config.guestWorkspace, readOnly: config.readOnly },
|
|
13
|
+
];
|
|
14
|
+
for (const m of config.mappings) {
|
|
15
|
+
const host = m.host;
|
|
16
|
+
assertGuestPath(m.guest, 'mappings[].guest for ' + host);
|
|
17
|
+
if (!dirExists(host)) {
|
|
18
|
+
if (m.optional) {
|
|
19
|
+
console.warn('Skipping optional mapping (folder not found): ' + host);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
fail('A mapped folder does not exist: ' + host, 'Create it, fix the path in your config, or mark the mapping "optional": true.');
|
|
23
|
+
}
|
|
24
|
+
assertSafeMapping(host, o.packageRoot, o.run.storageRoot);
|
|
25
|
+
mappings.push({ host, guest: m.guest, readOnly: m.readOnly });
|
|
26
|
+
}
|
|
27
|
+
// Project roots and other projects, so they can join the running sandbox without a restart (launch/projects.ts).
|
|
28
|
+
for (const m of [...boot.roots, ...boot.projects]) {
|
|
29
|
+
assertGuestPath(m.guest, 'project folder for ' + m.host);
|
|
30
|
+
mappings.push({ host: m.host, guest: m.guest, readOnly: false });
|
|
31
|
+
}
|
|
32
|
+
const { probe } = o;
|
|
33
|
+
if (probe.nodeDir)
|
|
34
|
+
mappings.push({ host: probe.nodeDir, guest: GUEST.hostTools + '\\node', readOnly: true });
|
|
35
|
+
if (probe.harnessExe && harnessName !== 'none')
|
|
36
|
+
mappings.push({ host: probe.harnessExe.dir, guest: GUEST.hostTools + '\\agents\\' + harnessName, readOnly: true });
|
|
37
|
+
if (probe.gitDir)
|
|
38
|
+
mappings.push({ host: probe.gitDir, guest: GUEST.hostTools + '\\git', readOnly: true });
|
|
39
|
+
if (probe.nvmDir)
|
|
40
|
+
mappings.push({ host: probe.nvmDir, guest: GUEST.hostTools + '\\nvm', readOnly: true });
|
|
41
|
+
assertNoGuestOverlap(mappings);
|
|
42
|
+
// Same check on where the folders really are (F:\x and C:\Mirror\F\x are one place).
|
|
43
|
+
assertNoGuestOverlap(mappings.map((m) => ({ guest: sandboxFolder(m.guest) })));
|
|
44
|
+
return mappings;
|
|
45
|
+
};
|
|
46
|
+
/** The schema checks this already; the host re-checks what goes into the .wsb and the subst list anyway. */
|
|
47
|
+
const assertGuestPath = (guest, what) => {
|
|
48
|
+
if (!isValidGuestPath(guest))
|
|
49
|
+
fail(what + ' is not a usable sandbox folder: ' + guest, 'Use a folder under C:\\ (not Windows, Program Files, Users, AgentData, Mirror, ...) or on drive D: to Z:, e.g. C:\\Assets or F:\\SteamLibrary\\steamapps\\common\\Game.');
|
|
50
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ResolvedRun } from '../../core/types.js';
|
|
2
|
+
import { type RootMapping } from '../../shared/projects.js';
|
|
3
|
+
/** More folders than this would make the .wsb (and every boot) unwieldy; the rest join with a restart later. */
|
|
4
|
+
export declare const MAX_BOOT_PROJECTS = 48;
|
|
5
|
+
export interface BootProjects {
|
|
6
|
+
roots: RootMapping[];
|
|
7
|
+
projects: RootMapping[];
|
|
8
|
+
notes: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const planBootProjects: (o: {
|
|
11
|
+
run: ResolvedRun;
|
|
12
|
+
packageRoot: string;
|
|
13
|
+
extraProjects?: string[];
|
|
14
|
+
knownProjects?: string[];
|
|
15
|
+
}) => BootProjects;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Which folders besides the launch folder are mapped so other projects can join without a restart: the project
|
|
2
|
+
// roots (projects.roots), projects carried over by a restart that added one, and with projects.all every project
|
|
3
|
+
// started before (except projects.exclude). Every one passes the same checks as a launch folder.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertSafeMapping, assertSafeWorkspace } from '../../core/safe-paths.js';
|
|
6
|
+
import { dirExists, errMsg, fail } from '../../core/util.js';
|
|
7
|
+
import { isWithin, planProjectMappings } from '../../shared/projects.js';
|
|
8
|
+
import { allProjectsToMap, readKnown } from '../projects.js';
|
|
9
|
+
/** More folders than this would make the .wsb (and every boot) unwieldy; the rest join with a restart later. */
|
|
10
|
+
export const MAX_BOOT_PROJECTS = 48;
|
|
11
|
+
export const planBootProjects = (o) => {
|
|
12
|
+
const { config, workspaceHost, storageRoot } = o.run;
|
|
13
|
+
const notes = [];
|
|
14
|
+
const safe = (folder) => {
|
|
15
|
+
try {
|
|
16
|
+
assertSafeWorkspace(folder, o.packageRoot, storageRoot);
|
|
17
|
+
assertSafeMapping(folder, o.packageRoot, storageRoot);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return errMsg(e);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const roots = [];
|
|
25
|
+
for (const root of config.projects.roots.map((r) => path.resolve(r))) {
|
|
26
|
+
if (!dirExists(root)) {
|
|
27
|
+
notes.push('Project root not found, skipped: ' + root);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const refusal = safe(root);
|
|
31
|
+
if (refusal)
|
|
32
|
+
fail('projects.roots: ' + refusal, 'Use a folder that holds your projects, e.g. F:\\Code (not a drive, your profile or a system folder).');
|
|
33
|
+
if (!roots.some((r) => isWithin(r, root)))
|
|
34
|
+
roots.push(root);
|
|
35
|
+
}
|
|
36
|
+
const known = o.knownProjects ?? readKnown(storageRoot).map((k) => k.host);
|
|
37
|
+
const wanted = [...(o.extraProjects ?? []), ...(config.projects.all ? allProjectsToMap(known, { primary: workspaceHost, roots, exclude: config.projects.exclude }) : [])];
|
|
38
|
+
const projects = [];
|
|
39
|
+
for (const folder of wanted.map((p) => path.resolve(p))) {
|
|
40
|
+
if (isWithin(workspaceHost, folder) || roots.some((r) => isWithin(r, folder)) || projects.some((p) => isWithin(p, folder) || isWithin(folder, p)))
|
|
41
|
+
continue;
|
|
42
|
+
if (!dirExists(folder))
|
|
43
|
+
continue;
|
|
44
|
+
const refusal = safe(folder);
|
|
45
|
+
if (refusal) {
|
|
46
|
+
notes.push('Not mapped: ' + refusal);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (projects.length >= MAX_BOOT_PROJECTS) {
|
|
50
|
+
notes.push('More than ' + MAX_BOOT_PROJECTS + ' projects: the rest are not mapped (a project root holds any number).');
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
projects.push(folder);
|
|
54
|
+
}
|
|
55
|
+
return { ...planProjectMappings(roots, projects, Boolean(o.run.mirror)), notes };
|
|
56
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Package hooks first, then ~/.agent-sandbox/hooks (same file name = user's wins), then --script / "scripts". */
|
|
2
|
+
export declare const stageHooks: (packageRoot: string, launchPath: string, scripts: string[]) => void;
|
|
3
|
+
/** --script / "scripts" files, run before the agent in this order. */
|
|
4
|
+
export declare const stageScripts: (target: string, scripts: string[]) => void;
|
|
5
|
+
export declare const RUNTIME_FILES: string[];
|
|
6
|
+
/** The bundled sandbox runtime (dist/runtime: one self-contained file per entry point) and the sandbox/ scripts. */
|
|
7
|
+
export declare const copyRuntime: (packageRoot: string, launchPath: string) => void;
|
|
8
|
+
/** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
|
|
9
|
+
export declare const gitIdentity: (gitconfig: string) => string;
|
|
10
|
+
/** Writes this PC's git name and email to home\.gitconfig in the launch folder (nothing when there are none). */
|
|
11
|
+
export declare const stageGitIdentity: (launchPath: string) => void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// What is copied into the launch folder (C:\AgentLaunch inside the sandbox): hooks, the bundled runtime, the git identity.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { userDir } from '../../core/config.js';
|
|
5
|
+
import { dirExists, ensureDir, fail, fileExists, homeDir } from '../../core/util.js';
|
|
6
|
+
const HOOK_STAGES = ['pre-provision', 'pre-agent', 'post-agent'];
|
|
7
|
+
/** Package hooks first, then ~/.agent-sandbox/hooks (same file name = user's wins), then --script / "scripts". */
|
|
8
|
+
export const stageHooks = (packageRoot, launchPath, scripts) => {
|
|
9
|
+
for (const stage of HOOK_STAGES) {
|
|
10
|
+
const target = path.join(launchPath, 'hooks', stage);
|
|
11
|
+
ensureDir(target);
|
|
12
|
+
for (const source of [path.join(packageRoot, 'hooks', stage), path.join(userDir(), 'hooks', stage)]) {
|
|
13
|
+
if (dirExists(source))
|
|
14
|
+
fs.cpSync(source, target, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
stageScripts(path.join(launchPath, 'hooks', 'pre-agent'), scripts);
|
|
18
|
+
};
|
|
19
|
+
/** --script / "scripts" files, run before the agent in this order. */
|
|
20
|
+
export const stageScripts = (target, scripts) => {
|
|
21
|
+
ensureDir(target);
|
|
22
|
+
scripts.forEach((script, index) => {
|
|
23
|
+
if (!fileExists(script))
|
|
24
|
+
fail('Script file not found: ' + script, 'Check the --script path.');
|
|
25
|
+
fs.copyFileSync(script, path.join(target, '90-extra-' + index + '-' + path.basename(script)));
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
export const RUNTIME_FILES = ['main.js', 'daemon.js', 'view.js', 'gitguard.js', 'db.js'];
|
|
29
|
+
/** The bundled sandbox runtime (dist/runtime: one self-contained file per entry point) and the sandbox/ scripts. */
|
|
30
|
+
export const copyRuntime = (packageRoot, launchPath) => {
|
|
31
|
+
const runtime = path.join(packageRoot, 'dist', 'runtime');
|
|
32
|
+
const missing = RUNTIME_FILES.filter((f) => !fileExists(path.join(runtime, f)));
|
|
33
|
+
if (missing.length)
|
|
34
|
+
fail('The sandbox runtime is missing from ' + runtime + ' (' + missing.join(', ') + ').', 'Rebuild it: npm run build (or reinstall the package).');
|
|
35
|
+
ensureDir(path.join(launchPath, 'runtime'));
|
|
36
|
+
for (const file of RUNTIME_FILES)
|
|
37
|
+
fs.copyFileSync(path.join(runtime, file), path.join(launchPath, 'runtime', file));
|
|
38
|
+
fs.writeFileSync(path.join(launchPath, 'package.json'), '{ "type": "module" }\n', 'utf8');
|
|
39
|
+
fs.cpSync(path.join(packageRoot, 'sandbox'), path.join(launchPath, 'sandbox'), { recursive: true });
|
|
40
|
+
};
|
|
41
|
+
/** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
|
|
42
|
+
export const gitIdentity = (gitconfig) => {
|
|
43
|
+
const values = {};
|
|
44
|
+
let section = '';
|
|
45
|
+
for (const line of gitconfig.split(/\r?\n/)) {
|
|
46
|
+
const header = /^\s*\[([^\]]+)\]/.exec(line);
|
|
47
|
+
if (header) {
|
|
48
|
+
section = header[1].trim().toLowerCase();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const pair = /^\s*(name|email)\s*=\s*(.*?)\s*$/i.exec(line);
|
|
52
|
+
if (section === 'user' && pair && !/[\r\n]/.test(pair[2]))
|
|
53
|
+
values[pair[1].toLowerCase()] = pair[2].replace(/^"(.*)"$/, '$1');
|
|
54
|
+
}
|
|
55
|
+
const lines = Object.entries(values).map(([k, v]) => '\t' + k + ' = ' + v);
|
|
56
|
+
return lines.length ? '[user]\n' + lines.join('\n') + '\n' : '';
|
|
57
|
+
};
|
|
58
|
+
/** Writes this PC's git name and email to home\.gitconfig in the launch folder (nothing when there are none). */
|
|
59
|
+
export const stageGitIdentity = (launchPath) => {
|
|
60
|
+
const gitconfig = path.join(homeDir(), '.gitconfig');
|
|
61
|
+
const identity = fileExists(gitconfig) ? gitIdentity(fs.readFileSync(gitconfig, 'utf8')) : '';
|
|
62
|
+
if (!identity)
|
|
63
|
+
return;
|
|
64
|
+
ensureDir(path.join(launchPath, 'home'));
|
|
65
|
+
fs.writeFileSync(path.join(launchPath, 'home', '.gitconfig'), identity, 'utf8');
|
|
66
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Config, ResolvedMapping } from '../../core/types.js';
|
|
2
|
+
export declare const GUEST: {
|
|
3
|
+
readonly launch: "C:\\AgentLaunch";
|
|
4
|
+
readonly data: "C:\\AgentData";
|
|
5
|
+
readonly hostTools: "C:\\HostTools";
|
|
6
|
+
readonly mirror: "C:\\Mirror";
|
|
7
|
+
};
|
|
8
|
+
/** "auto": free RAM minus the reserve, but never more than maxMemoryMb or maxMemoryPercent of the PC's total RAM. */
|
|
9
|
+
export declare const resolveMemoryMb: (sandbox: Config["sandbox"], freeMemoryBytes?: number, totalMemoryBytes?: number) => number;
|
|
10
|
+
export declare const buildWsb: (config: Config, memoryMb: number, mappings: ResolvedMapping[]) => string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The .wsb file Windows Sandbox starts from: memory, devices and the folders mapped in.
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { fail, xmlEscape } from '../../core/util.js';
|
|
4
|
+
import { MIRROR_ROOT, sandboxFolder } from '../../shared/guest-paths.js';
|
|
5
|
+
export const GUEST = { launch: 'C:\\AgentLaunch', data: 'C:\\AgentData', hostTools: 'C:\\HostTools', mirror: MIRROR_ROOT };
|
|
6
|
+
const toggle = (value) => (value ? 'Enable' : 'Disable');
|
|
7
|
+
/** "auto": free RAM minus the reserve, but never more than maxMemoryMb or maxMemoryPercent of the PC's total RAM. */
|
|
8
|
+
export const resolveMemoryMb = (sandbox, freeMemoryBytes = os.freemem(), totalMemoryBytes = os.totalmem()) => {
|
|
9
|
+
if (sandbox.memoryMb !== 'auto')
|
|
10
|
+
return sandbox.memoryMb;
|
|
11
|
+
const freeMb = Math.floor(freeMemoryBytes / 1048576);
|
|
12
|
+
const shareMb = Math.floor((totalMemoryBytes / 1048576) * sandbox.maxMemoryPercent / 100);
|
|
13
|
+
const rounded = Math.floor(Math.min(sandbox.maxMemoryMb, shareMb, freeMb - sandbox.reserveHostMb) / 1024) * 1024;
|
|
14
|
+
if (rounded < 4096)
|
|
15
|
+
fail('Not enough free RAM for the sandbox (' + freeMb + ' MB free, ' + sandbox.reserveHostMb + ' MB kept for this PC).', 'Close some apps, lower sandbox.reserveHostMb, or set sandbox.memoryMb to a fixed value (>= 4096).');
|
|
16
|
+
return rounded;
|
|
17
|
+
};
|
|
18
|
+
export const buildWsb = (config, memoryMb, mappings) => {
|
|
19
|
+
const s = config.sandbox;
|
|
20
|
+
// Guest paths on other drive letters (F:\x) really live under C:\Mirror\F; bootstrap.ps1 substs F: onto it at boot.
|
|
21
|
+
const folders = mappings.map((m) => '<MappedFolder><HostFolder>' + xmlEscape(m.host) + '</HostFolder><SandboxFolder>' + xmlEscape(sandboxFolder(m.guest)) + '</SandboxFolder><ReadOnly>' + String(m.readOnly) + '</ReadOnly></MappedFolder>').join('');
|
|
22
|
+
return '<Configuration>'
|
|
23
|
+
+ '<MemoryInMB>' + memoryMb + '</MemoryInMB>'
|
|
24
|
+
+ '<vGPU>' + toggle(s.vGpu) + '</vGPU>'
|
|
25
|
+
+ '<Networking>' + toggle(s.networking) + '</Networking>'
|
|
26
|
+
+ '<ClipboardRedirection>' + toggle(s.clipboard) + '</ClipboardRedirection>'
|
|
27
|
+
+ '<AudioInput>' + toggle(s.audioInput) + '</AudioInput>'
|
|
28
|
+
+ '<VideoInput>' + toggle(s.videoInput) + '</VideoInput>'
|
|
29
|
+
+ '<PrinterRedirection>' + toggle(s.printerRedirection) + '</PrinterRedirection>'
|
|
30
|
+
+ '<ProtectedClient>' + toggle(s.protectedClient) + '</ProtectedClient>'
|
|
31
|
+
+ '<MappedFolders>' + folders + '</MappedFolders>'
|
|
32
|
+
+ '<LogonCommand><Command>' + xmlEscape(GUEST.launch + '\\sandbox\\logon.cmd') + '</Command></LogonCommand>'
|
|
33
|
+
+ '</Configuration>';
|
|
34
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Database } from '../core/detect-databases.js';
|
|
2
|
+
import { type AutoToolchain } from '../core/detect-toolchains.js';
|
|
3
|
+
/** Languages this PC has (their commands on PATH; Windows' Store stubs for python do not count). */
|
|
4
|
+
export declare const pcLanguages: () => AutoToolchain[];
|
|
5
|
+
/** Database services installed on this PC (names from the service control manager; nothing is started or changed). */
|
|
6
|
+
export declare const hostDatabases: () => Database[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// What this PC has installed (languages on PATH, MSVC for Rust, database services), so `auto` settings can follow it.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { pcHasPwsh } from '../core/detect-pwsh.js';
|
|
6
|
+
import { databasesFromServices } from '../core/detect-databases.js';
|
|
7
|
+
import { AUTO_TOOLCHAINS } from '../core/detect-toolchains.js';
|
|
8
|
+
import { captureOutput, isWin } from '../core/util.js';
|
|
9
|
+
import { system32 } from '../shared/system.js';
|
|
10
|
+
/** This PC builds Rust with MSVC: rustup's default host is *-msvc (read from rustup's settings; nothing is run). */
|
|
11
|
+
const msvcRust = () => {
|
|
12
|
+
const settings = path.join(process.env.RUSTUP_HOME ?? path.join(os.homedir(), '.rustup'), 'settings.toml');
|
|
13
|
+
try {
|
|
14
|
+
return /default_host_triple\s*=\s*"[^"]*-msvc"/.test(fs.readFileSync(settings, 'utf8').slice(0, 64 * 1024));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** Languages this PC has (their commands on PATH; Windows' Store stubs for python do not count). */
|
|
21
|
+
export const pcLanguages = () => {
|
|
22
|
+
const dirs = (process.env.PATH ?? '').split(path.delimiter).filter((d) => d && !/WindowsApps/i.test(d));
|
|
23
|
+
const has = (...names) => dirs.some((d) => names.some((n) => fs.existsSync(path.join(d, n))));
|
|
24
|
+
const found = {
|
|
25
|
+
python: has('python.exe', 'py.exe'), rust: has('cargo.exe', 'rustc.exe'), cpp: has('gcc.exe', 'g++.exe', 'cl.exe', 'clang.exe'),
|
|
26
|
+
msvc: msvcRust(), pwsh: pcHasPwsh(), dotnet: has('dotnet.exe'), go: has('go.exe'), java: has('java.exe', 'javac.exe'), ruby: has('ruby.exe'), php: has('php.exe'), deno: has('deno.exe'),
|
|
27
|
+
};
|
|
28
|
+
return AUTO_TOOLCHAINS.filter((t) => found[t]);
|
|
29
|
+
};
|
|
30
|
+
/** Database services installed on this PC (names from the service control manager; nothing is started or changed). */
|
|
31
|
+
export const hostDatabases = () => {
|
|
32
|
+
if (!isWin)
|
|
33
|
+
return [];
|
|
34
|
+
const output = captureOutput(system32('sc.exe'), ['query', 'type=', 'service', 'state=', 'all'], { timeout: 10000 });
|
|
35
|
+
const names = [...output.matchAll(/^SERVICE_NAME:\s*(.+)$/gm)].map((m) => m[1].trim());
|
|
36
|
+
return databasesFromServices(names);
|
|
37
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface RegValue {
|
|
2
|
+
key: string;
|
|
3
|
+
name: string;
|
|
4
|
+
kind: string;
|
|
5
|
+
value: string | number | string[];
|
|
6
|
+
}
|
|
7
|
+
/** Formats captured values as a .reg file (UTF-16 friendly text; written as UTF-16LE with BOM). */
|
|
8
|
+
export declare const toRegFile: (values: RegValue[]) => string;
|
|
9
|
+
/** Writes personalize.reg (+ the wallpaper image) into `<launch>\personalize`. Returns a short summary. */
|
|
10
|
+
export declare const capturePersonalization: (launchPath: string) => string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Captures the look of this desktop (theme, accent colour, taskbar, Explorer options, wallpaper, mouse/keyboard,
|
|
2
|
+
// regional formats) from HKCU as a .reg file the sandbox imports at logon. Only an allow-list of values is read.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { ensureDir, errMsg, fileExists, isWin, runPowerShell } from '../core/util.js';
|
|
6
|
+
const VALUES = {
|
|
7
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize': ['AppsUseLightTheme', 'SystemUsesLightTheme', 'EnableTransparency', 'ColorPrevalence'],
|
|
8
|
+
'Software\\Microsoft\\Windows\\DWM': ['AccentColor', 'AccentColorInactive', 'ColorizationColor', 'ColorizationAfterglow', 'ColorizationColorBalance', 'ColorPrevalence', 'EnableWindowColorization'],
|
|
9
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Accent': ['AccentPalette', 'AccentColorMenu', 'StartColorMenu'],
|
|
10
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced': ['TaskbarAl', 'TaskbarSi', 'TaskbarDa', 'TaskbarMn', 'TaskbarGlomLevel', 'ShowTaskViewButton', 'Hidden', 'HideFileExt', 'ShowSuperHidden', 'LaunchTo', 'NavPaneExpandToCurrentFolder', 'NavPaneShowAllFolders', 'ShowCopilotButton', 'Start_TrackDocs', 'Start_Layout'],
|
|
11
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects3': ['Settings'],
|
|
12
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Search': ['SearchboxTaskbarMode'],
|
|
13
|
+
'Control Panel\\Desktop': ['WallpaperStyle', 'TileWallpaper', 'MenuShowDelay', 'CursorBlinkRate', 'DragFullWindows', 'FontSmoothing', 'FontSmoothingType', 'UserPreferencesMask'],
|
|
14
|
+
'Control Panel\\Colors': ['Background', 'Hilight', 'HotTrackingColor'],
|
|
15
|
+
'Control Panel\\Mouse': ['MouseSpeed', 'MouseSensitivity', 'MouseThreshold1', 'MouseThreshold2', 'DoubleClickSpeed', 'SwapMouseButtons'],
|
|
16
|
+
'Control Panel\\Keyboard': ['KeyboardDelay', 'KeyboardSpeed'],
|
|
17
|
+
'Control Panel\\International': ['sShortDate', 'sLongDate', 'sShortTime', 'sTimeFormat', 'iFirstDayOfWeek', 'sDecimal', 'sThousand', 'sList', 'iMeasure', 'iTime', 'iTLZero'],
|
|
18
|
+
'Control Panel\\Accessibility\\StickyKeys': ['Flags'],
|
|
19
|
+
};
|
|
20
|
+
const toHex = (bytes) => [...bytes].map((b) => b.toString(16).padStart(2, '0')).join(',');
|
|
21
|
+
const regString = (s) => '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
22
|
+
/** Formats captured values as a .reg file (UTF-16 friendly text; written as UTF-16LE with BOM). */
|
|
23
|
+
export const toRegFile = (values) => {
|
|
24
|
+
const byKey = new Map();
|
|
25
|
+
for (const v of values)
|
|
26
|
+
byKey.set(v.key, [...(byKey.get(v.key) ?? []), v]);
|
|
27
|
+
const lines = ['Windows Registry Editor Version 5.00', ''];
|
|
28
|
+
for (const [key, list] of byKey) {
|
|
29
|
+
lines.push('[HKEY_CURRENT_USER\\' + key + ']');
|
|
30
|
+
for (const v of list) {
|
|
31
|
+
const name = regString(v.name);
|
|
32
|
+
switch (v.kind) {
|
|
33
|
+
case 'DWord':
|
|
34
|
+
lines.push(name + '=dword:' + (Number(v.value) >>> 0).toString(16).padStart(8, '0'));
|
|
35
|
+
break;
|
|
36
|
+
case 'QWord':
|
|
37
|
+
lines.push(name + '=hex(b):' + toHex(Buffer.from(BigInt.asUintN(64, BigInt(v.value)).toString(16).padStart(16, '0'), 'hex').reverse()));
|
|
38
|
+
break;
|
|
39
|
+
case 'Binary':
|
|
40
|
+
lines.push(name + '=hex:' + toHex(Buffer.from(String(v.value), 'base64')));
|
|
41
|
+
break;
|
|
42
|
+
case 'ExpandString':
|
|
43
|
+
lines.push(name + '=hex(2):' + toHex(Buffer.from(String(v.value) + '\0', 'utf16le')));
|
|
44
|
+
break;
|
|
45
|
+
case 'MultiString':
|
|
46
|
+
lines.push(name + '=hex(7):' + toHex(Buffer.from(v.value.join('\0') + '\0\0', 'utf16le')));
|
|
47
|
+
break;
|
|
48
|
+
default: lines.push(name + '=' + regString(String(v.value)));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
lines.push('');
|
|
52
|
+
}
|
|
53
|
+
return lines.join('\r\n');
|
|
54
|
+
};
|
|
55
|
+
const CAPTURE_SCRIPT = (spec) => [
|
|
56
|
+
'$ErrorActionPreference = "SilentlyContinue"',
|
|
57
|
+
'$spec = \'' + JSON.stringify(spec).replace(/'/g, "''") + '\' | ConvertFrom-Json',
|
|
58
|
+
'$out = @()',
|
|
59
|
+
'foreach ($p in $spec.PSObject.Properties) {',
|
|
60
|
+
' $key = Get-Item -LiteralPath ("HKCU:\\" + $p.Name)',
|
|
61
|
+
' if (-not $key) { continue }',
|
|
62
|
+
' foreach ($name in $p.Value) {',
|
|
63
|
+
' $raw = $key.GetValue($name, $null, "DoNotExpandEnvironmentNames")',
|
|
64
|
+
' if ($null -eq $raw) { continue }',
|
|
65
|
+
' $kind = $key.GetValueKind($name).ToString()',
|
|
66
|
+
' if ($kind -eq "Binary") { $raw = [Convert]::ToBase64String([byte[]]$raw) }',
|
|
67
|
+
' $out += [pscustomobject]@{ key = $p.Name; name = $name; kind = $kind; value = $raw }',
|
|
68
|
+
' }',
|
|
69
|
+
'}',
|
|
70
|
+
'$wall = (Get-ItemProperty -LiteralPath "HKCU:\\Control Panel\\Desktop").WallPaper',
|
|
71
|
+
'[pscustomobject]@{ values = $out; wallpaper = $wall } | ConvertTo-Json -Depth 5 -Compress',
|
|
72
|
+
].join('\n');
|
|
73
|
+
/** Writes personalize.reg (+ the wallpaper image) into `<launch>\personalize`. Returns a short summary. */
|
|
74
|
+
export const capturePersonalization = (launchPath) => {
|
|
75
|
+
if (!isWin)
|
|
76
|
+
return 'skipped (not Windows)';
|
|
77
|
+
try {
|
|
78
|
+
const { status, stdout } = runPowerShell(CAPTURE_SCRIPT(VALUES), true);
|
|
79
|
+
if (status !== 0)
|
|
80
|
+
return 'skipped (registry read failed)';
|
|
81
|
+
// runPowerShell has PowerShell write UTF-8 (non-ASCII date formats and wallpaper paths survive); a BOM is tolerated.
|
|
82
|
+
const parsed = JSON.parse(stdout.replace(/^/, '').trim());
|
|
83
|
+
const values = parsed.values === null ? [] : Array.isArray(parsed.values) ? parsed.values : [parsed.values];
|
|
84
|
+
const dir = path.join(launchPath, 'personalize');
|
|
85
|
+
ensureDir(dir);
|
|
86
|
+
fs.writeFileSync(path.join(dir, 'personalize.reg'), '' + toRegFile(values), 'utf16le');
|
|
87
|
+
const wallpaper = parsed.wallpaper ?? '';
|
|
88
|
+
const themesCopy = path.join(process.env.APPDATA ?? '', 'Microsoft', 'Windows', 'Themes', 'TranscodedWallpaper');
|
|
89
|
+
const source = fileExists(wallpaper) ? wallpaper : fileExists(themesCopy) ? themesCopy : null;
|
|
90
|
+
if (source)
|
|
91
|
+
fs.copyFileSync(source, path.join(dir, 'wallpaper' + (path.extname(source) || '.jpg')));
|
|
92
|
+
return values.length + ' settings' + (source ? ' + wallpaper' : '');
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return 'skipped (' + errMsg(e) + ')';
|
|
96
|
+
}
|
|
97
|
+
};
|