windows-development-sandbox 1.0.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -8
- package/config.example.json +55 -5
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.js +38 -4
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.js +2 -2
- package/dist/cli/commands/agent-commands.d.ts +6 -0
- package/dist/cli/commands/agent-commands.js +61 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +5 -1
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +106 -0
- package/dist/cli/commands/data/collect.d.ts +11 -0
- package/dist/cli/commands/data/collect.js +54 -0
- package/dist/cli/commands/data/index.d.ts +4 -0
- package/dist/cli/commands/data/index.js +45 -0
- package/dist/cli/commands/data/remove.d.ts +2 -0
- package/dist/cli/commands/data/remove.js +30 -0
- package/dist/cli/commands/index.d.ts +6 -4
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +256 -42
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +67 -5
- package/dist/cli/commands/project.d.ts +29 -0
- package/dist/cli/commands/project.js +63 -0
- package/dist/cli/commands/projects.d.ts +14 -0
- package/dist/cli/commands/projects.js +146 -0
- package/dist/cli/commands/repair/checks.d.ts +2 -0
- package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
- package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
- package/dist/cli/commands/repair/finders.js +108 -0
- package/dist/cli/commands/repair/index.d.ts +12 -0
- package/dist/cli/commands/repair/index.js +61 -0
- package/dist/cli/commands/repair/types.d.ts +26 -0
- package/dist/cli/commands/repair/types.js +3 -0
- package/dist/cli/commands/restart.d.ts +6 -0
- package/dist/cli/commands/restart.js +85 -0
- package/dist/cli/commands/sessions.d.ts +2 -1
- package/dist/cli/commands/sessions.js +189 -45
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
- package/dist/cli/commands/setup/facts.js +60 -0
- package/dist/cli/commands/setup/index.d.ts +2 -0
- package/dist/cli/commands/setup/index.js +43 -0
- package/dist/cli/commands/user-config.d.ts +8 -0
- package/dist/cli/commands/user-config.js +41 -0
- package/dist/cli/history.d.ts +11 -0
- package/dist/cli/history.js +21 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.js +42 -15
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +40 -15
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/menu/several.d.ts +20 -0
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +12 -6
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +23 -0
- package/dist/core/local-config.js +79 -7
- package/dist/core/project-policy.d.ts +15 -0
- package/dist/core/project-policy.js +35 -0
- package/dist/core/prompt.d.ts +4 -0
- package/dist/core/prompt.js +15 -0
- package/dist/core/safe-paths.d.ts +4 -0
- package/dist/core/safe-paths.js +37 -0
- package/dist/core/schema/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -0
- package/dist/core/schema/common.d.ts +17 -0
- package/dist/core/schema/common.js +15 -0
- package/dist/core/schema/config.d.ts +659 -0
- package/dist/core/schema/config.js +75 -0
- package/dist/core/schema/databases.d.ts +33 -0
- package/dist/core/schema/databases.js +35 -0
- package/dist/core/schema/harness.d.ts +12 -0
- package/dist/core/schema/harness.js +17 -0
- package/dist/core/schema/json-schema.d.ts +5 -0
- package/dist/core/schema/json-schema.js +25 -0
- package/dist/core/schema/profile.d.ts +32 -0
- package/dist/core/schema/profile.js +31 -0
- package/dist/core/schema/projects.d.ts +9 -0
- package/dist/core/schema/projects.js +11 -0
- package/dist/core/schema/sandbox.d.ts +78 -0
- package/dist/core/schema/sandbox.js +64 -0
- package/dist/core/schema/toolchains.d.ts +96 -0
- package/dist/core/schema/toolchains.js +88 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +35 -1
- package/dist/core/util.d.ts +9 -0
- package/dist/core/util.js +14 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +33 -50
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -0
- package/dist/guest/boot-env.d.ts +4 -0
- package/dist/guest/boot-env.js +25 -0
- package/dist/guest/cmdline.d.ts +12 -0
- package/dist/guest/cmdline.js +23 -0
- package/dist/guest/daemon/exec.d.ts +5 -0
- package/dist/guest/daemon/exec.js +32 -0
- package/dist/guest/daemon/project.d.ts +14 -0
- package/dist/guest/daemon/project.js +132 -0
- package/dist/guest/daemon/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -0
- package/dist/guest/daemon/session.d.ts +13 -0
- package/dist/guest/daemon/session.js +139 -0
- package/dist/guest/daemon/shutdown.d.ts +1 -0
- package/dist/guest/daemon/shutdown.js +21 -0
- package/dist/guest/daemon/state.d.ts +17 -0
- package/dist/guest/daemon/state.js +31 -0
- package/dist/guest/daemon.js +12 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +3 -0
- package/dist/guest/env.js +7 -3
- package/dist/guest/guide.d.ts +6 -2
- package/dist/guest/guide.js +5 -4
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.js +31 -8
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -0
- package/dist/guest/ports.d.ts +6 -0
- package/dist/guest/ports.js +22 -0
- package/dist/guest/project-context.d.ts +7 -0
- package/dist/guest/project-context.js +10 -0
- package/dist/guest/project-env.d.ts +20 -0
- package/dist/guest/project-env.js +20 -0
- package/dist/guest/project-status.d.ts +5 -0
- package/dist/guest/project-status.js +17 -0
- package/dist/guest/ptyhost.d.ts +0 -18
- package/dist/guest/ptyhost.js +17 -65
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +59 -5
- package/dist/guest/setup/environment.d.ts +13 -3
- package/dist/guest/setup/environment.js +14 -10
- package/dist/guest/setup/hooks.d.ts +9 -1
- package/dist/guest/setup/hooks.js +28 -14
- package/dist/guest/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- package/dist/guest/toolchains/catalogue.d.ts +4 -0
- package/dist/guest/toolchains/catalogue.js +2 -2
- package/dist/guest/toolchains/cli.js +25 -17
- package/dist/guest/toolchains/common.d.ts +18 -1
- package/dist/guest/toolchains/common.js +34 -11
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
- package/dist/guest/toolchains/databases/index.js +62 -0
- package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
- package/dist/guest/toolchains/databases/mongodb.js +31 -0
- package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
- package/dist/guest/toolchains/databases/mysql.js +38 -0
- package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
- package/dist/guest/toolchains/databases/postgres.js +38 -0
- package/dist/guest/toolchains/databases/redis.d.ts +3 -0
- package/dist/guest/toolchains/databases/redis.js +16 -0
- package/dist/guest/toolchains/databases/server.d.ts +14 -0
- package/dist/guest/toolchains/databases/server.js +52 -0
- package/dist/guest/toolchains/git.js +25 -16
- package/dist/guest/toolchains/go.js +1 -1
- package/dist/guest/toolchains/index.d.ts +13 -9
- package/dist/guest/toolchains/index.js +40 -82
- package/dist/guest/toolchains/java.js +5 -5
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.js +12 -10
- package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
- package/dist/guest/toolchains/native/cpp.d.ts +4 -0
- package/dist/guest/toolchains/native/cpp.js +38 -0
- package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
- package/dist/guest/toolchains/native/dotnet.js +103 -0
- package/dist/guest/toolchains/native/mesa.d.ts +7 -0
- package/dist/guest/toolchains/native/mesa.js +56 -0
- package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
- package/dist/guest/toolchains/native/msvc/environment.js +57 -0
- package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/index.js +48 -0
- package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/packages.js +156 -0
- package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
- package/dist/guest/toolchains/native/msvc/tree.js +21 -0
- package/dist/guest/toolchains/native/rust.d.ts +3 -0
- package/dist/guest/toolchains/native/rust.js +45 -0
- package/dist/guest/toolchains/node.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +24 -0
- package/dist/guest/toolchains/plan.js +140 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +43 -12
- package/dist/guest/toolchains/runtimes.d.ts +2 -0
- package/dist/guest/toolchains/runtimes.js +6 -8
- package/dist/guest/toolchains/scripting/php.d.ts +3 -0
- package/dist/guest/toolchains/scripting/php.js +71 -0
- package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
- package/dist/guest/toolchains/scripting/ruby.js +47 -0
- package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
- package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
- package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- package/dist/guest/view.js +21 -1
- package/dist/host/accounts.js +14 -5
- package/dist/host/agent-commands.d.ts +25 -0
- package/dist/host/agent-commands.js +145 -0
- package/dist/host/attach.d.ts +17 -0
- package/dist/host/attach.js +70 -0
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -0
- package/dist/host/channel.d.ts +23 -22
- package/dist/host/channel.js +66 -84
- package/dist/host/claude-profile.js +7 -3
- package/dist/host/cleanup.js +2 -11
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +10 -12
- package/dist/host/generate.js +58 -135
- package/dist/host/join.d.ts +28 -0
- package/dist/host/join.js +76 -0
- package/dist/host/launch/mappings.d.ts +3 -0
- package/dist/host/launch/mappings.js +54 -0
- package/dist/host/launch/projects.d.ts +15 -0
- package/dist/host/launch/projects.js +56 -0
- package/dist/host/launch/stage.d.ts +11 -0
- package/dist/host/launch/stage.js +66 -0
- package/dist/host/launch/wsb.d.ts +10 -0
- package/dist/host/launch/wsb.js +34 -0
- package/dist/host/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/platform.d.ts +2 -0
- package/dist/host/platform.js +16 -1
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- package/dist/host/project-scan.d.ts +1 -17
- package/dist/host/project-scan.js +2 -86
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +25 -5
- package/dist/host/repo-guard.js +242 -23
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +48 -1
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.js +7 -3
- package/dist/host/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +11791 -2821
- package/dist/runtime/db.js +6585 -466
- package/dist/runtime/main.js +10238 -1724
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +6183 -59
- package/dist/selftest/accounts.test.d.ts +2 -0
- package/dist/selftest/accounts.test.js +44 -0
- package/dist/selftest/agent-commands.test.d.ts +2 -0
- package/dist/selftest/agent-commands.test.js +47 -0
- package/dist/selftest/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +44 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +145 -27
- package/dist/selftest/cmdline.test.d.ts +2 -0
- package/dist/selftest/cmdline.test.js +45 -0
- package/dist/selftest/config.test.d.ts +2 -3
- package/dist/selftest/config.test.js +36 -72
- package/dist/selftest/data.test.d.ts +2 -2
- package/dist/selftest/data.test.js +77 -70
- package/dist/selftest/detect.test.d.ts +2 -2
- package/dist/selftest/detect.test.js +54 -38
- package/dist/selftest/fixtures.d.ts +89 -4
- package/dist/selftest/fixtures.js +43 -24
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +68 -0
- package/dist/selftest/git-access.test.d.ts +2 -0
- package/dist/selftest/git-access.test.js +26 -0
- package/dist/selftest/guest-boot.test.d.ts +2 -0
- package/dist/selftest/guest-boot.test.js +76 -0
- package/dist/selftest/harness.d.ts +44 -7
- package/dist/selftest/harness.js +62 -21
- package/dist/selftest/host-sim.test.d.ts +2 -0
- package/dist/selftest/host-sim.test.js +93 -0
- package/dist/selftest/hostile-input.test.d.ts +2 -0
- package/dist/selftest/hostile-input.test.js +54 -0
- package/dist/selftest/index.d.ts +8 -1
- package/dist/selftest/index.js +137 -35
- package/dist/selftest/links.test.d.ts +2 -0
- package/dist/selftest/links.test.js +50 -0
- package/dist/selftest/live-daemon.test.d.ts +2 -0
- package/dist/selftest/live-daemon.test.js +91 -0
- package/dist/selftest/live-git.test.d.ts +2 -0
- package/dist/selftest/live-git.test.js +60 -0
- package/dist/selftest/live-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -0
- package/dist/selftest/live-projects.test.d.ts +2 -0
- package/dist/selftest/live-projects.test.js +127 -0
- package/dist/selftest/live.d.ts +65 -0
- package/dist/selftest/live.js +109 -0
- package/dist/selftest/local-config.test.d.ts +2 -0
- package/dist/selftest/local-config.test.js +120 -0
- package/dist/selftest/mirror.test.d.ts +2 -3
- package/dist/selftest/mirror.test.js +92 -65
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +40 -0
- package/dist/selftest/personalization.test.d.ts +2 -0
- package/dist/selftest/personalization.test.js +53 -0
- package/dist/selftest/projects.test.d.ts +2 -0
- package/dist/selftest/projects.test.js +254 -0
- package/dist/selftest/repair.test.d.ts +2 -0
- package/dist/selftest/repair.test.js +42 -0
- package/dist/selftest/repo-guard.test.d.ts +2 -0
- package/dist/selftest/repo-guard.test.js +121 -0
- package/dist/selftest/secrets.test.d.ts +2 -0
- package/dist/selftest/secrets.test.js +71 -0
- package/dist/selftest/setup.test.d.ts +2 -2
- package/dist/selftest/setup.test.js +45 -22
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -3
- package/dist/selftest/sync.test.js +130 -78
- package/dist/selftest/test-guard.test.d.ts +2 -0
- package/dist/selftest/test-guard.test.js +49 -0
- package/dist/selftest/toolchains.test.d.ts +2 -0
- package/dist/selftest/toolchains.test.js +133 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- package/dist/shared/paths.d.ts +5 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +12 -1
- package/dist/shared/rundir.js +14 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +453 -0
- package/dist/shared/schema.js +43 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +6 -3
- package/dist/sync/history.d.ts +29 -12
- package/dist/sync/history.js +72 -25
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +99 -8
- package/package.json +7 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +20 -0
- package/sandbox/logon.cmd +24 -16
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +120 -11
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +403 -3
- package/schemas/sandbox-config.schema.json +143 -13
- package/dist/cli/commands/data.d.ts +0 -46
- package/dist/cli/commands/data.js +0 -249
- package/dist/cli/commands/setup.js +0 -152
- package/dist/cli/menu.d.ts +0 -22
- package/dist/cli/menu.js +0 -187
- package/dist/cli/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
|
@@ -2,6 +2,20 @@ import type { Config, HostProbe, ResolvedRun, RunRequest } from '../core/types.j
|
|
|
2
2
|
import { generateSandbox } from '../host/generate.js';
|
|
3
3
|
import type { Harness } from './harness.js';
|
|
4
4
|
export declare const HOST_NODE_VERSION = "v22.3.0";
|
|
5
|
+
/** Colour codes are removed from captured output, so tests can match plain text. */
|
|
6
|
+
export declare const stripAnsi: (text: string) => string;
|
|
7
|
+
export interface NodeRun {
|
|
8
|
+
status: number | null;
|
|
9
|
+
out: string;
|
|
10
|
+
timedOut: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface NodeRunOptions {
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Runs `node <args>` and collects stdout + stderr (colours removed). Never throws; a hung process is killed at the timeout. */
|
|
18
|
+
export declare const runNode: (args: string[], options?: NodeRunOptions) => Promise<NodeRun>;
|
|
5
19
|
export declare const createFixtures: (t: Harness) => {
|
|
6
20
|
at: (...parts: string[]) => string;
|
|
7
21
|
hostNode: string;
|
|
@@ -17,6 +31,18 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
17
31
|
defaultHarness: string;
|
|
18
32
|
guestWorkspace: string;
|
|
19
33
|
readOnly: boolean;
|
|
34
|
+
workspaceFilter: {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
exclude: string[];
|
|
37
|
+
include: string[];
|
|
38
|
+
};
|
|
39
|
+
audit: {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
days: number;
|
|
42
|
+
maxMb: number;
|
|
43
|
+
processes: boolean;
|
|
44
|
+
files: boolean;
|
|
45
|
+
};
|
|
20
46
|
mappings: {
|
|
21
47
|
host: string;
|
|
22
48
|
guest: string;
|
|
@@ -26,6 +52,9 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
26
52
|
sandbox: {
|
|
27
53
|
memoryMb: number | "auto";
|
|
28
54
|
maxMemoryMb: number;
|
|
55
|
+
maxMemoryPercent: number;
|
|
56
|
+
cpuLimit: number | "none";
|
|
57
|
+
pageFileMb: number;
|
|
29
58
|
reserveHostMb: number;
|
|
30
59
|
vGpu: boolean;
|
|
31
60
|
networking: boolean;
|
|
@@ -35,14 +64,25 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
35
64
|
printerRedirection: boolean;
|
|
36
65
|
protectedClient: boolean;
|
|
37
66
|
headless: boolean;
|
|
67
|
+
keepAwake: boolean;
|
|
38
68
|
};
|
|
39
69
|
storage: {
|
|
40
70
|
root: string;
|
|
41
71
|
persistAgentState: boolean;
|
|
42
72
|
dataName: string;
|
|
73
|
+
markSandboxFiles: boolean;
|
|
74
|
+
warnDataGb: number;
|
|
43
75
|
};
|
|
44
76
|
network: {
|
|
45
77
|
blockPrivateRanges: boolean;
|
|
78
|
+
allowOnly: string[];
|
|
79
|
+
blockHosts: string[];
|
|
80
|
+
forwardPorts: {
|
|
81
|
+
pc: number;
|
|
82
|
+
sandbox: number;
|
|
83
|
+
}[];
|
|
84
|
+
dns: string[];
|
|
85
|
+
proxy: string;
|
|
46
86
|
};
|
|
47
87
|
provisioning: {
|
|
48
88
|
copyFromHost: boolean;
|
|
@@ -55,6 +95,12 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
55
95
|
terminal: {
|
|
56
96
|
enabled: boolean;
|
|
57
97
|
};
|
|
98
|
+
conpty: {
|
|
99
|
+
enabled: boolean;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
downloads: {
|
|
103
|
+
requireChecksum: boolean;
|
|
58
104
|
};
|
|
59
105
|
toolchains: {
|
|
60
106
|
node: {
|
|
@@ -145,6 +191,9 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
145
191
|
};
|
|
146
192
|
vscode: {
|
|
147
193
|
enabled: boolean;
|
|
194
|
+
setup: boolean;
|
|
195
|
+
recommended: boolean;
|
|
196
|
+
extensions: string[];
|
|
148
197
|
};
|
|
149
198
|
environment: Record<string, string>;
|
|
150
199
|
};
|
|
@@ -184,6 +233,10 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
184
233
|
auto: boolean;
|
|
185
234
|
autoHide: boolean;
|
|
186
235
|
};
|
|
236
|
+
agentCommands: {
|
|
237
|
+
enabled: boolean;
|
|
238
|
+
agents: string[] | "auto";
|
|
239
|
+
};
|
|
187
240
|
secrets: {
|
|
188
241
|
strip: boolean;
|
|
189
242
|
allow: string[];
|
|
@@ -193,6 +246,19 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
193
246
|
personalization: {
|
|
194
247
|
enabled: boolean;
|
|
195
248
|
};
|
|
249
|
+
apps: {
|
|
250
|
+
install: string[];
|
|
251
|
+
custom: Record<string, {
|
|
252
|
+
name: string;
|
|
253
|
+
exe: string;
|
|
254
|
+
github?: string | undefined;
|
|
255
|
+
asset?: string | undefined;
|
|
256
|
+
url?: string | undefined;
|
|
257
|
+
sha256?: string | undefined;
|
|
258
|
+
unpack?: "file" | "archive" | "nsis" | "msi" | undefined;
|
|
259
|
+
cli?: boolean | undefined;
|
|
260
|
+
}>;
|
|
261
|
+
};
|
|
196
262
|
claudeProfile: {
|
|
197
263
|
enabled: boolean;
|
|
198
264
|
hostDir: string;
|
|
@@ -201,7 +267,9 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
201
267
|
copyCredentials: boolean;
|
|
202
268
|
overwrite: boolean;
|
|
203
269
|
syncHistory: boolean;
|
|
270
|
+
historyScope: "project" | "all";
|
|
204
271
|
syncClaudeMem: boolean;
|
|
272
|
+
syncClaudeMemBack: boolean;
|
|
205
273
|
};
|
|
206
274
|
harnesses: Record<string, {
|
|
207
275
|
arguments: string[];
|
|
@@ -217,16 +285,33 @@ export declare const createFixtures: (t: Harness) => {
|
|
|
217
285
|
hooks: {
|
|
218
286
|
failFast: boolean;
|
|
219
287
|
};
|
|
288
|
+
projects: {
|
|
289
|
+
all: boolean;
|
|
290
|
+
exclude: string[];
|
|
291
|
+
roots: string[];
|
|
292
|
+
askToAddRoot: boolean;
|
|
293
|
+
declinedRoots: string[];
|
|
294
|
+
wsbShare: boolean;
|
|
295
|
+
};
|
|
220
296
|
$schema?: string | undefined;
|
|
221
297
|
};
|
|
222
298
|
probe: HostProbe;
|
|
223
299
|
run: (over?: Partial<RunRequest>, config?: Config) => Promise<ResolvedRun>;
|
|
224
300
|
generate: (r: ResolvedRun, args?: string[], p?: HostProbe) => ReturnType<typeof generateSandbox>;
|
|
301
|
+
launch: (over?: Partial<RunRequest>, options?: {
|
|
302
|
+
config?: Config;
|
|
303
|
+
args?: string[];
|
|
304
|
+
probe?: HostProbe;
|
|
305
|
+
}) => Promise<ReturnType<typeof generateSandbox>>;
|
|
225
306
|
writeLocal: (dir: string, content: unknown) => void;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
307
|
+
historySides: (name: string, hostWorkspace: string, guestWorkspace: string) => {
|
|
308
|
+
hostClaudeDir: string;
|
|
309
|
+
guestClaudeDir: string;
|
|
310
|
+
hostWorkspace: string;
|
|
311
|
+
guestWorkspace: string;
|
|
229
312
|
};
|
|
230
|
-
|
|
313
|
+
runGuest: (launchPath: string, data: string, workspace: string, extra?: Record<string, string>) => Promise<NodeRun>;
|
|
314
|
+
runBin: (name: string, args: string[], options?: NodeRunOptions) => Promise<NodeRun>;
|
|
315
|
+
readJson: <T = any>(file: string) => T;
|
|
231
316
|
};
|
|
232
317
|
export type Fixtures = ReturnType<typeof createFixtures>;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
// A fake "host PC" inside the temp folder: Node, claude.exe, a ~/.claude profile, workspaces, and helpers to resolve
|
|
2
|
-
// generate
|
|
3
|
-
import fs from 'node:fs';
|
|
1
|
+
// A fake "host PC" inside the temp folder: Node, claude.exe, a ~/.claude profile, workspaces, and helpers to resolve a
|
|
2
|
+
// run, generate the sandbox for it, run the guest against it and start the CLI. Shared by all suites.
|
|
4
3
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
6
5
|
import { DEFAULTS } from '../core/config.js';
|
|
7
6
|
import { generateSandbox } from '../host/generate.js';
|
|
8
|
-
import { ensureDir } from '../core/util.js';
|
|
9
7
|
import { resolveRun } from '../core/workspace.js';
|
|
10
8
|
export const HOST_NODE_VERSION = 'v22.3.0';
|
|
9
|
+
/** Colour codes are removed from captured output, so tests can match plain text. */
|
|
10
|
+
export const stripAnsi = (text) => text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
11
|
+
/** Runs `node <args>` and collects stdout + stderr (colours removed). Never throws; a hung process is killed at the timeout. */
|
|
12
|
+
export const runNode = (args, options = {}) => new Promise((resolve) => {
|
|
13
|
+
const child = spawn(process.execPath, args, { cwd: options.cwd, env: options.env ?? process.env, windowsHide: true });
|
|
14
|
+
let out = '';
|
|
15
|
+
let timedOut = false;
|
|
16
|
+
const timer = setTimeout(() => { timedOut = true; child.kill(); }, options.timeoutMs ?? 120000);
|
|
17
|
+
child.stdout.on('data', (c) => { out += c.toString(); });
|
|
18
|
+
child.stderr.on('data', (c) => { out += c.toString(); });
|
|
19
|
+
child.on('error', (e) => { out += String(e); });
|
|
20
|
+
child.on('close', (status) => { clearTimeout(timer); resolve({ status: timedOut ? null : status, out: stripAnsi(out), timedOut }); });
|
|
21
|
+
});
|
|
11
22
|
export const createFixtures = (t) => {
|
|
12
|
-
const {
|
|
13
|
-
|
|
23
|
+
const { at, write } = t;
|
|
24
|
+
// The PC: Node with npm, Claude Code, a ~/.claude profile (with a login that must never be copied) and ~/.claude.json.
|
|
14
25
|
const hostNode = at('host-node');
|
|
15
26
|
write(path.join(hostNode, 'node.exe'), 'x');
|
|
16
27
|
write(path.join(hostNode, 'node_modules', 'npm', 'bin', 'npm-cli.js'), '');
|
|
@@ -22,16 +33,18 @@ export const createFixtures = (t) => {
|
|
|
22
33
|
write(path.join(hostProfile, 'commands', 'review.md'), 'review cmd');
|
|
23
34
|
write(path.join(hostProfile, 'plugins', 'installed_plugins.json'), JSON.stringify({ path: hostProfile + '\\plugins\\cache\\claude-mem' }));
|
|
24
35
|
write(path.join(hostProfile, '.credentials.json'), '{ "token": "secret" }');
|
|
25
|
-
const hostGlobalState =
|
|
36
|
+
const hostGlobalState = t.writeJson(at('host-claude.json'), { hasCompletedOnboarding: true, mcpServers: { github: { command: 'npx' } } });
|
|
37
|
+
// The install folder, two workspaces (B has a setup script), a folder to share, the storage root, and tools "from this PC".
|
|
26
38
|
const install = at('install');
|
|
27
|
-
|
|
28
|
-
const workA =
|
|
39
|
+
t.dir('install', 'sub');
|
|
40
|
+
const workA = t.dir('work-a');
|
|
29
41
|
const workB = at('work-b');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
t.dir('work-b', 'assets');
|
|
43
|
+
const shared = t.dir('shared');
|
|
44
|
+
t.dir('storage');
|
|
33
45
|
write(path.join(workB, 'setup.mjs'), "import fs from 'node:fs'; import path from 'node:path'; fs.writeFileSync(path.join(process.env.AGENT_DATA, 'hook-ran.txt'), process.env.AGENT_WORKSPACE);\n");
|
|
34
46
|
write(at('host-tools', 'agents', 'claude', 'claude.exe'), 'fake-claude');
|
|
47
|
+
/** The default config, pointed at the fake PC. `copyFromHost = false` makes the sandbox download everything instead. */
|
|
35
48
|
const makeConfig = (copyFromHost = true) => {
|
|
36
49
|
const c = structuredClone(DEFAULTS);
|
|
37
50
|
c.storage.root = at('storage');
|
|
@@ -47,17 +60,23 @@ export const createFixtures = (t) => {
|
|
|
47
60
|
};
|
|
48
61
|
const baseConfig = makeConfig();
|
|
49
62
|
const probe = { nodeDir: hostNode, harnessExe: { dir: hostClaude, file: 'claude.exe' }, gitDir: null, nvmDir: null, hostNodeVersion: HOST_NODE_VERSION };
|
|
50
|
-
|
|
51
|
-
const
|
|
63
|
+
/** Resolves a run for `workA` (or whatever `over` says). */
|
|
64
|
+
const run = (over = {}, config = baseConfig) => resolveRun({ packageRoot: install, loaded: { config, configPath: null, base: t.temp }, cwd: workA, noAgent: false, noLocalConfig: false, trustConfig: false, scripts: [], ...over }, HOST_NODE_VERSION);
|
|
65
|
+
const generate = (r, args = [], p = probe) =>
|
|
66
|
+
// A fixed "this PC has" list, so "auto" toolchains decide the same on every machine the tests run on.
|
|
67
|
+
generateSandbox({ packageRoot: t.packageRoot, run: r, harnessArgs: args, probe: p, pcLanguages: ['python', 'rust', 'cpp', 'dotnet'] });
|
|
68
|
+
/** Resolves a run and generates its sandbox (.wsb + launch folder + session.json) in one step. */
|
|
69
|
+
const launch = async (over = {}, options = {}) => generate(await run(over, options.config ?? baseConfig), options.args, options.probe);
|
|
70
|
+
/** Writes a project's `.sandbox-config.json` (objects are serialised; strings are written as they are). */
|
|
52
71
|
const writeLocal = (dir, content) => { write(path.join(dir, '.sandbox-config.json'), typeof content === 'string' ? content : JSON.stringify(content)); };
|
|
72
|
+
/** The four folders history sync works with, for a workspace on the PC and its place in the sandbox. */
|
|
73
|
+
const historySides = (name, hostWorkspace, guestWorkspace) => ({ hostClaudeDir: at(name + '-host'), guestClaudeDir: at(name + '-guest'), hostWorkspace, guestWorkspace });
|
|
53
74
|
/** Runs the guest's bundled main.js in dry-run mode against fake sandbox folders. */
|
|
54
|
-
const runGuest = (launchPath, data, workspace, extra = {}) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
};
|
|
61
|
-
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
62
|
-
return { at, hostNode, hostClaude, hostProfile, hostGlobalState, install, workA, workB, shared, makeConfig, baseConfig, probe, run, generate, writeLocal, runGuest, readJson };
|
|
75
|
+
const runGuest = async (launchPath, data, workspace, extra = {}) => runNode([path.join(launchPath, 'runtime', 'main.js')], {
|
|
76
|
+
env: { ...process.env, AGENT_LAUNCH_DIR: launchPath, AGENT_DATA_DIR: data, AGENT_HOST_TOOLS_DIR: at('host-tools'), AGENT_WORKSPACE: workspace, AGENT_LOCAL_DIR: at('local'), AGENT_MANAGED_DIR: at('managed'), AGENT_DRY_RUN: '1', ...extra },
|
|
77
|
+
});
|
|
78
|
+
/** Runs a built command (dist/bin/<name>.js) as its own process. */
|
|
79
|
+
const runBin = (name, args, options = {}) => runNode([path.join(t.packageRoot, 'dist', 'bin', name + '.js'), ...args], options);
|
|
80
|
+
const readJson = t.readJson;
|
|
81
|
+
return { at, hostNode, hostClaude, hostProfile, hostGlobalState, install, workA, workB, shared, makeConfig, baseConfig, probe, run, generate, launch, writeLocal, historySides, runGuest, runBin, readJson };
|
|
63
82
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// What the host generates for a sandbox: the .wsb file, the launch folder (runtime, C# sources, boot scripts),
|
|
2
|
+
// session.json, the staged Claude profile, and the values read from this PC (git identity, free RAM).
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { LocalOverrides } from '../core/schema.js';
|
|
6
|
+
import { logonEnv } from '../host/generate.js';
|
|
7
|
+
import { gitIdentity } from '../host/launch/stage.js';
|
|
8
|
+
import { buildWsb, resolveMemoryMb } from '../host/launch/wsb.js';
|
|
9
|
+
export const generateSuite = {
|
|
10
|
+
name: 'generate',
|
|
11
|
+
title: 'generated .wsb, launch folder and session.json',
|
|
12
|
+
run: async (t, f) => {
|
|
13
|
+
t.section('the .wsb file');
|
|
14
|
+
const gen = await f.launch({}, { args: ['--version'], probe: { ...f.probe, gitDir: f.shared, nvmDir: f.workB } });
|
|
15
|
+
const wsb = fs.readFileSync(gen.wsbPath, 'utf8');
|
|
16
|
+
t.includes(wsb, '<MemoryInMB>8192</MemoryInMB>', 'wsb: the configured memory');
|
|
17
|
+
t.includes(wsb, '<SandboxFolder>C:\\Workspace\\work-a</SandboxFolder>', 'wsb: the workspace is mapped to its guest folder');
|
|
18
|
+
t.includes(wsb, 'C:\\HostTools\\git', 'wsb: this PC\'s git is mapped');
|
|
19
|
+
t.includes(wsb, 'C:\\HostTools\\nvm', 'wsb: this PC\'s nvm is mapped');
|
|
20
|
+
t.excludes(wsb, 'Missing', 'wsb: an optional mapping whose folder is missing is skipped');
|
|
21
|
+
t.includes(wsb, 'logon.cmd</Command>', 'wsb: the logon command');
|
|
22
|
+
t.includes(wsb, '<vGPU>Disable</vGPU>', 'wsb: vGPU is off');
|
|
23
|
+
t.includes(buildWsb(f.baseConfig, 4096, [{ host: 'C:\\a&b', guest: 'C:\\X', readOnly: true }]), 'C:\\a&b', 'wsb values are XML-escaped');
|
|
24
|
+
t.section('the launch folder');
|
|
25
|
+
const expectedFiles = ['package.json', 'runtime/main.js', 'runtime/daemon.js', 'runtime/view.js', 'runtime/gitguard.js', 'sandbox/bootstrap.ps1', 'sandbox/logon.cmd', 'sandbox/trust.cmd', 'sandbox/ptyhost.cs', 'sandbox/gitshim.cs', 'sandbox/cpucap.ps1', 'sandbox/cpucap.cs', 'sandbox/pagefile.ps1', 'sandbox/pagefile.cs', 'sandbox/memlog.ps1'];
|
|
26
|
+
const missing = expectedFiles.filter((file) => !fs.existsSync(path.join(gen.launchPath, file)));
|
|
27
|
+
t.equal(missing, [], 'the launch folder has the runtime, the C# sources and the boot scripts');
|
|
28
|
+
t.equal(fs.readdirSync(path.join(gen.launchPath, 'runtime')).length, 6, 'the runtime is 6 self-contained files');
|
|
29
|
+
const logon = fs.readFileSync(path.join(gen.launchPath, 'sandbox', 'logon.cmd'), 'utf8');
|
|
30
|
+
t.check(logon.indexOf('trust.cmd') < logon.indexOf('wt.exe'), 'logon.cmd applies the sandbox trust settings before opening Windows Terminal');
|
|
31
|
+
t.includes(logon, 'tools\\terminal\\wt.exe', 'logon.cmd opens Windows Terminal from the tools folder');
|
|
32
|
+
t.includes(logon, '-NoExit', 'logon.cmd falls back to a PowerShell window');
|
|
33
|
+
t.check(logon.indexOf('env.cmd') > 0 && logon.indexOf('env.cmd') < logon.indexOf('wt.exe'), 'logon.cmd sets AGENT_WORKSPACE (env.cmd) before Windows Terminal starts, so new tabs can open in it');
|
|
34
|
+
t.includes(fs.readFileSync(path.join(gen.launchPath, 'env.cmd'), 'utf8'), 'AGENT_WORKSPACE=', 'env.cmd names the workspace');
|
|
35
|
+
t.equal(logonEnv('C:\\Workspace\\100%\\x'), '@set "AGENT_WORKSPACE=C:\\Workspace\\100%%\\x"\r\n', 'a % in the workspace path is escaped for cmd');
|
|
36
|
+
t.section('session.json');
|
|
37
|
+
const session = f.readJson(gen.sessionPath);
|
|
38
|
+
t.equal({
|
|
39
|
+
launchId: session.launchId === gen.launchId,
|
|
40
|
+
workspace: session.workspace,
|
|
41
|
+
workspaceHost: session.workspaceHost,
|
|
42
|
+
firstArgument: session.harnessArguments[0],
|
|
43
|
+
gitFromHost: Boolean(session.hostTools.git),
|
|
44
|
+
nvmFromHost: Boolean(session.hostTools.nvm),
|
|
45
|
+
nodeSpec: session.versions.node.spec,
|
|
46
|
+
gitAccess: session.provisioning.git.access,
|
|
47
|
+
}, { launchId: true, workspace: 'C:\\Workspace\\work-a', workspaceHost: f.workA, firstArgument: '--version', gitFromHost: true, nvmFromHost: true, nodeSpec: '22.3.0', gitAccess: 'full' }, 'session.json carries the id, paths, agent arguments, host tools, versions and git access');
|
|
48
|
+
t.matches(session.hostTools.nodeVersion ?? '', /^\d+\.\d+\.\d+$/, 'session.json: the host Node version is a plain version');
|
|
49
|
+
t.equal(session.cpuLimitPercent, 80, 'session.json carries the CPU cap for cpucap.ps1 (default 80 %)');
|
|
50
|
+
t.equal(session.pageFileMb, 16384, 'session.json carries the paging file limit for pagefile.ps1 (default 16 GB)');
|
|
51
|
+
t.check(!LocalOverrides.safeParse({ sandbox: { cpuLimit: 'none' } }).success, "a project's .sandbox-config.json cannot lift your CPU cap (sandbox.cpuLimit is yours only)");
|
|
52
|
+
t.section('what is copied from this PC');
|
|
53
|
+
const staged = path.join(gen.launchPath, 'claude-profile');
|
|
54
|
+
const stagedMissing = ['settings.json', 'CLAUDE.md', '.claude.json', 'plugins'].filter((name) => !fs.existsSync(path.join(staged, name)));
|
|
55
|
+
t.equal(stagedMissing, [], 'Claude settings, instructions, global state and plugins are staged');
|
|
56
|
+
t.check(!fs.existsSync(path.join(staged, '.credentials.json')), 'the Claude login is not staged');
|
|
57
|
+
const gitconfig = '[user]\n\tname = Josh\n\temail = "j@x.io"\n[credential]\n\thelper = store\n[url "https://TOKEN@github.com/"]\n\tinsteadOf = https://github.com/\n';
|
|
58
|
+
t.equal(gitIdentity(gitconfig), '[user]\n\tname = Josh\n\temail = j@x.io\n', 'only name/email from ~/.gitconfig reach the sandbox (no credential helpers or token URLs)');
|
|
59
|
+
t.equal(gitIdentity('[core]\n\tname = x\n'), '', 'a name outside [user] is not an identity');
|
|
60
|
+
const auto = { ...f.baseConfig.sandbox, memoryMb: 'auto' };
|
|
61
|
+
const MB = 1048576;
|
|
62
|
+
const pcRam = 64 * 1024 * MB;
|
|
63
|
+
t.equal(resolveMemoryMb(auto, 40000 * MB, pcRam), 31744, "memory 'auto' follows free RAM minus the reserve");
|
|
64
|
+
await t.throws(() => resolveMemoryMb(auto, 9000 * MB, pcRam), 'Not enough free RAM', "memory 'auto' refuses when too little RAM is free");
|
|
65
|
+
t.equal(resolveMemoryMb({ ...auto, maxMemoryPercent: 80 }, 30000 * MB, 32 * 1024 * MB), 21504, "memory 'auto' never takes more than maxMemoryPercent of the PC's RAM");
|
|
66
|
+
t.equal(resolveMemoryMb({ ...auto, maxMemoryPercent: 50 }, 60000 * MB, pcRam), 32768, "memory 'auto' is capped at half the PC's RAM with maxMemoryPercent 50 (free RAM and maxMemoryMb still apply)");
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Git access modes ("full", "readonly", "none"): which git commands the guard allows and the matching Claude deny rules.
|
|
2
|
+
import { gitDecision } from '../guest/gitguard.js';
|
|
3
|
+
import { gitDenyRules } from '../guest/gitrules.js';
|
|
4
|
+
export const gitAccessSuite = {
|
|
5
|
+
name: 'git-access',
|
|
6
|
+
title: 'git access modes and Claude deny rules',
|
|
7
|
+
run: (t) => {
|
|
8
|
+
t.section('readonly');
|
|
9
|
+
const allowed = (args, mode = 'readonly') => gitDecision(args, mode).allow;
|
|
10
|
+
const reading = ['status', 'log --oneline', 'diff HEAD~1', 'show HEAD', 'blame a.ts', 'branch', 'branch -a', 'branch --contains HEAD', 'tag', 'tag -l v*', 'remote -v', 'config --get user.name', 'stash list', '-C sub status', '--no-pager log'];
|
|
11
|
+
t.equal(reading.filter((command) => !allowed(command.split(' '))), [], 'readonly: reading commands are allowed');
|
|
12
|
+
const writing = ['commit -m x', 'push', 'add .', 'checkout main', 'switch -c x', 'reset --hard', 'stash', 'stash pop', 'branch new', 'branch -D x', 'tag v1', 'merge x', 'rebase main', 'fetch', 'pull', 'config user.name x', 'remote add o u', '-c core.hooksPath=x status', '--git-dir=x status', 'clean -fd', 'apply p.diff'];
|
|
13
|
+
t.equal(writing.filter((command) => allowed(command.split(' '))), [], 'readonly: anything that writes (or overrides config) is refused');
|
|
14
|
+
t.section('none and full');
|
|
15
|
+
t.check(!allowed(['status'], 'none') && !allowed(['--version'], 'none'), 'none: every git call is refused');
|
|
16
|
+
t.check(allowed(['commit'], 'full'), 'full: everything is allowed');
|
|
17
|
+
t.section('Claude managed deny rules');
|
|
18
|
+
const readonlyRules = gitDenyRules('readonly');
|
|
19
|
+
const noneRules = gitDenyRules('none');
|
|
20
|
+
t.equal(gitDenyRules('full').length, 0, 'full: no deny rules');
|
|
21
|
+
t.includes(readonlyRules.join('\n'), 'Bash(git push:*)', 'readonly: pushing is denied');
|
|
22
|
+
t.check(!readonlyRules.some((r) => r.startsWith('Read(')), 'readonly: reading .git is not denied');
|
|
23
|
+
t.check(noneRules.includes('Bash(git:*)'), 'none: every git command is denied');
|
|
24
|
+
t.check(noneRules.includes('Read(**/.git/**)'), 'none: reading .git is denied too');
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The guest's boot script (runtime/main.js) run in dry-run mode against fake sandbox folders: what it provisions,
|
|
2
|
+
// what it copies in, and what it refuses to.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readJsonFile } from '../shared/rundir.js';
|
|
6
|
+
export const guestBootSuite = {
|
|
7
|
+
name: 'guest-boot',
|
|
8
|
+
title: 'guest boot in dry-run mode (provisioning, Claude state, git policy)',
|
|
9
|
+
run: async (t, f) => {
|
|
10
|
+
const gen = await f.launch({}, { args: ['--version'], probe: { ...f.probe, gitDir: f.shared, nvmDir: f.workB } });
|
|
11
|
+
t.section('first boot');
|
|
12
|
+
const data1 = t.at('data1');
|
|
13
|
+
const boot = await f.runGuest(gen.launchPath, data1, f.workA, { AGENT_TEST_EXEMPT: '172.28.0.1' });
|
|
14
|
+
t.check(boot.status === 0, 'the guest boots and exits cleanly', 'exit code ' + boot.status + '\n' + boot.out);
|
|
15
|
+
t.includes(boot.out, 'would run:', 'the guest starts the agent...');
|
|
16
|
+
t.includes(boot.out, '--version', '... with its arguments...');
|
|
17
|
+
t.includes(boot.out, 'cwd=' + f.workA, '... in the workspace');
|
|
18
|
+
for (const step of ['would provision nvm and Node 22.3.0 (from this PC)', 'would provision Python 3', 'would provision MinGW-w64 GCC', 'would provision Rust', '.NET SDK', 'would provision bun', 'would provision uv', 'would provision Git for Windows from this PC']) {
|
|
19
|
+
t.includes(boot.out, step, 'the guest provisions: ' + step);
|
|
20
|
+
}
|
|
21
|
+
t.includes(boot.out, 'AgentSandbox-Block-Private', 'the firewall plan blocks the LAN...');
|
|
22
|
+
t.includes(boot.out, 'remoteport=1-52,54-65535', '... but keeps DNS to the gateway');
|
|
23
|
+
t.equal(readJsonFile(path.join(data1, 'run', 'sandbox.json'))?.phase, 'ready', 'the guest reports "ready" in run/sandbox.json');
|
|
24
|
+
t.section('Claude state copied in');
|
|
25
|
+
const claudeState = path.join(data1, 'state', 'claude');
|
|
26
|
+
t.includes(fs.readFileSync(path.join(claudeState, 'settings.json'), 'utf8'), 'dark', 'the guest applied this PC\'s Claude settings');
|
|
27
|
+
t.check(fs.existsSync(path.join(claudeState, 'commands', 'review.md')), 'custom commands are copied');
|
|
28
|
+
t.check(!fs.existsSync(path.join(claudeState, '.credentials.json')), 'the login is not copied');
|
|
29
|
+
const plugins = fs.readFileSync(path.join(claudeState, 'plugins', 'installed_plugins.json'), 'utf8');
|
|
30
|
+
t.includes(plugins, JSON.stringify(claudeState).slice(1, -1), 'plugin manifests are re-pointed at the sandbox Claude folder');
|
|
31
|
+
const guestState = f.readJson(path.join(claudeState, '.claude.json'));
|
|
32
|
+
t.equal(guestState.mcpServers?.github?.command, 'npx', 'MCP servers from this PC are in the sandbox');
|
|
33
|
+
const manifest = fs.readFileSync(f.at('managed', 'CLAUDE.md'), 'utf8');
|
|
34
|
+
t.includes(manifest, 'DISPOSABLE Windows Sandbox', 'the agent manifest goes to the managed CLAUDE.md...');
|
|
35
|
+
t.includes(manifest, f.workA, '... naming the workspace');
|
|
36
|
+
t.check(manifest.split('\n').length < 16, 'the managed manifest stays tiny', manifest.split('\n').length + ' lines');
|
|
37
|
+
t.equal(fs.readFileSync(path.join(claudeState, 'CLAUDE.md'), 'utf8'), 'my rules', 'the user\'s own CLAUDE.md is untouched');
|
|
38
|
+
t.check(fs.existsSync(path.join(data1, 'SANDBOX.md')), 'SANDBOX.md is written to the data folder');
|
|
39
|
+
t.check(!fs.existsSync(f.at('managed', 'managed-settings.json')), 'git access full: no managed deny rules');
|
|
40
|
+
t.section('second boot');
|
|
41
|
+
// The host changed its MCP servers: the sandbox keeps its own login and .claude.json but gets the new servers.
|
|
42
|
+
fs.writeFileSync(path.join(claudeState, '.claude.json'), JSON.stringify({ ...guestState, oauthAccount: { emailAddress: 'sandbox@x' }, mcpServers: { local: { command: 'x' } } }));
|
|
43
|
+
t.writeJson(path.join(gen.launchPath, 'claude-profile', '.claude.json'), { mcpServers: { github: { command: 'npx2' } }, projects: { [f.workA.replace(/\\/g, '/')]: { mcpServers: { db: { command: 'pg' } } } } });
|
|
44
|
+
await f.runGuest(gen.launchPath, data1, f.workA);
|
|
45
|
+
const merged = f.readJson(path.join(claudeState, '.claude.json'));
|
|
46
|
+
t.equal(merged.oauthAccount.emailAddress, 'sandbox@x', 'MCP re-sync leaves the sandbox login alone');
|
|
47
|
+
t.equal(merged.mcpServers.github.command, 'npx2', 'MCP re-sync updates a server changed on this PC');
|
|
48
|
+
t.equal(merged.mcpServers.local.command, 'x', 'MCP re-sync keeps a server added in the sandbox');
|
|
49
|
+
const projectServers = Object.values(merged.projects);
|
|
50
|
+
t.check(projectServers.some((p) => p.mcpServers?.db), 'MCP re-sync brings in project-scope servers', JSON.stringify(merged.projects));
|
|
51
|
+
t.section('other modes');
|
|
52
|
+
const readonlyConfig = f.makeConfig();
|
|
53
|
+
readonlyConfig.provisioning.git.access = 'readonly';
|
|
54
|
+
const genRo = await f.launch({}, { config: readonlyConfig });
|
|
55
|
+
await f.runGuest(genRo.launchPath, t.at('data-ro'), f.workA, { AGENT_MANAGED_DIR: t.at('managed-ro') });
|
|
56
|
+
const deny = f.readJson(t.at('managed-ro', 'managed-settings.json')).permissions.deny;
|
|
57
|
+
t.check(deny.includes('Bash(git commit:*)') && deny.includes('PowerShell(git push:*)'), 'git readonly: Claude managed settings deny writing git commands', JSON.stringify(deny));
|
|
58
|
+
t.check(deny.includes('Edit(**/.git/**)'), 'git readonly: .git edits are denied');
|
|
59
|
+
t.check(!deny.includes('Bash(git status:*)'), 'git readonly: reading git commands stay allowed');
|
|
60
|
+
f.writeLocal(f.workB, { scripts: ['./setup.mjs'] });
|
|
61
|
+
const genHook = await f.launch({ cwd: f.workB, harness: 'claude', trustConfig: true });
|
|
62
|
+
const hook = await f.runGuest(genHook.launchPath, t.at('data-hook'), f.workB);
|
|
63
|
+
t.check(hook.status === 0, 'a project with "scripts" boots', hook.out);
|
|
64
|
+
t.equal(fs.readFileSync(t.at('data-hook', 'hook-ran.txt'), 'utf8'), f.workB, 'local "scripts" run inside the sandbox with the workspace set');
|
|
65
|
+
const genInstall = await f.launch({}, { config: f.makeConfig(false), probe: { ...f.probe, nodeDir: null, harnessExe: null } });
|
|
66
|
+
const install = await f.runGuest(genInstall.launchPath, t.at('data3'), f.workA, { AGENT_HOST_TOOLS_DIR: t.at('empty') });
|
|
67
|
+
t.check(install.status === 0, 'install mode boots', install.out);
|
|
68
|
+
t.includes(install.out, 'npm-cli.js', 'install mode: npm is used...');
|
|
69
|
+
t.includes(install.out, '@anthropic-ai/claude-code', '... to install the agent');
|
|
70
|
+
t.excludes(fs.readFileSync(genInstall.wsbPath, 'utf8'), 'HostTools', 'install mode: nothing is mapped from this PC');
|
|
71
|
+
const genNoAgent = await f.launch({ noAgent: true });
|
|
72
|
+
const noAgent = await f.runGuest(genNoAgent.launchPath, t.at('data5'), f.workA);
|
|
73
|
+
t.check(noAgent.status === 0, '--no-agent boots', noAgent.out);
|
|
74
|
+
t.includes(noAgent.out, 'no agent requested', '--no-agent provisions without an agent');
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -1,14 +1,51 @@
|
|
|
1
|
+
/** Text a failure message must contain (string) or match (RegExp). */
|
|
2
|
+
export type Expected = string | RegExp;
|
|
1
3
|
export interface Harness {
|
|
2
4
|
packageRoot: string;
|
|
5
|
+
/** The folder every test works in; removed at the end. */
|
|
3
6
|
temp: string;
|
|
4
|
-
check: (ok: boolean, name: string) => void;
|
|
5
|
-
rejects: (fn: () => unknown, needle: string) => Promise<boolean>;
|
|
6
|
-
write: (file: string, content: string) => string;
|
|
7
|
-
/** Polls `read` until it returns something truthy (or the timeout passes). */
|
|
8
|
-
waitFor: <T>(read: () => T | null | undefined | false | Promise<T | null | undefined | false>, ms?: number) => Promise<T | null>;
|
|
9
7
|
section: (title: string) => void;
|
|
8
|
+
/** Prints a SKIP line (not a check). */
|
|
9
|
+
skip: (what: string) => void;
|
|
10
|
+
/** The basic assertion. `detail` is printed under a failing check, so say what was found. */
|
|
11
|
+
check: (ok: boolean, name: string, detail?: string) => void;
|
|
12
|
+
/** Deep equality; on failure both values are shown. */
|
|
13
|
+
equal: <T>(actual: T, expected: T, name: string) => void;
|
|
14
|
+
/** `text` contains `needle`; on failure the text is shown. */
|
|
15
|
+
includes: (text: string, needle: string, name: string) => void;
|
|
16
|
+
/** `text` does not contain `needle`. */
|
|
17
|
+
excludes: (text: string, needle: string, name: string) => void;
|
|
18
|
+
/** `text` matches the pattern. */
|
|
19
|
+
matches: (text: string, pattern: RegExp, name: string) => void;
|
|
20
|
+
/** `fn` (sync or async) throws an error whose message (or `fix` hint) contains/matches `expected`. */
|
|
21
|
+
throws: (fn: () => unknown, expected: Expected, name: string) => Promise<void>;
|
|
22
|
+
/** Runs `fn` and returns the message of the error it throws ('' when it does not throw). */
|
|
23
|
+
thrownMessage: (fn: () => unknown) => Promise<string>;
|
|
24
|
+
/** Path below the temp folder (nothing is created). */
|
|
25
|
+
at: (...parts: string[]) => string;
|
|
26
|
+
/** Creates (if needed) and returns a folder below the temp folder. */
|
|
27
|
+
dir: (...parts: string[]) => string;
|
|
28
|
+
/** Writes a text file below anywhere in the temp folder (parents are created) and returns its path. */
|
|
29
|
+
write: (file: string, content: string) => string;
|
|
30
|
+
/** Writes `value` as JSON and returns the path. */
|
|
31
|
+
writeJson: (file: string, value: unknown) => string;
|
|
32
|
+
readJson: <T = any>(file: string) => T;
|
|
33
|
+
/**
|
|
34
|
+
* Sets a file's modified time to `secondsFromNow` (default: 10 s ahead). Sync decides what changed by comparing
|
|
35
|
+
* modified times, so tests make a change clearly newer this way instead of sleeping.
|
|
36
|
+
*/
|
|
37
|
+
touch: (file: string, secondsFromNow?: number) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Polls `read` every `everyMs` until it returns something truthy and returns that, or returns null after `ms`.
|
|
40
|
+
* Use this instead of fixed sleeps: it is fast when the machine is idle and patient when it is busy.
|
|
41
|
+
*/
|
|
42
|
+
waitFor: <T>(read: () => T | null | undefined | false | Promise<T | null | undefined | false>, ms?: number, everyMs?: number) => Promise<T | null>;
|
|
10
43
|
}
|
|
11
|
-
export
|
|
44
|
+
export interface RunningHarness extends Harness {
|
|
45
|
+
/** Number of checks that failed so far. */
|
|
12
46
|
failures: () => number;
|
|
47
|
+
/** Number of checks that ran so far (passed or failed). */
|
|
48
|
+
checks: () => number;
|
|
13
49
|
dispose: () => void;
|
|
14
|
-
}
|
|
50
|
+
}
|
|
51
|
+
export declare const createHarness: (packageRoot: string) => RunningHarness;
|
package/dist/selftest/harness.js
CHANGED
|
@@ -1,36 +1,77 @@
|
|
|
1
|
-
// Minimal test harness: PASS/FAIL lines
|
|
1
|
+
// Minimal test harness: PASS/FAIL lines (a failure shows what was expected and what was found), assertion helpers,
|
|
2
|
+
// and a temp folder that is always removed. Every test writes only below `t.temp`.
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
6
|
+
import { inspect, isDeepStrictEqual } from 'node:util';
|
|
5
7
|
import { ensureDir, errMsg, sleep } from '../core/util.js';
|
|
8
|
+
const show = (value) => inspect(value, { depth: 6, breakLength: 110, maxStringLength: 600, maxArrayLength: 40 });
|
|
9
|
+
const indent = (text) => text.split('\n').map((line) => ' ' + line).join('\n');
|
|
10
|
+
const clip = (text, max = 800) => (text.length > max ? text.slice(0, max) + ' ... (' + text.length + ' characters)' : text);
|
|
6
11
|
export const createHarness = (packageRoot) => {
|
|
7
|
-
let
|
|
12
|
+
let failed = 0;
|
|
13
|
+
let total = 0;
|
|
8
14
|
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-sandbox-test-'));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const check = (ok, name, detail) => {
|
|
16
|
+
total++;
|
|
17
|
+
console.log((ok ? '\x1b[32mPASS\x1b[0m ' : '\x1b[31mFAIL\x1b[0m ') + name);
|
|
18
|
+
if (ok)
|
|
19
|
+
return;
|
|
20
|
+
failed++;
|
|
21
|
+
if (detail)
|
|
22
|
+
console.log(indent(detail));
|
|
23
|
+
};
|
|
24
|
+
/** Runs `fn` once; the message includes the `fix` hint of a UserError. */
|
|
25
|
+
const attempt = async (fn) => {
|
|
26
|
+
try {
|
|
16
27
|
await fn();
|
|
17
|
-
return false;
|
|
28
|
+
return { threw: false };
|
|
18
29
|
}
|
|
19
30
|
catch (e) {
|
|
20
|
-
return (errMsg(e) + ' ' + (e.fix ?? '')).
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
return { threw: true, message: (errMsg(e) + ' ' + (e.fix ?? '')).trim() };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const thrownMessage = async (fn) => {
|
|
35
|
+
const outcome = await attempt(fn);
|
|
36
|
+
return outcome.threw ? outcome.message : '';
|
|
37
|
+
};
|
|
38
|
+
const write = (file, content) => { ensureDir(path.dirname(file)); fs.writeFileSync(file, content); return file; };
|
|
39
|
+
return {
|
|
40
|
+
packageRoot,
|
|
41
|
+
temp,
|
|
42
|
+
failures: () => failed,
|
|
43
|
+
checks: () => total,
|
|
44
|
+
check,
|
|
45
|
+
equal: (actual, expected, name) => check(isDeepStrictEqual(actual, expected), name, 'expected: ' + show(expected) + '\nactual: ' + show(actual)),
|
|
46
|
+
includes: (text, needle, name) => check(text.includes(needle), name, 'expected to contain: ' + show(needle) + '\nactual: ' + show(clip(text))),
|
|
47
|
+
excludes: (text, needle, name) => check(!text.includes(needle), name, 'expected NOT to contain: ' + show(needle) + '\nactual: ' + show(clip(text))),
|
|
48
|
+
matches: (text, pattern, name) => check(pattern.test(text), name, 'expected to match: ' + String(pattern) + '\nactual: ' + show(clip(text))),
|
|
49
|
+
throws: async (fn, expected, name) => {
|
|
50
|
+
const outcome = await attempt(fn);
|
|
51
|
+
const message = outcome.threw ? outcome.message : '';
|
|
52
|
+
const ok = outcome.threw && (typeof expected === 'string' ? message.includes(expected) : expected.test(message));
|
|
53
|
+
check(ok, name, 'expected an error containing: ' + String(expected) + '\n' + (outcome.threw ? 'actual error: ' + show(clip(message)) : 'but nothing was thrown'));
|
|
54
|
+
},
|
|
55
|
+
thrownMessage,
|
|
56
|
+
section: (title) => console.log('\n\x1b[36m-- ' + title + '\x1b[0m'),
|
|
57
|
+
skip: (what) => console.log('SKIP ' + what),
|
|
58
|
+
at: (...parts) => path.join(temp, ...parts),
|
|
59
|
+
dir: (...parts) => { const p = path.join(temp, ...parts); ensureDir(p); return p; },
|
|
60
|
+
write,
|
|
61
|
+
writeJson: (file, value) => write(file, JSON.stringify(value)),
|
|
62
|
+
readJson: (file) => JSON.parse(fs.readFileSync(file, 'utf8')),
|
|
63
|
+
touch: (file, secondsFromNow = 10) => { const when = new Date(Date.now() + secondsFromNow * 1000); fs.utimesSync(file, when, when); },
|
|
64
|
+
waitFor: async (read, ms = 30000, everyMs = 100) => {
|
|
24
65
|
const end = Date.now() + ms;
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
return
|
|
29
|
-
|
|
66
|
+
for (;;) {
|
|
67
|
+
const value = await read();
|
|
68
|
+
if (value)
|
|
69
|
+
return value;
|
|
70
|
+
if (Date.now() >= end)
|
|
71
|
+
return null;
|
|
72
|
+
await sleep(everyMs);
|
|
30
73
|
}
|
|
31
|
-
return null;
|
|
32
74
|
},
|
|
33
|
-
section: (title) => console.log('\n\x1b[36m-- ' + title + '\x1b[0m'),
|
|
34
75
|
dispose: () => fs.rmSync(temp, { recursive: true, force: true, maxRetries: 5, retryDelay: 300 }),
|
|
35
76
|
};
|
|
36
77
|
};
|