windows-development-sandbox 1.1.0 → 1.4.1

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.
Files changed (222) hide show
  1. package/.sandbox-config.example.json +2 -1
  2. package/README.md +3 -2
  3. package/config.example.json +39 -5
  4. package/dist/cli/args.d.ts +3 -0
  5. package/dist/cli/args.js +3 -1
  6. package/dist/cli/boot-progress.d.ts +22 -0
  7. package/dist/cli/boot-progress.js +59 -0
  8. package/dist/cli/commands/accounts.js +2 -2
  9. package/dist/cli/commands/agent-commands.js +1 -1
  10. package/dist/cli/commands/context.js +2 -1
  11. package/dist/cli/commands/data/clean.js +6 -1
  12. package/dist/cli/commands/data/collect.js +3 -3
  13. package/dist/cli/commands/index.d.ts +1 -1
  14. package/dist/cli/commands/index.js +1 -1
  15. package/dist/cli/commands/launch.d.ts +5 -2
  16. package/dist/cli/commands/launch.js +113 -25
  17. package/dist/cli/commands/maintenance.d.ts +2 -0
  18. package/dist/cli/commands/maintenance.js +70 -4
  19. package/dist/cli/commands/project.d.ts +10 -6
  20. package/dist/cli/commands/project.js +42 -3
  21. package/dist/cli/commands/restart.js +17 -7
  22. package/dist/cli/commands/sessions.js +5 -4
  23. package/dist/cli/commands/user-config.js +1 -1
  24. package/dist/cli/index.js +6 -4
  25. package/dist/cli/manifest.js +6 -1
  26. package/dist/cli/menu/apps.d.ts +21 -0
  27. package/dist/cli/menu/apps.js +122 -0
  28. package/dist/cli/menu/command-form.d.ts +28 -0
  29. package/dist/cli/menu/command-form.js +219 -0
  30. package/dist/cli/menu/index.d.ts +26 -0
  31. package/dist/cli/menu/index.js +180 -0
  32. package/dist/cli/menu/settings/categories.d.ts +9 -0
  33. package/dist/cli/menu/settings/categories.js +28 -0
  34. package/dist/cli/menu/settings/config-file.d.ts +40 -0
  35. package/dist/cli/menu/settings/config-file.js +71 -0
  36. package/dist/cli/menu/settings/editors.d.ts +30 -0
  37. package/dist/cli/menu/settings/editors.js +305 -0
  38. package/dist/cli/menu/settings/index.d.ts +32 -0
  39. package/dist/cli/menu/settings/index.js +136 -0
  40. package/dist/cli/menu/settings/schema-node.d.ts +62 -0
  41. package/dist/cli/menu/settings/schema-node.js +104 -0
  42. package/dist/cli/{menu-multi.d.ts → menu/several.d.ts} +10 -2
  43. package/dist/cli/menu/several.js +102 -0
  44. package/dist/cli/progress.d.ts +15 -0
  45. package/dist/cli/progress.js +52 -0
  46. package/dist/cli/tui/app.d.ts +39 -0
  47. package/dist/cli/tui/app.js +52 -0
  48. package/dist/cli/tui/dialogs.d.ts +57 -0
  49. package/dist/cli/tui/dialogs.js +162 -0
  50. package/dist/cli/tui/frame.d.ts +18 -0
  51. package/dist/cli/tui/frame.js +27 -0
  52. package/dist/cli/tui/index.d.ts +7 -0
  53. package/dist/cli/tui/index.js +8 -0
  54. package/dist/cli/tui/list-screen.d.ts +101 -0
  55. package/dist/cli/tui/list-screen.js +299 -0
  56. package/dist/cli/tui/terminal.d.ts +37 -0
  57. package/dist/cli/tui/terminal.js +89 -0
  58. package/dist/cli/tui/text.d.ts +20 -0
  59. package/dist/cli/tui/text.js +105 -0
  60. package/dist/cli/tui/theme.d.ts +45 -0
  61. package/dist/cli/tui/theme.js +30 -0
  62. package/dist/core/config.js +10 -7
  63. package/dist/core/local-config.d.ts +31 -1
  64. package/dist/core/local-config.js +72 -7
  65. package/dist/core/project-policy.d.ts +50 -3
  66. package/dist/core/project-policy.js +90 -4
  67. package/dist/core/schema/apps.d.ts +40 -0
  68. package/dist/core/schema/apps.js +27 -0
  69. package/dist/core/schema/config.d.ts +69 -3
  70. package/dist/core/schema/config.js +18 -6
  71. package/dist/core/schema/profile.d.ts +5 -0
  72. package/dist/core/schema/profile.js +4 -2
  73. package/dist/core/schema/projects.d.ts +1 -0
  74. package/dist/core/schema/projects.js +1 -0
  75. package/dist/core/schema/sandbox.d.ts +29 -1
  76. package/dist/core/schema/sandbox.js +29 -1
  77. package/dist/core/schema/toolchains.d.ts +3 -0
  78. package/dist/core/schema/toolchains.js +6 -1
  79. package/dist/core/types.d.ts +24 -1
  80. package/dist/core/util.d.ts +2 -0
  81. package/dist/core/util.js +2 -0
  82. package/dist/core/workspace.d.ts +21 -4
  83. package/dist/core/workspace.js +99 -21
  84. package/dist/guest/apps/index.d.ts +10 -0
  85. package/dist/guest/apps/index.js +206 -0
  86. package/dist/guest/apps/resolvers.d.ts +8 -0
  87. package/dist/guest/apps/resolvers.js +90 -0
  88. package/dist/guest/audit-parse.d.ts +7 -0
  89. package/dist/guest/audit-parse.js +24 -0
  90. package/dist/guest/audit.d.ts +4 -0
  91. package/dist/guest/audit.js +47 -0
  92. package/dist/guest/daemon/relays.d.ts +1 -0
  93. package/dist/guest/daemon/relays.js +18 -0
  94. package/dist/guest/daemon.js +4 -0
  95. package/dist/guest/env.d.ts +7 -2
  96. package/dist/guest/env.js +17 -3
  97. package/dist/guest/helpers.d.ts +1 -1
  98. package/dist/guest/main.js +20 -3
  99. package/dist/guest/netproxy.d.ts +1 -0
  100. package/dist/guest/netproxy.js +100 -0
  101. package/dist/guest/ptyhost.js +14 -4
  102. package/dist/guest/setup/claude.d.ts +0 -4
  103. package/dist/guest/setup/claude.js +48 -2
  104. package/dist/guest/setup/network.d.ts +9 -0
  105. package/dist/guest/setup/network.js +97 -4
  106. package/dist/guest/setup/user.js +4 -0
  107. package/dist/guest/toolchains/index.js +29 -3
  108. package/dist/guest/toolchains/native/mesa.js +13 -3
  109. package/dist/guest/toolchains/plan.d.ts +3 -1
  110. package/dist/guest/toolchains/plan.js +53 -9
  111. package/dist/guest/toolchains/python.js +26 -2
  112. package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
  113. package/dist/guest/toolchains/vscode-profile.js +69 -0
  114. package/dist/guest/toolchains/vscode.d.ts +17 -0
  115. package/dist/guest/toolchains/vscode.js +87 -1
  116. package/dist/host/attach.js +2 -1
  117. package/dist/host/audit/collect.d.ts +34 -0
  118. package/dist/host/audit/collect.js +126 -0
  119. package/dist/host/audit/log.d.ts +46 -0
  120. package/dist/host/audit/log.js +143 -0
  121. package/dist/host/channel.d.ts +2 -2
  122. package/dist/host/channel.js +3 -2
  123. package/dist/host/generate.d.ts +7 -1
  124. package/dist/host/generate.js +23 -6
  125. package/dist/host/join.d.ts +7 -0
  126. package/dist/host/join.js +23 -9
  127. package/dist/host/launch/mappings.d.ts +7 -2
  128. package/dist/host/launch/mappings.js +68 -18
  129. package/dist/host/launch/projects.d.ts +2 -0
  130. package/dist/host/launch/projects.js +11 -5
  131. package/dist/host/launch/stage.js +1 -1
  132. package/dist/host/motw.d.ts +17 -0
  133. package/dist/host/motw.js +74 -0
  134. package/dist/host/platform.js +3 -0
  135. package/dist/host/port-forward.d.ts +8 -0
  136. package/dist/host/port-forward.js +37 -0
  137. package/dist/host/projects.d.ts +25 -2
  138. package/dist/host/projects.js +54 -5
  139. package/dist/host/repo-guard.d.ts +10 -3
  140. package/dist/host/repo-guard.js +142 -14
  141. package/dist/host/stops.d.ts +67 -0
  142. package/dist/host/stops.js +126 -0
  143. package/dist/host/terminal.d.ts +8 -0
  144. package/dist/host/terminal.js +45 -1
  145. package/dist/host/workspace-view/index.d.ts +20 -0
  146. package/dist/host/workspace-view/index.js +53 -0
  147. package/dist/host/workspace-view/rules.d.ts +26 -0
  148. package/dist/host/workspace-view/rules.js +93 -0
  149. package/dist/host/workspace-view/view.d.ts +50 -0
  150. package/dist/host/workspace-view/view.js +281 -0
  151. package/dist/runtime/daemon.js +1387 -558
  152. package/dist/runtime/db.js +199 -14
  153. package/dist/runtime/main.js +3082 -631
  154. package/dist/runtime/netproxy.js +25948 -0
  155. package/dist/runtime/view.js +531 -148
  156. package/dist/selftest/audit.test.d.ts +2 -0
  157. package/dist/selftest/audit.test.js +57 -0
  158. package/dist/selftest/channel.test.js +45 -2
  159. package/dist/selftest/cli.test.js +47 -17
  160. package/dist/selftest/fixtures.d.ts +47 -1
  161. package/dist/selftest/fixtures.js +3 -1
  162. package/dist/selftest/generate.test.js +7 -2
  163. package/dist/selftest/index.js +6 -2
  164. package/dist/selftest/live-network.test.d.ts +2 -0
  165. package/dist/selftest/live-network.test.js +63 -0
  166. package/dist/selftest/live-terminal.test.d.ts +2 -0
  167. package/dist/selftest/live-terminal.test.js +169 -0
  168. package/dist/selftest/local-config.test.js +42 -4
  169. package/dist/selftest/mirror.test.js +36 -7
  170. package/dist/selftest/network.test.js +12 -0
  171. package/dist/selftest/projects.test.js +77 -8
  172. package/dist/selftest/repo-guard.test.js +59 -1
  173. package/dist/selftest/sync.test.js +50 -10
  174. package/dist/selftest/toolchains.test.js +30 -0
  175. package/dist/selftest/workspace-view.test.d.ts +2 -0
  176. package/dist/selftest/workspace-view.test.js +89 -0
  177. package/dist/shared/apps.d.ts +49 -0
  178. package/dist/shared/apps.js +114 -0
  179. package/dist/shared/console-input.d.ts +11 -0
  180. package/dist/shared/console-input.js +61 -0
  181. package/dist/shared/download.d.ts +3 -0
  182. package/dist/shared/download.js +25 -2
  183. package/dist/shared/guest-paths.d.ts +27 -0
  184. package/dist/shared/guest-paths.js +68 -0
  185. package/dist/shared/netpolicy.d.ts +27 -0
  186. package/dist/shared/netpolicy.js +51 -0
  187. package/dist/shared/projects.d.ts +27 -4
  188. package/dist/shared/projects.js +72 -13
  189. package/dist/shared/pty-client.d.ts +26 -4
  190. package/dist/shared/pty-client.js +204 -25
  191. package/dist/shared/rundir.d.ts +2 -0
  192. package/dist/shared/rundir.js +2 -0
  193. package/dist/shared/schema.d.ts +34 -0
  194. package/dist/shared/schema.js +14 -0
  195. package/dist/shared/system.d.ts +1 -0
  196. package/dist/shared/system.js +1 -0
  197. package/dist/sync/claude-mem.js +3 -2
  198. package/dist/sync/history.d.ts +6 -0
  199. package/dist/sync/history.js +26 -11
  200. package/dist/sync/watcher.js +78 -6
  201. package/package.json +2 -1
  202. package/sandbox/cargoshim.cs +56 -0
  203. package/sandbox/cpucap.cs +33 -0
  204. package/sandbox/cpucap.ps1 +4 -35
  205. package/sandbox/logon.cmd +6 -0
  206. package/sandbox/memlog.ps1 +34 -0
  207. package/sandbox/pagefile.cs +31 -0
  208. package/sandbox/pagefile.ps1 +21 -0
  209. package/sandbox/ptyhost.cs +162 -7
  210. package/schemas/config.schema.json +318 -3
  211. package/schemas/sandbox-config.schema.json +129 -14
  212. package/dist/cli/menu-multi.js +0 -82
  213. package/dist/cli/menu.d.ts +0 -14
  214. package/dist/cli/menu.js +0 -290
  215. package/dist/cli/settings/edit.d.ts +0 -13
  216. package/dist/cli/settings/edit.js +0 -19
  217. package/dist/cli/settings/index.d.ts +0 -8
  218. package/dist/cli/settings/index.js +0 -131
  219. package/dist/cli/settings/prompts.d.ts +0 -7
  220. package/dist/cli/settings/prompts.js +0 -48
  221. package/dist/cli/settings/schema.d.ts +0 -33
  222. package/dist/cli/settings/schema.js +0 -29
