windows-development-sandbox 1.10.0 → 1.11.3
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 +64 -17
- package/config.example.json +5 -1
- package/dist/app/features/accounts/accounts.def.d.ts +122 -0
- package/dist/app/features/accounts/accounts.def.js +243 -0
- package/dist/app/features/accounts/accounts.run.d.ts +51 -0
- package/dist/app/features/accounts/accounts.run.js +125 -0
- package/dist/app/features/accounts/index.d.ts +2 -0
- package/dist/app/features/accounts/index.js +3 -0
- package/dist/app/features/agents/agent-entry.d.ts +17 -0
- package/dist/app/features/agents/agent-entry.js +50 -0
- package/dist/app/features/agents/agents.def.d.ts +65 -0
- package/dist/app/features/agents/agents.def.js +142 -0
- package/dist/app/features/agents/agents.run.d.ts +31 -0
- package/dist/app/features/agents/agents.run.js +189 -0
- package/dist/app/features/agents/index.d.ts +3 -0
- package/dist/app/features/agents/index.js +4 -0
- package/dist/app/features/apps/apps-rows.d.ts +31 -0
- package/dist/app/features/apps/apps-rows.js +73 -0
- package/dist/app/features/apps/apps.def.d.ts +181 -0
- package/dist/app/features/apps/apps.def.js +264 -0
- package/dist/app/features/apps/apps.run.d.ts +58 -0
- package/dist/app/features/apps/apps.run.js +76 -0
- package/dist/app/features/apps/index.d.ts +3 -0
- package/dist/app/features/apps/index.js +4 -0
- package/dist/app/features/data/data.def.d.ts +154 -0
- package/dist/app/features/data/data.def.js +201 -0
- package/dist/app/features/data/data.run.d.ts +46 -0
- package/dist/app/features/data/data.run.js +141 -0
- package/dist/app/features/data/index.d.ts +2 -0
- package/dist/app/features/data/index.js +3 -0
- package/dist/app/features/defs.d.ts +952 -4
- package/dist/app/features/defs.js +147 -10
- package/dist/app/features/engine/engine.def.d.ts +228 -0
- package/dist/app/features/engine/engine.def.js +194 -0
- package/dist/app/features/engine/engine.run.d.ts +72 -0
- package/dist/app/features/engine/engine.run.js +77 -0
- package/dist/app/features/engine/index.d.ts +2 -0
- package/dist/app/features/engine/index.js +4 -0
- package/dist/app/features/files/files.def.d.ts +51 -0
- package/dist/app/features/files/files.def.js +38 -0
- package/dist/app/features/files/files.run.d.ts +20 -0
- package/dist/app/features/files/files.run.js +59 -0
- package/dist/app/features/files/index.d.ts +2 -0
- package/dist/app/features/files/index.js +3 -0
- package/dist/app/features/github/github.def.d.ts +80 -0
- package/dist/app/features/github/github.def.js +142 -0
- package/dist/app/features/github/github.run.d.ts +36 -0
- package/dist/app/features/github/github.run.js +59 -0
- package/dist/app/features/github/index.d.ts +2 -0
- package/dist/app/features/github/index.js +3 -0
- package/dist/app/features/health/health.def.d.ts +275 -0
- package/dist/app/features/health/health.def.js +281 -0
- package/dist/app/features/health/health.run.d.ts +125 -0
- package/dist/app/features/health/health.run.js +204 -0
- package/dist/app/features/health/index.d.ts +2 -0
- package/dist/app/features/health/index.js +3 -0
- package/dist/app/features/logs/index.d.ts +2 -0
- package/dist/app/features/logs/index.js +3 -0
- package/dist/app/features/logs/logs.def.d.ts +78 -0
- package/dist/app/features/logs/logs.def.js +115 -0
- package/dist/app/features/logs/logs.run.d.ts +20 -0
- package/dist/app/features/logs/logs.run.js +42 -0
- package/dist/app/features/projects/index.d.ts +2 -0
- package/dist/app/features/projects/index.js +3 -0
- package/dist/app/features/projects/projects.def.d.ts +101 -0
- package/dist/app/features/projects/projects.def.js +208 -0
- package/dist/app/features/projects/projects.run.d.ts +56 -0
- package/dist/app/features/projects/projects.run.js +155 -0
- package/dist/app/features/sandbox/sandbox.def.js +11 -5
- package/dist/app/features/sessions/sessions.def.js +3 -0
- package/dist/app/features/settings/categories.def.js +3 -2
- package/dist/{cli/menu/settings/share.d.ts → app/features/settings/settings-share.d.ts} +1 -1
- package/dist/{cli/menu/settings/share.js → app/features/settings/settings-share.js} +3 -2
- package/dist/app/features/settings/settings.def.d.ts +22 -5
- package/dist/app/features/settings/settings.def.js +81 -6
- package/dist/app/features/settings/settings.run.d.ts +17 -0
- package/dist/app/features/settings/settings.run.js +89 -4
- package/dist/app/features/storage/index.d.ts +2 -0
- package/dist/app/features/storage/index.js +3 -0
- package/dist/app/features/storage/storage.def.d.ts +133 -0
- package/dist/app/features/storage/storage.def.js +225 -0
- package/dist/app/features/storage/storage.run.d.ts +62 -0
- package/dist/app/features/storage/storage.run.js +370 -0
- package/dist/app/features/updates/index.d.ts +3 -0
- package/dist/app/features/updates/index.js +4 -0
- package/dist/app/features/updates/update-lines.d.ts +13 -0
- package/dist/app/features/updates/update-lines.js +61 -0
- package/dist/app/features/updates/updates.def.d.ts +89 -0
- package/dist/app/features/updates/updates.def.js +225 -0
- package/dist/app/features/updates/updates.run.d.ts +48 -0
- package/dist/app/features/updates/updates.run.js +154 -0
- package/dist/app/features/vpn/index.d.ts +2 -0
- package/dist/app/features/vpn/index.js +3 -0
- package/dist/app/features/vpn/vpn.def.d.ts +112 -0
- package/dist/app/features/vpn/vpn.def.js +174 -0
- package/dist/app/features/vpn/vpn.run.d.ts +37 -0
- package/dist/app/features/vpn/vpn.run.js +54 -0
- package/dist/app/features/web/index.d.ts +2 -0
- package/dist/app/features/web/index.js +3 -0
- package/dist/app/features/web/web.def.d.ts +33 -0
- package/dist/app/features/web/web.def.js +90 -0
- package/dist/app/features/web/web.run.d.ts +24 -0
- package/dist/app/features/web/web.run.js +93 -0
- package/dist/app/kernel/define.d.ts +10 -1
- package/dist/app/kernel/io.d.ts +34 -0
- package/dist/app/kernel/io.js +9 -0
- package/dist/app/kernel/rpc.d.ts +17 -0
- package/dist/app/kernel/text.d.ts +2 -0
- package/dist/app/kernel/text.js +7 -0
- package/dist/app/kernel/view.d.ts +24 -1
- package/dist/app/kernel/view.js +1 -1
- package/dist/app/registry.js +74 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/commands/context.d.ts +7 -0
- package/dist/cli/commands/context.js +29 -2
- package/dist/cli/commands/engine.js +5 -58
- package/dist/cli/commands/github.d.ts +2 -0
- package/dist/cli/commands/github.js +11 -0
- package/dist/cli/commands/index.d.ts +3 -5
- package/dist/cli/commands/index.js +3 -5
- package/dist/cli/commands/launch/launch.js +21 -4
- package/dist/cli/commands/launch/running.js +4 -1
- package/dist/cli/commands/maintenance/config.js +1 -6
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +1 -2
- package/dist/cli/commands/maintenance/logs.js +27 -32
- package/dist/cli/commands/maintenance/migrate.d.ts +0 -8
- package/dist/cli/commands/maintenance/migrate.js +2 -44
- package/dist/cli/commands/maintenance/update.d.ts +0 -19
- package/dist/cli/commands/maintenance/update.js +1 -99
- package/dist/cli/commands/projects/index.d.ts +0 -1
- package/dist/cli/commands/projects/index.js +0 -1
- package/dist/cli/commands/registry.d.ts +1 -1
- package/dist/cli/commands/registry.js +29 -11
- package/dist/cli/commands/repair/checks/data.js +1 -1
- package/dist/cli/commands/repair/checks/hcs.js +28 -1
- package/dist/cli/commands/repair/checks/sandbox.js +1 -1
- package/dist/cli/commands/repair/health-run.d.ts +30 -0
- package/dist/cli/commands/repair/health-run.js +32 -0
- package/dist/cli/commands/repair/printer.d.ts +2 -2
- package/dist/cli/commands/repair/printer.js +2 -11
- package/dist/cli/commands/repair/run.d.ts +1 -21
- package/dist/cli/commands/repair/run.js +13 -70
- package/dist/cli/commands/storage/index.d.ts +0 -1
- package/dist/cli/commands/storage/index.js +0 -1
- package/dist/cli/commands/storage/offer.js +6 -2
- package/dist/cli/commands/vpn.d.ts +1 -3
- package/dist/cli/commands/vpn.js +8 -89
- package/dist/cli/commands/web.d.ts +2 -1
- package/dist/cli/commands/web.js +14 -78
- package/dist/cli/index.js +3 -2
- package/dist/cli/manifest.d.ts +16 -153
- package/dist/cli/manifest.js +20 -50
- package/dist/cli/menu/command-form.js +1 -2
- package/dist/cli/menu/io-menu.d.ts +2 -2
- package/dist/cli/menu/io-menu.js +6 -4
- package/dist/cli/menu/menu.js +1 -1
- package/dist/cli/menu/pages/accounts-page.d.ts +5 -17
- package/dist/cli/menu/pages/accounts-page.js +28 -170
- package/dist/cli/menu/pages/agents-page.d.ts +17 -18
- package/dist/cli/menu/pages/agents-page.js +67 -124
- package/dist/cli/menu/pages/apps-page.d.ts +15 -8
- package/dist/cli/menu/pages/apps-page.js +121 -29
- package/dist/cli/menu/pages/list-page.js +10 -1
- package/dist/cli/menu/pages/maintenance-page.d.ts +51 -52
- package/dist/cli/menu/pages/maintenance-page.js +246 -427
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -5
- package/dist/cli/menu/pages/sandbox-page.js +34 -112
- package/dist/cli/menu/pages/settings-page.d.ts +21 -3
- package/dist/cli/menu/pages/settings-page.js +89 -17
- package/dist/cli/menu/render-view.js +2 -1
- package/dist/cli/menu/settings/index.d.ts +0 -1
- package/dist/cli/menu/settings/index.js +3 -3
- package/dist/cli/menu/views.js +28 -0
- package/dist/cli/operations/handler.js +7 -3
- package/dist/cli/operations/input.d.ts +1 -1
- package/dist/cli/operations/input.js +29 -5
- package/dist/cli/operations/io-cli.js +9 -1
- package/dist/cli/operations/spec.js +8 -2
- package/dist/cli/output/render-view.js +2 -0
- package/dist/cli/{commands/data/collect.js → services/data-collect.js} +2 -2
- package/dist/cli/{commands/data/remove.js → services/data-remove.js} +2 -2
- package/dist/cli/services/data-service.d.ts +1 -1
- package/dist/cli/services/data-service.js +2 -2
- package/dist/cli/services/databases-service.d.ts +5 -0
- package/dist/cli/services/databases-service.js +15 -0
- package/dist/cli/services/health-service.d.ts +8 -4
- package/dist/cli/services/health-service.js +10 -8
- package/dist/cli/services/index.d.ts +4 -1
- package/dist/cli/services/index.js +4 -1
- package/dist/cli/services/logs-service.d.ts +7 -0
- package/dist/cli/services/logs-service.js +13 -0
- package/dist/cli/services/storage-service.d.ts +19 -0
- package/dist/cli/services/storage-service.js +71 -0
- package/dist/cli/services/vpn-service.d.ts +6 -4
- package/dist/cli/services/vpn-service.js +5 -11
- package/dist/cli/services/web-service.d.ts +4 -3
- package/dist/cli/services/web-service.js +12 -32
- package/dist/core/config/defaults.js +2 -1
- package/dist/core/schema/config.d.ts +5 -0
- package/dist/core/schema/config.js +2 -0
- package/dist/core/schema/github.d.ts +4 -0
- package/dist/core/schema/github.js +7 -0
- package/dist/core/schema/index.d.ts +1 -0
- package/dist/core/schema/index.js +1 -0
- package/dist/core/schema/profile.d.ts +1 -0
- package/dist/core/schema/profile.js +3 -0
- package/dist/core/schema/sandbox.js +3 -3
- package/dist/core/schema/toolchains.js +2 -2
- package/dist/core/types.d.ts +6 -3
- package/dist/guest/boot/boot.js +8 -0
- package/dist/guest/daemon/daemon.js +1 -1
- package/dist/guest/daemon/sessions.d.ts +3 -2
- package/dist/guest/daemon/sessions.js +38 -7
- package/dist/guest/governor/core.d.ts +44 -0
- package/dist/guest/governor/core.js +124 -0
- package/dist/guest/governor/decide.d.ts +46 -18
- package/dist/guest/governor/decide.js +136 -82
- package/dist/guest/governor/governor.d.ts +2 -38
- package/dist/guest/governor/governor.js +33 -113
- package/dist/guest/governor/index.d.ts +1 -0
- package/dist/guest/governor/index.js +1 -0
- package/dist/guest/network/firewall.d.ts +2 -0
- package/dist/guest/network/firewall.js +3 -3
- package/dist/guest/sessions/viewer.js +5 -1
- package/dist/guest/setup/github-token.d.ts +7 -0
- package/dist/guest/setup/github-token.js +65 -0
- package/dist/guest/toolchains/languages/node.js +11 -1
- package/dist/guest/toolchains/native/rust-extras.js +2 -2
- package/dist/guest/toolchains/tools/cli.d.ts +8 -0
- package/dist/guest/toolchains/tools/cli.js +16 -2
- package/dist/guest/vpn/openvpn.d.ts +5 -0
- package/dist/guest/vpn/openvpn.js +14 -1
- package/dist/guest/vpn/vpn.js +55 -16
- package/dist/guest/vpn/wireguard.js +2 -0
- package/dist/host/channel/attach.d.ts +1 -0
- package/dist/host/channel/attach.js +1 -0
- package/dist/host/channel/channel.d.ts +1 -1
- package/dist/host/channel/channel.js +2 -2
- package/dist/host/engines/hcs/check.d.ts +1 -0
- package/dist/host/engines/hcs/check.js +1 -1
- package/dist/host/engines/hcs/engine.d.ts +1 -1
- package/dist/host/engines/hcs/engine.js +6 -4
- package/dist/host/engines/hcs/helper.d.ts +11 -0
- package/dist/host/engines/hcs/helper.js +31 -2
- package/dist/host/engines/hcs/leftovers.d.ts +42 -0
- package/dist/host/engines/hcs/leftovers.js +101 -0
- package/dist/host/engines/index.d.ts +1 -0
- package/dist/host/engines/index.js +1 -0
- package/dist/host/github/deliver.d.ts +13 -0
- package/dist/host/github/deliver.js +51 -0
- package/dist/host/github/index.d.ts +2 -0
- package/dist/host/github/index.js +3 -0
- package/dist/host/github/token.d.ts +21 -0
- package/dist/host/github/token.js +74 -0
- package/dist/host/launch/generate.js +11 -5
- package/dist/host/launch/index.d.ts +1 -1
- package/dist/host/launch/index.js +1 -1
- package/dist/host/launch/session-file.js +1 -2
- package/dist/host/launch/wsb-file.d.ts +11 -7
- package/dist/host/launch/wsb-file.js +30 -12
- package/dist/host/life/watchdog.d.ts +9 -0
- package/dist/host/life/watchdog.js +17 -1
- package/dist/host/sandbox/tasklist.d.ts +6 -0
- package/dist/host/sandbox/tasklist.js +37 -0
- package/dist/host/secrets/launch-secrets.d.ts +2 -1
- package/dist/host/secrets/launch-secrets.js +4 -2
- package/dist/host/vpn/status.js +13 -3
- package/dist/host/web/backend.d.ts +1 -3
- package/dist/host/web/index.d.ts +2 -1
- package/dist/host/web/index.js +2 -1
- package/dist/host/web/live.d.ts +23 -0
- package/dist/host/web/live.js +107 -0
- package/dist/host/web/rpc.d.ts +6 -1
- package/dist/host/web/rpc.js +23 -31
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -1
- package/dist/host/web/server.d.ts +30 -2
- package/dist/host/web/server.js +87 -29
- package/dist/host/web/state.d.ts +1 -0
- package/dist/host/web/state.js +2 -0
- package/dist/runtime/agent.js +30 -6
- package/dist/runtime/daemon.js +323 -176
- package/dist/runtime/db.js +30 -6
- package/dist/runtime/main.js +375 -76
- package/dist/runtime/netproxy.js +30 -6
- package/dist/runtime/view.js +495 -146
- package/dist/selftest/app.test.js +620 -12
- package/dist/selftest/channel.test.js +18 -1
- package/dist/selftest/cli.test.js +1 -1
- package/dist/selftest/config.test.js +1 -1
- package/dist/selftest/contained.test.js +1 -1
- package/dist/selftest/data.test.js +1 -2
- package/dist/selftest/engine.test.js +3 -1
- package/dist/selftest/github.test.d.ts +2 -0
- package/dist/selftest/github.test.js +243 -0
- package/dist/selftest/hcs.test.js +105 -8
- package/dist/selftest/health.test.js +71 -0
- package/dist/selftest/heavy-load.test.js +132 -72
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/layout.test.js +8 -0
- package/dist/selftest/live-terminal.test.js +74 -0
- package/dist/selftest/menu-screens.js +171 -56
- package/dist/selftest/menu.test.js +54 -37
- package/dist/selftest/migrate.test.js +14 -12
- package/dist/selftest/powershell-variables.test.d.ts +2 -0
- package/dist/selftest/powershell-variables.test.js +61 -0
- package/dist/selftest/support/engine-golden.js +1 -0
- package/dist/selftest/support/fixtures.d.ts +4 -0
- package/dist/selftest/support/web-preview.d.ts +1 -0
- package/dist/selftest/support/web-preview.js +856 -0
- package/dist/selftest/update-service.test.js +15 -8
- package/dist/selftest/vpn.test.js +86 -2
- package/dist/selftest/web.test.js +58 -11
- package/dist/shared/channel/run-dir.d.ts +2 -0
- package/dist/shared/channel/run-dir.js +3 -0
- package/dist/shared/channel/schema.d.ts +7 -0
- package/dist/shared/channel/schema.js +9 -1
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +3 -0
- package/dist/shared/github/index.d.ts +1 -0
- package/dist/shared/github/index.js +2 -0
- package/dist/shared/github/token.d.ts +29 -0
- package/dist/shared/github/token.js +89 -0
- package/dist/shared/memory/policy.d.ts +33 -12
- package/dist/shared/memory/policy.js +50 -19
- package/dist/shared/pty/ctrl-c.d.ts +18 -0
- package/dist/shared/pty/ctrl-c.js +36 -0
- package/dist/shared/pty/index.d.ts +1 -0
- package/dist/shared/pty/index.js +1 -0
- package/dist/shared/pty/terminal.d.ts +2 -0
- package/dist/shared/pty/terminal.js +21 -2
- package/dist/shared/vpn/kill-switch.d.ts +13 -2
- package/dist/shared/vpn/kill-switch.js +88 -8
- package/dist/shared/web/protocol.d.ts +0 -4
- package/dist/sync/watcher.js +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/{main-T5DMCHYV.css → main-6JF3ASB5.css} +1 -1
- package/dist/web/main-Z645W45E.js +219 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/package.json +10 -3
- package/sandbox/csharp/MemGov.cs +240 -38
- package/sandbox/csharp/PtyHost.cs +104 -15
- package/sandbox/csharp/SandboxTools/CpuCap.cs +50 -1
- package/sandbox/csharp/SandboxTools/Program.cs +1 -1
- package/sandbox/scripts/repair-wmi-services.ps1 +26 -0
- package/schemas/config.schema.json +19 -5
- package/schemas/sandbox-config.schema.json +7 -3
- package/dist/cli/commands/accounts.d.ts +0 -2
- package/dist/cli/commands/accounts.js +0 -96
- package/dist/cli/commands/data/clean.d.ts +0 -3
- package/dist/cli/commands/data/clean.js +0 -60
- package/dist/cli/commands/data/index.d.ts +0 -4
- package/dist/cli/commands/data/index.js +0 -62
- package/dist/cli/commands/maintenance/clean.d.ts +0 -2
- package/dist/cli/commands/maintenance/clean.js +0 -24
- package/dist/cli/commands/maintenance/config-share.d.ts +0 -12
- package/dist/cli/commands/maintenance/config-share.js +0 -80
- package/dist/cli/commands/projects/projects.d.ts +0 -2
- package/dist/cli/commands/projects/projects.js +0 -162
- package/dist/cli/commands/storage/contained.d.ts +0 -3
- package/dist/cli/commands/storage/contained.js +0 -179
- package/dist/cli/commands/storage/storage.d.ts +0 -8
- package/dist/cli/commands/storage/storage.js +0 -179
- package/dist/cli/menu/add-agent.d.ts +0 -47
- package/dist/cli/menu/add-agent.js +0 -168
- package/dist/cli/menu/apps.d.ts +0 -69
- package/dist/cli/menu/apps.js +0 -214
- package/dist/cli/menu/data-folder.d.ts +0 -18
- package/dist/cli/menu/data-folder.js +0 -99
- package/dist/cli/menu/share-settings.d.ts +0 -21
- package/dist/cli/menu/share-settings.js +0 -112
- package/dist/cli/menu/vpn-login.d.ts +0 -16
- package/dist/cli/menu/vpn-login.js +0 -74
- package/dist/web/main-NRRS5O6Z.js +0 -157
- /package/dist/cli/{commands/data/collect.d.ts → services/data-collect.d.ts} +0 -0
- /package/dist/cli/{commands/data/remove.d.ts → services/data-remove.d.ts} +0 -0
|
@@ -3,33 +3,57 @@
|
|
|
3
3
|
import { DEFINITIONS } from '../../app/features/defs.js';
|
|
4
4
|
import { fail } from '../../core/util/index.js';
|
|
5
5
|
import { inputField } from './spec.js';
|
|
6
|
-
/** The
|
|
6
|
+
/** The words of an action: `contained on` is two words, each one a positional argument. */
|
|
7
|
+
function actionWords(action) {
|
|
8
|
+
return action.split(' ');
|
|
9
|
+
}
|
|
10
|
+
/** Whether the positionals start with every word of the action (`contained`, `on`). */
|
|
11
|
+
function startsWithAction(action, positionals) {
|
|
12
|
+
return actionWords(action).every((word, i) => positionals[i] === word);
|
|
13
|
+
}
|
|
14
|
+
/** The definition a command runs: its own command, or an action of a hand-written one (`config get`, `storage use`). */
|
|
7
15
|
export function definitionFor(command, positionals) {
|
|
8
|
-
return DEFINITIONS.find((d) => d.cli?.name === command && (d.cli.action === undefined || d.cli.action
|
|
16
|
+
return DEFINITIONS.find((d) => d.cli?.name === command && (d.cli.action === undefined || startsWithAction(d.cli.action, positionals)));
|
|
9
17
|
}
|
|
10
18
|
/** commander's attribute name for a long flag: --keep-old -> keepOld. */
|
|
11
19
|
function attribute(flag) {
|
|
12
20
|
return flag.replace(/^--/, '').replace(/-([a-z])/g, (_m, ch) => ch.toUpperCase());
|
|
13
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* What a flag gave: --no-<flag> is stored by commander as <flag>: false, and the input field it sets is true then.
|
|
24
|
+
*/
|
|
25
|
+
function flagValue(raw, flag) {
|
|
26
|
+
if (flag.startsWith('--no-')) {
|
|
27
|
+
const positive = attribute('--' + flag.slice('--no-'.length));
|
|
28
|
+
return raw[positive] === false ? true : undefined;
|
|
29
|
+
}
|
|
30
|
+
return raw[attribute(flag)];
|
|
31
|
+
}
|
|
14
32
|
/** The input a command line gives a definition (fields not on the line are left to the schema's defaults). */
|
|
15
33
|
export function cliInput(def, options) {
|
|
16
34
|
const hint = def.cli;
|
|
17
35
|
if (!hint)
|
|
18
36
|
return {};
|
|
19
|
-
const positionals = hint.action ? options.positionals.slice(
|
|
37
|
+
const positionals = hint.action ? options.positionals.slice(actionWords(hint.action).length) : options.positionals;
|
|
20
38
|
const names = hint.positional ?? [];
|
|
21
39
|
const missing = names.slice(positionals.length).filter((name) => !inputField(def, name).optional);
|
|
22
40
|
if (missing.length)
|
|
23
41
|
fail('Missing ' + missing.join(' and ') + '.', 'Usage: sandbox ' + [hint.name, hint.action, ...names.map((n) => '<' + n + '>')].filter(Boolean).join(' '));
|
|
24
42
|
const input = {};
|
|
25
43
|
names.forEach((name, i) => {
|
|
26
|
-
|
|
44
|
+
// A list takes the rest of the line (`data clean tools/go logs`); only the last positional may be one.
|
|
45
|
+
if (inputField(def, name).kind === 'list') {
|
|
46
|
+
if (i !== names.length - 1)
|
|
47
|
+
throw new Error(def.id + ': only the last positional may be a list');
|
|
48
|
+
input[name] = positionals.slice(i);
|
|
49
|
+
}
|
|
50
|
+
else if (positionals[i] !== undefined)
|
|
27
51
|
input[name] = positionals[i];
|
|
28
52
|
});
|
|
29
53
|
for (const [name, flag] of Object.entries(hint.flags ?? {})) {
|
|
30
54
|
if (!flag)
|
|
31
55
|
continue;
|
|
32
|
-
const value = (flag.shownAs ? options.raw
|
|
56
|
+
const value = (flag.shownAs ? flagValue(options.raw, flag.shownAs) : undefined) ?? flagValue(options.raw, flag.flag);
|
|
33
57
|
if (value !== undefined)
|
|
34
58
|
input[name] = value;
|
|
35
59
|
}
|
|
@@ -21,8 +21,14 @@ function interactive() {
|
|
|
21
21
|
async function ask(q, options) {
|
|
22
22
|
if ('problem' in q && q.problem)
|
|
23
23
|
console.log(c.yellow(q.problem));
|
|
24
|
+
// A question with its own words for "no terminal" fails with them, before any other way of asking (confirm has its
|
|
25
|
+
// own rule: --yes and implied answer it).
|
|
26
|
+
if (q.kind !== 'confirm' && !interactive() && 'noTerminal' in q && q.noTerminal)
|
|
27
|
+
fail(q.noTerminal.message, q.noTerminal.fix);
|
|
24
28
|
switch (q.kind) {
|
|
25
29
|
case 'confirm': {
|
|
30
|
+
if (q.skipWithYes && options.yes)
|
|
31
|
+
return false;
|
|
26
32
|
if (q.implied || options.yes)
|
|
27
33
|
return true;
|
|
28
34
|
if (!interactive())
|
|
@@ -51,11 +57,13 @@ export function cliIO(options) {
|
|
|
51
57
|
progress.stop();
|
|
52
58
|
return (await ask(q, options));
|
|
53
59
|
};
|
|
60
|
+
// With --json the lines go to stderr, so stdout is the JSON alone.
|
|
61
|
+
const write = options.json ? console.error : console.log;
|
|
54
62
|
const io = ioFromAsker(asker, {
|
|
55
63
|
progress: (text) => progress.show(text),
|
|
56
64
|
log: (text, tone) => {
|
|
57
65
|
progress.stop();
|
|
58
|
-
|
|
66
|
+
write(tone ? PAINT[tone](text) : text);
|
|
59
67
|
},
|
|
60
68
|
});
|
|
61
69
|
return { io, close: () => progress.stop() };
|
|
@@ -58,6 +58,7 @@ function argSpec(def, name) {
|
|
|
58
58
|
description: field.help ?? '',
|
|
59
59
|
required: !field.optional,
|
|
60
60
|
...(choices ? { choices } : {}),
|
|
61
|
+
...(field.kind === 'list' ? { variadic: true } : {}),
|
|
61
62
|
};
|
|
62
63
|
}
|
|
63
64
|
/** The manifest command of a definition that is a command of its own. */
|
|
@@ -80,6 +81,10 @@ export function commandSpecOf(def) {
|
|
|
80
81
|
...(hint.examples ? { examples: [...hint.examples] } : {}),
|
|
81
82
|
};
|
|
82
83
|
}
|
|
84
|
+
/** The first word of an action: `contained` for `contained on`. */
|
|
85
|
+
function firstWord(action) {
|
|
86
|
+
return action.split(' ')[0];
|
|
87
|
+
}
|
|
83
88
|
/**
|
|
84
89
|
* A hand-written command with the actions the definitions add (`config reset`): the action is one of the first
|
|
85
90
|
* argument's choices, each later argument is asked for it in the menu's command form, and --json is offered when an
|
|
@@ -90,10 +95,11 @@ export function withActions(spec, defs = actionDefinitions()) {
|
|
|
90
95
|
if (!mine.length || !spec.args.length)
|
|
91
96
|
return spec;
|
|
92
97
|
const [first, ...rest] = spec.args;
|
|
93
|
-
|
|
98
|
+
// A two-word action (`contained on`) is listed by its first word: the argument's choice is that word.
|
|
99
|
+
const actions = mine.map((d) => firstWord(d.cli.action));
|
|
94
100
|
const choices = Array.isArray(first.choices) ? [...new Set([...first.choices, ...actions])] : first.choices;
|
|
95
101
|
const asked = (arg, position) => {
|
|
96
|
-
const users = mine.filter((d) => (d.cli?.positional?.length ?? 0) > position).map((d) => d.cli.action);
|
|
102
|
+
const users = mine.filter((d) => (d.cli?.positional?.length ?? 0) > position).map((d) => firstWord(d.cli.action));
|
|
97
103
|
if (!users.length || arg.when === undefined)
|
|
98
104
|
return arg;
|
|
99
105
|
const when = Array.isArray(arg.when) ? arg.when : [arg.when];
|
|
@@ -21,6 +21,8 @@ function sectionLines(section) {
|
|
|
21
21
|
lines.push(c.bold(section.title) + (section.summary ? ' ' + section.summary : ''));
|
|
22
22
|
if (section.facts?.length)
|
|
23
23
|
lines.push(...tableLines(section.facts.map((f) => [f.label, f.value])));
|
|
24
|
+
if (section.text?.length)
|
|
25
|
+
lines.push(...section.text);
|
|
24
26
|
if (section.columns && items.length) {
|
|
25
27
|
const columns = section.columns;
|
|
26
28
|
lines.push(...tableLines([
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import { sizeOf } from '
|
|
6
|
-
import { dirExists, isLink } from '
|
|
5
|
+
import { sizeOf } from '../../core/disk.js';
|
|
6
|
+
import { dirExists, isLink } from '../../core/util/index.js';
|
|
7
7
|
const AREA_ORDER = ['tools', 'databases', 'state', 'logs', 'data', 'host'];
|
|
8
8
|
/** Folders of the data folder that get one row per child; anything else at the top level is listed as itself. */
|
|
9
9
|
const PER_CHILD = ['tools', 'databases', 'state'];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Deleting inside the data folder. The sandbox can write there, so links are removed themselves and never followed.
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { dirExists, isLink, removeLink } from '
|
|
5
|
-
import { removeRefusal, safeRemove } from '
|
|
4
|
+
import { dirExists, isLink, removeLink } from '../../core/util/index.js';
|
|
5
|
+
import { removeRefusal, safeRemove } from '../../shared/io/index.js';
|
|
6
6
|
/** Deletes p (strictly inside root): links inside are removed first and never followed; see shared/safe-remove.ts. */
|
|
7
7
|
export function removeTree(p, root) {
|
|
8
8
|
const unlinkInside = (dir) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DiskUsage } from '../../core/disk.js';
|
|
2
2
|
import type { Context } from '../commands/context.js';
|
|
3
|
-
import { type DataEntry } from '
|
|
3
|
+
import { type DataEntry } from './data-collect.js';
|
|
4
4
|
import { type CleanTarget } from './data-targets.js';
|
|
5
5
|
export interface DataReport {
|
|
6
6
|
readonly dataPath: string;
|
|
@@ -6,8 +6,8 @@ import _ from 'lodash';
|
|
|
6
6
|
import { diskUsage, sizeOf } from '../../core/disk.js';
|
|
7
7
|
import { assertNoLinks, errMsg, fail, isLink } from '../../core/util/index.js';
|
|
8
8
|
import { isSandboxRunning } from '../../host/sandbox/index.js';
|
|
9
|
-
import { collectDataEntries } from '
|
|
10
|
-
import { removeTree } from '
|
|
9
|
+
import { collectDataEntries } from './data-collect.js';
|
|
10
|
+
import { removeTree } from './data-remove.js';
|
|
11
11
|
import { resolveCleanTargets } from './data-targets.js';
|
|
12
12
|
export class DataService {
|
|
13
13
|
ctx;
|
|
@@ -23,5 +23,10 @@ export declare class DatabasesService {
|
|
|
23
23
|
/** The databases the sandbox can start and whether each runs now. */
|
|
24
24
|
list(cwd: string): Promise<DatabaseRow[]>;
|
|
25
25
|
/** Running databases by port, from `sbx-db list` (best effort: null when the sandbox does not answer). */
|
|
26
|
+
/**
|
|
27
|
+
* The ports of the databases running in the sandbox (null: it could not be asked). Asked inside the sandbox, so a
|
|
28
|
+
* live page reading it every few seconds shares one answer for PORTS_FRESH_MS.
|
|
29
|
+
*/
|
|
26
30
|
runningPorts(cwd: string): Promise<Set<number> | null>;
|
|
31
|
+
private askPorts;
|
|
27
32
|
}
|
|
@@ -11,6 +11,9 @@ export function parseDatabaseList(stdout) {
|
|
|
11
11
|
return m ? [{ name: m[1], running: m[2] === 'running', port: Number(m[3]), title: safeText(m[4]) }] : [];
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
+
/** How long an answer about the running databases' ports is reused (they change only when one starts or stops). */
|
|
15
|
+
const PORTS_FRESH_MS = 20_000;
|
|
16
|
+
const recentPorts = new Map();
|
|
14
17
|
export class DatabasesService {
|
|
15
18
|
ctx;
|
|
16
19
|
constructor(ctx) {
|
|
@@ -41,7 +44,19 @@ export class DatabasesService {
|
|
|
41
44
|
return parseDatabaseList((await this.run('list', undefined, cwd)).stdout ?? '');
|
|
42
45
|
}
|
|
43
46
|
/** Running databases by port, from `sbx-db list` (best effort: null when the sandbox does not answer). */
|
|
47
|
+
/**
|
|
48
|
+
* The ports of the databases running in the sandbox (null: it could not be asked). Asked inside the sandbox, so a
|
|
49
|
+
* live page reading it every few seconds shares one answer for PORTS_FRESH_MS.
|
|
50
|
+
*/
|
|
44
51
|
async runningPorts(cwd) {
|
|
52
|
+
const known = recentPorts.get(cwd);
|
|
53
|
+
if (known && Date.now() - known.at < PORTS_FRESH_MS)
|
|
54
|
+
return known.ports;
|
|
55
|
+
const ports = await this.askPorts(cwd);
|
|
56
|
+
recentPorts.set(cwd, { at: Date.now(), ports });
|
|
57
|
+
return ports;
|
|
58
|
+
}
|
|
59
|
+
async askPorts(cwd) {
|
|
45
60
|
try {
|
|
46
61
|
const r = await new SandboxChannel(this.ctx.dataPath).exec(['sbx-db', 'list'], cwd, 30);
|
|
47
62
|
return new Set((r.stdout ?? '')
|
|
@@ -11,12 +11,16 @@ export interface SetupReport {
|
|
|
11
11
|
export declare class HealthService {
|
|
12
12
|
private readonly ctx;
|
|
13
13
|
constructor(ctx: Context);
|
|
14
|
-
/** The table `doctor` shows: [name, detail] rows. */
|
|
15
|
-
doctor(): string[][];
|
|
14
|
+
/** The table `doctor` shows: [name, detail] rows (for the agent given, default: this folder's or the default one). */
|
|
15
|
+
doctor(harness?: string | undefined): string[][];
|
|
16
16
|
/** Fails unless this is Windows: Windows Sandbox runs nowhere else. */
|
|
17
17
|
assertWindows(): void;
|
|
18
18
|
/** The setup checklist for this PC. */
|
|
19
19
|
setupReport(): SetupReport;
|
|
20
|
-
/**
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Runs the enable command in an elevated PowerShell (Windows asks for permission); true if it worked. The helper
|
|
22
|
+
* reads no input and writes nothing to the console (its exit code is the result), so 'ignore' is safe where there is
|
|
23
|
+
* no terminal to give the prompt (the menu and the web).
|
|
24
|
+
*/
|
|
25
|
+
enableFeature(stdio?: 'inherit' | 'ignore'): boolean;
|
|
22
26
|
}
|
|
@@ -15,10 +15,10 @@ export class HealthService {
|
|
|
15
15
|
constructor(ctx) {
|
|
16
16
|
this.ctx = ctx;
|
|
17
17
|
}
|
|
18
|
-
/** The table `doctor` shows: [name, detail] rows. */
|
|
19
|
-
doctor() {
|
|
18
|
+
/** The table `doctor` shows: [name, detail] rows (for the agent given, default: this folder's or the default one). */
|
|
19
|
+
doctor(harness = this.ctx.options.harness) {
|
|
20
20
|
const { ctx } = this;
|
|
21
|
-
const rows = doctorRows(ctx.loaded.config,
|
|
21
|
+
const rows = doctorRows(ctx.loaded.config, harness ?? ctx.loaded.config.defaultHarness, ctx.storageRoot, ctx.loaded.configPath);
|
|
22
22
|
// Inside a sandbox the feature and "running" rows describe the sandbox itself, and the host rows describe nothing
|
|
23
23
|
// useful.
|
|
24
24
|
if (insideSandbox())
|
|
@@ -73,11 +73,13 @@ export class HealthService {
|
|
|
73
73
|
const facts = gatherFacts(this.ctx.storageRoot);
|
|
74
74
|
return { facts, checks: buildChecks(facts), canEnable: canEnableFeature(facts) };
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Runs the enable command in an elevated PowerShell (Windows asks for permission); true if it worked. The helper
|
|
78
|
+
* reads no input and writes nothing to the console (its exit code is the result), so 'ignore' is safe where there is
|
|
79
|
+
* no terminal to give the prompt (the menu and the web).
|
|
80
|
+
*/
|
|
81
|
+
enableFeature(stdio = 'inherit') {
|
|
78
82
|
refuseInSelfTest('turn on Windows features');
|
|
79
|
-
return (spawnSync(SYSTEM.powershell(), nativeScript('scripts/run-elevated.ps1', ['-Command', ENABLE_COMMAND + ' | Out-Null']), {
|
|
80
|
-
stdio: 'inherit',
|
|
81
|
-
}).status === 0);
|
|
83
|
+
return (spawnSync(SYSTEM.powershell(), nativeScript('scripts/run-elevated.ps1', ['-Command', ENABLE_COMMAND + ' | Out-Null']), { stdio }).status === 0);
|
|
82
84
|
}
|
|
83
85
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export { AccountsService, type AccountRef, type AccountsContext, type AgentAccounts } from './accounts-service.js';
|
|
2
2
|
export { AgentCommandsService, type AgentCommand, type AgentCommandsStatus } from './agent-commands-service.js';
|
|
3
3
|
export { DataService, type CleanOutcome, type CleanPlan, type DataReport, type SizedTarget } from './data-service.js';
|
|
4
|
+
export { cleanTargetOf, collectDataEntries, type DataArea, type DataEntry } from './data-collect.js';
|
|
5
|
+
export { removeTree } from './data-remove.js';
|
|
4
6
|
export { resolveCleanTargets, type CleanTarget } from './data-targets.js';
|
|
5
7
|
export { DatabasesService, parseDatabaseList, type DatabaseAction, type DatabaseRow } from './databases-service.js';
|
|
6
8
|
export { autoMigrates, continuesAfterFailedMigration, MigrateService, type MigrateContext, type MigrateResult, } from './migrate-service.js';
|
|
7
9
|
export { NPM_UPDATE_COMMAND, UpdateService, runsAsSystem, type CheckResult, type UpdateServiceOptions, type UpdateStatus, type UpdateTaskPort, } from './update-service.js';
|
|
8
|
-
export {
|
|
10
|
+
export { VpnService, type VpnReport } from './vpn-service.js';
|
|
9
11
|
export { LogsService, type LogFile } from './logs-service.js';
|
|
10
12
|
export { WebService, webSession } from './web-service.js';
|
|
11
13
|
export { HealthService, type SetupReport } from './health-service.js';
|
|
12
14
|
export { ProjectsService, type ProjectRow, type ProjectSettings, type ProjectsListing } from './projects-service.js';
|
|
13
15
|
export { SandboxService, type OtherSession, type ProjectInfo, type SandboxReport, type SandboxState, type StopPlan, } from './sandbox-service.js';
|
|
14
16
|
export { SessionsService, projectOf, viewerText, type ListedSession, type RestartScope } from './sessions-service.js';
|
|
17
|
+
export { StorageService, storageBusy } from './storage-service.js';
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export { AccountsService } from './accounts-service.js';
|
|
2
2
|
export { AgentCommandsService } from './agent-commands-service.js';
|
|
3
3
|
export { DataService } from './data-service.js';
|
|
4
|
+
export { cleanTargetOf, collectDataEntries } from './data-collect.js';
|
|
5
|
+
export { removeTree } from './data-remove.js';
|
|
4
6
|
export { resolveCleanTargets } from './data-targets.js';
|
|
5
7
|
export { DatabasesService, parseDatabaseList } from './databases-service.js';
|
|
6
8
|
export { autoMigrates, continuesAfterFailedMigration, MigrateService, } from './migrate-service.js';
|
|
7
9
|
export { NPM_UPDATE_COMMAND, UpdateService, runsAsSystem, } from './update-service.js';
|
|
8
|
-
export {
|
|
10
|
+
export { VpnService } from './vpn-service.js';
|
|
9
11
|
export { LogsService } from './logs-service.js';
|
|
10
12
|
export { WebService, webSession } from './web-service.js';
|
|
11
13
|
export { HealthService } from './health-service.js';
|
|
12
14
|
export { ProjectsService } from './projects-service.js';
|
|
13
15
|
export { SandboxService, } from './sandbox-service.js';
|
|
14
16
|
export { SessionsService, projectOf, viewerText } from './sessions-service.js';
|
|
17
|
+
export { StorageService, storageBusy } from './storage-service.js';
|
|
@@ -12,6 +12,13 @@ export declare class LogsService {
|
|
|
12
12
|
private dir;
|
|
13
13
|
/** Log files, newest first. */
|
|
14
14
|
files(): LogFile[];
|
|
15
|
+
/** The folder the logs are in (whether or not it exists yet). */
|
|
16
|
+
folder(): string;
|
|
17
|
+
/**
|
|
18
|
+
* The path of the log called `name`: only an exact name from files() is accepted, so a name from outside (a browser)
|
|
19
|
+
* can never become a path of its own.
|
|
20
|
+
*/
|
|
21
|
+
byName(name: string): string;
|
|
15
22
|
/** The newest log, or a UserError when there is none yet. */
|
|
16
23
|
newest(): string;
|
|
17
24
|
/**
|
|
@@ -32,6 +32,19 @@ export class LogsService {
|
|
|
32
32
|
});
|
|
33
33
|
return _.orderBy(found, 'modified', 'desc');
|
|
34
34
|
}
|
|
35
|
+
/** The folder the logs are in (whether or not it exists yet). */
|
|
36
|
+
folder() {
|
|
37
|
+
return hostDataFolder(this.ctx.dataPath, 'logs');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The path of the log called `name`: only an exact name from files() is accepted, so a name from outside (a browser)
|
|
41
|
+
* can never become a path of its own.
|
|
42
|
+
*/
|
|
43
|
+
byName(name) {
|
|
44
|
+
const found = this.files().find((f) => f.name === name);
|
|
45
|
+
return (found?.path ??
|
|
46
|
+
fail('There is no log called ' + safeText(name) + '.', 'Pick one from: sandbox logs, or the Logs page.'));
|
|
47
|
+
}
|
|
35
48
|
/** The newest log, or a UserError when there is none yet. */
|
|
36
49
|
newest() {
|
|
37
50
|
return (this.files()[0]?.path ??
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type MoveProgress, type MoveResult } from '../../host/storage/index.js';
|
|
2
|
+
import type { Context } from '../commands/context.js';
|
|
3
|
+
/** Why a move cannot start (a background sync is still on a data folder in the old storage), or null. */
|
|
4
|
+
export declare function storageBusy(root: string): string | null;
|
|
5
|
+
export declare class StorageService {
|
|
6
|
+
private readonly ctx;
|
|
7
|
+
constructor(ctx: Pick<Context, 'loaded' | 'storageRoot'>);
|
|
8
|
+
/** Why a move or a switch cannot happen now, or null. */
|
|
9
|
+
busy(): string | null;
|
|
10
|
+
/** Throws a UserError when moving to `to` is not possible right now. */
|
|
11
|
+
validate(to: string): void;
|
|
12
|
+
/** Moves the data (copying across drives) and remembers both folders. */
|
|
13
|
+
move(to: string, keepOld: boolean, onProgress?: (progress: MoveProgress) => void): Promise<MoveResult>;
|
|
14
|
+
/** Looks for the data in `to` from now on, without moving it. The caller has checked `busy()`. */
|
|
15
|
+
use(to: string): void;
|
|
16
|
+
/** The folders offered for a choice: the default, folders that hold data, and each other drive's AgentSandbox. */
|
|
17
|
+
knownFolders(): string[];
|
|
18
|
+
private mover;
|
|
19
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// The storage folder's work for the command line, the menu and the web: where the saved data is, moving it (both
|
|
2
|
+
// folders remembered), switching to another folder without moving, and the folders offered for a choice. Never prints.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { userConfigPath, userDir } from '../../core/config/index.js';
|
|
6
|
+
import { diskUsage } from '../../core/disk.js';
|
|
7
|
+
import { protectPrivateFolder } from '../../host/sharing/index.js';
|
|
8
|
+
import { isSandboxRunning } from '../../host/sandbox/index.js';
|
|
9
|
+
import { mountedSets, sharedDefaultRoot, StorageHistory, StorageMove, otherDataFolders, } from '../../host/storage/index.js';
|
|
10
|
+
import { watcherRunning } from '../../sync/index.js';
|
|
11
|
+
/** Why a move cannot start (a background sync is still on a data folder in the old storage), or null. */
|
|
12
|
+
export function storageBusy(root) {
|
|
13
|
+
const mounted = mountedSets(root);
|
|
14
|
+
if (mounted.length > 0)
|
|
15
|
+
return ('The contained storage of "' +
|
|
16
|
+
mounted.join('", "') +
|
|
17
|
+
'" is mounted (a disk image; moving now would reach into it).');
|
|
18
|
+
const data = path.join(root, 'data');
|
|
19
|
+
try {
|
|
20
|
+
for (const name of fs.readdirSync(data))
|
|
21
|
+
if (watcherRunning(path.join(data, name)))
|
|
22
|
+
return 'The background sync of "' + name + '" is still finishing.';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// no data folder: nothing runs
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
export class StorageService {
|
|
30
|
+
ctx;
|
|
31
|
+
constructor(ctx) {
|
|
32
|
+
this.ctx = ctx;
|
|
33
|
+
}
|
|
34
|
+
/** Why a move or a switch cannot happen now, or null. */
|
|
35
|
+
busy() {
|
|
36
|
+
return storageBusy(this.ctx.storageRoot);
|
|
37
|
+
}
|
|
38
|
+
/** Throws a UserError when moving to `to` is not possible right now. */
|
|
39
|
+
validate(to) {
|
|
40
|
+
this.mover().validate(this.ctx.storageRoot, to);
|
|
41
|
+
}
|
|
42
|
+
/** Moves the data (copying across drives) and remembers both folders. */
|
|
43
|
+
async move(to, keepOld, onProgress) {
|
|
44
|
+
const from = this.ctx.storageRoot;
|
|
45
|
+
const result = await this.mover().move(from, to, {
|
|
46
|
+
keepOld,
|
|
47
|
+
...(onProgress ? { onProgress } : {}),
|
|
48
|
+
});
|
|
49
|
+
const history = new StorageHistory(userDir());
|
|
50
|
+
history.remember(from);
|
|
51
|
+
history.remember(to);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/** Looks for the data in `to` from now on, without moving it. The caller has checked `busy()`. */
|
|
55
|
+
use(to) {
|
|
56
|
+
new StorageHistory(userDir()).remember(this.ctx.storageRoot);
|
|
57
|
+
this.mover().pin(to);
|
|
58
|
+
}
|
|
59
|
+
/** The folders offered for a choice: the default, folders that hold data, and each other drive's AgentSandbox. */
|
|
60
|
+
knownFolders() {
|
|
61
|
+
const root = this.ctx.storageRoot;
|
|
62
|
+
const known = [sharedDefaultRoot(), ...otherDataFolders(root, new StorageHistory(userDir()))];
|
|
63
|
+
const drives = diskUsage(root)
|
|
64
|
+
.filter((d) => !d.holdsStorage)
|
|
65
|
+
.map((d) => path.join(d.drive, 'AgentSandbox'));
|
|
66
|
+
return [...new Set([...known, ...drives])].filter((f) => f.toLowerCase() !== root.toLowerCase());
|
|
67
|
+
}
|
|
68
|
+
mover() {
|
|
69
|
+
return new StorageMove(this.ctx.loaded.configPath ?? userConfigPath(), () => storageBusy(this.ctx.storageRoot), isSandboxRunning, protectPrivateFolder);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Config } from '../../core/types.js';
|
|
2
2
|
import type { VpnProvider } from '../../core/schema/index.js';
|
|
3
|
+
import type { SecretStore } from '../../host/secrets/index.js';
|
|
3
4
|
import { type LoginState, type VpnCredentials, type VpnLine } from '../../host/vpn/index.js';
|
|
4
5
|
import type { Context } from '../commands/context.js';
|
|
5
6
|
export interface VpnReport {
|
|
@@ -13,15 +14,16 @@ export interface VpnReport {
|
|
|
13
14
|
/** The running sandbox's tunnel, when one runs with the VPN on. */
|
|
14
15
|
readonly running: VpnLine | null;
|
|
15
16
|
}
|
|
16
|
-
/** The steps to get the NordVPN service credentials, in the words of Nord Account (shown before asking). */
|
|
17
|
-
export declare const NORD_STEPS: readonly string[];
|
|
18
17
|
export declare class VpnService {
|
|
19
18
|
private readonly ctx;
|
|
20
19
|
private readonly login;
|
|
21
|
-
constructor(ctx: Context);
|
|
20
|
+
constructor(ctx: Context, secrets?: SecretStore);
|
|
22
21
|
private get config();
|
|
23
22
|
get provider(): VpnProvider;
|
|
24
|
-
|
|
23
|
+
/** Without `tunnel` the sandbox is not asked (no request to the network): `running` is then null. */
|
|
24
|
+
report({ tunnel }?: {
|
|
25
|
+
tunnel?: boolean;
|
|
26
|
+
}): Promise<VpnReport>;
|
|
25
27
|
loginState(): LoginState;
|
|
26
28
|
save(credentials: VpnCredentials): void;
|
|
27
29
|
remove(): boolean;
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { isSandboxRunning } from '../../host/sandbox/index.js';
|
|
2
2
|
import { hostPublicIp, readVpnLine, VpnLogin, } from '../../host/vpn/index.js';
|
|
3
|
-
/** The steps to get the NordVPN service credentials, in the words of Nord Account (shown before asking). */
|
|
4
|
-
export const NORD_STEPS = [
|
|
5
|
-
'Sign in at https://my.nordaccount.com with your usual NordVPN e-mail and password.',
|
|
6
|
-
'Open NordVPN, then "Set up NordVPN manually" (under Advanced settings).',
|
|
7
|
-
'Verify your e-mail if it asks, then copy the "Service credentials": a user name and a password.',
|
|
8
|
-
'These are NOT your account password. You can revoke them in the same place whenever you like.',
|
|
9
|
-
];
|
|
10
3
|
export class VpnService {
|
|
11
4
|
ctx;
|
|
12
5
|
login;
|
|
13
|
-
constructor(ctx) {
|
|
6
|
+
constructor(ctx, secrets) {
|
|
14
7
|
this.ctx = ctx;
|
|
15
|
-
this.login = new VpnLogin(ctx.storageRoot);
|
|
8
|
+
this.login = new VpnLogin(ctx.storageRoot, secrets);
|
|
16
9
|
}
|
|
17
10
|
get config() {
|
|
18
11
|
return this.ctx.loaded.config.vpn;
|
|
@@ -20,9 +13,10 @@ export class VpnService {
|
|
|
20
13
|
get provider() {
|
|
21
14
|
return this.config.provider;
|
|
22
15
|
}
|
|
23
|
-
|
|
16
|
+
/** Without `tunnel` the sandbox is not asked (no request to the network): `running` is then null. */
|
|
17
|
+
async report({ tunnel = true } = {}) {
|
|
24
18
|
const c = this.config;
|
|
25
|
-
const running = isSandboxRunning();
|
|
19
|
+
const running = tunnel && isSandboxRunning();
|
|
26
20
|
return {
|
|
27
21
|
enabled: c.enabled,
|
|
28
22
|
provider: c.provider,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { SessionStatus } from '../../core/types.js';
|
|
2
2
|
import type { WebBackend } from '../../host/web/index.js';
|
|
3
3
|
import type { Endpoint } from '../../shared/pty/index.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { WebInfo, WebProject, WebSession } from '../../shared/web/index.js';
|
|
5
5
|
import type { Context } from '../commands/context.js';
|
|
6
|
+
import { type SandboxView } from '../commands/shared-use.js';
|
|
6
7
|
import { type ListedSession } from './sessions-service.js';
|
|
7
8
|
/** A session as the browser may see it: no attach address or token, text made safe. */
|
|
8
9
|
export declare function webSession(s: SessionStatus | ListedSession, viewers: number | null | undefined): WebSession;
|
|
10
|
+
/** The projects of the running sandbox as the browser may see them (its guest folders made safe). */
|
|
11
|
+
export declare function runningProjects(view: SandboxView): WebProject[];
|
|
9
12
|
export declare class WebService implements WebBackend {
|
|
10
13
|
private readonly ctx;
|
|
11
14
|
private cached;
|
|
@@ -14,9 +17,7 @@ export declare class WebService implements WebBackend {
|
|
|
14
17
|
private view;
|
|
15
18
|
/** The sandbox is this user's, or another user's and shared with them. */
|
|
16
19
|
private mayUse;
|
|
17
|
-
private projects;
|
|
18
20
|
info(): Promise<WebInfo>;
|
|
19
21
|
sessions(): Promise<WebSession[]>;
|
|
20
22
|
endpoint(sessionId: string): Endpoint | null;
|
|
21
|
-
create(request: NewSessionRequest): Promise<WebSession>;
|
|
22
23
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// What the web UI's server may do with the running sandbox: the same sessions, projects and rules as the CLI (another
|
|
2
2
|
// user's sandbox is shown and used only as far as the sharing rules allow). Returns data, never prints.
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import { hostEndpoint
|
|
4
|
+
import { safeText } from '../../core/util/index.js';
|
|
5
|
+
import { hostEndpoint } from '../../host/channel/index.js';
|
|
6
6
|
import { insideSandbox, isSandboxRunning } from '../../host/sandbox/index.js';
|
|
7
7
|
import { ownerRule } from '../../host/sharing/index.js';
|
|
8
8
|
import { RunDir } from '../../shared/channel/index.js';
|
|
@@ -11,7 +11,6 @@ import { registryOf } from '../commands/projects/here.js';
|
|
|
11
11
|
import { SessionsService } from './sessions-service.js';
|
|
12
12
|
/** The owner is asked (a process listing) at most this often: the browser polls. */
|
|
13
13
|
const VIEW_TTL_MS = 3000;
|
|
14
|
-
const SESSION_START_MS = 15 * 60 * 1000;
|
|
15
14
|
const MAX_SESSIONS = 50;
|
|
16
15
|
/** A session as the browser may see it: no attach address or token, text made safe. */
|
|
17
16
|
export function webSession(s, viewers) {
|
|
@@ -27,6 +26,15 @@ export function webSession(s, viewers) {
|
|
|
27
26
|
canOpen: s.phase === 'running' && hostEndpoint(s) !== null,
|
|
28
27
|
};
|
|
29
28
|
}
|
|
29
|
+
/** The projects of the running sandbox as the browser may see them (its guest folders made safe). */
|
|
30
|
+
export function runningProjects(view) {
|
|
31
|
+
const known = registryOf(view.ctx).read()?.projects ?? [];
|
|
32
|
+
if (known.length)
|
|
33
|
+
return known.map((p) => ({ name: p.name, guest: safeText(p.guest) }));
|
|
34
|
+
// A sandbox from before projects existed has only its launch folder.
|
|
35
|
+
const status = new RunDir(view.ctx.dataPath).readSandboxStatus();
|
|
36
|
+
return status ? [{ name: 'workspace', guest: safeText(status.guestWorkspace) }] : [];
|
|
37
|
+
}
|
|
30
38
|
export class WebService {
|
|
31
39
|
ctx;
|
|
32
40
|
cached = null;
|
|
@@ -46,14 +54,6 @@ export class WebService {
|
|
|
46
54
|
mayUse(view) {
|
|
47
55
|
return !view.owner || ownerRule(view.owner, { kind: 'use' }).kind === 'allow';
|
|
48
56
|
}
|
|
49
|
-
projects(view) {
|
|
50
|
-
const known = registryOf(view.ctx).read()?.projects ?? [];
|
|
51
|
-
if (known.length)
|
|
52
|
-
return known.map((p) => ({ name: p.name, guest: safeText(p.guest) }));
|
|
53
|
-
// A sandbox from before projects existed has only its launch folder.
|
|
54
|
-
const status = new RunDir(view.ctx.dataPath).readSandboxStatus();
|
|
55
|
-
return status ? [{ name: 'workspace', guest: safeText(status.guestWorkspace) }] : [];
|
|
56
|
-
}
|
|
57
57
|
info() {
|
|
58
58
|
const config = this.ctx.loaded.config;
|
|
59
59
|
const view = this.view();
|
|
@@ -63,7 +63,7 @@ export class WebService {
|
|
|
63
63
|
canUse: view !== null && this.mayUse(view),
|
|
64
64
|
harnesses: Object.keys(config.harnesses),
|
|
65
65
|
defaultHarness: config.defaultHarness,
|
|
66
|
-
projects: view && this.mayUse(view) ?
|
|
66
|
+
projects: view && this.mayUse(view) ? runningProjects(view) : [],
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
async sessions() {
|
|
@@ -88,24 +88,4 @@ export class WebService {
|
|
|
88
88
|
return null;
|
|
89
89
|
return hostEndpoint(session);
|
|
90
90
|
}
|
|
91
|
-
async create(request) {
|
|
92
|
-
const view = this.view();
|
|
93
|
-
if (!view)
|
|
94
|
-
return fail('No Windows Sandbox is running.', 'Start one with sandbox claude, then try again.');
|
|
95
|
-
if (view.owner && view.owner.kind !== 'none' && view.owner.kind !== 'mine') {
|
|
96
|
-
const rule = ownerRule(view.owner, { kind: 'use' });
|
|
97
|
-
if (rule.kind === 'refuse')
|
|
98
|
-
return fail(rule.reason, rule.fix);
|
|
99
|
-
}
|
|
100
|
-
const project = this.projects(view).find((p) => p.name === request.project);
|
|
101
|
-
if (!project)
|
|
102
|
-
return fail('The running sandbox has no project "' + request.project + '".');
|
|
103
|
-
const harness = view.ctx.loaded.config.harnesses[request.harness];
|
|
104
|
-
if (!harness)
|
|
105
|
-
return fail('There is no agent called "' + request.harness + '".');
|
|
106
|
-
const channel = new SandboxChannel(view.ctx.dataPath);
|
|
107
|
-
const id = channel.requestSession(request.harness, [], project.guest, { harnessSpec: harness });
|
|
108
|
-
const session = await channel.waitForSession(id, SESSION_START_MS, () => undefined);
|
|
109
|
-
return webSession(session, null);
|
|
110
|
-
}
|
|
111
91
|
}
|
|
@@ -37,6 +37,7 @@ export const DEFAULTS = {
|
|
|
37
37
|
},
|
|
38
38
|
network: { blockPrivateRanges: true, allowOnly: [], blockHosts: [], forwardPorts: [], dns: [], proxy: '' },
|
|
39
39
|
vpn: { enabled: false, provider: 'nordvpn', protocol: 'udp', country: '', configFile: '', killSwitch: true },
|
|
40
|
+
github: { shareToken: false },
|
|
40
41
|
provisioning: {
|
|
41
42
|
copyFromHost: true,
|
|
42
43
|
fallbackToInstall: true,
|
|
@@ -93,7 +94,7 @@ export const DEFAULTS = {
|
|
|
93
94
|
dynamodb: { enabled: 'auto', port: 8000, adminPort: 8001, admin: true },
|
|
94
95
|
sqlserver: { enabled: 'auto' },
|
|
95
96
|
},
|
|
96
|
-
attach: { enabled: true, reuseSession: true, auto: false, autoHide: false },
|
|
97
|
+
attach: { enabled: true, reuseSession: true, auto: false, autoHide: false, confirmCtrlC: true },
|
|
97
98
|
web: { enabled: false, port: 47800, openOnLaunch: false },
|
|
98
99
|
agentCommands: { enabled: false, agents: 'auto' },
|
|
99
100
|
secrets: { strip: true, allow: [], patterns: [], warnFiles: true },
|