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,29 @@
|
|
|
1
|
+
import type { SandboxStatus } from '../../core/types.js';
|
|
2
|
+
import { type RegistryEntry } from '../../host/projects.js';
|
|
3
|
+
import type { ProjectSpecT } from '../../shared/schema.js';
|
|
4
|
+
import { type Context } from './context.js';
|
|
5
|
+
/** The folder the command is about: --workspace, else the current one. */
|
|
6
|
+
export declare const hereDir: (ctx: Context) => string;
|
|
7
|
+
export interface Located {
|
|
8
|
+
project: RegistryEntry | null;
|
|
9
|
+
guestDir: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Where this folder is in the running sandbox, if it is in it at all (nothing is registered). */
|
|
12
|
+
export declare const locateHere: (ctx: Context, status: SandboxStatus | null) => Located;
|
|
13
|
+
export interface Joined {
|
|
14
|
+
project: RegistryEntry | null;
|
|
15
|
+
guestDir: string;
|
|
16
|
+
spec: ProjectSpecT | null;
|
|
17
|
+
harness: string | null;
|
|
18
|
+
notes: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Makes this folder a project of the running sandbox (nothing to do for the launch folder and projects already in
|
|
22
|
+
* it): its .sandbox-config.json is approved like at a launch, its protections are checked, and it is made visible.
|
|
23
|
+
* Throws NeedsRestart (host/projects.ts) when that is impossible without a restart.
|
|
24
|
+
*/
|
|
25
|
+
export declare const joinHere: (ctx: Context, status: SandboxStatus) => Promise<Joined>;
|
|
26
|
+
/** Waits until the sandbox has set a project up, with its progress on a spinner (plain lines without a terminal). */
|
|
27
|
+
export declare const provisionJoined: (ctx: Context, joined: Joined) => Promise<void>;
|
|
28
|
+
/** joinHere, then (for another project) waits until the sandbox has set it up. */
|
|
29
|
+
export declare const readyHere: (ctx: Context, status: SandboxStatus) => Promise<Joined>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Which project of the running sandbox a command is run from. Every command that works on a running sandbox uses
|
|
2
|
+
// these, so they all agree: `locateHere` only looks (attach, sessions, status, restart), `joinHere` also makes the
|
|
3
|
+
// folder a project of the running sandbox when it can without a restart (launch, start, exec, db).
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { askYesNo } from '../../core/prompt.js';
|
|
6
|
+
import { isInsideOrEqual } from '../../core/util.js';
|
|
7
|
+
import { resolveRun } from '../../core/workspace.js';
|
|
8
|
+
import { prepareProject } from '../../host/channel.js';
|
|
9
|
+
import { projectHere, readRegistry } from '../../host/projects.js';
|
|
10
|
+
import { joinRunning } from '../../host/join.js';
|
|
11
|
+
import { working } from '../progress.js';
|
|
12
|
+
import { guestPathFor, sandboxAlive } from './context.js';
|
|
13
|
+
/** The folder the command is about: --workspace, else the current one. */
|
|
14
|
+
export const hereDir = (ctx) => path.resolve(ctx.options.workspace ?? process.cwd());
|
|
15
|
+
/** Where this folder is in the running sandbox, if it is in it at all (nothing is registered). */
|
|
16
|
+
export const locateHere = (ctx, status) => {
|
|
17
|
+
const dir = hereDir(ctx);
|
|
18
|
+
const here = projectHere(ctx.storageRoot, dir);
|
|
19
|
+
if (here)
|
|
20
|
+
return here;
|
|
21
|
+
// A sandbox started before projects existed: only its launch folder.
|
|
22
|
+
if (!readRegistry(ctx.storageRoot) && status && isInsideOrEqual(status.workspaceHost, dir))
|
|
23
|
+
return { project: null, guestDir: guestPathFor(dir, status.workspaceHost, status.guestWorkspace) };
|
|
24
|
+
return { project: null, guestDir: null };
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Makes this folder a project of the running sandbox (nothing to do for the launch folder and projects already in
|
|
28
|
+
* it): its .sandbox-config.json is approved like at a launch, its protections are checked, and it is made visible.
|
|
29
|
+
* Throws NeedsRestart (host/projects.ts) when that is impossible without a restart.
|
|
30
|
+
*/
|
|
31
|
+
export const joinHere = async (ctx, status) => {
|
|
32
|
+
const { options, loaded, packageRoot, storageRoot, dataPath } = ctx;
|
|
33
|
+
const dir = hereDir(ctx);
|
|
34
|
+
// A sandbox started before projects existed: only its launch folder (guestPathFor explains why otherwise).
|
|
35
|
+
if (!readRegistry(storageRoot))
|
|
36
|
+
return { project: null, guestDir: guestPathFor(dir, status.workspaceHost, status.guestWorkspace), spec: null, harness: null, notes: [] };
|
|
37
|
+
const joined = await joinRunning({
|
|
38
|
+
storageRoot, dataPath, packageRoot, hostDir: dir, wsbShare: loaded.config.projects.wsbShare,
|
|
39
|
+
resolve: (root) => resolveRun({
|
|
40
|
+
packageRoot, loaded, cwd: root, workspace: root, harness: options.harness, noAgent: options.noAgent,
|
|
41
|
+
noLocalConfig: options.noLocalConfig, trustConfig: options.trustConfig, scripts: options.scripts, databases: options.databases,
|
|
42
|
+
nodeVersion: options.node, pythonVersion: options.python, ask: askYesNo,
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
const harness = joined.run && joined.run.harnessName !== 'none' ? joined.run.harnessName : null;
|
|
46
|
+
return { project: joined.project, guestDir: joined.guestDir, spec: joined.spec, harness, notes: joined.notes };
|
|
47
|
+
};
|
|
48
|
+
/** Waits until the sandbox has set a project up, with its progress on a spinner (plain lines without a terminal). */
|
|
49
|
+
export const provisionJoined = async (ctx, joined) => {
|
|
50
|
+
if (!joined.spec)
|
|
51
|
+
return;
|
|
52
|
+
const spec = joined.spec;
|
|
53
|
+
let step = 'starting';
|
|
54
|
+
await working(() => 'Setting up ' + spec.name + ' in the running sandbox: ' + step, () => prepareProject(ctx.dataPath, spec, sandboxAlive, (m) => { step = m; }));
|
|
55
|
+
};
|
|
56
|
+
/** joinHere, then (for another project) waits until the sandbox has set it up. */
|
|
57
|
+
export const readyHere = async (ctx, status) => {
|
|
58
|
+
const joined = await joinHere(ctx, status);
|
|
59
|
+
for (const note of joined.notes)
|
|
60
|
+
console.log(note);
|
|
61
|
+
await provisionJoined(ctx, joined);
|
|
62
|
+
return joined;
|
|
63
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Context } from './context.js';
|
|
2
|
+
interface Row {
|
|
3
|
+
folder: string;
|
|
4
|
+
here: boolean;
|
|
5
|
+
known: boolean;
|
|
6
|
+
root: string | null;
|
|
7
|
+
excluded: boolean;
|
|
8
|
+
mappedAtStart: boolean;
|
|
9
|
+
running: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Every project worth listing: known ones, the running sandbox's, and this folder. */
|
|
12
|
+
export declare const projectRows: (ctx: Context) => Row[];
|
|
13
|
+
export declare const projects: (ctx: Context) => Promise<number>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// `sandbox projects`: which projects a sandbox can open without a restart. Project roots (whole folders of projects),
|
|
2
|
+
// the projects this tool has started before, and projects.all / projects.exclude. Changes to what is mapped take
|
|
3
|
+
// effect at the next start; a project you start while a sandbox runs joins it when it can (projects.ts in host/).
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { checkbox } from '@inquirer/prompts';
|
|
6
|
+
import { assertSafeMapping, assertSafeWorkspace } from '../../core/safe-paths.js';
|
|
7
|
+
import { dirExists, expandPath, fail, isRootPath } from '../../core/util.js';
|
|
8
|
+
import { isSandboxRunning } from '../../host/platform.js';
|
|
9
|
+
import { forgetProject, projectMapper, readKnown, readRegistry, rememberProject } from '../../host/projects.js';
|
|
10
|
+
import { isWithin } from '../../shared/projects.js';
|
|
11
|
+
import { c, pageSizeFor, shortenMiddle, termWidth } from '../colors.js';
|
|
12
|
+
import { printJson, table } from './context.js';
|
|
13
|
+
import { hereDir } from './project.js';
|
|
14
|
+
import { addToList, removeFromList, setSetting } from './user-config.js';
|
|
15
|
+
const settings = (ctx) => {
|
|
16
|
+
const p = ctx.loaded.config.projects;
|
|
17
|
+
const expand = (list) => list.map((x) => expandPath(x, ctx.loaded.base));
|
|
18
|
+
return { all: p.all, roots: expand(p.roots), exclude: expand(p.exclude) };
|
|
19
|
+
};
|
|
20
|
+
/** Every project worth listing: known ones, the running sandbox's, and this folder. */
|
|
21
|
+
export const projectRows = (ctx) => {
|
|
22
|
+
const s = settings(ctx);
|
|
23
|
+
const registry = isSandboxRunning() ? readRegistry(ctx.storageRoot) : null;
|
|
24
|
+
const here = hereDir(ctx);
|
|
25
|
+
const folders = [...readKnown(ctx.storageRoot).map((k) => k.host), ...(registry?.projects.map((p) => p.host) ?? [])];
|
|
26
|
+
const unique = folders.filter((f, i) => folders.findIndex((g) => g.toLowerCase() === f.toLowerCase()) === i);
|
|
27
|
+
return unique.map((folder) => {
|
|
28
|
+
const root = s.roots.find((r) => isWithin(r, folder)) ?? null;
|
|
29
|
+
const excluded = s.exclude.some((e) => isWithin(e, folder));
|
|
30
|
+
const inSandbox = registry?.projects.find((p) => p.host.toLowerCase() === folder.toLowerCase());
|
|
31
|
+
return {
|
|
32
|
+
folder, here: isWithin(folder, here), known: readKnown(ctx.storageRoot).some((k) => k.host.toLowerCase() === folder.toLowerCase()), root, excluded,
|
|
33
|
+
mappedAtStart: root !== null || (s.all && !excluded && dirExists(folder)), running: inSandbox ? inSandbox.name + ' (' + inSandbox.mechanism + ')' : null,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const NEXT_START = 'This changes what is mapped from the next start.';
|
|
38
|
+
const runningNote = () => (isSandboxRunning() ? ' The running sandbox keeps what it has; start a project in it (sandbox claude there) and it joins without a restart when it can, or you are offered one.' : '');
|
|
39
|
+
/** A folder for roots: it holds projects, so it must pass the same checks as a workspace and must not be a drive. */
|
|
40
|
+
const checkFolder = (ctx, folder, what) => {
|
|
41
|
+
const dir = path.resolve(folder);
|
|
42
|
+
if (!dirExists(dir))
|
|
43
|
+
fail('The folder ' + dir + ' does not exist.', 'Check the path.');
|
|
44
|
+
if (isRootPath(dir))
|
|
45
|
+
fail('A whole drive cannot be ' + what + ': ' + dir, 'Use the folder that holds your projects, e.g. F:\\Code.');
|
|
46
|
+
assertSafeWorkspace(dir, ctx.packageRoot, ctx.storageRoot);
|
|
47
|
+
assertSafeMapping(dir, ctx.packageRoot, ctx.storageRoot);
|
|
48
|
+
return dir;
|
|
49
|
+
};
|
|
50
|
+
const list = (ctx) => {
|
|
51
|
+
const rows = projectRows(ctx);
|
|
52
|
+
const s = settings(ctx);
|
|
53
|
+
if (printJson(ctx, { all: s.all, roots: s.roots, exclude: s.exclude, projects: rows }))
|
|
54
|
+
return 0;
|
|
55
|
+
console.log(c.bold('projects.all') + ': ' + (s.all ? 'on (every project below is mapped at every start, except excluded ones)' : 'off') + c.dim(' sandbox projects all on|off'));
|
|
56
|
+
console.log(c.bold('Roots') + ': ' + (s.roots.length ? s.roots.join(', ') : c.dim('none (sandbox projects roots add <folder>)')));
|
|
57
|
+
if (!rows.length) {
|
|
58
|
+
console.log('No projects yet: start one with sandbox claude in its folder.');
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
const width = Math.max(20, termWidth() - 46);
|
|
62
|
+
table([['', 'FOLDER', 'AT START', 'RUNNING SANDBOX'], ...rows.map((r) => [
|
|
63
|
+
r.here ? '*' : '', shortenMiddle(r.folder, width), r.root ? 'root' : r.excluded ? 'excluded' : r.mappedAtStart ? 'mapped' : '-', r.running ?? '-',
|
|
64
|
+
])]);
|
|
65
|
+
console.log(c.dim('* = this folder. Choose with: sandbox projects choose'));
|
|
66
|
+
return 0;
|
|
67
|
+
};
|
|
68
|
+
/** Ticks the projects mapped at every start (turns projects.all on when any is ticked); the rest are excluded. */
|
|
69
|
+
const choose = async (ctx) => {
|
|
70
|
+
const rows = projectRows(ctx).filter((r) => !r.root);
|
|
71
|
+
if (!rows.length) {
|
|
72
|
+
console.log('No projects to choose from yet (projects in a root are always mapped with it). Start one with sandbox claude in its folder.');
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
if (!process.stdin.isTTY)
|
|
76
|
+
fail('sandbox projects choose needs an interactive terminal.', 'Use sandbox projects include|exclude <folder> instead.');
|
|
77
|
+
const s = settings(ctx);
|
|
78
|
+
const picked = await checkbox({
|
|
79
|
+
message: 'Projects mapped at every start, so they open without a restart (every agent in the sandbox can see them):',
|
|
80
|
+
pageSize: pageSizeFor(rows.length),
|
|
81
|
+
choices: rows.map((r) => ({ value: r.folder, name: shortenMiddle(r.folder, termWidth() - 12) + (r.here ? c.dim(' (this folder)') : ''), checked: s.all && !r.excluded })),
|
|
82
|
+
});
|
|
83
|
+
const exclude = rows.filter((r) => !picked.includes(r.folder)).map((r) => r.folder);
|
|
84
|
+
setSetting(ctx, ['projects', 'exclude'], [...s.exclude.filter((e) => !rows.some((r) => r.folder.toLowerCase() === e.toLowerCase())), ...exclude]);
|
|
85
|
+
setSetting(ctx, ['projects', 'all'], picked.length > 0);
|
|
86
|
+
console.log(picked.length + ' project(s) mapped at every start, ' + exclude.length + ' excluded. ' + NEXT_START + runningNote());
|
|
87
|
+
return 0;
|
|
88
|
+
};
|
|
89
|
+
export const projects = async (ctx) => {
|
|
90
|
+
const [action = 'list', value, folder] = ctx.options.positionals;
|
|
91
|
+
switch (action) {
|
|
92
|
+
case 'list': return list(ctx);
|
|
93
|
+
case 'choose': return choose(ctx);
|
|
94
|
+
case 'add': {
|
|
95
|
+
const dir = checkFolder(ctx, value, 'a project');
|
|
96
|
+
rememberProject(ctx.storageRoot, dir);
|
|
97
|
+
console.log('Added ' + dir + ' to your projects.' + (settings(ctx).all ? ' ' + NEXT_START : ' It is mapped at every start once projects.all is on (sandbox projects all on).'));
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
case 'remove': {
|
|
101
|
+
const dir = path.resolve(value);
|
|
102
|
+
const forgot = forgetProject(ctx.storageRoot, dir);
|
|
103
|
+
// The running sandbox forgets it too (its folder stays visible until the sandbox stops, if it was mapped).
|
|
104
|
+
const registered = isSandboxRunning() ? readRegistry(ctx.storageRoot)?.projects.find((p) => !p.primary && p.host.toLowerCase() === dir.toLowerCase()) : undefined;
|
|
105
|
+
if (registered)
|
|
106
|
+
projectMapper({ storageRoot: ctx.storageRoot, packageRoot: ctx.packageRoot, wsbShare: false }).unregister(registered.name);
|
|
107
|
+
console.log(forgot || registered ? 'Removed ' + dir + ' from your projects (nothing in the folder was touched).' : dir + ' was not in your projects.');
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
case 'include':
|
|
111
|
+
case 'exclude': {
|
|
112
|
+
const dir = path.resolve(value);
|
|
113
|
+
const changed = action === 'exclude' ? addToList(ctx, ['projects', 'exclude'], dir) : removeFromList(ctx, ['projects', 'exclude'], dir);
|
|
114
|
+
if (action === 'include')
|
|
115
|
+
rememberProject(ctx.storageRoot, dir);
|
|
116
|
+
console.log((changed ? (action === 'exclude' ? 'Excluded ' : 'Included ') + dir + '. ' + NEXT_START : 'Nothing changed.') + (action === 'include' && !settings(ctx).all ? ' projects.all is off: sandbox projects all on maps your projects at every start.' : '') + (changed ? runningNote() : ''));
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
case 'all': {
|
|
120
|
+
if (value === undefined) {
|
|
121
|
+
console.log('projects.all is ' + (settings(ctx).all ? 'on' : 'off') + '.');
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
setSetting(ctx, ['projects', 'all'], value === 'on');
|
|
125
|
+
console.log('projects.all is ' + value + '. ' + NEXT_START + (value === 'on' ? ' Every agent in the sandbox can then see all of those projects.' : '') + runningNote());
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
case 'roots': {
|
|
129
|
+
if (!value || value === 'list') {
|
|
130
|
+
const roots = settings(ctx).roots;
|
|
131
|
+
console.log(roots.length ? roots.join('\n') : 'No project roots. Add one: sandbox projects roots add <folder>');
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (value === 'add') {
|
|
135
|
+
const dir = checkFolder(ctx, folder, 'a project root');
|
|
136
|
+
const added = addToList(ctx, ['projects', 'roots'], dir);
|
|
137
|
+
console.log(added ? 'Added the project root ' + dir + ': every project in it (also new ones) opens without a restart. Every agent in the sandbox can see all of them. ' + NEXT_START + runningNote() : dir + ' is a root already.');
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
const dir = path.resolve(folder);
|
|
141
|
+
console.log(removeFromList(ctx, ['projects', 'roots'], dir) ? 'Removed the project root ' + dir + '. ' + NEXT_START : dir + ' was not a root.');
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
default: return fail('Unknown projects action "' + action + '".');
|
|
145
|
+
}
|
|
146
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// The repair checks, in the order they run. Each either confirms all is well, fixes the problem itself, or says what
|
|
2
|
+
// you need to do. Nothing under the data folder is followed through a link.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import _ from 'lodash';
|
|
7
|
+
import { GB, freeBytesAt, humanBytes } from '../../../core/disk.js';
|
|
8
|
+
import { assertTestWritable } from '../../../shared/test-guard.js';
|
|
9
|
+
import { assertNoLinks, fileExists, isLink, isWin, removeLink } from '../../../core/util.js';
|
|
10
|
+
import { resetRunDir } from '../../../host/channel.js';
|
|
11
|
+
import { sandboxFeatureInstalled } from '../../../host/platform.js';
|
|
12
|
+
import { listSessions, runPaths, scanRequests } from '../../../shared/rundir.js';
|
|
13
|
+
import { removeTree } from '../data/remove.js';
|
|
14
|
+
import { deadPidFiles, findHalfInstalled, findLinks, findStagingLeftovers, globalPackages, resolvedVersionsValid } from './finders.js';
|
|
15
|
+
import { ok, whileRunning } from './types.js';
|
|
16
|
+
const rel = (root, p) => path.relative(root, p) || '.';
|
|
17
|
+
/** Fails (throws) unless every folder from the data folder to `p` is real. */
|
|
18
|
+
const guard = (env, p) => assertNoLinks(path.dirname(env.dataPath), p);
|
|
19
|
+
export const CHECKS = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Config file',
|
|
22
|
+
run: (env) => (env.configError
|
|
23
|
+
? { status: 'needs-you', detail: env.configError.message + (env.configError.fix ? ' ' + env.configError.fix : '') + ' (repair never rewrites your config; built-in defaults were used for the other checks.)' }
|
|
24
|
+
: ok(env.ctx.loaded.configPath ? env.ctx.loaded.configPath + ' is valid.' : 'none; using the built-in defaults.')),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'Windows Sandbox feature',
|
|
28
|
+
run: () => (sandboxFeatureInstalled() ? ok('enabled.') : { status: 'needs-you', detail: (isWin ? 'Windows Sandbox is not turned on.' : 'This is not Windows.') + ' Run: sandbox setup (it checks this PC and offers to turn it on).' }),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Links in the data folder',
|
|
32
|
+
run: (env) => {
|
|
33
|
+
if (isLink(env.dataPath))
|
|
34
|
+
return { status: 'needs-you', detail: env.dataPath + ' itself is a link or junction. agent-sandbox refuses to work through links; point storage.root at the real folder instead (sandbox config path).' };
|
|
35
|
+
// A running sandbox can swap folders for links while we look; only check a stopped one.
|
|
36
|
+
if (env.running)
|
|
37
|
+
return whileRunning;
|
|
38
|
+
const links = findLinks(env.dataPath);
|
|
39
|
+
if (!links.length)
|
|
40
|
+
return ok('none (nothing in ' + env.dataPath + ' points elsewhere).');
|
|
41
|
+
// A link here can only have been made inside the sandbox; it could point at this PC's own files. Remove the link
|
|
42
|
+
// itself, and only if every folder above it is still real.
|
|
43
|
+
for (const link of links) {
|
|
44
|
+
guard(env, path.dirname(link));
|
|
45
|
+
removeLink(link);
|
|
46
|
+
}
|
|
47
|
+
return { status: 'fixed', detail: 'removed ' + links.length + ' link(s)/junction(s) the sandbox created, without following them: ' + links.map((l) => rel(env.dataPath, l)).join(', ') };
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'Sandbox run state',
|
|
52
|
+
run: (env) => {
|
|
53
|
+
if (env.running)
|
|
54
|
+
return ok('a sandbox is running; its state is in use.');
|
|
55
|
+
if (isLink(runPaths(env.dataPath).run)) {
|
|
56
|
+
resetRunDir(env.dataPath);
|
|
57
|
+
return { status: 'fixed', detail: 'the run folder was a link; removed the link itself.' };
|
|
58
|
+
}
|
|
59
|
+
// The last sandbox's status file is normal after it closes; sessions still marked running or unanswered requests are not.
|
|
60
|
+
const ghosts = listSessions(env.dataPath).filter((s) => s.phase === 'running' || s.phase === 'starting').length;
|
|
61
|
+
const requests = scanRequests(env.dataPath);
|
|
62
|
+
const pending = requests.valid.length + requests.invalid.length;
|
|
63
|
+
if (!ghosts && !pending)
|
|
64
|
+
return ok('clean.');
|
|
65
|
+
resetRunDir(env.dataPath);
|
|
66
|
+
return { status: 'fixed', detail: 'no sandbox is running, but ' + _.compact([ghosts && ghosts + ' session(s) were still marked running', pending && pending + ' request(s) were never answered']).join(' and ') + '; cleared the run state so status/attach do not show ghosts.' };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'Hidden-window state',
|
|
71
|
+
run: (env) => {
|
|
72
|
+
const file = path.join(env.storageRoot, 'run', 'hidden-windows.json');
|
|
73
|
+
if (!fs.existsSync(file) && !isLink(file))
|
|
74
|
+
return ok('clean.');
|
|
75
|
+
if (env.running)
|
|
76
|
+
return ok('a sandbox is running; kept.');
|
|
77
|
+
assertTestWritable(file);
|
|
78
|
+
fs.rmSync(file, { force: true });
|
|
79
|
+
return { status: 'fixed', detail: 'removed the saved hidden-window list from a sandbox that is no longer running.' };
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'Sync watcher',
|
|
84
|
+
run: (env) => {
|
|
85
|
+
const dead = deadPidFiles(path.join(env.storageRoot, 'run'));
|
|
86
|
+
if (!dead.length)
|
|
87
|
+
return ok('no stale watcher records.');
|
|
88
|
+
for (const f of dead) {
|
|
89
|
+
assertTestWritable(f);
|
|
90
|
+
fs.rmSync(f, { force: true });
|
|
91
|
+
}
|
|
92
|
+
return { status: 'fixed', detail: 'removed ' + dead.length + ' record(s) of a history sync watcher that is no longer running (the next launch starts a new one).' };
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Interrupted downloads',
|
|
97
|
+
run: (env) => {
|
|
98
|
+
if (env.running)
|
|
99
|
+
return whileRunning;
|
|
100
|
+
guard(env, env.toolsDir);
|
|
101
|
+
const found = findStagingLeftovers(env.toolsDir);
|
|
102
|
+
if (!found.length)
|
|
103
|
+
return ok('none.');
|
|
104
|
+
for (const p of found)
|
|
105
|
+
removeTree(p, env.dataPath);
|
|
106
|
+
return { status: 'fixed', detail: 'removed ' + found.length + ' half-finished download/copy folder(s): ' + found.map((p) => rel(env.toolsDir, p)).join(', ') };
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Half-installed toolchains',
|
|
111
|
+
run: (env) => {
|
|
112
|
+
if (env.running)
|
|
113
|
+
return whileRunning;
|
|
114
|
+
guard(env, env.toolsDir);
|
|
115
|
+
const found = findHalfInstalled(env.toolsDir);
|
|
116
|
+
if (!found.length)
|
|
117
|
+
return ok('every toolchain folder has its program.');
|
|
118
|
+
for (const f of found)
|
|
119
|
+
removeTree(f.path, env.dataPath);
|
|
120
|
+
return { status: 'fixed', detail: 'removed ' + found.map((f) => rel(env.toolsDir, f.path) + ' (no ' + f.missing + ')').join(', ') + '; the next sandbox reinstalls them.' };
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'Resolved versions cache',
|
|
125
|
+
run: (env) => {
|
|
126
|
+
const file = path.join(env.toolsDir, 'resolved-versions.json');
|
|
127
|
+
if (!fs.existsSync(file) && !isLink(file))
|
|
128
|
+
return ok('not created yet.');
|
|
129
|
+
if (env.running)
|
|
130
|
+
return whileRunning;
|
|
131
|
+
guard(env, env.toolsDir);
|
|
132
|
+
if (isLink(file)) {
|
|
133
|
+
removeLink(file);
|
|
134
|
+
return { status: 'fixed', detail: 'removed a link in place of tools\\resolved-versions.json.' };
|
|
135
|
+
}
|
|
136
|
+
if (fs.statSync(file).size < 4 * 1024 * 1024 && resolvedVersionsValid(fs.readFileSync(file, 'utf8')))
|
|
137
|
+
return ok('valid.');
|
|
138
|
+
assertTestWritable(file);
|
|
139
|
+
fs.rmSync(file, { force: true });
|
|
140
|
+
return { status: 'fixed', detail: 'tools\\resolved-versions.json was damaged; removed it (versions are looked up again on the next start).' };
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'Installed packages',
|
|
145
|
+
run: () => {
|
|
146
|
+
const npmCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
147
|
+
if (!fileExists(npmCli))
|
|
148
|
+
return { status: 'skipped', detail: 'npm not found next to this Node (' + npmCli + ').' };
|
|
149
|
+
// cwd = Node's own folder: npm reads .npmrc from the current folder, which could be a sandbox-writable workspace.
|
|
150
|
+
const result = spawnSync(process.execPath, [npmCli, 'ls', '-g', '--json', '--depth=0'], { cwd: path.dirname(process.execPath), encoding: 'utf8', windowsHide: true, timeout: 60000, maxBuffer: 16 * 1024 * 1024 });
|
|
151
|
+
const names = globalPackages(result.stdout ?? '');
|
|
152
|
+
if (!names.length)
|
|
153
|
+
return { status: 'skipped', detail: 'could not read the global npm packages.' };
|
|
154
|
+
if (names.includes('agent-sandbox') && names.includes('windows-development-sandbox'))
|
|
155
|
+
return { status: 'needs-you', detail: 'the old agent-sandbox package is still installed next to windows-development-sandbox, and their commands clash. Run: npm uninstall -g agent-sandbox' };
|
|
156
|
+
return ok(names.includes('agent-sandbox') ? 'agent-sandbox installed (the old name; fine on its own).' : 'no old agent-sandbox package.');
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'Free disk',
|
|
161
|
+
run: (env) => {
|
|
162
|
+
const free = freeBytesAt(env.storageRoot);
|
|
163
|
+
if (free === null)
|
|
164
|
+
return { status: 'skipped', detail: 'could not check the drive of ' + env.storageRoot };
|
|
165
|
+
if (free >= 20 * GB)
|
|
166
|
+
return ok(humanBytes(free) + ' free.');
|
|
167
|
+
return { status: 'warn', detail: humanBytes(free) + ' free on the drive of ' + env.storageRoot + (free < 5 * GB ? ': toolchains need several GB.' : ': getting low.') + ' See what is stored with: sandbox data, and free space with: sandbox data clean <target>' };
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Interrupted downloads and copies: `<name>.staging-<hex>` and `node-staging`, in tools\ and one folder down (tools\go\1.23.staging-ab12). */
|
|
2
|
+
export declare const findStagingLeftovers: (toolsDir: string) => string[];
|
|
3
|
+
/**
|
|
4
|
+
* A toolchain folder and the program it must contain. `versions` means one folder per installed version (tools\go\1.23.2);
|
|
5
|
+
* `match` limits which version folders count (tools\nvm also holds settings and nvm.exe).
|
|
6
|
+
*/
|
|
7
|
+
export interface ToolRule {
|
|
8
|
+
folder: string;
|
|
9
|
+
exe: string[];
|
|
10
|
+
versions?: boolean;
|
|
11
|
+
match?: RegExp;
|
|
12
|
+
}
|
|
13
|
+
export declare const TOOL_RULES: ToolRule[];
|
|
14
|
+
/** Toolchain folders that exist but lack their main program (an install that stopped half way). Never enters links. */
|
|
15
|
+
export declare const findHalfInstalled: (toolsDir: string, rules?: ToolRule[]) => {
|
|
16
|
+
path: string;
|
|
17
|
+
missing: string;
|
|
18
|
+
}[];
|
|
19
|
+
/**
|
|
20
|
+
* Every link or junction under `root` (the links themselves; nothing behind them is visited). Before descending into
|
|
21
|
+
* a folder its real path must still be inside root: a folder swapped for a junction between two looks is not followed.
|
|
22
|
+
*/
|
|
23
|
+
export declare const findLinks: (root: string) => string[];
|
|
24
|
+
/** tools\resolved-versions.json is a JSON object (anything else makes the guest stumble). */
|
|
25
|
+
export declare const resolvedVersionsValid: (text: string) => boolean;
|
|
26
|
+
/** Package names from `npm ls -g --json`; [] when the output is not what npm prints. */
|
|
27
|
+
export declare const globalPackages: (json: string) => string[];
|
|
28
|
+
export declare const pidAlive: (pid: number) => boolean;
|
|
29
|
+
/** Sync-watcher pid files (storage\run\watcher-*.pid) whose process is gone (or whose content is not a pid). */
|
|
30
|
+
export declare const deadPidFiles: (runDir: string, alive?: (pid: number) => boolean) => string[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Pure finders the repair checks use (exported for tests): they look, they never change anything.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { dirExists, fileExists, isLink } from '../../../core/util.js';
|
|
6
|
+
/** Interrupted downloads and copies: `<name>.staging-<hex>` and `node-staging`, in tools\ and one folder down (tools\go\1.23.staging-ab12). */
|
|
7
|
+
export const findStagingLeftovers = (toolsDir) => {
|
|
8
|
+
const isStaging = (name) => /\.staging-[0-9a-f]+$/i.test(name) || name.toLowerCase() === 'node-staging';
|
|
9
|
+
const realChildren = (dir) => (dirExists(dir) && !isLink(dir) ? fs.readdirSync(dir).map((n) => path.join(dir, n)) : []);
|
|
10
|
+
return realChildren(toolsDir).flatMap((p) => (isStaging(path.basename(p)) ? [p] : isLink(p) ? [] : realChildren(p).filter((c) => isStaging(path.basename(c)))));
|
|
11
|
+
};
|
|
12
|
+
export const TOOL_RULES = [
|
|
13
|
+
{ folder: 'nvm', exe: ['nvm.exe'] },
|
|
14
|
+
{ folder: 'nvm', exe: ['node.exe'], versions: true, match: /^v\d+\.\d+\.\d+$/ },
|
|
15
|
+
{ folder: 'node', exe: ['node.exe'] },
|
|
16
|
+
{ folder: 'git', exe: ['bin', 'bash.exe'] },
|
|
17
|
+
{ folder: 'dotnet', exe: ['dotnet.exe'] },
|
|
18
|
+
{ folder: 'go', exe: ['bin', 'go.exe'], versions: true },
|
|
19
|
+
{ folder: 'java', exe: ['bin', 'java.exe'], versions: true },
|
|
20
|
+
{ folder: 'python', exe: ['python.exe'], versions: true },
|
|
21
|
+
];
|
|
22
|
+
/** Toolchain folders that exist but lack their main program (an install that stopped half way). Never enters links. */
|
|
23
|
+
export const findHalfInstalled = (toolsDir, rules = TOOL_RULES) => {
|
|
24
|
+
const realDir = (p) => dirExists(p) && !isLink(p);
|
|
25
|
+
const staging = (name) => /\.staging-[0-9a-f]+$/i.test(name) || name.toLowerCase() === 'node-staging';
|
|
26
|
+
return rules.flatMap((rule) => {
|
|
27
|
+
const base = path.join(toolsDir, rule.folder);
|
|
28
|
+
if (!realDir(base))
|
|
29
|
+
return [];
|
|
30
|
+
const folders = rule.versions
|
|
31
|
+
? fs.readdirSync(base).filter((n) => !staging(n) && (!rule.match || rule.match.test(n))).map((n) => path.join(base, n)).filter(realDir)
|
|
32
|
+
: [base];
|
|
33
|
+
return folders.filter((f) => !fileExists(path.join(f, ...rule.exe))).map((f) => ({ path: f, missing: path.join(...rule.exe) }));
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Every link or junction under `root` (the links themselves; nothing behind them is visited). Before descending into
|
|
38
|
+
* a folder its real path must still be inside root: a folder swapped for a junction between two looks is not followed.
|
|
39
|
+
*/
|
|
40
|
+
export const findLinks = (root) => {
|
|
41
|
+
if (isLink(root))
|
|
42
|
+
return [root];
|
|
43
|
+
const real = (p) => { try {
|
|
44
|
+
return fs.realpathSync.native(p).toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return '';
|
|
48
|
+
} };
|
|
49
|
+
const top = real(root);
|
|
50
|
+
const inside = (p) => { const r = real(p); return r !== '' && (r === top || r.startsWith(top + path.sep)); };
|
|
51
|
+
const walk = (dir) => {
|
|
52
|
+
if (!inside(dir))
|
|
53
|
+
return [];
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return entries.flatMap((d) => {
|
|
62
|
+
const p = path.join(dir, d.name);
|
|
63
|
+
if (d.isSymbolicLink() || isLink(p))
|
|
64
|
+
return [p];
|
|
65
|
+
return d.isDirectory() ? walk(p) : [];
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
return dirExists(root) ? walk(root) : [];
|
|
69
|
+
};
|
|
70
|
+
const ResolvedVersions = z.record(z.string(), z.unknown());
|
|
71
|
+
/** tools\resolved-versions.json is a JSON object (anything else makes the guest stumble). */
|
|
72
|
+
export const resolvedVersionsValid = (text) => {
|
|
73
|
+
try {
|
|
74
|
+
return ResolvedVersions.safeParse(JSON.parse(text.replace(/^/, ''))).success;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const NpmList = z.object({ dependencies: z.record(z.string(), z.unknown()).optional() });
|
|
81
|
+
/** Package names from `npm ls -g --json`; [] when the output is not what npm prints. */
|
|
82
|
+
export const globalPackages = (json) => {
|
|
83
|
+
try {
|
|
84
|
+
return Object.keys(NpmList.parse(JSON.parse(json)).dependencies ?? {});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const Pid = z.coerce.number().int().positive();
|
|
91
|
+
export const pidAlive = (pid) => { try {
|
|
92
|
+
process.kill(pid, 0);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
return e.code === 'EPERM';
|
|
97
|
+
} };
|
|
98
|
+
/** Sync-watcher pid files (storage\run\watcher-*.pid) whose process is gone (or whose content is not a pid). */
|
|
99
|
+
export const deadPidFiles = (runDir, alive = pidAlive) => (dirExists(runDir) ? fs.readdirSync(runDir) : [])
|
|
100
|
+
.filter((n) => /^watcher-.*\.pid$/i.test(n))
|
|
101
|
+
.map((n) => path.join(runDir, n))
|
|
102
|
+
.filter((f) => { try {
|
|
103
|
+
const pid = Pid.safeParse(fs.readFileSync(f, 'utf8').trim());
|
|
104
|
+
return !pid.success || !alive(pid.data);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
} });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UserError } from '../../../core/util.js';
|
|
2
|
+
import type { CliOptions } from '../../args.js';
|
|
3
|
+
import { type Context } from '../context.js';
|
|
4
|
+
import type { RepairCheck, RepairEnv, RepairResult } from './types.js';
|
|
5
|
+
/** Runs every check in order; a check that throws is reported, never stops the rest. */
|
|
6
|
+
export declare const runChecks: (env: RepairEnv, checks?: RepairCheck[]) => RepairResult[];
|
|
7
|
+
export declare const repair: (ctx: Context, configError?: UserError | null) => number;
|
|
8
|
+
/**
|
|
9
|
+
* Entry point that works even when the config file is broken (createContext would throw): falls back to the built-in
|
|
10
|
+
* defaults so the other checks still run, and reports the config problem as the first check.
|
|
11
|
+
*/
|
|
12
|
+
export declare const repairCommand: (packageRoot: string, entry: string, options: CliOptions) => number;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// `sandbox repair`: finds and fixes common problems. Each check (checks.ts) either confirms all is well (OK), fixes the
|
|
2
|
+
// problem itself (FIXED), or says what you need to do (NEEDS YOU). Toolchain and run-state fixes wait until no
|
|
3
|
+
// sandbox is running.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import { DEFAULTS, loadConfig, userDir } from '../../../core/config.js';
|
|
7
|
+
import { errMsg, expandPath, UserError } from '../../../core/util.js';
|
|
8
|
+
import { isSandboxRunning } from '../../../host/platform.js';
|
|
9
|
+
import { createContext, printJson, table } from '../context.js';
|
|
10
|
+
import { CHECKS } from './checks.js';
|
|
11
|
+
const LABEL = { ok: 'OK', fixed: 'FIXED', 'needs-you': 'NEEDS YOU', warn: 'WARNING', skipped: 'SKIPPED' };
|
|
12
|
+
/** Runs every check in order; a check that throws is reported, never stops the rest. */
|
|
13
|
+
export const runChecks = (env, checks = CHECKS) => checks.map((check) => {
|
|
14
|
+
try {
|
|
15
|
+
return { name: check.name, ...check.run(env) };
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
const fix = e instanceof UserError && e.fix ? ' ' + e.fix : '';
|
|
19
|
+
return { name: check.name, status: 'needs-you', detail: 'check failed: ' + errMsg(e) + fix };
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
export const repair = (ctx, configError = null) => {
|
|
23
|
+
const env = { ctx, running: isSandboxRunning(), dataPath: ctx.dataPath, storageRoot: ctx.storageRoot, toolsDir: path.join(ctx.dataPath, 'tools'), configError };
|
|
24
|
+
// Re-check the config file even when the context loaded (it may have been edited since, or come from --config).
|
|
25
|
+
if (!env.configError) {
|
|
26
|
+
try {
|
|
27
|
+
loadConfig(ctx.options.config);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
if (e instanceof UserError)
|
|
31
|
+
env.configError = e;
|
|
32
|
+
else
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const results = runChecks(env);
|
|
37
|
+
if (printJson(ctx, results))
|
|
38
|
+
return results.some((r) => r.status === 'needs-you') ? 1 : 0;
|
|
39
|
+
table(results.map((r) => [LABEL[r.status], r.name, r.detail]));
|
|
40
|
+
const counts = _.countBy(results, 'status');
|
|
41
|
+
console.log('\n' + (counts['needs-you'] ? counts['needs-you'] + ' thing(s) need you (see NEEDS YOU above).' : 'All good' + (counts.fixed ? ': fixed ' + counts.fixed + ' problem(s).' : '.')));
|
|
42
|
+
return counts['needs-you'] ? 1 : 0;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Entry point that works even when the config file is broken (createContext would throw): falls back to the built-in
|
|
46
|
+
* defaults so the other checks still run, and reports the config problem as the first check.
|
|
47
|
+
*/
|
|
48
|
+
export const repairCommand = (packageRoot, entry, options) => {
|
|
49
|
+
let ctx;
|
|
50
|
+
try {
|
|
51
|
+
ctx = createContext(packageRoot, entry, options);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (!(e instanceof UserError))
|
|
55
|
+
throw e;
|
|
56
|
+
const loaded = { config: structuredClone(DEFAULTS), configPath: null, base: userDir() };
|
|
57
|
+
const storageRoot = expandPath(loaded.config.storage.root, loaded.base);
|
|
58
|
+
return repair({ packageRoot, entry, options, loaded, storageRoot, dataPath: path.join(storageRoot, 'data', loaded.config.storage.dataName) }, e);
|
|
59
|
+
}
|
|
60
|
+
return repair(ctx);
|
|
61
|
+
};
|