windows-development-sandbox 1.0.5 → 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/LICENSE +21 -0
- package/README.md +22 -6
- package/config.example.json +17 -0
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +35 -3
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -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 +2 -0
- package/dist/cli/commands/context.js +3 -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 +5 -3
- package/dist/cli/commands/index.js +5 -3
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +220 -39
- package/dist/cli/commands/maintenance.js +33 -4
- 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 +38 -13
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +35 -15
- package/dist/cli/menu-multi.d.ts +12 -0
- package/dist/cli/menu-multi.js +82 -0
- package/dist/cli/menu.d.ts +5 -13
- package/dist/cli/menu.js +149 -46
- 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.js +4 -1
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +8 -0
- package/dist/core/local-config.js +18 -5
- 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 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +29 -1
- package/dist/core/util.d.ts +7 -0
- package/dist/core/util.js +12 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +12 -46
- 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.js +8 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +2 -0
- package/dist/guest/env.js +4 -2
- 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 +17 -7
- 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 +5 -63
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.js +11 -3
- 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/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 +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.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +22 -0
- package/dist/guest/toolchains/plan.js +96 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +17 -10
- 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/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 +69 -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 +4 -12
- package/dist/host/generate.js +48 -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 +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/platform.d.ts +2 -0
- package/dist/host/platform.js +13 -1
- 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 +15 -2
- package/dist/host/repo-guard.js +103 -12
- package/dist/host/terminal.js +3 -0
- 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/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +10997 -2794
- package/dist/runtime/db.js +6428 -464
- package/dist/runtime/main.js +7359 -1135
- package/dist/runtime/view.js +5922 -47
- 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 -2
- package/dist/selftest/cli.test.js +115 -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 +44 -4
- package/dist/selftest/fixtures.js +40 -23
- 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 +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 +134 -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-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 -3
- package/dist/selftest/mirror.test.js +92 -65
- 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 -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 +90 -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 +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 +3 -1
- package/dist/shared/download.js +19 -4
- 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/rundir.d.ts +10 -1
- package/dist/shared/rundir.js +12 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +443 -0
- package/dist/shared/schema.js +41 -2
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +3 -1
- package/dist/sync/history.d.ts +23 -12
- package/dist/sync/history.js +52 -20
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +23 -4
- package/package.json +6 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cpucap.ps1 +51 -0
- package/sandbox/logon.cmd +18 -16
- package/sandbox/openssl.cs +42 -0
- package/sandbox/ptyhost.cs +15 -4
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +100 -1
- package/schemas/sandbox-config.schema.json +17 -1
- 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/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// openssl.exe stand-in on the sandbox's PATH (tools\shims), running the copy Git for Windows ships in
|
|
2
|
+
// <tools>\git\mingw64\bin. A real .exe, so CreateProcess callers (Rust's Command::new("openssl"), Node's spawn) find it,
|
|
3
|
+
// which a .cmd would not be; and mingw64\bin itself stays off PATH, where it would shadow the MinGW gcc.
|
|
4
|
+
// It finds Git relative to itself (<tools>\shims\openssl.exe -> <tools>\git\...) and sets OPENSSL_CONF for the child
|
|
5
|
+
// only when the caller has none. C# 5 (compiled in the sandbox by the .NET Framework csc).
|
|
6
|
+
using System;
|
|
7
|
+
using System.Diagnostics;
|
|
8
|
+
using System.IO;
|
|
9
|
+
|
|
10
|
+
static class OpensslShim
|
|
11
|
+
{
|
|
12
|
+
static int Main()
|
|
13
|
+
{
|
|
14
|
+
string here = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
|
15
|
+
string mingw = Path.GetFullPath(Path.Combine(here, "..\\git\\mingw64"));
|
|
16
|
+
string exe = Path.Combine(mingw, "bin\\openssl.exe");
|
|
17
|
+
if (!File.Exists(exe))
|
|
18
|
+
{
|
|
19
|
+
Console.Error.WriteLine("openssl is not available: Git for Windows is not installed in this sandbox.");
|
|
20
|
+
return 127;
|
|
21
|
+
}
|
|
22
|
+
var start = new ProcessStartInfo(exe, ArgumentsOf(Environment.CommandLine)) { UseShellExecute = false };
|
|
23
|
+
string conf = Path.Combine(mingw, "etc\\ssl\\openssl.cnf");
|
|
24
|
+
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENSSL_CONF")) && File.Exists(conf))
|
|
25
|
+
start.EnvironmentVariables["OPENSSL_CONF"] = conf;
|
|
26
|
+
using (Process p = Process.Start(start))
|
|
27
|
+
{
|
|
28
|
+
p.WaitForExit();
|
|
29
|
+
return p.ExitCode;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Everything after the program name, untouched, so quoting survives exactly as the caller wrote it.
|
|
34
|
+
static string ArgumentsOf(string commandLine)
|
|
35
|
+
{
|
|
36
|
+
string s = commandLine.TrimStart();
|
|
37
|
+
int end;
|
|
38
|
+
if (s.StartsWith("\"")) { end = s.IndexOf('"', 1); end = end < 0 ? s.Length : end + 1; }
|
|
39
|
+
else { end = s.IndexOf(' '); if (end < 0) end = s.Length; }
|
|
40
|
+
return s.Substring(end).TrimStart();
|
|
41
|
+
}
|
|
42
|
+
}
|
package/sandbox/ptyhost.cs
CHANGED
|
@@ -181,6 +181,8 @@ sealed class Client
|
|
|
181
181
|
public readonly NetworkStream Stream;
|
|
182
182
|
public readonly BlockingCollection<byte[]> Outbox = new BlockingCollection<byte[]>(4096);
|
|
183
183
|
public readonly bool Remote;
|
|
184
|
+
/** Set by the writer thread once everything queued has been written and the connection is closed. */
|
|
185
|
+
public readonly ManualResetEvent Finished = new ManualResetEvent(false);
|
|
184
186
|
public bool ControlOnly, Query;
|
|
185
187
|
public short Cols, Rows;
|
|
186
188
|
long lastSeen = DateTime.UtcNow.Ticks;
|
|
@@ -203,7 +205,11 @@ sealed class Client
|
|
|
203
205
|
{
|
|
204
206
|
try { foreach (byte[] frame in Outbox.GetConsumingEnumerable()) Stream.Write(frame, 0, frame.Length); }
|
|
205
207
|
catch (Exception) { }
|
|
206
|
-
finally
|
|
208
|
+
finally
|
|
209
|
+
{
|
|
210
|
+
try { Tcp.Close(); } catch (Exception) { }
|
|
211
|
+
Finished.Set();
|
|
212
|
+
}
|
|
207
213
|
}
|
|
208
214
|
}
|
|
209
215
|
|
|
@@ -323,7 +329,7 @@ sealed class ClientHub
|
|
|
323
329
|
var everyone = Snapshot();
|
|
324
330
|
foreach (Client c in everyone) { Send(c, Frame.Exit, code); Retire(c); }
|
|
325
331
|
// Give the writer threads a moment to deliver the EXIT frame before the process ends.
|
|
326
|
-
foreach (Client c in everyone)
|
|
332
|
+
foreach (Client c in everyone) c.Finished.WaitOne(1000);
|
|
327
333
|
}
|
|
328
334
|
|
|
329
335
|
/** Stops sending to a client after what is already queued (its writer closes the connection when done). */
|
|
@@ -343,11 +349,16 @@ sealed class ClientHub
|
|
|
343
349
|
if (next != null) TakeOwnership(next);
|
|
344
350
|
}
|
|
345
351
|
|
|
346
|
-
/**
|
|
352
|
+
/**
|
|
353
|
+
* Disconnects a client. Frames already queued (e.g. "access denied", STATUS) are written first: the client's writer
|
|
354
|
+
* thread closes the connection when it is done, so wait for that rather than for an empty queue (the writer takes a
|
|
355
|
+
* frame off the queue before it has written it, so an empty queue does not mean "sent"), and only force the close
|
|
356
|
+
* if the peer is not reading.
|
|
357
|
+
*/
|
|
347
358
|
public void Kill(Client client)
|
|
348
359
|
{
|
|
349
360
|
Retire(client);
|
|
350
|
-
|
|
361
|
+
client.Finished.WaitOne(3000);
|
|
351
362
|
try { client.Tcp.Close(); } catch (Exception) { }
|
|
352
363
|
}
|
|
353
364
|
|
package/sandbox/trust.cmd
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
@echo off
|
|
2
|
-
rem Runs first at logon, INSIDE the disposable sandbox only (the host PC is never touched).
|
|
3
|
-
rem Lets the sandbox's own tools run without "Open File - Security Warning", SmartScreen, script-policy or UAC prompts.
|
|
4
|
-
rem The security boundary is Windows Sandbox itself plus workspace-only write access and the LAN block; these prompts add
|
|
5
|
-
rem nothing inside a throwaway VM except interruptions.
|
|
6
|
-
set "POL=HKCU\Software\Microsoft\Windows\CurrentVersion\Policies"
|
|
7
|
-
reg add "%POL%\Associations" /v LowRiskFileTypes /t REG_SZ /d ".exe;.com;.cmd;.bat;.ps1;.psm1;.msi;.vbs;.js;.zip;.7z" /f >nul
|
|
8
|
-
reg add "%POL%\Attachments" /v SaveZoneInformation /t REG_DWORD /d 1 /f >nul
|
|
9
|
-
rem Mapped folders and UNC paths count as Local Intranet, where launching programs is allowed without asking.
|
|
10
|
-
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap" /v UNCAsIntranet /t REG_DWORD /d 1 /f >nul
|
|
11
|
-
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap" /v IntranetName /t REG_DWORD /d 1 /f >nul
|
|
12
|
-
rem Files copied from this PC keep their "downloaded from the internet" mark, so launching is allowed in every zone
|
|
13
|
-
rem (1806 = 0), for this user and machine-wide, and ShellExecute skips the zone check altogether.
|
|
14
|
-
for %%Z in (0 1 2 3 4) do (
|
|
15
|
-
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\%%Z" /v 1806 /t REG_DWORD /d 0 /f >nul
|
|
16
|
-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\%%Z" /v 1806 /t REG_DWORD /d 0 /f >nul 2>&1
|
|
17
|
-
)
|
|
18
|
-
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v SEE_MASK_NOZONECHECKS /t REG_SZ /d 1 /f >nul 2>&1
|
|
19
|
-
set "SEE_MASK_NOZONECHECKS=1"
|
|
20
|
-
rem Paths over 260 characters (deep node_modules, Gradle and NuGet caches) work for every program.
|
|
21
|
-
reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f >nul 2>&1
|
|
22
|
-
rem "Windows Defender Firewall has blocked some features of this app" pops up whenever a program (node, a dev server,
|
|
23
|
-
rem the session host) listens on the network. Inbound stays blocked, just silently; the attach rule lets this PC in.
|
|
24
|
-
netsh advfirewall set allprofiles settings inboundusernotification disable >nul 2>&1
|
|
25
|
-
for %%P in (DomainProfile PrivateProfile PublicProfile) do reg add "HKLM\SOFTWARE\Policies\Microsoft\WindowsFirewall\%%P" /v DisableNotifications /t REG_DWORD /d 1 /f >nul 2>&1
|
|
26
|
-
rem SmartScreen ("Windows protected your PC") for downloaded, unsigned tools.
|
|
27
|
-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\System" /v EnableSmartScreen /t REG_DWORD /d 0 /f >nul 2>&1
|
|
28
|
-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" /v SmartScreenEnabled /t REG_SZ /d Off /f >nul 2>&1
|
|
29
|
-
rem PowerShell scripts (hooks, installers) run without the "Do you want to run ..." question.
|
|
30
|
-
reg add "HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /v ExecutionPolicy /t REG_SZ /d Bypass /f >nul 2>&1
|
|
31
|
-
reg add "HKCU\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /v ExecutionPolicy /t REG_SZ /d Bypass /f >nul
|
|
32
|
-
rem Elevation requests from the (already admin) sandbox user are granted without a dialog.
|
|
33
|
-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f >nul 2>&1
|
|
34
|
-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v PromptOnSecureDesktop /t REG_DWORD /d 0 /f >nul 2>&1
|
|
35
|
-
exit /b 0
|
|
1
|
+
@echo off
|
|
2
|
+
rem Runs first at logon, INSIDE the disposable sandbox only (the host PC is never touched).
|
|
3
|
+
rem Lets the sandbox's own tools run without "Open File - Security Warning", SmartScreen, script-policy or UAC prompts.
|
|
4
|
+
rem The security boundary is Windows Sandbox itself plus workspace-only write access and the LAN block; these prompts add
|
|
5
|
+
rem nothing inside a throwaway VM except interruptions.
|
|
6
|
+
set "POL=HKCU\Software\Microsoft\Windows\CurrentVersion\Policies"
|
|
7
|
+
reg add "%POL%\Associations" /v LowRiskFileTypes /t REG_SZ /d ".exe;.com;.cmd;.bat;.ps1;.psm1;.msi;.vbs;.js;.zip;.7z" /f >nul
|
|
8
|
+
reg add "%POL%\Attachments" /v SaveZoneInformation /t REG_DWORD /d 1 /f >nul
|
|
9
|
+
rem Mapped folders and UNC paths count as Local Intranet, where launching programs is allowed without asking.
|
|
10
|
+
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap" /v UNCAsIntranet /t REG_DWORD /d 1 /f >nul
|
|
11
|
+
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap" /v IntranetName /t REG_DWORD /d 1 /f >nul
|
|
12
|
+
rem Files copied from this PC keep their "downloaded from the internet" mark, so launching is allowed in every zone
|
|
13
|
+
rem (1806 = 0), for this user and machine-wide, and ShellExecute skips the zone check altogether.
|
|
14
|
+
for %%Z in (0 1 2 3 4) do (
|
|
15
|
+
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\%%Z" /v 1806 /t REG_DWORD /d 0 /f >nul
|
|
16
|
+
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\%%Z" /v 1806 /t REG_DWORD /d 0 /f >nul 2>&1
|
|
17
|
+
)
|
|
18
|
+
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v SEE_MASK_NOZONECHECKS /t REG_SZ /d 1 /f >nul 2>&1
|
|
19
|
+
set "SEE_MASK_NOZONECHECKS=1"
|
|
20
|
+
rem Paths over 260 characters (deep node_modules, Gradle and NuGet caches) work for every program.
|
|
21
|
+
reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f >nul 2>&1
|
|
22
|
+
rem "Windows Defender Firewall has blocked some features of this app" pops up whenever a program (node, a dev server,
|
|
23
|
+
rem the session host) listens on the network. Inbound stays blocked, just silently; the attach rule lets this PC in.
|
|
24
|
+
netsh advfirewall set allprofiles settings inboundusernotification disable >nul 2>&1
|
|
25
|
+
for %%P in (DomainProfile PrivateProfile PublicProfile) do reg add "HKLM\SOFTWARE\Policies\Microsoft\WindowsFirewall\%%P" /v DisableNotifications /t REG_DWORD /d 1 /f >nul 2>&1
|
|
26
|
+
rem SmartScreen ("Windows protected your PC") for downloaded, unsigned tools.
|
|
27
|
+
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\System" /v EnableSmartScreen /t REG_DWORD /d 0 /f >nul 2>&1
|
|
28
|
+
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" /v SmartScreenEnabled /t REG_SZ /d Off /f >nul 2>&1
|
|
29
|
+
rem PowerShell scripts (hooks, installers) run without the "Do you want to run ..." question.
|
|
30
|
+
reg add "HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /v ExecutionPolicy /t REG_SZ /d Bypass /f >nul 2>&1
|
|
31
|
+
reg add "HKCU\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /v ExecutionPolicy /t REG_SZ /d Bypass /f >nul
|
|
32
|
+
rem Elevation requests from the (already admin) sandbox user are granted without a dialog.
|
|
33
|
+
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f >nul 2>&1
|
|
34
|
+
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v PromptOnSecureDesktop /t REG_DWORD /d 0 /f >nul 2>&1
|
|
35
|
+
exit /b 0
|
|
@@ -82,6 +82,26 @@
|
|
|
82
82
|
"maximum": 9007199254740991,
|
|
83
83
|
"description": "Upper limit for memoryMb \"auto\"."
|
|
84
84
|
},
|
|
85
|
+
"maxMemoryPercent": {
|
|
86
|
+
"type": "integer",
|
|
87
|
+
"minimum": 10,
|
|
88
|
+
"maximum": 100,
|
|
89
|
+
"description": "memoryMb \"auto\" never takes more than this share of this PC's total RAM (percent)."
|
|
90
|
+
},
|
|
91
|
+
"cpuLimit": {
|
|
92
|
+
"anyOf": [
|
|
93
|
+
{
|
|
94
|
+
"type": "string",
|
|
95
|
+
"const": "none"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "integer",
|
|
99
|
+
"minimum": 5,
|
|
100
|
+
"maximum": 100
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"description": "Most of this PC's CPU the sandbox may use, in percent (5-100), e.g. 50 keeps half free for you; \"none\" = no cap (Windows Sandbox would take every processor). Covers everything this tool starts in the sandbox (agents, installs, builds, its terminal)."
|
|
104
|
+
},
|
|
85
105
|
"reserveHostMb": {
|
|
86
106
|
"type": "integer",
|
|
87
107
|
"minimum": 0,
|
|
@@ -200,6 +220,16 @@
|
|
|
200
220
|
},
|
|
201
221
|
"additionalProperties": false
|
|
202
222
|
},
|
|
223
|
+
"downloads": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"properties": {
|
|
226
|
+
"requireChecksum": {
|
|
227
|
+
"type": "boolean",
|
|
228
|
+
"description": "Refuse any toolchain, database or tool download that has no published checksum (SHA-256/SHA-512) to verify it against. false = allow those, as before. Downloads that do have a published checksum are always verified. A project's .sandbox-config.json can only turn this on."
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"additionalProperties": false
|
|
232
|
+
},
|
|
203
233
|
"toolchains": {
|
|
204
234
|
"type": "object",
|
|
205
235
|
"properties": {
|
|
@@ -874,7 +904,7 @@
|
|
|
874
904
|
"const": "auto"
|
|
875
905
|
}
|
|
876
906
|
],
|
|
877
|
-
"description": "SQL Server. It cannot run inside Windows Sandbox; when a project needs it you get a
|
|
907
|
+
"description": "SQL Server. It cannot run inside Windows Sandbox; when a project needs it you get a note to use Postgres or SQLite for local work: true, false, or \"auto\" = only when the project needs it (found from compose files, dependencies of every language, Prisma/ORM config and connection strings)."
|
|
878
908
|
}
|
|
879
909
|
},
|
|
880
910
|
"additionalProperties": false
|
|
@@ -905,6 +935,32 @@
|
|
|
905
935
|
},
|
|
906
936
|
"additionalProperties": false
|
|
907
937
|
},
|
|
938
|
+
"agentCommands": {
|
|
939
|
+
"type": "object",
|
|
940
|
+
"properties": {
|
|
941
|
+
"enabled": {
|
|
942
|
+
"type": "boolean",
|
|
943
|
+
"description": "(Beta) Typing an agent's own command on this PC (claude, codex, ...) starts it in a hidden sandbox for the folder you are in and attaches that terminal. Adds one folder to the start of YOUR PATH; off removes it. SANDBOX_AGENT_DIRECT=1 runs the real agent instead."
|
|
944
|
+
},
|
|
945
|
+
"agents": {
|
|
946
|
+
"anyOf": [
|
|
947
|
+
{
|
|
948
|
+
"type": "string",
|
|
949
|
+
"const": "auto"
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
"type": "array",
|
|
953
|
+
"items": {
|
|
954
|
+
"type": "string",
|
|
955
|
+
"pattern": "^[A-Za-z0-9_-]+$"
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
],
|
|
959
|
+
"description": "\"auto\" = every agent installed on this PC, or a list of agent names, e.g. [\"claude\", \"codex\"]."
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
"additionalProperties": false
|
|
963
|
+
},
|
|
908
964
|
"secrets": {
|
|
909
965
|
"type": "object",
|
|
910
966
|
"properties": {
|
|
@@ -1056,6 +1112,49 @@
|
|
|
1056
1112
|
}
|
|
1057
1113
|
},
|
|
1058
1114
|
"additionalProperties": false
|
|
1115
|
+
},
|
|
1116
|
+
"projects": {
|
|
1117
|
+
"type": "object",
|
|
1118
|
+
"properties": {
|
|
1119
|
+
"all": {
|
|
1120
|
+
"type": "boolean",
|
|
1121
|
+
"description": "Map every project you have started before (and every folder in your roots) at every start, except those in exclude, so all of them open in the running sandbox without a restart. Every agent in the sandbox can then see all of them. Each one is still only set up when you first use it."
|
|
1122
|
+
},
|
|
1123
|
+
"exclude": {
|
|
1124
|
+
"type": "array",
|
|
1125
|
+
"items": {
|
|
1126
|
+
"type": "string",
|
|
1127
|
+
"minLength": 1
|
|
1128
|
+
},
|
|
1129
|
+
"description": "Projects left out when \"all\" is on (folders on this PC)."
|
|
1130
|
+
},
|
|
1131
|
+
"roots": {
|
|
1132
|
+
"type": "array",
|
|
1133
|
+
"items": {
|
|
1134
|
+
"type": "string",
|
|
1135
|
+
"minLength": 1
|
|
1136
|
+
},
|
|
1137
|
+
"description": "Folders whose subfolders are your projects (e.g. \"F:\\\\Code\"), mapped read/write at every start. Any project inside one, including ones you create later, opens in the running sandbox at once, without a restart. Every agent in the sandbox can see all of them."
|
|
1138
|
+
},
|
|
1139
|
+
"askToAddRoot": {
|
|
1140
|
+
"type": "boolean",
|
|
1141
|
+
"description": "When you start a project whose parent folder is not a root yet, ask once whether to add it."
|
|
1142
|
+
},
|
|
1143
|
+
"declinedRoots": {
|
|
1144
|
+
"type": "array",
|
|
1145
|
+
"items": {
|
|
1146
|
+
"type": "string",
|
|
1147
|
+
"minLength": 1
|
|
1148
|
+
},
|
|
1149
|
+
"description": "Parent folders you said no to (not asked again)."
|
|
1150
|
+
},
|
|
1151
|
+
"wsbShare": {
|
|
1152
|
+
"type": "boolean",
|
|
1153
|
+
"description": "On Windows 11 24H2+ with the Windows Sandbox command line (wsb), add other folders to the running sandbox with `wsb share` (untested). Otherwise starting a project outside every root offers a restart."
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
"additionalProperties": false,
|
|
1157
|
+
"description": "Several projects in one sandbox: folders of projects mapped at every start, and adding folders while it runs."
|
|
1059
1158
|
}
|
|
1060
1159
|
},
|
|
1061
1160
|
"additionalProperties": false
|
|
@@ -94,6 +94,12 @@
|
|
|
94
94
|
"maximum": 9007199254740991,
|
|
95
95
|
"description": "Upper limit for memoryMb \"auto\"."
|
|
96
96
|
},
|
|
97
|
+
"maxMemoryPercent": {
|
|
98
|
+
"type": "integer",
|
|
99
|
+
"minimum": 10,
|
|
100
|
+
"maximum": 100,
|
|
101
|
+
"description": "memoryMb \"auto\" never takes more than this share of this PC's total RAM (percent)."
|
|
102
|
+
},
|
|
97
103
|
"reserveHostMb": {
|
|
98
104
|
"type": "integer",
|
|
99
105
|
"minimum": 0,
|
|
@@ -191,6 +197,16 @@
|
|
|
191
197
|
},
|
|
192
198
|
"additionalProperties": false
|
|
193
199
|
},
|
|
200
|
+
"downloads": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"requireChecksum": {
|
|
204
|
+
"type": "boolean",
|
|
205
|
+
"description": "Refuse any toolchain, database or tool download that has no published checksum (SHA-256/SHA-512) to verify it against. false = allow those, as before. Downloads that do have a published checksum are always verified. A project's .sandbox-config.json can only turn this on."
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"additionalProperties": false
|
|
209
|
+
},
|
|
194
210
|
"toolchains": {
|
|
195
211
|
"type": "object",
|
|
196
212
|
"properties": {
|
|
@@ -864,7 +880,7 @@
|
|
|
864
880
|
"const": "auto"
|
|
865
881
|
}
|
|
866
882
|
],
|
|
867
|
-
"description": "SQL Server. It cannot run inside Windows Sandbox; when a project needs it you get a
|
|
883
|
+
"description": "SQL Server. It cannot run inside Windows Sandbox; when a project needs it you get a note to use Postgres or SQLite for local work: true, false, or \"auto\" = only when the project needs it (found from compose files, dependencies of every language, Prisma/ORM config and connection strings)."
|
|
868
884
|
}
|
|
869
885
|
},
|
|
870
886
|
"additionalProperties": false
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { type Context } from './context.js';
|
|
2
|
-
export type DataArea = 'tools' | 'databases' | 'state' | 'logs' | 'data' | 'host';
|
|
3
|
-
export interface DataEntry {
|
|
4
|
-
area: DataArea;
|
|
5
|
-
name: string;
|
|
6
|
-
path: string;
|
|
7
|
-
bytes: number;
|
|
8
|
-
}
|
|
9
|
-
/** 1536 -> "1.5 KB". */
|
|
10
|
-
export declare const humanBytes: (bytes: number) => string;
|
|
11
|
-
/** Size on disk, never following links or junctions (a link counts as 0). */
|
|
12
|
-
export declare const sizeOf: (p: string) => number;
|
|
13
|
-
/**
|
|
14
|
-
* Deletes a folder or file without ever following a link: links found anywhere inside are removed themselves first,
|
|
15
|
-
* then the (now link-free) tree is deleted. A link in place of `p` is removed itself.
|
|
16
|
-
*/
|
|
17
|
-
/** Deletes p (strictly inside root): links inside are removed first and never followed; see shared/safe-remove.ts. */
|
|
18
|
-
export declare const removeTree: (p: string, root: string) => void;
|
|
19
|
-
/** Every row of `sandbox data`: the sandbox's data folder by area, then what this PC keeps for it. */
|
|
20
|
-
export declare const collectDataEntries: (dataPath: string, storageRoot: string) => DataEntry[];
|
|
21
|
-
export interface DiskUsage {
|
|
22
|
-
drive: string;
|
|
23
|
-
totalBytes: number;
|
|
24
|
-
freeBytes: number;
|
|
25
|
-
usedBytes: number;
|
|
26
|
-
holdsStorage: boolean;
|
|
27
|
-
}
|
|
28
|
-
/** Every local drive's size and free space, and which one holds the sandbox storage. */
|
|
29
|
-
export declare const diskUsage: (storageRoot: string) => DiskUsage[];
|
|
30
|
-
export declare const dataInfo: (ctx: Context) => number;
|
|
31
|
-
export interface CleanTarget {
|
|
32
|
-
/** The target as normalised (forward slashes), e.g. "tools/go". */
|
|
33
|
-
target: string;
|
|
34
|
-
path: string;
|
|
35
|
-
/** Folder the target must stay inside, with no links between the two. */
|
|
36
|
-
root: string;
|
|
37
|
-
warning?: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Turns `sandbox data clean` arguments into folders. Pure (no disk access); throws a UserError for anything that is
|
|
41
|
-
* not a plain relative target inside the data folder: absolute paths, drive letters, `..`, wildcards, unknown areas.
|
|
42
|
-
*/
|
|
43
|
-
export declare const resolveCleanTargets: (inputs: string[], dataPath: string, storageRoot: string) => CleanTarget[];
|
|
44
|
-
export declare const dataClean: (ctx: Context) => Promise<number>;
|
|
45
|
-
/** `sandbox data [info|clean] [targets...]`. */
|
|
46
|
-
export declare const data: (ctx: Context) => number | Promise<number>;
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
// `sandbox data`: what persists between sandboxes (toolchains, databases, agent state, logs, host storage) and
|
|
2
|
-
// removing chosen parts of it. Everything under the data folder is sandbox-writable: sizes and deletes never follow
|
|
3
|
-
// links, and targets resolve strictly inside the data folder.
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import readline from 'node:readline';
|
|
7
|
-
import _ from 'lodash';
|
|
8
|
-
import { removeRefusal, safeRemove } from '../../shared/safe-remove.js';
|
|
9
|
-
import { assertNoLinks, dirExists, fail, isInsideOrEqual, isLink, removeLink } from '../../core/util.js';
|
|
10
|
-
import { askYesNo } from '../../core/workspace.js';
|
|
11
|
-
import { isSandboxRunning } from '../../host/platform.js';
|
|
12
|
-
import { printJson, table } from './context.js';
|
|
13
|
-
const AREA_ORDER = ['tools', 'databases', 'state', 'logs', 'data', 'host'];
|
|
14
|
-
/** Folders of the data folder that get one row per child; anything else at the top level is listed as itself. */
|
|
15
|
-
const PER_CHILD = ['tools', 'databases', 'state'];
|
|
16
|
-
/** 1536 -> "1.5 KB". */
|
|
17
|
-
export const humanBytes = (bytes) => {
|
|
18
|
-
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
19
|
-
const i = bytes <= 0 ? 0 : Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
20
|
-
const value = bytes / 1024 ** i;
|
|
21
|
-
return (i === 0 ? String(bytes) : value.toFixed(value < 10 ? 1 : 0)) + ' ' + units[i];
|
|
22
|
-
};
|
|
23
|
-
/** Size on disk, never following links or junctions (a link counts as 0). */
|
|
24
|
-
export const sizeOf = (p) => {
|
|
25
|
-
try {
|
|
26
|
-
const stat = fs.lstatSync(p);
|
|
27
|
-
if (stat.isSymbolicLink())
|
|
28
|
-
return 0;
|
|
29
|
-
if (!stat.isDirectory())
|
|
30
|
-
return stat.size;
|
|
31
|
-
return _.sumBy(fs.readdirSync(p, { withFileTypes: true }), (d) => (d.isSymbolicLink() ? 0 : sizeOf(path.join(p, d.name))));
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Deletes a folder or file without ever following a link: links found anywhere inside are removed themselves first,
|
|
39
|
-
* then the (now link-free) tree is deleted. A link in place of `p` is removed itself.
|
|
40
|
-
*/
|
|
41
|
-
/** Deletes p (strictly inside root): links inside are removed first and never followed; see shared/safe-remove.ts. */
|
|
42
|
-
export const removeTree = (p, root) => {
|
|
43
|
-
const unlinkInside = (dir) => {
|
|
44
|
-
for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
45
|
-
const child = path.join(dir, d.name);
|
|
46
|
-
if (d.isSymbolicLink() || isLink(child))
|
|
47
|
-
removeLink(child);
|
|
48
|
-
else if (d.isDirectory())
|
|
49
|
-
unlinkInside(child);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
if (isLink(p)) {
|
|
53
|
-
removeLink(p);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (!fs.existsSync(p))
|
|
57
|
-
return;
|
|
58
|
-
// Refused before anything is touched (safeRemove below checks again).
|
|
59
|
-
const refusal = removeRefusal(p, root);
|
|
60
|
-
if (refusal)
|
|
61
|
-
throw new Error('Refusing to delete ' + p + ': ' + refusal);
|
|
62
|
-
if (dirExists(p))
|
|
63
|
-
unlinkInside(p);
|
|
64
|
-
safeRemove(p, root);
|
|
65
|
-
};
|
|
66
|
-
/** Real (non-link) children of a real folder; a link in place of the folder yields nothing. */
|
|
67
|
-
const entriesOf = (dir) => (dirExists(dir) && !isLink(dir) ? fs.readdirSync(dir, { withFileTypes: true }).filter((d) => !d.isSymbolicLink()) : []);
|
|
68
|
-
const toolRows = (toolsDir) => {
|
|
69
|
-
const entries = entriesOf(toolsDir);
|
|
70
|
-
const folders = entries.filter((d) => d.isDirectory());
|
|
71
|
-
const files = entries.filter((d) => !d.isDirectory());
|
|
72
|
-
const row = (name, p) => ({ area: 'tools', name, path: p, bytes: sizeOf(p) });
|
|
73
|
-
return [
|
|
74
|
-
...folders.flatMap((d) => (d.name.toLowerCase() === 'agents'
|
|
75
|
-
? entriesOf(path.join(toolsDir, d.name)).map((a) => row('agents/' + a.name, path.join(toolsDir, d.name, a.name)))
|
|
76
|
-
: [row(d.name, path.join(toolsDir, d.name))])),
|
|
77
|
-
...(files.length ? [{ area: 'tools', name: '(files in tools)', path: toolsDir, bytes: _.sumBy(files, (f) => sizeOf(path.join(toolsDir, f.name))) }] : []),
|
|
78
|
-
];
|
|
79
|
-
};
|
|
80
|
-
/** Every row of `sandbox data`: the sandbox's data folder by area, then what this PC keeps for it. */
|
|
81
|
-
export const collectDataEntries = (dataPath, storageRoot) => {
|
|
82
|
-
const inData = entriesOf(dataPath);
|
|
83
|
-
const rows = inData.flatMap((d) => {
|
|
84
|
-
const p = path.join(dataPath, d.name);
|
|
85
|
-
const area = d.name.toLowerCase();
|
|
86
|
-
if (area === 'tools')
|
|
87
|
-
return toolRows(p);
|
|
88
|
-
if (PER_CHILD.includes(area))
|
|
89
|
-
return entriesOf(p).map((c) => ({ area, name: area + '/' + c.name, path: path.join(p, c.name), bytes: sizeOf(path.join(p, c.name)) }));
|
|
90
|
-
return [{ area: area === 'logs' ? 'logs' : 'data', name: d.name, path: p, bytes: sizeOf(p) }];
|
|
91
|
-
});
|
|
92
|
-
const otherData = entriesOf(path.join(storageRoot, 'data')).filter((d) => d.isDirectory() && path.join(storageRoot, 'data', d.name).toLowerCase() !== path.resolve(dataPath).toLowerCase());
|
|
93
|
-
const host = [
|
|
94
|
-
...['launches', 'accounts', 'sync', 'logs', 'run', 'host'].map((name) => ({ area: 'host', name, path: path.join(storageRoot, name), bytes: sizeOf(path.join(storageRoot, name)) })),
|
|
95
|
-
...otherData.map((d) => ({ area: 'host', name: 'other data folder: ' + d.name, path: path.join(storageRoot, 'data', d.name), bytes: sizeOf(path.join(storageRoot, 'data', d.name)) })),
|
|
96
|
-
].filter((r) => fs.existsSync(r.path));
|
|
97
|
-
return _.sortBy([...rows, ...host], (r) => AREA_ORDER.indexOf(r.area), (r) => -r.bytes, (r) => r.name.toLowerCase());
|
|
98
|
-
};
|
|
99
|
-
const AREA_LABEL = { tools: 'Toolchains', databases: 'Databases', state: 'Agent state', logs: 'Logs', data: 'Other', host: 'On this PC' };
|
|
100
|
-
/** Every local drive's size and free space, and which one holds the sandbox storage. */
|
|
101
|
-
export const diskUsage = (storageRoot) => {
|
|
102
|
-
const storageDrive = path.parse(path.resolve(storageRoot)).root.toUpperCase();
|
|
103
|
-
return 'CDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((letter) => letter + ':\\').flatMap((drive) => {
|
|
104
|
-
try {
|
|
105
|
-
const s = fs.statfsSync(drive);
|
|
106
|
-
const totalBytes = s.blocks * s.bsize;
|
|
107
|
-
if (!totalBytes)
|
|
108
|
-
return [];
|
|
109
|
-
return [{ drive, totalBytes, freeBytes: s.bavail * s.bsize, usedBytes: totalBytes - s.bfree * s.bsize, holdsStorage: drive.toUpperCase() === storageDrive }];
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
export const dataInfo = (ctx) => {
|
|
117
|
-
const entries = collectDataEntries(ctx.dataPath, ctx.storageRoot);
|
|
118
|
-
const total = _.sumBy(entries, 'bytes');
|
|
119
|
-
const disks = diskUsage(ctx.storageRoot);
|
|
120
|
-
if (printJson(ctx, { dataPath: ctx.dataPath, storageRoot: ctx.storageRoot, totalBytes: total, entries, disks }))
|
|
121
|
-
return 0;
|
|
122
|
-
console.log('Disks\n');
|
|
123
|
-
table([['DRIVE', 'USED', 'FREE', 'SIZE', ''], ...disks.map((d) => [d.drive, humanBytes(d.usedBytes) + ' (' + Math.round((d.usedBytes / d.totalBytes) * 100) + '%)', humanBytes(d.freeBytes), humanBytes(d.totalBytes), d.holdsStorage ? '<- sandbox storage (' + humanBytes(total) + ' of it)' : ''])]);
|
|
124
|
-
console.log('');
|
|
125
|
-
console.log('Persistent data for the sandbox: ' + ctx.dataPath + ' (C:\\AgentData inside)\n');
|
|
126
|
-
if (!entries.length) {
|
|
127
|
-
console.log('Nothing stored yet. Toolchains, databases and agent logins appear here after the first start.');
|
|
128
|
-
return 0;
|
|
129
|
-
}
|
|
130
|
-
const rows = _.flatMap(_.groupBy(entries, 'area'), (group, area) => [
|
|
131
|
-
[AREA_LABEL[area], humanBytes(_.sumBy(group, 'bytes')), ''],
|
|
132
|
-
...group.map((e) => [' ' + e.name, humanBytes(e.bytes), e.path]),
|
|
133
|
-
]);
|
|
134
|
-
table([...rows, ['', '', ''], ['Total', humanBytes(total), '']]);
|
|
135
|
-
console.log('\nFree space with: sandbox data clean <target...> (e.g. tools/go, databases/postgres-18, logs, launches)');
|
|
136
|
-
return 0;
|
|
137
|
-
};
|
|
138
|
-
const DATA_AREAS = ['tools', 'databases', 'state', 'logs'];
|
|
139
|
-
const SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._ +@-]*$/;
|
|
140
|
-
const LOGIN_WARNING = 'removes the agent logins, settings and history inside the sandbox (save a Claude login first with: sandbox account save <name>)';
|
|
141
|
-
const ALL_WARNING = 'removes EVERYTHING the sandbox keeps: toolchains, databases, agent logins and history, logs and launch folders';
|
|
142
|
-
/**
|
|
143
|
-
* Turns `sandbox data clean` arguments into folders. Pure (no disk access); throws a UserError for anything that is
|
|
144
|
-
* not a plain relative target inside the data folder: absolute paths, drive letters, `..`, wildcards, unknown areas.
|
|
145
|
-
*/
|
|
146
|
-
export const resolveCleanTargets = (inputs, dataPath, storageRoot) => {
|
|
147
|
-
const usage = 'Targets: all | tools | tools/<name> | tools/agents/<name> | databases | databases/<name> | state | state/<agent> | logs | launches. See them with: sandbox data';
|
|
148
|
-
if (!inputs.length)
|
|
149
|
-
fail('Say what to remove.', usage);
|
|
150
|
-
const resolved = inputs.map((raw) => {
|
|
151
|
-
const input = raw.trim();
|
|
152
|
-
if (!input)
|
|
153
|
-
fail('Empty target.', usage);
|
|
154
|
-
if (path.win32.isAbsolute(input) || path.posix.isAbsolute(input) || /^[A-Za-z]:/.test(input) || input.includes(':'))
|
|
155
|
-
fail('Refusing "' + raw + '": targets are relative to the data folder, not paths on this PC.', usage);
|
|
156
|
-
const parts = input.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
157
|
-
const bad = parts.find((p) => !SEGMENT.test(p) || /^\.+$/.test(p) || /[. ]$/.test(p));
|
|
158
|
-
if (bad !== undefined)
|
|
159
|
-
fail('Refusing "' + raw + '": "' + bad + '" is not a plain folder name (no .., wildcards or special characters).', usage);
|
|
160
|
-
const [head, ...rest] = parts;
|
|
161
|
-
const area = head.toLowerCase();
|
|
162
|
-
if (parts.length > 3)
|
|
163
|
-
fail('Refusing "' + raw + '": too deep.', usage);
|
|
164
|
-
const target = [area, ...rest].join('/');
|
|
165
|
-
if (area === 'all' && !rest.length)
|
|
166
|
-
return { target: 'all', path: dataPath, root: path.join(storageRoot, 'data'), warning: ALL_WARNING };
|
|
167
|
-
if (area === 'launches')
|
|
168
|
-
return { target, path: path.join(storageRoot, 'launches', ...rest), root: storageRoot };
|
|
169
|
-
if (area === 'accounts' || area === 'sync')
|
|
170
|
-
fail('"' + area + '" is kept on this PC on purpose and is not removed here.', area === 'accounts' ? 'Remove a saved login with: sandbox account remove <name>' : usage);
|
|
171
|
-
if (!DATA_AREAS.includes(area))
|
|
172
|
-
fail('Unknown target "' + raw + '".', usage);
|
|
173
|
-
if (area === 'logs' && rest.length)
|
|
174
|
-
fail('Remove all logs ("logs"), not single files.', usage);
|
|
175
|
-
if (rest.length > (area === 'tools' && rest[0]?.toLowerCase() === 'agents' ? 2 : 1))
|
|
176
|
-
fail('Refusing "' + raw + '": too deep.', usage);
|
|
177
|
-
return { target, path: path.join(dataPath, area, ...rest), root: dataPath, ...(area === 'state' ? { warning: LOGIN_WARNING } : {}) };
|
|
178
|
-
});
|
|
179
|
-
// "all" covers everything; otherwise drop targets inside another target (tools + tools/go = tools).
|
|
180
|
-
const all = resolved.find((t) => t.target === 'all');
|
|
181
|
-
if (all)
|
|
182
|
-
return [all, { target: 'launches', path: path.join(storageRoot, 'launches'), root: storageRoot }];
|
|
183
|
-
const unique = _.uniqBy(resolved, (t) => t.path.toLowerCase());
|
|
184
|
-
return unique.filter((t) => !unique.some((o) => o !== t && isInsideOrEqual(o.path, t.path)));
|
|
185
|
-
};
|
|
186
|
-
/** Asks the user to type `expected` (a stronger confirmation than y/N). */
|
|
187
|
-
const askTyped = async (question, expected) => {
|
|
188
|
-
if (!process.stdin.isTTY)
|
|
189
|
-
return false;
|
|
190
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
191
|
-
const answer = await new Promise((resolve) => rl.question(question + ' Type "' + expected + '" to confirm: ', resolve));
|
|
192
|
-
rl.close();
|
|
193
|
-
return answer.trim().toLowerCase() === expected;
|
|
194
|
-
};
|
|
195
|
-
export const dataClean = async (ctx) => {
|
|
196
|
-
const targets = resolveCleanTargets(ctx.options.positionals.slice(1), ctx.dataPath, ctx.storageRoot);
|
|
197
|
-
if (isSandboxRunning())
|
|
198
|
-
fail('A Windows Sandbox is running, and it uses these folders.', 'Close it first (sandbox stop), then run this again.');
|
|
199
|
-
// Every folder from the root down to the target's parent must be real; a link in place of the target is removed itself.
|
|
200
|
-
for (const t of targets)
|
|
201
|
-
assertNoLinks(t.root, path.dirname(t.path));
|
|
202
|
-
const present = targets.filter((t) => fs.existsSync(t.path) || isLink(t.path));
|
|
203
|
-
if (!present.length) {
|
|
204
|
-
console.log('Nothing to remove: ' + targets.map((t) => t.target).join(', ') + ' do(es) not exist.');
|
|
205
|
-
return 0;
|
|
206
|
-
}
|
|
207
|
-
const sized = present.map((t) => ({ ...t, bytes: sizeOf(t.path) }));
|
|
208
|
-
const total = _.sumBy(sized, 'bytes');
|
|
209
|
-
table(sized.map((t) => [' ' + t.target, humanBytes(t.bytes), t.path]));
|
|
210
|
-
for (const t of sized)
|
|
211
|
-
if (t.warning)
|
|
212
|
-
console.log('Warning: ' + t.target + ' ' + t.warning + '.');
|
|
213
|
-
const yes = Boolean(ctx.options.yes);
|
|
214
|
-
if (!yes) {
|
|
215
|
-
const ok = sized.some((t) => t.target === 'all')
|
|
216
|
-
? await askTyped('This cannot be undone; the next sandbox starts from scratch and reinstalls everything.', 'all')
|
|
217
|
-
: await askYesNo('Remove ' + (sized.length === 1 ? 'this' : 'these ' + sized.length) + ' (' + humanBytes(total) + ')?');
|
|
218
|
-
if (!ok) {
|
|
219
|
-
console.log('Nothing removed.' + (process.stdin.isTTY ? '' : ' Pass --yes to confirm without a prompt.'));
|
|
220
|
-
return 1;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const failed = [];
|
|
224
|
-
for (const t of sized) {
|
|
225
|
-
try {
|
|
226
|
-
removeTree(t.path, t.root);
|
|
227
|
-
}
|
|
228
|
-
catch (e) {
|
|
229
|
-
failed.push(t.target + ' (' + (e instanceof Error ? e.message : String(e)) + ')');
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const freed = total - _.sumBy(sized, (t) => sizeOf(t.path));
|
|
233
|
-
console.log('Freed ' + humanBytes(freed) + '. ' + (sized.some((t) => t.root === ctx.dataPath || t.target === 'all') ? 'The next sandbox reinstalls what it needs.' : ''));
|
|
234
|
-
if (failed.length)
|
|
235
|
-
fail('Could not remove everything: ' + failed.join('; '), 'Close programs using those folders and run the same command again.');
|
|
236
|
-
return 0;
|
|
237
|
-
};
|
|
238
|
-
/** `sandbox data [info|clean] [targets...]`. */
|
|
239
|
-
export const data = (ctx) => {
|
|
240
|
-
const [action = 'info'] = ctx.options.positionals;
|
|
241
|
-
switch (action) {
|
|
242
|
-
case 'info':
|
|
243
|
-
case 'show':
|
|
244
|
-
case 'list': return dataInfo(ctx);
|
|
245
|
-
case 'clean':
|
|
246
|
-
case 'remove': return dataClean(ctx);
|
|
247
|
-
default: return fail('Unknown data action "' + action + '".', 'Use: sandbox data (what is stored) or sandbox data clean <target...>');
|
|
248
|
-
}
|
|
249
|
-
};
|