windows-development-sandbox 1.12.0 → 1.13.2
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/README.md +1 -0
- package/config.example.json +11 -1
- package/dist/app/features/access/access.def.d.ts +190 -0
- package/dist/app/features/access/access.def.js +273 -0
- package/dist/app/features/access/access.run.d.ts +79 -0
- package/dist/app/features/access/access.run.js +129 -0
- package/dist/app/features/access/index.d.ts +2 -0
- package/dist/app/features/access/index.js +3 -0
- package/dist/app/features/accounts/accounts.def.d.ts +1 -0
- package/dist/app/features/accounts/accounts.def.js +12 -3
- package/dist/app/features/accounts/accounts.run.d.ts +1 -0
- package/dist/app/features/accounts/accounts.run.js +20 -11
- package/dist/app/features/agents/agent-entry.js +5 -0
- package/dist/app/features/agents/agents.def.js +1 -0
- package/dist/app/features/apps/apps.def.js +1 -0
- package/dist/app/features/audit/audit.def.d.ts +11 -2
- package/dist/app/features/audit/audit.def.js +7 -4
- package/dist/app/features/audit/audit.run.d.ts +3 -0
- package/dist/app/features/defs.d.ts +156 -3
- package/dist/app/features/defs.js +12 -1
- package/dist/app/features/files/files.def.js +1 -0
- package/dist/app/features/health/health.def.js +1 -0
- package/dist/app/features/sandbox/sandbox.def.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +36 -0
- package/dist/app/features/sessions/sessions.def.js +77 -9
- package/dist/app/features/sessions/sessions.run.d.ts +25 -2
- package/dist/app/features/sessions/sessions.run.js +71 -3
- package/dist/app/features/settings/settings-file.d.ts +7 -0
- package/dist/app/features/settings/settings-file.js +19 -2
- package/dist/app/features/settings/settings-rows.js +9 -1
- package/dist/app/features/settings/settings.def.d.ts +8 -0
- package/dist/app/features/settings/settings.def.js +17 -0
- package/dist/app/features/web/web.def.d.ts +9 -0
- package/dist/app/features/web/web.def.js +26 -1
- package/dist/app/features/web/web.run.d.ts +3 -0
- package/dist/app/features/web/web.run.js +11 -3
- package/dist/app/kernel/access.d.ts +33 -0
- package/dist/app/kernel/access.js +44 -0
- package/dist/app/kernel/define.d.ts +14 -1
- package/dist/app/kernel/index.d.ts +1 -0
- package/dist/app/kernel/index.js +1 -0
- package/dist/app/kernel/rpc.d.ts +5 -2
- package/dist/app/registry.d.ts +4 -1
- package/dist/app/registry.js +24 -4
- package/dist/bin/agent-sandbox.js +7 -1
- package/dist/bin/chunk-IL7BXJF2.js +7 -0
- package/dist/bin/chunk-K4SU3MHJ.js +10432 -0
- package/dist/bin/chunk-VENWSGX2.js +78437 -0
- package/dist/bin/claude-host.js +10 -4
- package/dist/bin/claude-sbx.js +8 -2
- package/dist/bin/codex-sbx.js +8 -2
- package/dist/bin/gemini-sbx.js +8 -2
- package/dist/bin/sandbox.js +7 -2
- package/dist/bin/sbx-hide.js +8 -2
- package/dist/bin/sbx-show.js +8 -2
- package/dist/cli/commands/launch/prepare.js +24 -7
- package/dist/cli/commands/launch/running.js +4 -0
- package/dist/cli/commands/registry.js +2 -0
- package/dist/cli/commands/remote.d.ts +2 -0
- package/dist/cli/commands/remote.js +49 -0
- package/dist/cli/commands/sessions/reopen.js +3 -0
- package/dist/cli/commands/storage/offer.js +1 -1
- package/dist/cli/commands/web.js +66 -0
- package/dist/cli/manifest.d.ts +63 -4
- package/dist/cli/manifest.js +33 -4
- package/dist/cli/menu/io-menu.d.ts +5 -3
- package/dist/cli/menu/io-menu.js +12 -5
- package/dist/cli/menu/live.d.ts +29 -0
- package/dist/cli/menu/live.js +92 -21
- package/dist/cli/menu/menu.d.ts +2 -1
- package/dist/cli/menu/menu.js +16 -2
- package/dist/cli/menu/pages/list-page.d.ts +5 -0
- package/dist/cli/menu/pages/list-page.js +19 -0
- package/dist/cli/menu/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/shell.d.ts +2 -0
- package/dist/cli/menu/shell.js +1 -1
- package/dist/cli/menu/views.js +2 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/accounts-service.d.ts +7 -0
- package/dist/cli/services/accounts-service.js +20 -0
- package/dist/cli/services/remote-client.d.ts +16 -0
- package/dist/cli/services/remote-client.js +141 -0
- package/dist/cli/services/sessions-service.d.ts +5 -0
- package/dist/cli/services/sessions-service.js +17 -1
- package/dist/cli/tui/app.d.ts +4 -0
- package/dist/cli/tui/app.js +5 -0
- package/dist/cli/tui/terminal.d.ts +6 -0
- package/dist/cli/tui/terminal.js +8 -1
- package/dist/cli/validate.js +8 -4
- package/dist/core/config/defaults.js +11 -1
- package/dist/core/local-config/file.js +16 -2
- package/dist/core/local-config/overrides.js +7 -0
- package/dist/core/package-info.js +1 -1
- package/dist/core/run/versions.js +9 -8
- package/dist/core/schema/config.d.ts +22 -3
- package/dist/core/schema/config.js +39 -2
- package/dist/core/schema/web.d.ts +51 -0
- package/dist/core/schema/web.js +46 -1
- package/dist/core/util/fs.d.ts +5 -0
- package/dist/core/util/fs.js +23 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -1
- package/dist/guest/daemon/sessions.d.ts +16 -2
- package/dist/guest/daemon/sessions.js +126 -20
- package/dist/guest/git/decision.js +7 -0
- package/dist/guest/sessions/cmdline.d.ts +14 -0
- package/dist/guest/sessions/cmdline.js +10 -0
- package/dist/guest/toolchains/setup.js +3 -0
- package/dist/host/audit/log.d.ts +3 -2
- package/dist/host/audit/log.js +3 -3
- package/dist/host/channel/channel.d.ts +5 -1
- package/dist/host/channel/channel.js +12 -0
- package/dist/host/guard/agent-files.js +7 -1
- package/dist/host/guard/commands.js +13 -1
- package/dist/host/guard/git-config.js +27 -7
- package/dist/host/guard/git-dirs.js +78 -3
- package/dist/host/guard/mark-of-the-web.js +3 -1
- package/dist/host/guard/nested-repos.js +7 -2
- package/dist/host/guard/store.js +1 -0
- package/dist/host/guard/types.d.ts +2 -0
- package/dist/host/launch/mappings.js +11 -1
- package/dist/host/launch/project-scan.js +2 -21
- package/dist/host/launch/stage.js +4 -0
- package/dist/host/life/session-ledger.js +2 -1
- package/dist/host/remote/access-store.d.ts +156 -0
- package/dist/host/remote/access-store.js +308 -0
- package/dist/host/remote/cert.d.ts +20 -0
- package/dist/host/remote/cert.js +103 -0
- package/dist/host/remote/door-client.d.ts +13 -0
- package/dist/host/remote/door-client.js +54 -0
- package/dist/host/remote/firewall.d.ts +44 -0
- package/dist/host/remote/firewall.js +118 -0
- package/dist/host/remote/gate.d.ts +125 -0
- package/dist/host/remote/gate.js +391 -0
- package/dist/host/remote/index.d.ts +8 -0
- package/dist/host/remote/index.js +10 -0
- package/dist/host/remote/lan.d.ts +24 -0
- package/dist/host/remote/lan.js +88 -0
- package/dist/host/remote/tls-store.d.ts +22 -0
- package/dist/host/remote/tls-store.js +63 -0
- package/dist/host/remote/windows-helper.d.ts +67 -0
- package/dist/host/remote/windows-helper.js +129 -0
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +19 -4
- package/dist/host/storage/contained/state.d.ts +3 -2
- package/dist/host/storage/contained/state.js +5 -2
- package/dist/host/web/bridge.d.ts +5 -1
- package/dist/host/web/bridge.js +6 -2
- package/dist/host/web/browser.js +1 -1
- package/dist/host/web/control.d.ts +4 -1
- package/dist/host/web/control.js +28 -4
- package/dist/host/web/rpc.d.ts +16 -2
- package/dist/host/web/rpc.js +37 -4
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -0
- package/dist/host/web/server.d.ts +18 -2
- package/dist/host/web/server.js +97 -24
- package/dist/host/web/state.d.ts +5 -0
- package/dist/host/web/state.js +8 -0
- package/dist/runtime/agent.js +74 -3
- package/dist/runtime/daemon.js +205 -24
- package/dist/runtime/db.js +74 -3
- package/dist/runtime/gitguard.js +6 -0
- package/dist/runtime/main.js +77 -3
- package/dist/runtime/netproxy.js +74 -3
- package/dist/runtime/view.js +76 -3
- package/dist/selftest/app.test.js +41 -6
- package/dist/selftest/channel.test.js +16 -0
- package/dist/selftest/cmdline.test.js +16 -0
- package/dist/selftest/git-access.test.js +8 -0
- package/dist/selftest/hostile-input.test.js +12 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/layout.test.js +10 -1
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/menu-perf.test.js +198 -12
- package/dist/selftest/menu.test.js +4 -0
- package/dist/selftest/remote.test.d.ts +2 -0
- package/dist/selftest/remote.test.js +536 -0
- package/dist/selftest/repo-guard.test.js +56 -2
- package/dist/selftest/resume.test.js +2 -1
- package/dist/selftest/sharing.test.js +9 -2
- package/dist/selftest/support/fixtures.d.ts +13 -0
- package/dist/selftest/support/web-preview.js +63 -0
- package/dist/selftest/web.test.js +2 -0
- package/dist/shared/channel/schema.d.ts +23 -0
- package/dist/shared/channel/schema.js +8 -0
- package/dist/shared/pty/protocol.d.ts +2 -0
- package/dist/shared/pty/protocol.js +2 -0
- package/dist/web/index.html +1 -1
- package/dist/web/main-NBQ3LF4E.js +221 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Program.cs +17 -1
- package/native/csharp/HostTools/WebHelper.cs +499 -0
- package/native/scripts/get-network-profile.ps1 +19 -0
- package/native/scripts/set-folder-access.ps1 +11 -0
- package/native/scripts/set-web-firewall.ps1 +103 -0
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +20 -0
- package/schemas/config.schema.json +65 -0
- package/schemas/sandbox-config.schema.json +0 -18
- package/dist/web/main-UD4QCXZR.js +0 -219
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// `sandbox remote`'s line to the owner's web UI (docs/design/remote-access.md): signs in with the one-time token of
|
|
2
|
+
// Windows sign-in, then runs the shared core's queries and operations over the RPC socket, as the browser does. What
|
|
3
|
+
// the owner's server sends is checked against the kernel's schemas (anything else is dropped) and every text is shown
|
|
4
|
+
// through safeText by the menu. The owner's server checks the role of every call.
|
|
5
|
+
import http from 'node:http';
|
|
6
|
+
import { WebSocket } from 'ws';
|
|
7
|
+
import { fromWireError, ServerMessageSchema, } from '../../app/kernel/index.js';
|
|
8
|
+
import { fail } from '../../core/util/index.js';
|
|
9
|
+
import { CSRF_HEADER, CSRF_VALUE } from '../../shared/web/index.js';
|
|
10
|
+
const CONNECT_TIMEOUT_MS = 10_000;
|
|
11
|
+
/** Asks an OperationIO a question that came over the socket. */
|
|
12
|
+
function askIO(io, q) {
|
|
13
|
+
switch (q.kind) {
|
|
14
|
+
case 'confirm':
|
|
15
|
+
return io.confirm(q);
|
|
16
|
+
case 'choose':
|
|
17
|
+
return io.choose(q);
|
|
18
|
+
case 'checklist':
|
|
19
|
+
return io.checklist(q);
|
|
20
|
+
case 'text':
|
|
21
|
+
return io.text(q);
|
|
22
|
+
case 'secret':
|
|
23
|
+
return io.secret(q);
|
|
24
|
+
case 'path':
|
|
25
|
+
return io.path(q);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class RemoteClient {
|
|
29
|
+
socket;
|
|
30
|
+
pending = new Map();
|
|
31
|
+
next = 1;
|
|
32
|
+
constructor(socket) {
|
|
33
|
+
this.socket = socket;
|
|
34
|
+
socket.on('message', (data, binary) => {
|
|
35
|
+
if (binary)
|
|
36
|
+
return;
|
|
37
|
+
let value;
|
|
38
|
+
try {
|
|
39
|
+
value = JSON.parse(data.toString());
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const parsed = ServerMessageSchema.safeParse(value);
|
|
45
|
+
if (parsed.success)
|
|
46
|
+
this.receive(parsed.data);
|
|
47
|
+
});
|
|
48
|
+
socket.on('close', () => {
|
|
49
|
+
for (const p of this.pending.values())
|
|
50
|
+
p.reject(new Error('The connection to the web UI closed.'));
|
|
51
|
+
this.pending.clear();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Trades the one-time token for a sign-in cookie, then opens the RPC socket. */
|
|
55
|
+
static async connect(port, token) {
|
|
56
|
+
const origin = 'http://127.0.0.1:' + port;
|
|
57
|
+
const cookie = await new Promise((resolve, reject) => {
|
|
58
|
+
const body = JSON.stringify({ token });
|
|
59
|
+
const req = http.request({
|
|
60
|
+
host: '127.0.0.1',
|
|
61
|
+
port,
|
|
62
|
+
method: 'POST',
|
|
63
|
+
path: '/api/login/once',
|
|
64
|
+
timeout: CONNECT_TIMEOUT_MS,
|
|
65
|
+
headers: {
|
|
66
|
+
Origin: origin,
|
|
67
|
+
[CSRF_HEADER]: CSRF_VALUE,
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
'Content-Length': Buffer.byteLength(body),
|
|
70
|
+
},
|
|
71
|
+
}, (res) => {
|
|
72
|
+
res.resume();
|
|
73
|
+
const value = (res.headers['set-cookie'] ?? [])[0]?.split(';')[0];
|
|
74
|
+
if (res.statusCode === 200 && value)
|
|
75
|
+
resolve(value);
|
|
76
|
+
else
|
|
77
|
+
reject(new Error('The web UI did not sign you in (' + res.statusCode + ').'));
|
|
78
|
+
});
|
|
79
|
+
req.on('timeout', () => req.destroy(new Error('The web UI did not answer.')));
|
|
80
|
+
req.on('error', reject);
|
|
81
|
+
req.end(body);
|
|
82
|
+
});
|
|
83
|
+
const socket = new WebSocket('ws://127.0.0.1:' + port + '/api/rpc', {
|
|
84
|
+
headers: { Cookie: cookie, Origin: origin },
|
|
85
|
+
handshakeTimeout: CONNECT_TIMEOUT_MS,
|
|
86
|
+
});
|
|
87
|
+
await new Promise((resolve, reject) => {
|
|
88
|
+
socket.once('open', () => resolve());
|
|
89
|
+
socket.once('error', reject);
|
|
90
|
+
socket.once('unexpected-response', (_req, res) => reject(new Error('Refused (' + res.statusCode + ').')));
|
|
91
|
+
});
|
|
92
|
+
return new RemoteClient(socket);
|
|
93
|
+
}
|
|
94
|
+
receive(m) {
|
|
95
|
+
const p = this.pending.get(m.id);
|
|
96
|
+
if (!p)
|
|
97
|
+
return;
|
|
98
|
+
switch (m.t) {
|
|
99
|
+
case 'progress':
|
|
100
|
+
return p.io.progress(m.text);
|
|
101
|
+
case 'log':
|
|
102
|
+
return p.io.log(m.text, m.tone);
|
|
103
|
+
case 'ask':
|
|
104
|
+
void askIO(p.io, m.question).then((value) => this.send({ t: 'answer', id: m.id, value }), () => this.send({ t: 'answer', id: m.id, value: m.question.kind === 'confirm' ? false : undefined }));
|
|
105
|
+
return;
|
|
106
|
+
case 'result':
|
|
107
|
+
case 'data':
|
|
108
|
+
this.pending.delete(m.id);
|
|
109
|
+
return p.resolve(m.output);
|
|
110
|
+
case 'error':
|
|
111
|
+
this.pending.delete(m.id);
|
|
112
|
+
return p.reject(fromWireError(m.error));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
send(message) {
|
|
116
|
+
if (this.socket.readyState === WebSocket.OPEN)
|
|
117
|
+
this.socket.send(JSON.stringify(message));
|
|
118
|
+
}
|
|
119
|
+
/** Runs a definition on the owner's side (a query or an operation) and returns its output. */
|
|
120
|
+
run(id, input, io) {
|
|
121
|
+
if (this.socket.readyState !== WebSocket.OPEN)
|
|
122
|
+
return Promise.reject(new Error('Not connected to the web UI.'));
|
|
123
|
+
const callId = 'c' + this.next++;
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
this.pending.set(callId, { io, resolve, reject });
|
|
126
|
+
this.send({ t: 'call', id: callId, op: id, input: input ?? {} });
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
close() {
|
|
130
|
+
this.socket.close(1000);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Connects, or fails with the reason and what to do. */
|
|
134
|
+
export async function remoteClient(port, token) {
|
|
135
|
+
try {
|
|
136
|
+
return await RemoteClient.connect(port, token);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return fail('Could not reach the web UI on port ' + port + ': ' + (e instanceof Error ? e.message : String(e)), 'Ask its owner to start it (sandbox web start), then try again.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionStatus } from '../../core/types.js';
|
|
2
|
+
import type { ShellChoice } from '../../shared/channel/index.js';
|
|
2
3
|
import type { RegistryEntry } from '../../host/projects/index.js';
|
|
3
4
|
import type { Viewers } from '../../shared/pty/index.js';
|
|
4
5
|
import type { Context } from '../commands/context.js';
|
|
@@ -38,5 +39,9 @@ export declare class SessionsService {
|
|
|
38
39
|
restartScope(all: boolean): RestartScope;
|
|
39
40
|
/** Restarts the agents (one kind, or all) of a project or of every session; returns the sandbox's message. */
|
|
40
41
|
restart(harness: string | null, scope?: string): Promise<string>;
|
|
42
|
+
/** Starts a plain shell in a folder of the sandbox and waits until it runs. */
|
|
43
|
+
openShell(shell: ShellChoice, guestDir: string): Promise<SessionStatus>;
|
|
44
|
+
/** Ends a running session and everything it started; returns the sandbox's message (throws when it refused). */
|
|
45
|
+
close(sessionId: string): Promise<string>;
|
|
41
46
|
private projects;
|
|
42
47
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Sessions of the running sandbox (agents and plain shells): list, pick one to attach to, restart the agents, open a
|
|
2
|
+
// shell, close one. Returns data, never prints.
|
|
2
3
|
import _ from 'lodash';
|
|
4
|
+
import { fail } from '../../core/util/index.js';
|
|
3
5
|
import { SandboxChannel, sessionsFirstIn, sessionViewers } from '../../host/channel/index.js';
|
|
4
6
|
import { isSandboxRunning } from '../../host/sandbox/index.js';
|
|
5
7
|
import { RunDir } from '../../shared/channel/index.js';
|
|
6
8
|
import { isWithin, projectForGuestDir } from '../../shared/paths/index.js';
|
|
7
9
|
import { hereDir, locateHere, registryOf } from '../commands/projects/here.js';
|
|
10
|
+
/** A shell needs no install: the longest it may take is its project's first setup. */
|
|
11
|
+
const SHELL_START_MS = 15 * 60 * 1000;
|
|
8
12
|
export function viewerText(v) {
|
|
9
13
|
return v === undefined
|
|
10
14
|
? '-'
|
|
@@ -75,6 +79,18 @@ export class SessionsService {
|
|
|
75
79
|
async restart(harness, scope) {
|
|
76
80
|
return (await this.channel.restart(harness, scope)).message;
|
|
77
81
|
}
|
|
82
|
+
/** Starts a plain shell in a folder of the sandbox and waits until it runs. */
|
|
83
|
+
openShell(shell, guestDir) {
|
|
84
|
+
const channel = this.channel;
|
|
85
|
+
return channel.waitForSession(channel.requestShell(shell, guestDir), SHELL_START_MS, () => undefined);
|
|
86
|
+
}
|
|
87
|
+
/** Ends a running session and everything it started; returns the sandbox's message (throws when it refused). */
|
|
88
|
+
async close(sessionId) {
|
|
89
|
+
const answer = await this.channel.closeSession(sessionId);
|
|
90
|
+
if (!answer.ok)
|
|
91
|
+
fail(answer.message);
|
|
92
|
+
return answer.message;
|
|
93
|
+
}
|
|
78
94
|
projects() {
|
|
79
95
|
return registryOf(this.ctx).read()?.projects ?? [];
|
|
80
96
|
}
|
package/dist/cli/tui/app.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export declare class App {
|
|
|
13
13
|
status: () => string;
|
|
14
14
|
/** Awaited after an action changed something, before the screen loads what it shows again. */
|
|
15
15
|
afterAction: () => Promise<void>;
|
|
16
|
+
/** Settles when the facts pages load from are known (the first screen is drawn without waiting for it). */
|
|
17
|
+
ready: Promise<void>;
|
|
18
|
+
/** Told about every key (after it was handled and drawn): someone is looking at the screen. */
|
|
19
|
+
onKey: () => void;
|
|
16
20
|
/** The navigation on the left of every screen (none: no navigation column). */
|
|
17
21
|
nav: () => NavState | undefined;
|
|
18
22
|
/** The screens being shown, innermost last: a dialog is drawn over the one below it. */
|
package/dist/cli/tui/app.js
CHANGED
|
@@ -19,6 +19,10 @@ export class App {
|
|
|
19
19
|
status = () => '';
|
|
20
20
|
/** Awaited after an action changed something, before the screen loads what it shows again. */
|
|
21
21
|
afterAction = () => Promise.resolve();
|
|
22
|
+
/** Settles when the facts pages load from are known (the first screen is drawn without waiting for it). */
|
|
23
|
+
ready = Promise.resolve();
|
|
24
|
+
/** Told about every key (after it was handled and drawn): someone is looking at the screen. */
|
|
25
|
+
onKey = () => undefined;
|
|
22
26
|
/** The navigation on the left of every screen (none: no navigation column). */
|
|
23
27
|
nav = () => undefined;
|
|
24
28
|
/** The screens being shown, innermost last: a dialog is drawn over the one below it. */
|
|
@@ -67,6 +71,7 @@ export class App {
|
|
|
67
71
|
drawnAt = Date.now();
|
|
68
72
|
}
|
|
69
73
|
const step = await screen.handle(await this.terminal.key());
|
|
74
|
+
this.onKey();
|
|
70
75
|
if (step)
|
|
71
76
|
return step.done;
|
|
72
77
|
}
|
|
@@ -10,6 +10,12 @@ export interface Key {
|
|
|
10
10
|
export declare class ExitRequested extends Error {
|
|
11
11
|
constructor();
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* How long a lone ESC waits for the rest of an escape sequence before it counts as the Esc key. Node's default is
|
|
15
|
+
* 500 ms, which every Esc (back, close) waited for. Terminals send a key's whole sequence in one write, so a short wait
|
|
16
|
+
* is enough.
|
|
17
|
+
*/
|
|
18
|
+
export declare const ESCAPE_TIMEOUT_MS = 25;
|
|
13
19
|
/** Whether this terminal is known to take synchronized updates (AGENT_SANDBOX_SYNC_UPDATE=1 or 0 overrides). */
|
|
14
20
|
export declare function supportsSyncUpdate(env?: NodeJS.ProcessEnv): boolean;
|
|
15
21
|
export declare class Terminal {
|
package/dist/cli/tui/terminal.js
CHANGED
|
@@ -14,6 +14,12 @@ export class ExitRequested extends Error {
|
|
|
14
14
|
/** DEC 2026: the terminal shows a frame only when it is complete. Others ignore it, so only these are asked. */
|
|
15
15
|
const SYNC_BEGIN = '\x1b[?2026h';
|
|
16
16
|
const SYNC_END = '\x1b[?2026l';
|
|
17
|
+
/**
|
|
18
|
+
* How long a lone ESC waits for the rest of an escape sequence before it counts as the Esc key. Node's default is
|
|
19
|
+
* 500 ms, which every Esc (back, close) waited for. Terminals send a key's whole sequence in one write, so a short wait
|
|
20
|
+
* is enough.
|
|
21
|
+
*/
|
|
22
|
+
export const ESCAPE_TIMEOUT_MS = 25;
|
|
17
23
|
/** Whether this terminal is known to take synchronized updates (AGENT_SANDBOX_SYNC_UPDATE=1 or 0 overrides). */
|
|
18
24
|
export function supportsSyncUpdate(env = process.env) {
|
|
19
25
|
const forced = env.AGENT_SANDBOX_SYNC_UPDATE;
|
|
@@ -59,7 +65,8 @@ export class Terminal {
|
|
|
59
65
|
return;
|
|
60
66
|
this.active = true;
|
|
61
67
|
this.frame = [];
|
|
62
|
-
readline.
|
|
68
|
+
// The first call on a stream sets its decoder; later calls (and readline interfaces) keep it.
|
|
69
|
+
readline.emitKeypressEvents(this.stdin, { escapeCodeTimeout: ESCAPE_TIMEOUT_MS });
|
|
63
70
|
if (this.stdin.isTTY)
|
|
64
71
|
this.stdin.setRawMode(true);
|
|
65
72
|
this.stdin.on('keypress', this.onKeypress);
|
package/dist/cli/validate.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { DATABASES } from '../core/detect/index.js';
|
|
6
6
|
import { fail } from '../core/util/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import { AccountsService } from './services/accounts-service.js';
|
|
8
8
|
/** Edit distance, for "did you mean". */
|
|
9
9
|
export function distance(a, b) {
|
|
10
10
|
let row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
@@ -79,11 +79,15 @@ export function validateOptions(ctx) {
|
|
|
79
79
|
unknownValue('database', db, DATABASES, 'Repeat --database for several.');
|
|
80
80
|
}
|
|
81
81
|
if (o.account !== undefined) {
|
|
82
|
-
|
|
82
|
+
// The launched agent's saved logins (an agent that keeps none says so).
|
|
83
|
+
const agent = o.harness ?? ctx.loaded.config.defaultHarness;
|
|
84
|
+
const names = new AccountsService(ctx).list(agent).map((a) => a.name);
|
|
83
85
|
if (!names.includes(o.account))
|
|
84
86
|
unknownValue('account', o.account, names, names.length
|
|
85
|
-
? 'See them with: sandbox account list'
|
|
86
|
-
: 'None saved yet: sign in inside the sandbox, then run sandbox account save <name
|
|
87
|
+
? 'See them with: sandbox account list' + (agent === 'claude' ? '' : ' --agent ' + agent)
|
|
88
|
+
: 'None saved yet: sign in inside the sandbox, then run sandbox account save <name>' +
|
|
89
|
+
(agent === 'claude' ? '' : ' --agent ' + agent) +
|
|
90
|
+
'.');
|
|
87
91
|
}
|
|
88
92
|
for (const s of o.sessions) {
|
|
89
93
|
checkHarness(s.harness, harnesses);
|
|
@@ -95,7 +95,17 @@ export const DEFAULTS = {
|
|
|
95
95
|
sqlserver: { enabled: 'auto' },
|
|
96
96
|
},
|
|
97
97
|
attach: { enabled: true, reuseSession: true, auto: false, autoHide: false, confirmCtrlC: true },
|
|
98
|
-
web: {
|
|
98
|
+
web: {
|
|
99
|
+
enabled: false,
|
|
100
|
+
port: 47800,
|
|
101
|
+
openOnLaunch: false,
|
|
102
|
+
remote: {
|
|
103
|
+
windowsSignIn: false,
|
|
104
|
+
users: [],
|
|
105
|
+
rememberDays: 30,
|
|
106
|
+
lan: { enabled: false, port: 47801, address: '' },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
99
109
|
agentCommands: { enabled: false, agents: 'auto' },
|
|
100
110
|
secrets: { strip: true, allow: [], patterns: [], warnFiles: true },
|
|
101
111
|
personalization: { enabled: true },
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
|
-
import { fileExists, parseJsonc } from '../util/index.js';
|
|
4
|
+
import { fail, fileExists, parseJsonc } from '../util/index.js';
|
|
5
5
|
export const LOCAL_CONFIG_NAME = '.sandbox-config.json';
|
|
6
|
+
const MAX_LOCAL_CONFIG_BYTES = 1024 * 1024;
|
|
6
7
|
export function readLocalConfig(dir) {
|
|
7
8
|
const file = path.join(dir, LOCAL_CONFIG_NAME);
|
|
8
9
|
if (!fileExists(file))
|
|
9
10
|
return null;
|
|
10
|
-
|
|
11
|
+
// One handle for the size check and the read (the hash covers exactly the bytes parsed); a repo's file over 1 MB is
|
|
12
|
+
// refused, not loaded.
|
|
13
|
+
const fd = fs.openSync(file, 'r');
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
const size = fs.fstatSync(fd).size;
|
|
17
|
+
if (size > MAX_LOCAL_CONFIG_BYTES)
|
|
18
|
+
fail(file + ' is over 1 MB, so it is not read.', 'Make it smaller, or start with --no-local-config.');
|
|
19
|
+
raw = Buffer.alloc(size);
|
|
20
|
+
fs.readSync(fd, raw, 0, size, 0);
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
fs.closeSync(fd);
|
|
24
|
+
}
|
|
11
25
|
return {
|
|
12
26
|
path: file,
|
|
13
27
|
hash: crypto.createHash('sha256').update(raw).digest('hex'),
|
|
@@ -14,6 +14,11 @@ export function normalizeOverrides(data, dir, source) {
|
|
|
14
14
|
const missing = scripts?.filter((s) => !fileExists(s)) ?? [];
|
|
15
15
|
if (missing.length)
|
|
16
16
|
fail(source + ' is not valid:\n - scripts: file not found: ' + missing.join(', '));
|
|
17
|
+
// A repo ships this file: a script path outside the project (%USERPROFILE%\.ssh\id_ed25519, ..\..) would copy that
|
|
18
|
+
// file of this PC into the launch folder, which the sandbox reads.
|
|
19
|
+
const outside = scripts?.filter((s) => !insideProject(dir, s)) ?? [];
|
|
20
|
+
if (outside.length)
|
|
21
|
+
fail(source + ' is not valid:\n - scripts: only files inside the project can be used: ' + outside.join(', '));
|
|
17
22
|
const mappings = overrides.mappings?.map((m) => ({ ...m, host: expandPath(m.host, dir) }));
|
|
18
23
|
return { ...overrides, scripts, mappings };
|
|
19
24
|
}
|
|
@@ -62,6 +67,8 @@ export const OFF_IS_SAFER = [
|
|
|
62
67
|
'claudeProfile.enabled',
|
|
63
68
|
'claudeProfile.syncHistory',
|
|
64
69
|
'claudeProfile.syncClaudeMem',
|
|
70
|
+
// Maps this PC's Node, Git, nvm and agent folders into the sandbox.
|
|
71
|
+
'provisioning.copyFromHost',
|
|
65
72
|
];
|
|
66
73
|
export const GIT_STRICTNESS = { full: 0, readonly: 1, none: 2 };
|
|
67
74
|
export const NEVER_LOOSER = [...ON_IS_SAFER, ...OFF_IS_SAFER, 'provisioning.git.access'];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Where this package is installed and its version. This file is dist/core/package-info.js, two folders below
|
|
2
|
-
// package.json.
|
|
2
|
+
// package.json, or bundled into a chunk in dist/bin (scripts/bundle.mjs), also two folders below.
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { z } from 'zod';
|
|
4
|
-
import { fileExists } from '../util/index.js';
|
|
3
|
+
import { fileExists, readSmallFile } from '../util/index.js';
|
|
4
|
+
/** A version spec as version files hold it (20, lts/iron, 3.12.1, v18.*): anything else is ignored, never printed. */
|
|
5
|
+
const VERSION_SPEC = /^[\w./*~^-]{1,64}$/;
|
|
5
6
|
const firstLine = (file) => {
|
|
6
|
-
|
|
7
|
+
const text = fileExists(file) ? readSmallFile(file, 64 * 1024) : null;
|
|
8
|
+
if (text === null)
|
|
7
9
|
return null;
|
|
8
|
-
const line =
|
|
9
|
-
.readFileSync(file, 'utf8')
|
|
10
|
+
const line = text
|
|
10
11
|
.replace(/^\uFEFF/, '')
|
|
11
12
|
.split(/\r?\n/)
|
|
12
13
|
.map((l) => l.replace(/#.*$/, '').trim())
|
|
13
14
|
.find((l) => l !== '');
|
|
14
|
-
return line
|
|
15
|
+
return line !== undefined && VERSION_SPEC.test(line) ? line : null;
|
|
15
16
|
};
|
|
16
17
|
const VOLTA_PIN = 'package.json volta.node';
|
|
17
18
|
const PackageJsonSchema = z.object({
|
|
@@ -32,8 +33,8 @@ export function voltaNode(workspace) {
|
|
|
32
33
|
if (!fileExists(file))
|
|
33
34
|
return null;
|
|
34
35
|
try {
|
|
35
|
-
return (PackageJsonSchema.safeParse(JSON.parse(
|
|
36
|
-
null);
|
|
36
|
+
return (PackageJsonSchema.safeParse(JSON.parse((readSmallFile(file, 4 * 1024 * 1024) ?? '').replace(/^\uFEFF/, ''))).data
|
|
37
|
+
?.volta?.node ?? null);
|
|
37
38
|
}
|
|
38
39
|
catch {
|
|
39
40
|
return null;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* An environment variable a repo's .sandbox-config.json may not set: the git guard's and the sandbox's own (setting
|
|
4
|
+
* AGENT_GIT_ACCESS or GIT_CONFIG_COUNT would undo provisioning.git.access), interpreter hooks (NODE_OPTIONS reaches the
|
|
5
|
+
* git guard's node), proxies and API endpoints (ANTHROPIC_BASE_URL would send your API key to the repo's server).
|
|
6
|
+
*/
|
|
7
|
+
export declare function reservedEnvName(name: string): boolean;
|
|
2
8
|
export declare const ConfigSchema: z.ZodObject<{
|
|
3
9
|
$schema: z.ZodOptional<z.ZodString>;
|
|
4
10
|
defaultHarness: z.ZodString;
|
|
@@ -253,6 +259,22 @@ export declare const ConfigSchema: z.ZodObject<{
|
|
|
253
259
|
enabled: z.ZodBoolean;
|
|
254
260
|
port: z.ZodNumber;
|
|
255
261
|
openOnLaunch: z.ZodBoolean;
|
|
262
|
+
remote: z.ZodObject<{
|
|
263
|
+
windowsSignIn: z.ZodBoolean;
|
|
264
|
+
users: z.ZodArray<z.ZodObject<{
|
|
265
|
+
name: z.ZodString;
|
|
266
|
+
role: z.ZodEnum<{
|
|
267
|
+
view: "view";
|
|
268
|
+
control: "control";
|
|
269
|
+
}>;
|
|
270
|
+
}, z.core.$strict>>;
|
|
271
|
+
rememberDays: z.ZodNumber;
|
|
272
|
+
lan: z.ZodObject<{
|
|
273
|
+
enabled: z.ZodBoolean;
|
|
274
|
+
port: z.ZodNumber;
|
|
275
|
+
address: z.ZodString;
|
|
276
|
+
}, z.core.$strict>;
|
|
277
|
+
}, z.core.$strict>;
|
|
256
278
|
}, z.core.$strict>;
|
|
257
279
|
agentCommands: z.ZodObject<{
|
|
258
280
|
enabled: z.ZodBoolean;
|
|
@@ -367,9 +389,6 @@ export declare const LocalOverridesSchema: z.ZodObject<{
|
|
|
367
389
|
scripts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
368
390
|
sandbox: z.ZodOptional<z.ZodObject<{
|
|
369
391
|
memoryMb: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodNumber]>>;
|
|
370
|
-
maxMemoryMb: z.ZodOptional<z.ZodNumber>;
|
|
371
|
-
maxMemoryPercent: z.ZodOptional<z.ZodNumber>;
|
|
372
|
-
reserveHostMb: z.ZodOptional<z.ZodNumber>;
|
|
373
392
|
vGpu: z.ZodOptional<z.ZodBoolean>;
|
|
374
393
|
networking: z.ZodOptional<z.ZodBoolean>;
|
|
375
394
|
clipboard: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -14,6 +14,39 @@ import { ResumeSchema } from './resume.js';
|
|
|
14
14
|
import { WebSchema } from './web.js';
|
|
15
15
|
import { VpnSchema } from './vpn.js';
|
|
16
16
|
import { GithubSchema } from './github.js';
|
|
17
|
+
const RESERVED_ENV_PREFIX = /^(AGENT_|GIT_|GH_|GITHUB_|NODE_|NPM_CONFIG_(USERCONFIG|GLOBALCONFIG|PREFIX|SCRIPT_SHELL)|SSL_|CLAUDE|ANTHROPIC_|OPENAI_|CODEX_|GEMINI_|GOOGLE_|COPILOT_|AWS_|AZURE_)/i;
|
|
18
|
+
const RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
|
|
19
|
+
const RESERVED_ENV_NAMES = new Set([
|
|
20
|
+
'PATH',
|
|
21
|
+
'PATHEXT',
|
|
22
|
+
'BASH_ENV',
|
|
23
|
+
'ENV',
|
|
24
|
+
'COMSPEC',
|
|
25
|
+
'PSMODULEPATH',
|
|
26
|
+
'PYTHONPATH',
|
|
27
|
+
'PYTHONSTARTUP',
|
|
28
|
+
'PERL5OPT',
|
|
29
|
+
'RUBYOPT',
|
|
30
|
+
'JAVA_TOOL_OPTIONS',
|
|
31
|
+
'LD_PRELOAD',
|
|
32
|
+
'SYSTEMROOT',
|
|
33
|
+
'WINDIR',
|
|
34
|
+
'HOME',
|
|
35
|
+
'USERPROFILE',
|
|
36
|
+
'APPDATA',
|
|
37
|
+
'LOCALAPPDATA',
|
|
38
|
+
'PROGRAMDATA',
|
|
39
|
+
'TEMP',
|
|
40
|
+
'TMP',
|
|
41
|
+
].map((n) => n.toUpperCase()));
|
|
42
|
+
/**
|
|
43
|
+
* An environment variable a repo's .sandbox-config.json may not set: the git guard's and the sandbox's own (setting
|
|
44
|
+
* AGENT_GIT_ACCESS or GIT_CONFIG_COUNT would undo provisioning.git.access), interpreter hooks (NODE_OPTIONS reaches the
|
|
45
|
+
* git guard's node), proxies and API endpoints (ANTHROPIC_BASE_URL would send your API key to the repo's server).
|
|
46
|
+
*/
|
|
47
|
+
export function reservedEnvName(name) {
|
|
48
|
+
return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
|
|
49
|
+
}
|
|
17
50
|
export const ConfigSchema = z
|
|
18
51
|
.strictObject({
|
|
19
52
|
$schema: SchemaRefSchema.optional(),
|
|
@@ -88,10 +121,14 @@ export const LocalOverridesSchema = z
|
|
|
88
121
|
.array(MappingSchema)
|
|
89
122
|
.describe('Extra folders (relative paths are relative to this file), e.g. a read-only data folder at another place in the sandbox. Folders outside the project are used only as your projects.allowOutsideMappings setting allows (by default you are asked once).'),
|
|
90
123
|
scripts: StringsSchema.describe('Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent.'),
|
|
91
|
-
// This PC's resources are yours to share: CPU
|
|
124
|
+
// This PC's resources are yours to share: CPU, paging file (this PC's disk) and the RAM kept for this PC are not a
|
|
125
|
+
// project's to set.
|
|
92
126
|
sandbox: SandboxSchema.omit({
|
|
93
127
|
cpuLimit: true,
|
|
94
128
|
pageFileMb: true,
|
|
129
|
+
reserveHostMb: true,
|
|
130
|
+
maxMemoryPercent: true,
|
|
131
|
+
maxMemoryMb: true,
|
|
95
132
|
engine: true,
|
|
96
133
|
agentMemoryHeadroomMb: true,
|
|
97
134
|
pressureResponse: true,
|
|
@@ -108,7 +145,7 @@ export const LocalOverridesSchema = z
|
|
|
108
145
|
toolchains: z
|
|
109
146
|
.strictObject({
|
|
110
147
|
..._.mapValues(_.omit(ToolchainsSchema.shape, 'environment'), (shape) => shape.partial()),
|
|
111
|
-
environment: ToolchainsSchema.shape.environment,
|
|
148
|
+
environment: ToolchainsSchema.shape.environment.refine((env) => !Object.keys(env).some(reservedEnvName), 'names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project'),
|
|
112
149
|
})
|
|
113
150
|
.partial(),
|
|
114
151
|
databases: z.strictObject(_.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
|
|
@@ -1,7 +1,58 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const WEB_DEFAULT_PORT = 47800;
|
|
3
|
+
export declare const WEB_LAN_DEFAULT_PORT = 47801;
|
|
4
|
+
export declare const REMOTE_ROLES: readonly ["view", "control"];
|
|
5
|
+
export type RemoteRole = (typeof REMOTE_ROLES)[number];
|
|
6
|
+
/** A Windows account name: "bob", "PC\bob" or "DOMAIN\bob" (no paths, quotes or wildcards). */
|
|
7
|
+
export declare const WindowsUserNameSchema: z.ZodString;
|
|
8
|
+
export declare const RemoteUserSchema: z.ZodObject<{
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
role: z.ZodEnum<{
|
|
11
|
+
view: "view";
|
|
12
|
+
control: "control";
|
|
13
|
+
}>;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
export type RemoteUser = z.output<typeof RemoteUserSchema>;
|
|
16
|
+
export declare const WebLanSchema: z.ZodObject<{
|
|
17
|
+
enabled: z.ZodBoolean;
|
|
18
|
+
port: z.ZodNumber;
|
|
19
|
+
address: z.ZodString;
|
|
20
|
+
}, z.core.$strict>;
|
|
21
|
+
export declare const WebRemoteSchema: z.ZodObject<{
|
|
22
|
+
windowsSignIn: z.ZodBoolean;
|
|
23
|
+
users: z.ZodArray<z.ZodObject<{
|
|
24
|
+
name: z.ZodString;
|
|
25
|
+
role: z.ZodEnum<{
|
|
26
|
+
view: "view";
|
|
27
|
+
control: "control";
|
|
28
|
+
}>;
|
|
29
|
+
}, z.core.$strict>>;
|
|
30
|
+
rememberDays: z.ZodNumber;
|
|
31
|
+
lan: z.ZodObject<{
|
|
32
|
+
enabled: z.ZodBoolean;
|
|
33
|
+
port: z.ZodNumber;
|
|
34
|
+
address: z.ZodString;
|
|
35
|
+
}, z.core.$strict>;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
export type WebRemote = z.output<typeof WebRemoteSchema>;
|
|
3
38
|
export declare const WebSchema: z.ZodObject<{
|
|
4
39
|
enabled: z.ZodBoolean;
|
|
5
40
|
port: z.ZodNumber;
|
|
6
41
|
openOnLaunch: z.ZodBoolean;
|
|
42
|
+
remote: z.ZodObject<{
|
|
43
|
+
windowsSignIn: z.ZodBoolean;
|
|
44
|
+
users: z.ZodArray<z.ZodObject<{
|
|
45
|
+
name: z.ZodString;
|
|
46
|
+
role: z.ZodEnum<{
|
|
47
|
+
view: "view";
|
|
48
|
+
control: "control";
|
|
49
|
+
}>;
|
|
50
|
+
}, z.core.$strict>>;
|
|
51
|
+
rememberDays: z.ZodNumber;
|
|
52
|
+
lan: z.ZodObject<{
|
|
53
|
+
enabled: z.ZodBoolean;
|
|
54
|
+
port: z.ZodNumber;
|
|
55
|
+
address: z.ZodString;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
}, z.core.$strict>;
|
|
7
58
|
}, z.core.$strict>;
|
package/dist/core/schema/web.js
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
|
-
// The web UI settings (sandbox web): not part of LocalOverrides, a repo never turns a way into your agents on.
|
|
1
|
+
// The web UI settings (sandbox web): not part of LocalOverrides, a repo never turns a way into your agents on. The
|
|
2
|
+
// remote group (other Windows users of this PC, devices on your network) is off by default: design/remote-access.md.
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
export const WEB_DEFAULT_PORT = 47800;
|
|
5
|
+
export const WEB_LAN_DEFAULT_PORT = 47801;
|
|
6
|
+
export const REMOTE_ROLES = ['view', 'control'];
|
|
7
|
+
/** A Windows account name: "bob", "PC\bob" or "DOMAIN\bob" (no paths, quotes or wildcards). */
|
|
8
|
+
export const WindowsUserNameSchema = z
|
|
9
|
+
.string()
|
|
10
|
+
.trim()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(120)
|
|
13
|
+
.regex(/^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/, 'a Windows user name (name or DOMAIN\\name)');
|
|
14
|
+
export const RemoteUserSchema = z.strictObject({
|
|
15
|
+
name: WindowsUserNameSchema.describe('The Windows user (name, or DOMAIN\\name)'),
|
|
16
|
+
role: z
|
|
17
|
+
.enum(REMOTE_ROLES)
|
|
18
|
+
.describe('view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.'),
|
|
19
|
+
});
|
|
20
|
+
export const WebLanSchema = z.strictObject({
|
|
21
|
+
enabled: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.describe('(Beta) Devices on your network (a phone): an HTTPS address on this PC for devices you pair with a one-time code (sandbox web pair). Needs a firewall rule (sandbox web lan on asks Windows once). Off: every paired device is signed out.'),
|
|
24
|
+
port: z.number().int().min(1024).max(65535).describe('HTTPS port for paired devices on your network.'),
|
|
25
|
+
address: z
|
|
26
|
+
.string()
|
|
27
|
+
.trim()
|
|
28
|
+
.max(15)
|
|
29
|
+
.regex(/^$|^\d{1,3}(\.\d{1,3}){3}$/, 'an IPv4 address or empty')
|
|
30
|
+
.describe("This PC's private address to listen on. Empty: the one of its network adapter (never a public one)."),
|
|
31
|
+
});
|
|
32
|
+
export const WebRemoteSchema = z.strictObject({
|
|
33
|
+
windowsSignIn: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.describe('(Beta) Other Windows users of this PC named in web.remote.users sign in to your web UI (and sandbox remote) with their own Windows account: no password, no link.'),
|
|
36
|
+
users: z
|
|
37
|
+
.array(RemoteUserSchema)
|
|
38
|
+
.max(32)
|
|
39
|
+
.describe('Who else may sign in with Windows, and their role (view or control).'),
|
|
40
|
+
rememberDays: z
|
|
41
|
+
.number()
|
|
42
|
+
.int()
|
|
43
|
+
.min(1)
|
|
44
|
+
.max(90)
|
|
45
|
+
.describe('How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed).'),
|
|
46
|
+
lan: WebLanSchema,
|
|
47
|
+
});
|
|
4
48
|
export const WebSchema = z.strictObject({
|
|
5
49
|
enabled: z
|
|
6
50
|
.boolean()
|
|
@@ -14,4 +58,5 @@ export const WebSchema = z.strictObject({
|
|
|
14
58
|
openOnLaunch: z
|
|
15
59
|
.boolean()
|
|
16
60
|
.describe('(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.'),
|
|
61
|
+
remote: WebRemoteSchema.describe('(Beta) Access for other Windows users of this PC and for paired devices.'),
|
|
17
62
|
});
|
package/dist/core/util/fs.d.ts
CHANGED
|
@@ -3,6 +3,11 @@ export declare function dirExists(p: string): boolean;
|
|
|
3
3
|
export declare function fileExists(p: string): boolean;
|
|
4
4
|
/** fileExists without blocking the program (the menu looks for programs while it stays responsive). */
|
|
5
5
|
export declare function fileExistsAsync(p: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* A file the sandbox or a repo may have written, read through one handle and only if that handle is a regular file of
|
|
8
|
+
* at most `maxBytes` (null otherwise): a multi-gigabyte .nvmrc must not stop a launch.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readSmallFile(file: string, maxBytes?: number): string | null;
|
|
6
11
|
export declare function readJson<T>(p: string): T;
|
|
7
12
|
export declare function tryReadJson<T>(p: string): T | null;
|
|
8
13
|
/** Atomic (a crash or a second command never leaves half a file); refused outside temp during the self-test. */
|