@@ -2,6 +2,7 @@
2
2
  // guest path like F:\SteamLibrary\x is mapped to C:\Mirror\F\SteamLibrary\x and, at boot (bootstrap.ps1, before
3
3
  // anything else uses the workspace), `subst F: C:\Mirror\F` makes it appear at F:\SteamLibrary\x.
4
4
  // Pure functions only: used by the schema, the host (.wsb, session.json) and history sync.
5
+ import { win32 } from 'node:path';
5
6
  /** Guest folders no mapping may use: the OS, the sandbox's own folders and the mirror root. */
6
7
  export const RESERVED_GUEST = /^C:\\(?:Windows|Program Files|Program Files \(x86\)|ProgramData|Users|AgentLaunch|AgentData|HostTools|nvm|Mirror)(?:\\|$)/i;
7
8
  /** Where folders for other drive letters really live inside the sandbox: C:\Mirror\<letter>. */
@@ -31,6 +32,73 @@ export const isValidGuestPath = (guest) => {
31
32
  return !RESERVED_GUEST.test('C:\\' + rest);
32
33
  return MIRROR_LETTER.test(letter);
33
34
  };
35
+ /** Where a mapping lands when the path it asks for (or the PC path) cannot be used inside the sandbox. */
36
+ export const MAPPED_ROOT = 'C:\\Mapped';
37
+ /** A guest path as typed, tidied: '/' -> '\', no doubled or trailing backslashes, upper-case drive letter. */
38
+ export const tidyGuestPath = (guest) => {
39
+ const p = guest.trim().replace(/\//g, '\\').replace(/\\{2,}/g, '\\').replace(/\\+$/, '');
40
+ return /^[a-z]:/.test(p) ? p[0].toUpperCase() + p.slice(1) : p;
41
+ };
42
+ /** Why a guest path cannot be used as it is (null: it can). Short, plain English. */
43
+ export const guestPathProblem = (guest) => {
44
+ if (isValidGuestPath(guest))
45
+ return null;
46
+ const letter = guestDrive(guest);
47
+ if (letter === 'C' && RESERVED_GUEST.test(guest))
48
+ return guest + ' is a folder the sandbox keeps for itself (Windows, Program Files, Users, AgentData, ...)';
49
+ if (letter && !MIRROR_LETTER.test(letter) && letter !== 'C')
50
+ return 'the sandbox cannot show drive ' + letter + ':';
51
+ if (letter && /^[A-Za-z]:\\?$/.test(guest))
52
+ return guest + ' is a whole drive';
53
+ return guest + ' is not a folder path the sandbox can use';
54
+ };
55
+ /** C:\Mapped\<drive>\<rest of path>: a safe place for a folder whose own path cannot be used (odd characters made safe). */
56
+ export const mappedFallback = (path) => {
57
+ const m = /^([A-Za-z]):\\?(.*)$/.exec(tidyGuestPath(path)) ?? /^\\\\(.*)$/.exec(path);
58
+ const letter = m && m.length === 3 ? m[1].toUpperCase() : 'UNC';
59
+ const rest = (m ? m[m.length - 1] : path).split(/[\\/]+/).map((s) => s.replace(/[<>:"|?*\x00-\x1f]/g, '_').replace(/[. ]+$/, '')).filter((s) => s !== '' && s !== '..').join('\\');
60
+ return MAPPED_ROOT + '\\' + letter + (rest ? '\\' + rest : '');
61
+ };
62
+ /** Two guest folders overlap where they really are (F:\x is C:\Mirror\F\x): the same, or one inside the other. */
63
+ export const guestOverlaps = (a, b) => {
64
+ const x = sandboxFolder(a).replace(/\\+$/, '').toLowerCase();
65
+ const y = sandboxFolder(b).replace(/\\+$/, '').toLowerCase();
66
+ return x === y || x.startsWith(y + '\\') || y.startsWith(x + '\\');
67
+ };
68
+ /**
69
+ * Where an extra folder appears in the sandbox. `wanted` is the guest path asked for (or the one derived from the PC
70
+ * path); when the sandbox cannot use it (reserved, invalid, or overlapping a folder in `taken`), the folder goes to
71
+ * C:\Mapped\<drive>\<path> instead (made unique with -2, -3 ...), and `note` says where and why.
72
+ */
73
+ export const placeGuest = (host, wanted, taken) => {
74
+ const clash = (g) => taken.find((t) => guestOverlaps(g, t.guest));
75
+ const tidy = tidyGuestPath(wanted);
76
+ const problem = guestPathProblem(tidy);
77
+ const other = problem ? null : clash(tidy);
78
+ if (!problem && !other)
79
+ return { guest: tidy, note: null };
80
+ const why = problem ?? tidy + ' is already used by ' + other.what + (other.guest.toLowerCase() === tidy.toLowerCase() ? '' : ' (' + other.guest + ')');
81
+ const guest = freePlace(mappedFallback(/^[A-Za-z]:/.test(tidy) ? tidy : host), clash);
82
+ return { guest, note: host + ': ' + why + ', so it is at ' + guest + ' in the sandbox.' };
83
+ };
84
+ /**
85
+ * The guest path a mapping asks for when it gives none: a folder inside the project at the same place in the project's
86
+ * guest folder, any other folder at the same path as on this PC (D: to Z: through C:\Mirror and subst).
87
+ */
88
+ export const deriveGuest = (host, projectHost, projectGuest) => {
89
+ const rel = win32.relative(win32.resolve(projectHost), win32.resolve(host));
90
+ const inside = rel === '' || (!rel.startsWith('..') && !win32.isAbsolute(rel));
91
+ return inside ? (rel ? win32.join(projectGuest, rel) : projectGuest) : tidyGuestPath(win32.resolve(host));
92
+ };
93
+ const freePlace = (base, clash) => {
94
+ const ok = (g) => isValidGuestPath(g) && !clash(g);
95
+ if (ok(base))
96
+ return base;
97
+ for (let i = 2; i < 1000; i++)
98
+ if (ok(base + '-' + i))
99
+ return base + '-' + i;
100
+ return base + '-' + Date.now();
101
+ };
34
102
  /** Where a guest path really is inside the sandbox (the .wsb SandboxFolder): C: paths as-is, F:\x -> C:\Mirror\F\x. */
35
103
  export const sandboxFolder = (guest) => {
36
104
  const m = DRIVE_PATH.exec(guest);
@@ -0,0 +1,27 @@
1
+ /** A host name or a "*.example.com" pattern (lower case, no scheme, no port). */
2
+ export declare const HOST_PATTERN: RegExp;
3
+ /**
4
+ * What "@developer" stands for in allowOnly: package registries, source hosts, toolchain downloads and the AI
5
+ * providers the agents talk to. Enough for an agent to install dependencies, push to GitHub and reach its model.
6
+ */
7
+ export declare const DEVELOPER_HOSTS: string[];
8
+ /** The allowOnly list with "@developer" expanded, lower case, without duplicates. */
9
+ export declare const expandAllowList: (list: string[]) => string[];
10
+ /** Does `host` match one of the patterns? "*.example.com" matches its subdomains (not example.com itself). */
11
+ export declare const hostAllowed: (host: string, patterns: string[]) => boolean;
12
+ export declare const HOSTS_BEGIN = "# >>> windows-development-sandbox network.blockHosts";
13
+ export declare const HOSTS_END = "# <<< windows-development-sandbox network.blockHosts";
14
+ /** The hosts file with our block section replaced by `hosts` (each sent to 0.0.0.0 and ::); everything else stays. */
15
+ export declare const withBlockedHosts: (hostsFile: string, hosts: string[]) => string;
16
+ /** One forwarded port: <pc> on this PC (localhost only) reaches <sandbox> inside. */
17
+ export interface PortForward {
18
+ pc: number;
19
+ sandbox: number;
20
+ }
21
+ /**
22
+ * The port the sandbox's relay for forward `index` listens on (it connects on to localhost:<sandbox> inside, so a
23
+ * server that only listens on localhost is reachable too). Out of the range Windows hands out for outgoing connections.
24
+ */
25
+ export declare const relayPort: (index: number) => number;
26
+ /** The address the sandbox's built-in proxy listens on (allowOnly). */
27
+ export declare const PROXY_PORT = 38080;
@@ -0,0 +1,51 @@
1
+ // The network settings as rules: which hosts the sandbox may reach (network.allowOnly), which never (blockHosts), and
2
+ // the ports forwarded from this PC into the sandbox. Pure (tested); the guest applies them (guest/setup/network.ts,
3
+ // guest/netproxy.ts) and the host runs the forwarders (host/port-forward.ts).
4
+ /** A host name or a "*.example.com" pattern (lower case, no scheme, no port). */
5
+ export const HOST_PATTERN = /^(\*\.)?([a-z0-9-]+\.)*[a-z0-9-]+$/i;
6
+ /**
7
+ * What "@developer" stands for in allowOnly: package registries, source hosts, toolchain downloads and the AI
8
+ * providers the agents talk to. Enough for an agent to install dependencies, push to GitHub and reach its model.
9
+ */
10
+ export const DEVELOPER_HOSTS = [
11
+ // AI providers
12
+ 'api.anthropic.com', 'claude.ai', '*.claude.ai', 'console.anthropic.com', 'statsig.anthropic.com', 'sentry.io', '*.sentry.io',
13
+ 'api.openai.com', 'chatgpt.com', '*.openai.com', 'generativelanguage.googleapis.com', 'oauth2.googleapis.com', 'accounts.google.com',
14
+ // source hosts
15
+ 'github.com', '*.github.com', '*.githubusercontent.com', 'codeload.github.com', 'gitlab.com', '*.gitlab.com', 'bitbucket.org',
16
+ // package registries
17
+ 'registry.npmjs.org', '*.npmjs.org', 'registry.yarnpkg.com', 'pypi.org', 'files.pythonhosted.org', 'crates.io', 'index.crates.io', 'static.crates.io',
18
+ 'proxy.golang.org', 'sum.golang.org', 'repo1.maven.org', 'repo.maven.apache.org', 'plugins.gradle.org', 'services.gradle.org', 'api.nuget.org',
19
+ '*.nuget.org', 'rubygems.org', 'index.rubygems.org', 'repo.packagist.org', 'packagist.org', 'jsr.io', 'deno.land',
20
+ // toolchains
21
+ 'nodejs.org', '*.nodejs.org', 'static.rust-lang.org', 'go.dev', 'dl.google.com', 'aka.ms', 'download.visualstudio.microsoft.com',
22
+ 'dotnetcli.azureedge.net', 'builds.dotnet.microsoft.com', 'api.adoptium.net', 'objects.githubusercontent.com', 'marketplace.visualstudio.com',
23
+ '*.gallerycdn.vsassets.io', '*.vscode-cdn.net', 'update.code.visualstudio.com',
24
+ ];
25
+ /** The allowOnly list with "@developer" expanded, lower case, without duplicates. */
26
+ export const expandAllowList = (list) => [...new Set(list.flatMap((h) => (h.trim().toLowerCase() === '@developer' ? DEVELOPER_HOSTS : [h])).map((h) => h.trim().toLowerCase()).filter((h) => HOST_PATTERN.test(h)))];
27
+ /** Does `host` match one of the patterns? "*.example.com" matches its subdomains (not example.com itself). */
28
+ export const hostAllowed = (host, patterns) => {
29
+ const h = host.toLowerCase().replace(/\.$/, '');
30
+ return patterns.some((p) => (p.startsWith('*.') ? h.endsWith(p.slice(1)) && h.length > p.length - 1 : h === p));
31
+ };
32
+ export const HOSTS_BEGIN = '# >>> windows-development-sandbox network.blockHosts';
33
+ export const HOSTS_END = '# <<< windows-development-sandbox network.blockHosts';
34
+ /** The hosts file with our block section replaced by `hosts` (each sent to 0.0.0.0 and ::); everything else stays. */
35
+ export const withBlockedHosts = (hostsFile, hosts) => {
36
+ const begin = hostsFile.indexOf(HOSTS_BEGIN);
37
+ const end = hostsFile.indexOf(HOSTS_END);
38
+ const outside = begin >= 0 && end > begin ? hostsFile.slice(0, begin) + hostsFile.slice(end + HOSTS_END.length) : hostsFile;
39
+ const clean = [...new Set(hosts.map((h) => h.trim().toLowerCase()).filter((h) => HOST_PATTERN.test(h) && !h.startsWith('*.')))];
40
+ const base = outside.replace(/\s+$/, '');
41
+ if (!clean.length)
42
+ return base + '\r\n';
43
+ return [base, HOSTS_BEGIN, ...clean.flatMap((h) => ['0.0.0.0 ' + h, ':: ' + h]), HOSTS_END, ''].join('\r\n');
44
+ };
45
+ /**
46
+ * The port the sandbox's relay for forward `index` listens on (it connects on to localhost:<sandbox> inside, so a
47
+ * server that only listens on localhost is reachable too). Out of the range Windows hands out for outgoing connections.
48
+ */
49
+ export const relayPort = (index) => 38100 + index;
50
+ /** The address the sandbox's built-in proxy listens on (allowOnly). */
51
+ export const PROXY_PORT = 38080;
@@ -1,5 +1,12 @@
1
- /** Where roots, boot-mapped projects and shared folders appear inside the sandbox. */
1
+ /** Where project roots appear inside the sandbox. */
2
2
  export declare const PROJECTS_GUEST = "C:\\Projects";
3
+ /** Where projects mapped on their own appear (the launch folder with guestWorkspace "auto", boot-mapped and shared ones). */
4
+ export declare const WORKSPACES_GUEST = "C:\\Workspace";
5
+ /**
6
+ * guestWorkspace "auto" for a folder: C:\Workspace\<folder name>. Keeping the real folder name means Claude's
7
+ * per-project history and claude-mem's project name match this PC instead of every project being "Workspace".
8
+ */
9
+ export declare const autoGuestFolder: (hostDir: string) => string;
3
10
  /** How a project got into the sandbox. */
4
11
  export type Mechanism = 'launch' | 'root' | 'boot' | 'wsb';
5
12
  /** One project the running sandbox works on. */
@@ -37,17 +44,33 @@ export declare const hostDirFor: (mapped: {
37
44
  }, guestDir: string) => string;
38
45
  /** Strictly below `parent` (never `parent` itself). */
39
46
  export declare const isBelow: (parent: string, child: string) => boolean;
40
- /** A guest folder some project can be in: below C:\Projects, or inside one of the roots (their guest paths). */
47
+ /**
48
+ * A guest folder some project can be in: below C:\Projects or C:\Workspace (projects mapped on their own, also shared
49
+ * after boot), or inside one of `roots` (the roots' and boot-mapped projects' guest paths).
50
+ */
41
51
  export declare const insideProjects: (guestDir: string, roots?: string[], projectsDir?: string) => boolean;
42
52
  export interface RootMapping {
43
53
  host: string;
44
54
  guest: string;
45
55
  }
56
+ /** The project roots that are mapped, in order: existing folders, none inside a root listed before it. */
57
+ export declare const usableRoots: (roots: string[], exists: (dir: string) => boolean) => string[];
46
58
  /**
47
- * Guest folders for the project roots and the projects mapped at boot: C:\Projects\<name>, unique; with
48
- * guestWorkspace "mirror" a root appears at its PC path when the sandbox can show it there.
59
+ * Where a project mapped on its own appears, the same place it has as the launch folder: with "mirror" its PC path
60
+ * when the sandbox can show it there, else C:\Workspace\<name>. -2, -3, ... when that overlaps a folder in `taken`.
61
+ */
62
+ export declare const ownGuestFolder: (hostDir: string, mirror: boolean, taken?: string[]) => string;
63
+ /**
64
+ * Guest folders for the project roots (C:\Projects\<name>, unique; with guestWorkspace "mirror" at its PC path when
65
+ * the sandbox can show it there) and the projects mapped at boot (ownGuestFolder, unique against `taken` and each
66
+ * other). A root's folder depends only on the roots, so every launch with the same roots shows them at the same place.
49
67
  */
50
68
  export declare const planProjectMappings: (roots: string[], projects: string[], mirror: boolean, taken?: string[]) => {
51
69
  roots: RootMapping[];
52
70
  projects: RootMapping[];
53
71
  };
72
+ /**
73
+ * Where a folder inside one of the (usable) roots appears: under the root's guest folder, whichever project started
74
+ * the sandbox. Null when it is in no root.
75
+ */
76
+ export declare const rootGuestDir: (roots: string[], mirror: boolean, hostDir: string) => string | null;
@@ -1,16 +1,30 @@
1
1
  // Several projects in one running sandbox. Windows Sandbox fixes its mapped folders at boot, so a project can only
2
2
  // join a running sandbox when it is already visible inside:
3
- // - the launch folder (mapped directly, as always);
3
+ // - the launch folder (mapped directly, or shown by the project root it is in);
4
4
  // - a folder inside a project root (projects.roots: whole folders of projects, mapped at every start), including
5
5
  // projects created after boot;
6
6
  // - projects mapped at boot because they were in use when the sandbox was restarted to add them;
7
7
  // - a folder shared into the running sandbox with `wsb share` (Windows 11 24H2+, untested).
8
- // Roots, boot-mapped projects and shares appear under C:\Projects (roots at their PC path with guestWorkspace "mirror").
8
+ // A project is at the same place inside the sandbox whichever project started it (git worktrees and Claude's project
9
+ // folders store absolute paths):
10
+ // - a project inside a root is always under the root's folder, C:\Projects\<root>\<name> (also as the launch folder);
11
+ // - any other project, mapped on its own, is always at C:\Workspace\<name> (also when it is mapped at boot or shared).
12
+ // With guestWorkspace "mirror" both appear at their PC paths when the sandbox can show them there.
9
13
  // Pure functions only: used by the host (registration, commands), the guest (daemon checks) and the tests.
10
14
  import path from 'node:path';
11
- import { isValidGuestPath } from './guest-paths.js';
12
- /** Where roots, boot-mapped projects and shared folders appear inside the sandbox. */
15
+ import { isValidGuestPath, planMirrorWorkspace } from './guest-paths.js';
16
+ /** Where project roots appear inside the sandbox. */
13
17
  export const PROJECTS_GUEST = 'C:\\Projects';
18
+ /** Where projects mapped on their own appear (the launch folder with guestWorkspace "auto", boot-mapped and shared ones). */
19
+ export const WORKSPACES_GUEST = 'C:\\Workspace';
20
+ /**
21
+ * guestWorkspace "auto" for a folder: C:\Workspace\<folder name>. Keeping the real folder name means Claude's
22
+ * per-project history and claude-mem's project name match this PC instead of every project being "Workspace".
23
+ */
24
+ export const autoGuestFolder = (hostDir) => {
25
+ const name = path.win32.basename(hostDir.replace(/[\\/]+$/, '')).replace(/[^A-Za-z0-9._ -]/g, '_').replace(/^[. ]+|[. ]+$/g, '');
26
+ return WORKSPACES_GUEST + '\\' + (name || 'project');
27
+ };
14
28
  const norm = (p) => path.win32.resolve(p).replace(/[\\/]+$/, '').toLowerCase();
15
29
  /** `child` is `parent` or somewhere below it (Windows paths, case-insensitive). */
16
30
  export const isWithin = (parent, child) => {
@@ -47,21 +61,66 @@ export const hostDirFor = (mapped, guestDir) => {
47
61
  };
48
62
  /** Strictly below `parent` (never `parent` itself). */
49
63
  export const isBelow = (parent, child) => isWithin(parent, child) && norm(parent) !== norm(child);
50
- /** A guest folder some project can be in: below C:\Projects, or inside one of the roots (their guest paths). */
51
- export const insideProjects = (guestDir, roots = [], projectsDir = PROJECTS_GUEST) => isBelow(projectsDir, guestDir) || roots.some((r) => isWithin(r, guestDir));
52
64
  /**
53
- * Guest folders for the project roots and the projects mapped at boot: C:\Projects\<name>, unique; with
54
- * guestWorkspace "mirror" a root appears at its PC path when the sandbox can show it there.
65
+ * A guest folder some project can be in: below C:\Projects or C:\Workspace (projects mapped on their own, also shared
66
+ * after boot), or inside one of `roots` (the roots' and boot-mapped projects' guest paths).
67
+ */
68
+ export const insideProjects = (guestDir, roots = [], projectsDir = PROJECTS_GUEST) => isBelow(projectsDir, guestDir) || isBelow(WORKSPACES_GUEST, guestDir) || roots.some((r) => isWithin(r, guestDir));
69
+ const resolveDir = (dir) => path.win32.resolve(dir).replace(/[\\/]+$/, '');
70
+ /** The project roots that are mapped, in order: existing folders, none inside a root listed before it. */
71
+ export const usableRoots = (roots, exists) => {
72
+ const out = [];
73
+ for (const root of roots.map(resolveDir))
74
+ if (exists(root) && !out.some((r) => isWithin(r, root)))
75
+ out.push(root);
76
+ return out;
77
+ };
78
+ /**
79
+ * Where a project mapped on its own appears, the same place it has as the launch folder: with "mirror" its PC path
80
+ * when the sandbox can show it there, else C:\Workspace\<name>. -2, -3, ... when that overlaps a folder in `taken`.
81
+ */
82
+ export const ownGuestFolder = (hostDir, mirror, taken = []) => {
83
+ const host = resolveDir(hostDir);
84
+ const free = (guest) => !taken.some((t) => isWithin(t, guest) || isWithin(guest, t));
85
+ const mirrored = mirror ? planMirrorWorkspace(host, '').guest : '';
86
+ if (mirrored && !isWithin(PROJECTS_GUEST, mirrored) && !isWithin(WORKSPACES_GUEST, mirrored) && free(mirrored))
87
+ return mirrored;
88
+ const base = autoGuestFolder(host);
89
+ if (free(base))
90
+ return base;
91
+ for (let i = 2;; i++)
92
+ if (free(base + '-' + i))
93
+ return base + '-' + i;
94
+ };
95
+ /**
96
+ * Guest folders for the project roots (C:\Projects\<name>, unique; with guestWorkspace "mirror" at its PC path when
97
+ * the sandbox can show it there) and the projects mapped at boot (ownGuestFolder, unique against `taken` and each
98
+ * other). A root's folder depends only on the roots, so every launch with the same roots shows them at the same place.
55
99
  */
56
100
  export const planProjectMappings = (roots, projects, mirror, taken = []) => {
57
- const names = [...taken];
58
- const place = (host, tryMirror) => {
59
- const resolved = path.win32.resolve(host).replace(/[\\/]+$/, '');
60
- if (tryMirror && isValidGuestPath(resolved) && !isWithin(PROJECTS_GUEST, resolved))
101
+ const names = [];
102
+ const placeRoot = (host) => {
103
+ const resolved = resolveDir(host);
104
+ if (mirror && isValidGuestPath(resolved) && !isWithin(PROJECTS_GUEST, resolved))
61
105
  return { host: resolved, guest: resolved };
62
106
  const name = hubName(resolved, names);
63
107
  names.push(name);
64
108
  return { host: resolved, guest: path.win32.join(PROJECTS_GUEST, name) };
65
109
  };
66
- return { roots: roots.map((r) => place(r, mirror)), projects: projects.map((p) => place(p, false)) };
110
+ const planned = roots.map(placeRoot);
111
+ const used = [...taken, ...planned.map((r) => r.guest)];
112
+ const placed = projects.map((p) => {
113
+ const guest = ownGuestFolder(p, mirror, used);
114
+ used.push(guest);
115
+ return { host: resolveDir(p), guest };
116
+ });
117
+ return { roots: planned, projects: placed };
118
+ };
119
+ /**
120
+ * Where a folder inside one of the (usable) roots appears: under the root's guest folder, whichever project started
121
+ * the sandbox. Null when it is in no root.
122
+ */
123
+ export const rootGuestDir = (roots, mirror, hostDir) => {
124
+ const root = projectForHostDir(planProjectMappings(roots, [], mirror).roots, hostDir);
125
+ return root ? guestDirFor(root, hostDir) : null;
67
126
  };
@@ -10,6 +10,7 @@ export declare const FrameType: {
10
10
  readonly Error: 18;
11
11
  readonly Restarting: 19;
12
12
  readonly Status: 20;
13
+ readonly Console: 21;
13
14
  };
14
15
  /** HELLO flags: control-only connections get no output and never size the console; a query only asks for STATUS. */
15
16
  export declare const HelloFlag: {
@@ -36,6 +37,8 @@ export type ClientResult = {
36
37
  reason: 'error';
37
38
  message: string;
38
39
  };
40
+ /** Turns off every mouse tracking mode a program may have turned on in your terminal (normal, button, any, UTF-8, SGR, urxvt). */
41
+ export declare const MOUSE_OFF = "\u001B[?1000l\u001B[?1002l\u001B[?1003l\u001B[?1005l\u001B[?1006l\u001B[?1015l";
39
42
  /** Ctrl+] — the same detach key telnet uses; Claude Code does not bind it. */
40
43
  export declare const DETACH_KEY = 29;
41
44
  export declare const encodeFrame: (type: number, payload?: Buffer) => Buffer;
@@ -49,12 +52,16 @@ export declare class FrameReader {
49
52
  push(chunk: Buffer): Frame[];
50
53
  }
51
54
  /**
52
- * Removes OSC 52 (clipboard write) from the sandbox's output before it reaches your terminal: an untrusted program
53
- * must not be able to silently put text into your clipboard. Everything else (colours, titles, links) passes.
54
- * Stateful, so a sequence split across chunks is still caught.
55
+ * Removes the terminal "strings" an untrusted program could abuse from the sandbox's output before it reaches your
56
+ * terminal: clipboard writes (OSC 52), terminal macros (ConEmu's OSC 9), DCS / APC / PM / SOS, and their C1 forms.
57
+ * Titles, links, progress and colours pass. A byte-level state machine, so no split across chunks, leading zero
58
+ * (ESC]052) or huge payload slips through: a dropped string stays dropped until its terminator, however long it is.
55
59
  */
56
60
  export declare class OutputFilter {
57
- private carry;
61
+ /** text: normal output; esc: after ESC; c2: after a 0xC2 lead byte; string: inside an OSC/DCS/...; stringEsc / stringC2: a possible terminator inside one. */
62
+ private state;
63
+ /** The string being read: an OSC worth keeping is collected until its terminator; null = being dropped. */
64
+ private body;
58
65
  push(chunk: Buffer): Buffer;
59
66
  }
60
67
  /** Tries every address the sandbox published and returns the first one that answers. */
@@ -77,6 +84,21 @@ export declare const queryViewers: (endpoint: Endpoint, timeoutMs?: number) => P
77
84
  * what terminals send. ConPTY reads ^H as Ctrl+Backspace (delete a word), so swap them back.
78
85
  */
79
86
  export declare const fixWindowsKeys: (chunk: Buffer) => Buffer;
87
+ /** How long an unfinished escape sequence is held back waiting for the rest (a lone Esc key press waits this long). */
88
+ export declare const JOIN_MS = 30;
89
+ /**
90
+ * Keeps each escape sequence typed into your terminal (keys, mouse reports, the terminal's replies) in one INPUT frame.
91
+ * The console can hand one over in pieces; ConPTY reads a lone "ESC[" as Alt+[ and the rest as typed text, so a split
92
+ * Right arrow (ESC[C) left a stray "C" in the agent's prompt. An unfinished sequence at the end of a chunk is held
93
+ * until the rest arrives (or JOIN_MS passes).
94
+ */
95
+ export declare class InputJoiner {
96
+ private held;
97
+ /** The bytes to send now; an unfinished sequence at the end is kept for the next chunk. */
98
+ push(chunk: Buffer): Buffer;
99
+ holding(): boolean;
100
+ flush(): Buffer;
101
+ }
80
102
  /**
81
103
  * Bridges this process's terminal to the remote program until it exits, the connection drops, or the user presses
82
104
  * the detach key. Restores the terminal before resolving.