windows-development-sandbox 1.1.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/config.example.json +38 -5
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +3 -1
- package/dist/cli/commands/accounts.js +2 -2
- package/dist/cli/commands/agent-commands.js +1 -1
- package/dist/cli/commands/context.js +2 -1
- package/dist/cli/commands/data/clean.js +6 -1
- package/dist/cli/commands/data/collect.js +3 -3
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch.js +36 -3
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +35 -2
- package/dist/cli/commands/sessions.js +3 -3
- package/dist/cli/commands/user-config.js +1 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/manifest.js +5 -0
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/{menu-multi.d.ts → menu/several.d.ts} +10 -2
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/core/config.js +9 -6
- package/dist/core/local-config.d.ts +15 -0
- package/dist/core/local-config.js +63 -4
- package/dist/core/schema/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -0
- package/dist/core/schema/config.d.ts +66 -1
- package/dist/core/schema/config.js +18 -6
- package/dist/core/schema/profile.d.ts +5 -0
- package/dist/core/schema/profile.js +4 -2
- package/dist/core/schema/sandbox.d.ts +28 -0
- package/dist/core/schema/sandbox.js +28 -0
- package/dist/core/schema/toolchains.d.ts +3 -0
- package/dist/core/schema/toolchains.js +6 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/core/util.d.ts +2 -0
- package/dist/core/util.js +2 -0
- package/dist/core/workspace.js +22 -5
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -0
- package/dist/guest/daemon/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -0
- package/dist/guest/daemon.js +4 -0
- package/dist/guest/env.d.ts +1 -0
- package/dist/guest/env.js +3 -1
- package/dist/guest/helpers.d.ts +1 -1
- package/dist/guest/main.js +15 -2
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -0
- package/dist/guest/ptyhost.js +14 -4
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +48 -2
- package/dist/guest/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- package/dist/guest/toolchains/native/mesa.js +13 -3
- package/dist/guest/toolchains/plan.d.ts +3 -1
- package/dist/guest/toolchains/plan.js +53 -9
- package/dist/guest/toolchains/python.js +26 -2
- package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- package/dist/host/attach.js +2 -1
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -0
- package/dist/host/generate.d.ts +6 -0
- package/dist/host/generate.js +10 -0
- package/dist/host/launch/mappings.js +5 -1
- package/dist/host/launch/stage.js +1 -1
- package/dist/host/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -0
- package/dist/host/platform.js +3 -0
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- package/dist/host/repo-guard.d.ts +10 -3
- package/dist/host/repo-guard.js +142 -14
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +45 -1
- package/dist/host/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/runtime/daemon.js +1319 -552
- package/dist/runtime/db.js +168 -13
- package/dist/runtime/main.js +2898 -608
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +272 -23
- package/dist/selftest/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.js +7 -0
- package/dist/selftest/cli.test.js +47 -17
- package/dist/selftest/fixtures.d.ts +45 -0
- package/dist/selftest/fixtures.js +3 -1
- package/dist/selftest/generate.test.js +7 -2
- package/dist/selftest/index.js +5 -2
- package/dist/selftest/live-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -0
- package/dist/selftest/local-config.test.js +16 -3
- package/dist/selftest/network.test.js +12 -0
- package/dist/selftest/projects.test.js +1 -1
- package/dist/selftest/repo-guard.test.js +59 -1
- package/dist/selftest/sync.test.js +50 -10
- package/dist/selftest/toolchains.test.js +30 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- package/dist/shared/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +2 -0
- package/dist/shared/rundir.js +2 -0
- package/dist/shared/schema.d.ts +10 -0
- package/dist/shared/schema.js +2 -0
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/sync/claude-mem.js +3 -2
- package/dist/sync/history.d.ts +6 -0
- package/dist/sync/history.js +26 -11
- package/dist/sync/watcher.js +78 -6
- package/package.json +2 -1
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +4 -35
- package/sandbox/logon.cmd +6 -0
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +105 -7
- package/schemas/config.schema.json +303 -2
- package/schemas/sandbox-config.schema.json +126 -12
- package/dist/cli/menu-multi.js +0 -82
- package/dist/cli/menu.d.ts +0 -14
- package/dist/cli/menu.js +0 -290
- package/dist/cli/settings/edit.d.ts +0 -13
- package/dist/cli/settings/edit.js +0 -19
- package/dist/cli/settings/index.d.ts +0 -8
- package/dist/cli/settings/index.js +0 -131
- package/dist/cli/settings/prompts.d.ts +0 -7
- package/dist/cli/settings/prompts.js +0 -48
- package/dist/cli/settings/schema.d.ts +0 -33
- package/dist/cli/settings/schema.js +0 -29
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ works too. [Other ways to install](docs/install.md)
|
|
|
31
31
|
```powershell
|
|
32
32
|
cd C:\code\my-app
|
|
33
33
|
|
|
34
|
-
sandbox # menu: start
|
|
34
|
+
sandbox # the menu: start agents, sessions, apps, every setting, diagnostics ...
|
|
35
35
|
sandbox claude # Claude Code in a sandbox for this folder (or: claude-sandbox)
|
|
36
36
|
sandbox claude -t # ... in this terminal (Ctrl+] detaches, the session keeps running)
|
|
37
37
|
sandbox start # just the sandbox, no agent
|
|
@@ -49,6 +49,7 @@ The first start downloads what the project needs (a few minutes, once). After th
|
|
|
49
49
|
|---|---|
|
|
50
50
|
| **Safe by default** | The agent sees only your project folder. Credentials are stripped before anything is copied in, your LAN is blocked, and nothing the sandbox writes can run on your PC. |
|
|
51
51
|
| **Languages** | Node, Python, Rust, C/C++ (GCC or MSVC), .NET (incl. .NET Framework targeting), Go, Java, Ruby, PHP, Deno, Bun, PowerShell 7, with pnpm, yarn, Volta, uv, pyenv, Maven, Gradle, Composer, gh ... |
|
|
52
|
+
| **Apps and tools** | VS Code (set up for your languages, with their extensions) and Windows Terminal, plus 60 optional apps and tools by category: DBeaver, pgAdmin, Bruno, Godot, Blender, kubectl, jq, ripgrep ... and programs of your own |
|
|
52
53
|
| **Databases** | PostgreSQL, MySQL, MongoDB, Redis and DynamoDB Local with a web UI, started when the project uses them, or any time with `--database` / `sandbox db` |
|
|
53
54
|
| **Agents** | Claude Code, Codex, Gemini, opencode, Qwen, Crush, Copilot, Kilo, Amp, Cline, Continue, Kimi, Aider, DeepSeek agents |
|
|
54
55
|
| **Smart** | Detects what the project needs from its files, respects version files, installs in parallel and reuses everything |
|
|
@@ -60,7 +61,7 @@ The first start downloads what the project needs (a few minutes, once). After th
|
|
|
60
61
|
|
|
61
62
|
| Command | |
|
|
62
63
|
|---|---|
|
|
63
|
-
| `sandbox` | the
|
|
64
|
+
| `sandbox` | the menu, a full-screen app: start agents, attach to sessions, pick apps, edit every setting (yours and the project's), all commands, with search |
|
|
64
65
|
| `sandbox <agent>`, `claude-sandbox` | start an agent; `-t` for this terminal, `--headless` for no window |
|
|
65
66
|
| `sandbox start` | the sandbox without an agent |
|
|
66
67
|
| `sandbox attach` / `sessions` / `status` | work with a running sandbox (this folder's project first) |
|
package/config.example.json
CHANGED
|
@@ -3,12 +3,25 @@
|
|
|
3
3
|
"defaultHarness": "claude",
|
|
4
4
|
"guestWorkspace": "auto",
|
|
5
5
|
"readOnly": false,
|
|
6
|
+
"workspaceFilter": {
|
|
7
|
+
"enabled": false,
|
|
8
|
+
"exclude": [],
|
|
9
|
+
"include": []
|
|
10
|
+
},
|
|
11
|
+
"audit": {
|
|
12
|
+
"enabled": false,
|
|
13
|
+
"days": 14,
|
|
14
|
+
"maxMb": 100,
|
|
15
|
+
"processes": true,
|
|
16
|
+
"files": true
|
|
17
|
+
},
|
|
6
18
|
"mappings": [],
|
|
7
19
|
"sandbox": {
|
|
8
20
|
"memoryMb": "auto",
|
|
9
21
|
"maxMemoryMb": 32768,
|
|
10
22
|
"maxMemoryPercent": 80,
|
|
11
23
|
"cpuLimit": 80,
|
|
24
|
+
"pageFileMb": 16384,
|
|
12
25
|
"reserveHostMb": 8192,
|
|
13
26
|
"vGpu": false,
|
|
14
27
|
"networking": true,
|
|
@@ -17,15 +30,23 @@
|
|
|
17
30
|
"videoInput": false,
|
|
18
31
|
"printerRedirection": false,
|
|
19
32
|
"protectedClient": true,
|
|
20
|
-
"headless": false
|
|
33
|
+
"headless": false,
|
|
34
|
+
"keepAwake": true
|
|
21
35
|
},
|
|
22
36
|
"storage": {
|
|
23
37
|
"root": "%LOCALAPPDATA%\\AgentSandbox",
|
|
24
38
|
"persistAgentState": true,
|
|
25
|
-
"dataName": "default"
|
|
39
|
+
"dataName": "default",
|
|
40
|
+
"markSandboxFiles": true,
|
|
41
|
+
"warnDataGb": 50
|
|
26
42
|
},
|
|
27
43
|
"network": {
|
|
28
|
-
"blockPrivateRanges": true
|
|
44
|
+
"blockPrivateRanges": true,
|
|
45
|
+
"allowOnly": [],
|
|
46
|
+
"blockHosts": [],
|
|
47
|
+
"forwardPorts": [],
|
|
48
|
+
"dns": [],
|
|
49
|
+
"proxy": ""
|
|
29
50
|
},
|
|
30
51
|
"provisioning": {
|
|
31
52
|
"copyFromHost": true,
|
|
@@ -37,6 +58,9 @@
|
|
|
37
58
|
},
|
|
38
59
|
"terminal": {
|
|
39
60
|
"enabled": true
|
|
61
|
+
},
|
|
62
|
+
"conpty": {
|
|
63
|
+
"enabled": true
|
|
40
64
|
}
|
|
41
65
|
},
|
|
42
66
|
"downloads": {
|
|
@@ -135,7 +159,10 @@
|
|
|
135
159
|
"vulkan": false
|
|
136
160
|
},
|
|
137
161
|
"vscode": {
|
|
138
|
-
"enabled": true
|
|
162
|
+
"enabled": true,
|
|
163
|
+
"setup": true,
|
|
164
|
+
"recommended": true,
|
|
165
|
+
"extensions": []
|
|
139
166
|
},
|
|
140
167
|
"environment": {}
|
|
141
168
|
},
|
|
@@ -188,6 +215,10 @@
|
|
|
188
215
|
"personalization": {
|
|
189
216
|
"enabled": true
|
|
190
217
|
},
|
|
218
|
+
"apps": {
|
|
219
|
+
"install": [],
|
|
220
|
+
"custom": {}
|
|
221
|
+
},
|
|
191
222
|
"claudeProfile": {
|
|
192
223
|
"enabled": true,
|
|
193
224
|
"hostDir": "auto",
|
|
@@ -203,7 +234,9 @@
|
|
|
203
234
|
"copyCredentials": false,
|
|
204
235
|
"overwrite": true,
|
|
205
236
|
"syncHistory": true,
|
|
206
|
-
"
|
|
237
|
+
"historyScope": "project",
|
|
238
|
+
"syncClaudeMem": true,
|
|
239
|
+
"syncClaudeMemBack": false
|
|
207
240
|
},
|
|
208
241
|
"harnesses": {
|
|
209
242
|
"claude": {
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -29,6 +29,9 @@ export interface CliOptions {
|
|
|
29
29
|
all: boolean;
|
|
30
30
|
/** launch --session <agent>@<folder> (repeatable): several sessions at once, folders made absolute. */
|
|
31
31
|
sessions: SessionWant[];
|
|
32
|
+
/** audit: only events of this kind, and containing this text. */
|
|
33
|
+
kind?: string;
|
|
34
|
+
grep?: string;
|
|
32
35
|
}
|
|
33
36
|
/** One session to start: an agent in a folder, with arguments of its own (after the ones given after --). */
|
|
34
37
|
export interface SessionWant {
|
package/dist/cli/args.js
CHANGED
|
@@ -134,7 +134,9 @@ export const parseCli = (argv, wrapper = false) => {
|
|
|
134
134
|
all: Boolean(o.all),
|
|
135
135
|
sessions: (o.session ?? []).map((s) => parseSessionWant(s)),
|
|
136
136
|
timeout: o.timeout ?? 600,
|
|
137
|
-
days: o.days ?? 7,
|
|
137
|
+
days: o.days ?? (name === 'audit' ? 1 : 7),
|
|
138
|
+
kind: o.kind,
|
|
139
|
+
grep: o.grep,
|
|
138
140
|
};
|
|
139
141
|
};
|
|
140
142
|
const defaults = () => ({
|
|
@@ -3,7 +3,7 @@ import { activateAccount, listAccounts, removeAccount, saveAccount, setDefaultAc
|
|
|
3
3
|
import { restartSessions, runningSessions } from '../../host/channel.js';
|
|
4
4
|
import { hostClaudeDir, hostGlobalStateFile } from '../../host/claude-profile.js';
|
|
5
5
|
import { isSandboxRunning } from '../../host/platform.js';
|
|
6
|
-
import { fail } from '../../core/util.js';
|
|
6
|
+
import { fail, safeText } from '../../core/util.js';
|
|
7
7
|
import { printJson, sandboxAlive, table } from './context.js';
|
|
8
8
|
const label = (a) => '"' + a.name + '"' + (a.email ? ' (' + a.email + ')' : '');
|
|
9
9
|
/** This PC's Claude login location (config folder + global state file). */
|
|
@@ -27,7 +27,7 @@ const use = async (ctx, name) => {
|
|
|
27
27
|
console.log('Sandbox now uses ' + label(activateAccount(ctx.storageRoot, ctx.dataPath, name)) + '.');
|
|
28
28
|
const claudeRunning = isSandboxRunning() && runningSessions(ctx.dataPath).some((s) => s.harness === 'claude');
|
|
29
29
|
if (claudeRunning && !ctx.options.noRestart)
|
|
30
|
-
console.log((await restartSessions(ctx.dataPath, 'claude', sandboxAlive)).message + ' with --continue; the conversation carries on under the new account.');
|
|
30
|
+
console.log(safeText((await restartSessions(ctx.dataPath, 'claude', sandboxAlive)).message) + ' with --continue; the conversation carries on under the new account.');
|
|
31
31
|
return 0;
|
|
32
32
|
};
|
|
33
33
|
export const account = async (ctx) => {
|
|
@@ -7,7 +7,7 @@ import { agentCommands, commandsDir, reconcileAgentCommands } from '../../host/a
|
|
|
7
7
|
import { insideSandbox } from '../../host/platform.js';
|
|
8
8
|
import { writeFileAtomic } from '../../shared/rundir.js';
|
|
9
9
|
import { c } from '../colors.js';
|
|
10
|
-
import { editConfigText } from '../settings/
|
|
10
|
+
import { editConfigText } from '../menu/settings/config-file.js';
|
|
11
11
|
import { table } from './context.js';
|
|
12
12
|
const entryOf = (ctx) => path.join(ctx.packageRoot, 'dist', 'bin', 'sandbox.js');
|
|
13
13
|
/** Brings this PC in line with agentCommands and says what changed (used after on/off, settings and launch). */
|
|
@@ -36,8 +36,9 @@ export const table = (rows) => {
|
|
|
36
36
|
};
|
|
37
37
|
/** Prints `value` as JSON when --json was given; returns whether it did. */
|
|
38
38
|
export const printJson = (ctx, value) => {
|
|
39
|
+
// JSON.stringify leaves C1 controls (U+0080-U+009F) as they are; text from the sandbox must not reach a terminal raw.
|
|
39
40
|
if (ctx.options.json)
|
|
40
|
-
console.log(JSON.stringify(value, null, 2));
|
|
41
|
+
console.log(JSON.stringify(value, null, 2).replace(/[\u0080-\u009f]/g, (ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0')));
|
|
41
42
|
return ctx.options.json;
|
|
42
43
|
};
|
|
43
44
|
export const sandboxAlive = () => isSandboxRunning();
|
|
@@ -17,7 +17,7 @@ const ALL_WARNING = 'removes EVERYTHING the sandbox keeps: toolchains, databases
|
|
|
17
17
|
* not a plain relative target inside the data folder: absolute paths, drive letters, `..`, wildcards, unknown areas.
|
|
18
18
|
*/
|
|
19
19
|
export const resolveCleanTargets = (inputs, dataPath, storageRoot) => {
|
|
20
|
-
const usage = 'Targets: all | tools | tools/<name> | tools/agents/<name> | databases | databases/<name> | state | state/<agent> | logs | launches. See them with: sandbox data';
|
|
20
|
+
const usage = 'Targets: all | tools | tools/<name> | tools/agents/<name> | databases | databases/<name> | state | state/<agent> | logs | launches | views. See them with: sandbox data';
|
|
21
21
|
if (!inputs.length)
|
|
22
22
|
fail('Say what to remove.', usage);
|
|
23
23
|
const resolved = inputs.map((raw) => {
|
|
@@ -39,6 +39,11 @@ export const resolveCleanTargets = (inputs, dataPath, storageRoot) => {
|
|
|
39
39
|
return { target: 'all', path: dataPath, root: path.join(storageRoot, 'data'), warning: ALL_WARNING };
|
|
40
40
|
if (area === 'launches')
|
|
41
41
|
return { target, path: path.join(storageRoot, 'launches', ...rest), root: storageRoot };
|
|
42
|
+
if (area === 'views') {
|
|
43
|
+
if (rest.length)
|
|
44
|
+
fail('Remove all views ("views"), not single ones.', usage);
|
|
45
|
+
return { target, path: path.join(storageRoot, 'views'), root: storageRoot, warning: 'Views are the copies of your projects without their git-ignored files (workspaceFilter). Your projects are not touched; what the sandbox built or installed in them (node_modules, build output) goes, and the next start copies the projects again.' };
|
|
46
|
+
}
|
|
42
47
|
if (area === 'accounts' || area === 'sync')
|
|
43
48
|
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);
|
|
44
49
|
if (!DATA_AREAS.includes(area))
|
|
@@ -35,7 +35,7 @@ export const collectDataEntries = (dataPath, storageRoot) => {
|
|
|
35
35
|
});
|
|
36
36
|
const otherData = entriesOf(path.join(storageRoot, 'data')).filter((d) => d.isDirectory() && path.join(storageRoot, 'data', d.name).toLowerCase() !== path.resolve(dataPath).toLowerCase());
|
|
37
37
|
const host = [
|
|
38
|
-
...['launches', 'accounts', 'sync', 'logs', 'run', 'host'].map((name) => ({ area: 'host', name, path: path.join(storageRoot, name), bytes: sizeOf(path.join(storageRoot, name)) })),
|
|
38
|
+
...['launches', 'views', 'accounts', 'sync', 'logs', 'run', 'host'].map((name) => ({ area: 'host', name, path: path.join(storageRoot, name), bytes: sizeOf(path.join(storageRoot, name)) })),
|
|
39
39
|
...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)) })),
|
|
40
40
|
].filter((r) => fs.existsSync(r.path));
|
|
41
41
|
return _.sortBy([...rows, ...host], (r) => AREA_ORDER.indexOf(r.area), (r) => -r.bytes, (r) => r.name.toLowerCase());
|
|
@@ -48,7 +48,7 @@ export const cleanTargetOf = (entry) => {
|
|
|
48
48
|
return entry.name;
|
|
49
49
|
if (entry.area === 'logs')
|
|
50
50
|
return 'logs';
|
|
51
|
-
if (entry.area === 'host' && entry.name === 'launches')
|
|
52
|
-
return
|
|
51
|
+
if (entry.area === 'host' && (entry.name === 'launches' || entry.name === 'views'))
|
|
52
|
+
return entry.name;
|
|
53
53
|
return null;
|
|
54
54
|
};
|
|
@@ -6,5 +6,5 @@ export { repairCommand } from './repair/index.js';
|
|
|
6
6
|
export { attach, db, exec, hide, restart, sessions, show, status, stop } from './sessions.js';
|
|
7
7
|
export { account } from './accounts.js';
|
|
8
8
|
export { projects } from './projects.js';
|
|
9
|
-
export { claudeHost, clean, config, doctor, logs, sync, watcher } from './maintenance.js';
|
|
9
|
+
export { audit, claudeHost, clean, config, doctor, logs, sync, watcher } from './maintenance.js';
|
|
10
10
|
export { agentCommandsCommand, syncAgentCommands } from './agent-commands.js';
|
|
@@ -6,5 +6,5 @@ export { repairCommand } from './repair/index.js';
|
|
|
6
6
|
export { attach, db, exec, hide, restart, sessions, show, status, stop } from './sessions.js';
|
|
7
7
|
export { account } from './accounts.js';
|
|
8
8
|
export { projects } from './projects.js';
|
|
9
|
-
export { claudeHost, clean, config, doctor, logs, sync, watcher } from './maintenance.js';
|
|
9
|
+
export { audit, claudeHost, clean, config, doctor, logs, sync, watcher } from './maintenance.js';
|
|
10
10
|
export { agentCommandsCommand, syncAgentCommands } from './agent-commands.js';
|
|
@@ -13,7 +13,12 @@ import { findSecretFiles } from '../../host/secrets.js';
|
|
|
13
13
|
import { checkRepo, saveRepoSnapshot } from '../../host/repo-guard.js';
|
|
14
14
|
import { assertCanLaunch, insideSandbox, isSandboxRunning, probeHost, spawnDetached, startSandbox } from '../../host/platform.js';
|
|
15
15
|
import { NeedsRestart, rememberProject, startRegistry } from '../../host/projects.js';
|
|
16
|
-
import { ensureTerminal } from '../../host/terminal.js';
|
|
16
|
+
import { ensureConpty, ensureTerminal } from '../../host/terminal.js';
|
|
17
|
+
import { planView, saveActiveViews, viewSkipped, WorkspaceView } from '../../host/workspace-view/index.js';
|
|
18
|
+
import { saveForwards } from '../../host/port-forward.js';
|
|
19
|
+
import { saveAuditSettings } from '../../host/audit/collect.js';
|
|
20
|
+
import { readDataSize, saveMarkedProjects } from '../../host/motw.js';
|
|
21
|
+
import { humanBytes } from '../../core/disk.js';
|
|
17
22
|
import { hideSandboxWindow, showSandboxWindow } from '../../host/window.js';
|
|
18
23
|
import { readSandboxStatus } from '../../shared/rundir.js';
|
|
19
24
|
import { isWithin } from '../../shared/projects.js';
|
|
@@ -31,6 +36,7 @@ import _ from 'lodash';
|
|
|
31
36
|
import { hereDir, joinHere, readyHere } from './project.js';
|
|
32
37
|
import { offerRestart } from './restart.js';
|
|
33
38
|
import { addToList, setSetting } from './user-config.js';
|
|
39
|
+
import { lastStopWarning, startLife, UPDATE_RESTART_WARNING, updateRestartPending } from '../../host/stops.js';
|
|
34
40
|
/** Attaches this terminal; with attach.autoHide the sandbox window hides meanwhile (best effort, never fails the attach). */
|
|
35
41
|
export const attachHere = (ctx, session) => {
|
|
36
42
|
if (ctx.loaded.config.attach.autoHide && !insideSandbox())
|
|
@@ -216,6 +222,25 @@ const maybeAddRoot = async (ctx, run) => {
|
|
|
216
222
|
if (answer === 'never')
|
|
217
223
|
setSetting(ctx, ['projects', 'askToAddRoot'], false);
|
|
218
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* workspaceFilter: brings the project's filtered view in step before the sandbox maps it, and tells the background
|
|
227
|
+
* watcher to keep it in step while the sandbox runs. Returns the line for the launch table.
|
|
228
|
+
*/
|
|
229
|
+
const prepareWorkspaceView = async (ctx, run) => {
|
|
230
|
+
const plan = planView(run.config, ctx.storageRoot, run.workspaceHost);
|
|
231
|
+
saveActiveViews(ctx.storageRoot, ctx.dataPath, plan ? [plan.options] : []);
|
|
232
|
+
saveForwards(ctx.storageRoot, ctx.dataPath, run.config.sandbox.networking ? run.config.network.forwardPorts : []);
|
|
233
|
+
saveAuditSettings(ctx.storageRoot, ctx.dataPath, { ...run.config.audit, projects: [run.workspaceHost] });
|
|
234
|
+
saveMarkedProjects(ctx.storageRoot, ctx.dataPath, run.config.storage.markSandboxFiles ? [run.workspaceHost] : []);
|
|
235
|
+
const size = readDataSize(ctx.storageRoot, ctx.dataPath);
|
|
236
|
+
const limit = run.config.storage.warnDataGb;
|
|
237
|
+
if (limit > 0 && size && size.bytes > limit * 1024 ** 3)
|
|
238
|
+
console.warn(c.yellow('The persisted data takes ' + humanBytes(size.bytes) + ' (more than storage.warnDataGb, ' + limit + ' GB). See what: sandbox data; free space: sandbox data clean <what>.'));
|
|
239
|
+
if (!plan)
|
|
240
|
+
return viewSkipped(run.config, run.workspaceHost) ?? 'off (git-ignored files are visible in the sandbox; workspaceFilter.enabled keeps them out)';
|
|
241
|
+
const stats = await working('Preparing the workspace without its git-ignored files', () => new WorkspaceView(plan.options).prepare());
|
|
242
|
+
return 'on: git-ignored files stay on this PC' + (stats.toView || stats.toHost ? ' (' + stats.toView + ' copied in, ' + stats.toHost + ' out' + (stats.conflicts ? ', ' + stats.conflicts + ' conflicts kept next to the file' : '') + ')' : '') + '; ' + plan.options.view;
|
|
243
|
+
};
|
|
219
244
|
/** Everything a real launch does before the sandbox starts (skipped by `generate`, which only writes files). */
|
|
220
245
|
const prepare = async (ctx, run) => {
|
|
221
246
|
const { options, dataPath, storageRoot } = ctx;
|
|
@@ -224,12 +249,19 @@ const prepare = async (ctx, run) => {
|
|
|
224
249
|
if (account && run.harnessName === 'claude' && account !== activeAccount(dataPath))
|
|
225
250
|
activateAccount(storageRoot, dataPath, account);
|
|
226
251
|
const terminal = config.provisioning.terminal.enabled ? await ensureTerminal(dataPath, config.provisioning.refresh, { pwsh: config.toolchains.pwsh.enabled !== false }) : { exe: null, message: 'disabled' };
|
|
252
|
+
const conpty = config.provisioning.conpty.enabled ? await ensureConpty(dataPath, config.provisioning.refresh) : 'off';
|
|
227
253
|
const history = syncSummary(config, dataPath, run.workspaceHost);
|
|
228
254
|
const guarded = checkRepo(dataPath);
|
|
229
255
|
if (guarded.length)
|
|
230
256
|
console.warn(c.yellow('SECURITY: neutralised ' + guarded.length + ' change(s) the last sandbox made that could run code on this PC (see sandbox status).'));
|
|
257
|
+
const stopped = lastStopWarning(storageRoot, dataPath, watcherRunning(dataPath));
|
|
258
|
+
if (stopped)
|
|
259
|
+
console.warn(c.yellow(stopped));
|
|
260
|
+
if (updateRestartPending())
|
|
261
|
+
console.warn(c.yellow(UPDATE_RESTART_WARNING));
|
|
231
262
|
resetRunDir(dataPath);
|
|
232
|
-
|
|
263
|
+
const filter = await prepareWorkspaceView(ctx, run);
|
|
264
|
+
return { account, terminal: terminal.message + '; pseudo console: ' + conpty, history, pruned: describePrune(pruneStorage(storageRoot)), filter };
|
|
233
265
|
};
|
|
234
266
|
export const launch = async (ctx, generateOnly = false, extras = {}) => {
|
|
235
267
|
const { options, loaded, packageRoot, dataPath, storageRoot } = ctx;
|
|
@@ -284,7 +316,7 @@ export const launch = async (ctx, generateOnly = false, extras = {}) => {
|
|
|
284
316
|
['Workspace', run.workspaceHost + ' -> ' + config.guestWorkspace + (config.readOnly ? ' (read-only)' : '')],
|
|
285
317
|
['Agent', run.harnessName + (prepared?.account ? ' (account: ' + prepared.account + ')' : '') + ' Memory: ' + generated.memoryMb + ' MB CPU: ' + (config.sandbox.cpuLimit === 'none' ? 'all' : 'max ' + config.sandbox.cpuLimit + '%') + ' Network: ' + config.sandbox.networking + (config.sandbox.headless ? ' HEADLESS' : '')],
|
|
286
318
|
['Node', run.versions.node.spec + ' (' + run.versions.node.source + ') Python: ' + run.versions.python.spec + ' (' + run.versions.python.source + ')'],
|
|
287
|
-
...(prepared ? [['Terminal', prepared.terminal], ['History', prepared.history], ['Cleanup', prepared.pruned]] : []),
|
|
319
|
+
...(prepared ? [['Ignored files', prepared.filter], ['Terminal', prepared.terminal], ['History', prepared.history], ['Cleanup', prepared.pruned]] : []),
|
|
288
320
|
['Toolchains', Object.entries(generated.toolchains).filter(([, d]) => d.on).map(([name, d]) => name + ' (' + d.reason + ')').join(', ') || 'Node only (nothing else used by this project)'],
|
|
289
321
|
...(generated.databases.length
|
|
290
322
|
? generated.databases.map((d, i) => [i ? '' : 'Databases', d.database + (d.database === 'sqlserver' ? ' (cannot run in the sandbox; use Postgres or SQLite for local work)' : ' on port ' + d.port) + ' because ' + d.reasons[0] + (d.reasons.length > 1 ? ' (+' + (d.reasons.length - 1) + ' more)' : '')])
|
|
@@ -304,6 +336,7 @@ export const launch = async (ctx, generateOnly = false, extras = {}) => {
|
|
|
304
336
|
saveRepoSnapshot(dataPath, run.workspaceHost, projects.map((p) => p.host), roots.map((r) => r.host));
|
|
305
337
|
startRegistry(storageRoot, { launchId: generated.launchId, protections: protectionsOf(config), primary: { host: run.workspaceHost, guest: config.guestWorkspace }, roots, boot: projects });
|
|
306
338
|
rememberProject(storageRoot, run.workspaceHost);
|
|
339
|
+
startLife(storageRoot, dataPath, config.sandbox.keepAwake);
|
|
307
340
|
startSandbox(generated.wsbPath);
|
|
308
341
|
// Absolute: the watcher runs from another folder, and a relative --config there would make it exit silently.
|
|
309
342
|
if (!watcherRunning(dataPath))
|
|
@@ -6,3 +6,5 @@ export declare const config: (ctx: Context) => number;
|
|
|
6
6
|
export declare const doctor: (ctx: Context) => number;
|
|
7
7
|
export declare const claudeHost: (ctx: Context) => number;
|
|
8
8
|
export declare const watcher: (ctx: Context) => Promise<void>;
|
|
9
|
+
/** `sandbox audit`: the audit log of the last days (audit.enabled), newest last. */
|
|
10
|
+
export declare const audit: (ctx: Context) => Promise<number>;
|
|
@@ -5,14 +5,17 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
import _ from 'lodash';
|
|
6
6
|
import { DEFAULTS, userConfigPath, userDir } from '../../core/config.js';
|
|
7
7
|
import { describeOverrides, isTrusted, LOCAL_CONFIG_NAME, normalizeOverrides, readLocalConfig, trustStorePath } from '../../core/local-config.js';
|
|
8
|
-
import { expandPath, fail, safeText, sleep, writeJson } from '../../core/util.js';
|
|
8
|
+
import { assertNoLinks, expandPath, fail, plainText, safeText, sleep, writeJson } from '../../core/util.js';
|
|
9
|
+
import { humanBytes } from '../../core/disk.js';
|
|
10
|
+
import { AuditLog } from '../../host/audit/log.js';
|
|
9
11
|
import { readKnown, readRegistry } from '../../host/projects.js';
|
|
10
12
|
import { detectWsb } from '../../host/wsb.js';
|
|
11
13
|
import { describePrune, pruneStorage } from '../../host/cleanup.js';
|
|
12
14
|
import { doctorRows, hostClaude, insideSandbox, isSandboxRunning } from '../../host/platform.js';
|
|
13
15
|
import { readSandboxStatus } from '../../shared/rundir.js';
|
|
14
16
|
import { syncBack, watch } from '../../sync/watcher.js';
|
|
15
|
-
import { table } from './context.js';
|
|
17
|
+
import { printJson, table } from './context.js';
|
|
18
|
+
import { c } from '../colors.js';
|
|
16
19
|
export const sync = (ctx) => {
|
|
17
20
|
const running = isSandboxRunning();
|
|
18
21
|
const result = syncBack(ctx.dataPath, !running);
|
|
@@ -28,6 +31,8 @@ const newestLog = (dir) => {
|
|
|
28
31
|
};
|
|
29
32
|
export const logs = async (ctx) => {
|
|
30
33
|
const dir = path.join(ctx.dataPath, 'logs');
|
|
34
|
+
// The sandbox writes this folder: never read through a link it planted there.
|
|
35
|
+
assertNoLinks(ctx.dataPath, dir);
|
|
31
36
|
const file = newestLog(dir) ?? fail('No sandbox logs yet in ' + dir + '.', 'Logs appear after the first start (sandbox claude).');
|
|
32
37
|
console.log('== ' + file + ' ==');
|
|
33
38
|
let printed = 0;
|
|
@@ -152,3 +157,31 @@ export const doctor = (ctx) => {
|
|
|
152
157
|
};
|
|
153
158
|
export const claudeHost = (ctx) => hostClaude(ctx.storageRoot, ctx.options.personal, ctx.options.passthrough);
|
|
154
159
|
export const watcher = (ctx) => watch(ctx.dataPath);
|
|
160
|
+
const KINDS = ['proc', 'file', 'session', 'guard', 'note'];
|
|
161
|
+
const KIND_LABEL = { proc: 'program', file: 'file', session: 'session', guard: 'GUARD', note: 'note' };
|
|
162
|
+
/** `sandbox audit`: the audit log of the last days (audit.enabled), newest last. */
|
|
163
|
+
export const audit = async (ctx) => {
|
|
164
|
+
const settings = ctx.loaded.config.audit;
|
|
165
|
+
const kind = ctx.options.kind;
|
|
166
|
+
if (kind && !KINDS.includes(kind))
|
|
167
|
+
fail('Unknown kind "' + kind + '".', 'Use one of: ' + KINDS.join(', '));
|
|
168
|
+
const log = new AuditLog(AuditLog.dirFor(ctx.storageRoot, ctx.dataPath), settings);
|
|
169
|
+
const events = log.read({ days: ctx.options.days, kinds: kind ? [kind] : undefined, grep: ctx.options.grep });
|
|
170
|
+
if (printJson(ctx, events))
|
|
171
|
+
return 0;
|
|
172
|
+
if (!events.length) {
|
|
173
|
+
console.log(settings.enabled ? 'Nothing in the audit log for the last ' + ctx.options.days + ' day(s).' : 'The audit log is off. Turn it on in the menu (Settings > Security & privacy > Audit log) or with audit.enabled in your config.');
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
for (const e of events) {
|
|
177
|
+
// What the sandbox reported is text from the sandbox: no control codes reach your terminal.
|
|
178
|
+
const [p, cmd, parent, dir, act] = [e.p, e.c, e.pp, e.w, e.a].map((v) => plainText(v ?? ''));
|
|
179
|
+
const what = e.k === 'proc' ? (cmd || p) + (parent ? c.dim(' <- ' + path.win32.basename(parent)) : '')
|
|
180
|
+
: e.k === 'file' ? act + ' ' + p + c.dim(' in ' + dir)
|
|
181
|
+
: e.k === 'session' ? act + ' ' + p + c.dim(' ' + dir + ' ' + cmd)
|
|
182
|
+
: p;
|
|
183
|
+
console.log(c.dim(new Date(e.t).toLocaleString()) + ' ' + (e.k === 'guard' ? c.yellow(KIND_LABEL[e.k].padEnd(8)) : KIND_LABEL[e.k].padEnd(8)) + what + (e.n && e.n > 1 ? c.dim(' x' + e.n) : ''));
|
|
184
|
+
}
|
|
185
|
+
console.log(c.dim('\n' + events.length + ' event(s); the log takes ' + humanBytes(log.bytes()) + ' (' + AuditLog.dirFor(ctx.storageRoot, ctx.dataPath) + ').'));
|
|
186
|
+
return 0;
|
|
187
|
+
};
|
|
@@ -40,7 +40,7 @@ export const attach = async (ctx) => {
|
|
|
40
40
|
const projects = readRegistry(ctx.storageRoot)?.projects ?? [];
|
|
41
41
|
const chosen = await select({
|
|
42
42
|
message: 'Which session?' + (here.project ? ' (' + here.project.name + ' first)' : ''),
|
|
43
|
-
choices: running.map((s) => ({ value: s.id, name: (here.guestDir && isWithin(here.guestDir, s.cwd) ? '* ' : ' ') + safeText(s.harness).padEnd(9) + ' ' + projectOf(projects, s).padEnd(16) + ' ' + ago(s.startedAt).padEnd(11) + ' ' + s.id })),
|
|
43
|
+
choices: running.map((s) => ({ value: s.id, name: (here.guestDir && isWithin(here.guestDir, s.cwd) ? '* ' : ' ') + safeText(s.harness).padEnd(9) + ' ' + safeText(projectOf(projects, s)).padEnd(16) + ' ' + ago(s.startedAt).padEnd(11) + ' ' + s.id })),
|
|
44
44
|
});
|
|
45
45
|
return attachHere(ctx, pickSession(ctx.dataPath, chosen));
|
|
46
46
|
}
|
|
@@ -155,7 +155,7 @@ const printExecResult = (result) => {
|
|
|
155
155
|
if (result.stderr)
|
|
156
156
|
process.stderr.write(safeText(result.stderr));
|
|
157
157
|
if (!result.ok && !result.stdout && !result.stderr)
|
|
158
|
-
console.error(result.message);
|
|
158
|
+
console.error(safeText(result.message));
|
|
159
159
|
};
|
|
160
160
|
/** The guest folder for this folder, joining the running sandbox (and setting the project up) when needed. */
|
|
161
161
|
const guestFolderHere = async (ctx, what) => {
|
|
@@ -210,7 +210,7 @@ export const restart = async (ctx) => {
|
|
|
210
210
|
const scope = ctx.options.all || !here.project ? undefined : here.project.guest;
|
|
211
211
|
if (!scope && !ctx.options.all)
|
|
212
212
|
console.log(c.dim(hereDir(ctx) + ' is not a project of the running sandbox: restarting every session.'));
|
|
213
|
-
console.log((await working('Restarting the agents' + (scope ? ' of ' + here.project?.name : ''), () => restartSessions(ctx.dataPath, ctx.options.harness ?? null, sandboxAlive, scope))).message);
|
|
213
|
+
console.log(safeText((await working('Restarting the agents' + (scope ? ' of ' + here.project?.name : ''), () => restartSessions(ctx.dataPath, ctx.options.harness ?? null, sandboxAlive, scope))).message));
|
|
214
214
|
return 0;
|
|
215
215
|
};
|
|
216
216
|
export const stop = async (ctx) => {
|
|
@@ -6,7 +6,7 @@ import { parse } from 'jsonc-parser';
|
|
|
6
6
|
import _ from 'lodash';
|
|
7
7
|
import { userConfigPath } from '../../core/config.js';
|
|
8
8
|
import { fail } from '../../core/util.js';
|
|
9
|
-
import { editConfigText } from '../settings/
|
|
9
|
+
import { editConfigText } from '../menu/settings/config-file.js';
|
|
10
10
|
export const configFile = (ctx) => ctx.loaded.configPath ?? userConfigPath();
|
|
11
11
|
/** The value as written in your file (not merged with the defaults). */
|
|
12
12
|
export const fileSetting = (ctx, key, fallback) => {
|
package/dist/cli/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import { buildProgram, parseCli } from './args.js';
|
|
|
5
5
|
import { commandSpec } from './manifest.js';
|
|
6
6
|
import { c } from './colors.js';
|
|
7
7
|
import { recordHistory } from './history.js';
|
|
8
|
-
import { menu } from './menu.js';
|
|
8
|
+
import { menu } from './menu/index.js';
|
|
9
9
|
import { validateOptions } from './validate.js';
|
|
10
10
|
import { loadConfig } from '../core/config.js';
|
|
11
11
|
import * as commands from './commands/index.js';
|
|
12
|
-
import { UserError, errMsg, fail } from '../core/util.js';
|
|
12
|
+
import { UserError, errMsg, fail, plainText } from '../core/util.js';
|
|
13
13
|
/** The package folder (this file is dist/cli/index.js). */
|
|
14
14
|
export const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
15
15
|
const entry = path.join(packageRoot, 'dist', 'bin', 'agent-sandbox.js');
|
|
@@ -30,6 +30,7 @@ const HANDLERS = {
|
|
|
30
30
|
projects: commands.projects,
|
|
31
31
|
sync: commands.sync,
|
|
32
32
|
logs: commands.logs,
|
|
33
|
+
audit: commands.audit,
|
|
33
34
|
clean: commands.clean,
|
|
34
35
|
config: commands.config,
|
|
35
36
|
doctor: commands.doctor,
|
|
@@ -106,9 +107,10 @@ export const report = (e) => {
|
|
|
106
107
|
if (e instanceof CommanderError)
|
|
107
108
|
return ['commander.helpDisplayed', 'commander.help', 'commander.version'].includes(e.code) ? 0 : e.exitCode || 1;
|
|
108
109
|
if (e instanceof UserError) {
|
|
109
|
-
|
|
110
|
+
// Messages can quote what the sandbox wrote (a session's status, an address): never with its control codes.
|
|
111
|
+
console.error(c.red('Error: ') + plainText(e.message));
|
|
110
112
|
if (e.fix)
|
|
111
|
-
console.error(' Fix: ' + e.fix);
|
|
113
|
+
console.error(' Fix: ' + plainText(e.fix));
|
|
112
114
|
return 1;
|
|
113
115
|
}
|
|
114
116
|
console.error(c.red('Unexpected error: ') + errMsg(e));
|
package/dist/cli/manifest.js
CHANGED
|
@@ -93,6 +93,11 @@ export const COMMANDS = z.array(CommandSpec).parse([
|
|
|
93
93
|
{ name: 'data', summary: 'show what is kept between sandboxes (tools, databases, logins, logs), or delete some of it', group: 'Data', args: [{ name: 'action', description: 'show or clean', required: false, choices: ['show', 'clean'] }, { name: 'targets', description: 'what to clean, e.g. tools/go databases logs', required: false, variadic: true, when: 'clean' }], options: [bool('--yes', 'do not ask before deleting', { noMenu: true })], json },
|
|
94
94
|
{ name: 'clean', summary: 'delete old launch folders, logs and leftovers', group: 'Data', args: [], options: [{ flag: '--days', type: 'number', value: 'n', description: 'launch folders older than this many days', default: 7 }] },
|
|
95
95
|
{ name: 'sync', summary: 'copy conversation history back to this PC now', group: 'Data', args: [], options: [] },
|
|
96
|
+
{
|
|
97
|
+
name: 'audit', summary: 'what happened in your sandboxes: programs started, files changed, sessions, repo guard (audit.enabled)', group: 'Health', json,
|
|
98
|
+
args: [],
|
|
99
|
+
options: [{ flag: '--days', type: 'number', value: 'n', description: 'the last n days (default: today)', default: 1 }, text('--kind', 'kind', 'only proc, file, session or guard events', { choices: ['proc', 'file', 'session', 'guard'] }), text('--grep', 'text', 'only events containing this text')],
|
|
100
|
+
},
|
|
96
101
|
{ name: 'logs', summary: 'print the newest sandbox log', group: 'Health', args: [], options: [bool('--follow', 'keep printing new lines', { short: '-f' })] },
|
|
97
102
|
{ name: 'setup', summary: 'check this PC can run Windows Sandbox and offer to turn it on', group: 'Health', args: [], options: [] },
|
|
98
103
|
{ name: 'doctor', summary: 'check this PC and show what will be copied or downloaded', group: 'Health', args: [], options: [text('--harness', 'name', 'agent to check', { choices: 'harnesses' })] },
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { App } from '../tui/index.js';
|
|
2
|
+
import type { ConfigFile } from './settings/config-file.js';
|
|
3
|
+
export declare class AppsCatalogue {
|
|
4
|
+
private readonly app;
|
|
5
|
+
private readonly file;
|
|
6
|
+
private readonly dataPath;
|
|
7
|
+
constructor(app: App, file: ConfigFile, dataPath: string);
|
|
8
|
+
/** Downloaded already (into this data folder), so the next sandbox starts it without a download. */
|
|
9
|
+
private installed;
|
|
10
|
+
/** Is this entry on now (its setting, or listed in apps.install)? */
|
|
11
|
+
private chosen;
|
|
12
|
+
private item;
|
|
13
|
+
/** Shows the catalogue until you save (Enter) or leave it (Esc); returns the notice to show afterwards. */
|
|
14
|
+
run(): Promise<string | undefined>;
|
|
15
|
+
private save;
|
|
16
|
+
/** Your own programs: the apps.custom map, each entry a form (GitHub release or link, program, command line or not). */
|
|
17
|
+
private editCustom;
|
|
18
|
+
}
|
|
19
|
+
/** For the home screen: "12 apps chosen". */
|
|
20
|
+
export declare const appsSummary: (file: ConfigFile) => string;
|
|
21
|
+
export declare const appLabel: (id: string) => string;
|