windows-development-sandbox 1.11.7 → 1.13.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 +13 -6
- package/config.example.json +15 -4
- package/dist/app/features/access/access.def.d.ts +190 -0
- package/dist/app/features/access/access.def.js +273 -0
- package/dist/app/features/access/access.run.d.ts +79 -0
- package/dist/app/features/access/access.run.js +129 -0
- package/dist/app/features/access/index.d.ts +2 -0
- package/dist/app/features/access/index.js +3 -0
- package/dist/app/features/agents/agent-entry.js +5 -0
- package/dist/app/features/agents/agents.def.js +1 -0
- package/dist/app/features/apps/apps-rows.d.ts +4 -1
- package/dist/app/features/apps/apps-rows.js +7 -2
- package/dist/app/features/apps/apps.def.d.ts +19 -0
- package/dist/app/features/apps/apps.def.js +50 -12
- package/dist/app/features/apps/apps.run.d.ts +2 -0
- package/dist/app/features/apps/apps.run.js +1 -1
- package/dist/app/features/audit/audit.def.d.ts +350 -0
- package/dist/app/features/audit/audit.def.js +457 -0
- package/dist/app/features/audit/audit.run.d.ts +110 -0
- package/dist/app/features/audit/audit.run.js +133 -0
- package/dist/app/features/audit/index.d.ts +2 -0
- package/dist/app/features/audit/index.js +3 -0
- package/dist/app/features/defs.d.ts +349 -4
- package/dist/app/features/defs.js +16 -1
- package/dist/app/features/files/files.def.js +1 -0
- package/dist/app/features/health/health.def.js +1 -0
- package/dist/app/features/sandbox/sandbox.def.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +36 -0
- package/dist/app/features/sessions/sessions.def.js +77 -9
- package/dist/app/features/sessions/sessions.run.d.ts +25 -2
- package/dist/app/features/sessions/sessions.run.js +71 -3
- package/dist/app/features/settings/settings.def.d.ts +8 -0
- package/dist/app/features/settings/settings.def.js +17 -0
- package/dist/app/features/settings/settings.run.d.ts +3 -0
- package/dist/app/features/settings/settings.run.js +16 -2
- package/dist/app/features/web/web.def.d.ts +9 -0
- package/dist/app/features/web/web.def.js +26 -1
- package/dist/app/features/web/web.run.d.ts +3 -0
- package/dist/app/features/web/web.run.js +11 -3
- package/dist/app/kernel/access.d.ts +33 -0
- package/dist/app/kernel/access.js +44 -0
- package/dist/app/kernel/define.d.ts +26 -2
- package/dist/app/kernel/index.d.ts +1 -0
- package/dist/app/kernel/index.js +1 -0
- package/dist/app/kernel/rpc.d.ts +5 -2
- package/dist/app/registry.d.ts +4 -1
- package/dist/app/registry.js +26 -4
- package/dist/cli/args.d.ts +0 -3
- package/dist/cli/args.js +3 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/prepare.js +1 -1
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +0 -1
- package/dist/cli/commands/registry.js +3 -2
- package/dist/cli/commands/remote.d.ts +2 -0
- package/dist/cli/commands/remote.js +49 -0
- package/dist/cli/commands/sessions/reopen.js +3 -0
- package/dist/cli/commands/web.js +66 -0
- package/dist/cli/manifest.d.ts +63 -45
- package/dist/cli/manifest.js +33 -18
- package/dist/cli/menu/io-menu.d.ts +5 -3
- package/dist/cli/menu/io-menu.js +12 -5
- package/dist/cli/menu/pages/apps-page.js +2 -15
- package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
- package/dist/cli/menu/pages/maintenance-page.js +51 -8
- package/dist/cli/menu/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/views.js +4 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/remote-client.d.ts +16 -0
- package/dist/cli/services/remote-client.js +141 -0
- package/dist/cli/services/sessions-service.d.ts +5 -0
- package/dist/cli/services/sessions-service.js +17 -1
- package/dist/core/config/defaults.js +12 -2
- package/dist/core/local-config/file.js +16 -2
- package/dist/core/local-config/overrides.js +7 -0
- package/dist/core/run/versions.js +9 -8
- package/dist/core/schema/apps.d.ts +9 -0
- package/dist/core/schema/apps.js +9 -0
- package/dist/core/schema/config.d.ts +23 -3
- package/dist/core/schema/config.js +42 -5
- package/dist/core/schema/sandbox.d.ts +1 -0
- package/dist/core/schema/sandbox.js +11 -3
- package/dist/core/schema/web.d.ts +51 -0
- package/dist/core/schema/web.js +46 -1
- package/dist/core/util/fs.d.ts +5 -0
- package/dist/core/util/fs.js +23 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/apps/app-installer.js +50 -4
- package/dist/guest/apps/index.d.ts +1 -1
- package/dist/guest/apps/index.js +1 -1
- package/dist/guest/apps/sources.js +1 -1
- package/dist/guest/audit/parse.d.ts +32 -2
- package/dist/guest/audit/parse.js +43 -5
- package/dist/guest/audit/process-audit.d.ts +3 -1
- package/dist/guest/audit/process-audit.js +9 -6
- package/dist/guest/boot/boot.js +12 -3
- package/dist/guest/boot/guide.d.ts +1 -1
- package/dist/guest/boot/guide.js +27 -3
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -1
- package/dist/guest/daemon/sessions.d.ts +23 -2
- package/dist/guest/daemon/sessions.js +151 -20
- package/dist/guest/entries/daemon.js +2 -1
- package/dist/guest/git/decision.js +7 -0
- package/dist/guest/sessions/cmdline.d.ts +14 -0
- package/dist/guest/sessions/cmdline.js +10 -0
- package/dist/guest/setup/addon-mcp.d.ts +2 -0
- package/dist/guest/setup/addon-mcp.js +49 -0
- package/dist/guest/setup/agent-addons.d.ts +9 -0
- package/dist/guest/setup/agent-addons.js +22 -0
- package/dist/guest/setup/headroom.d.ts +7 -0
- package/dist/guest/setup/headroom.js +60 -0
- package/dist/guest/setup/managed-policy.d.ts +2 -1
- package/dist/guest/setup/managed-policy.js +15 -6
- package/dist/guest/toolchains/plan.js +8 -3
- package/dist/guest/toolchains/setup.js +3 -0
- package/dist/host/audit/attribute.d.ts +46 -0
- package/dist/host/audit/attribute.js +76 -0
- package/dist/host/audit/collect.d.ts +29 -3
- package/dist/host/audit/collect.js +256 -61
- package/dist/host/audit/git-commits.d.ts +29 -0
- package/dist/host/audit/git-commits.js +157 -0
- package/dist/host/audit/index.d.ts +5 -0
- package/dist/host/audit/index.js +6 -0
- package/dist/host/audit/log.d.ts +45 -7
- package/dist/host/audit/log.js +148 -45
- package/dist/host/audit/redact.d.ts +2 -0
- package/dist/host/audit/redact.js +67 -0
- package/dist/host/channel/channel.d.ts +5 -1
- package/dist/host/channel/channel.js +12 -0
- package/dist/host/guard/agent-files.js +7 -1
- package/dist/host/guard/commands.js +13 -1
- package/dist/host/guard/git-config.js +27 -7
- package/dist/host/guard/git-dirs.js +78 -3
- package/dist/host/guard/mark-of-the-web.js +3 -1
- package/dist/host/guard/nested-repos.js +7 -2
- package/dist/host/guard/store.js +1 -0
- package/dist/host/guard/types.d.ts +2 -0
- package/dist/host/launch/mappings.js +11 -1
- package/dist/host/launch/project-scan.js +2 -21
- package/dist/host/launch/stage.js +4 -0
- package/dist/host/life/session-ledger.js +2 -1
- package/dist/host/remote/access-store.d.ts +156 -0
- package/dist/host/remote/access-store.js +308 -0
- package/dist/host/remote/cert.d.ts +20 -0
- package/dist/host/remote/cert.js +103 -0
- package/dist/host/remote/door-client.d.ts +13 -0
- package/dist/host/remote/door-client.js +54 -0
- package/dist/host/remote/firewall.d.ts +44 -0
- package/dist/host/remote/firewall.js +118 -0
- package/dist/host/remote/gate.d.ts +125 -0
- package/dist/host/remote/gate.js +391 -0
- package/dist/host/remote/index.d.ts +8 -0
- package/dist/host/remote/index.js +10 -0
- package/dist/host/remote/lan.d.ts +24 -0
- package/dist/host/remote/lan.js +88 -0
- package/dist/host/remote/tls-store.d.ts +22 -0
- package/dist/host/remote/tls-store.js +63 -0
- package/dist/host/remote/windows-helper.d.ts +67 -0
- package/dist/host/remote/windows-helper.js +129 -0
- package/dist/host/web/bridge.d.ts +5 -1
- package/dist/host/web/bridge.js +6 -2
- package/dist/host/web/browser.js +1 -1
- package/dist/host/web/control.d.ts +4 -1
- package/dist/host/web/control.js +28 -4
- package/dist/host/web/rpc.d.ts +16 -2
- package/dist/host/web/rpc.js +37 -4
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -0
- package/dist/host/web/server.d.ts +18 -2
- package/dist/host/web/server.js +97 -24
- package/dist/host/web/state.d.ts +5 -0
- package/dist/host/web/state.js +8 -0
- package/dist/runtime/agent.js +184 -18
- package/dist/runtime/daemon.js +772 -338
- package/dist/runtime/db.js +184 -18
- package/dist/runtime/gitguard.js +6 -0
- package/dist/runtime/main.js +966 -553
- package/dist/runtime/netproxy.js +184 -18
- package/dist/runtime/view.js +186 -18
- package/dist/selftest/app.test.js +66 -7
- package/dist/selftest/audit.test.js +354 -47
- package/dist/selftest/channel.test.js +16 -0
- package/dist/selftest/cmdline.test.js +16 -0
- package/dist/selftest/git-access.test.js +8 -0
- package/dist/selftest/hcs.test.js +35 -0
- package/dist/selftest/hostile-input.test.js +12 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/menu-screens.js +1 -1
- package/dist/selftest/menu.test.js +3 -3
- package/dist/selftest/remote.test.d.ts +2 -0
- package/dist/selftest/remote.test.js +536 -0
- package/dist/selftest/repo-guard.test.js +56 -2
- package/dist/selftest/resume.test.js +2 -1
- package/dist/selftest/sharing.test.js +9 -2
- package/dist/selftest/support/engine-golden.js +9 -0
- package/dist/selftest/support/fixtures.d.ts +14 -0
- package/dist/selftest/support/web-preview.js +157 -0
- package/dist/selftest/toolchains.test.js +147 -2
- package/dist/selftest/web.test.js +2 -0
- package/dist/shared/apps/agent-addons.d.ts +64 -0
- package/dist/shared/apps/agent-addons.js +95 -0
- package/dist/shared/apps/catalogue.d.ts +33 -2
- package/dist/shared/apps/catalogue.js +102 -11
- package/dist/shared/apps/index.d.ts +3 -2
- package/dist/shared/apps/index.js +3 -2
- package/dist/shared/apps/selection.d.ts +2 -0
- package/dist/shared/apps/selection.js +7 -0
- package/dist/shared/channel/schema.d.ts +23 -0
- package/dist/shared/channel/schema.js +8 -0
- package/dist/shared/pty/protocol.d.ts +2 -0
- package/dist/shared/pty/protocol.js +2 -0
- package/dist/sync/watcher.js +9 -1
- package/dist/web/index.html +2 -2
- package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
- package/dist/web/main-Z2WYAE2Z.js +221 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
- package/native/csharp/HcsHost/Hcs.cs +9 -0
- package/native/csharp/HcsHost/Program.cs +26 -1
- package/native/csharp/HcsHost/Share.cs +173 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Program.cs +17 -1
- package/native/csharp/HostTools/WebHelper.cs +499 -0
- package/native/scripts/get-network-profile.ps1 +19 -0
- package/native/scripts/set-folder-access.ps1 +11 -0
- package/native/scripts/set-web-firewall.ps1 +103 -0
- package/package.json +1 -1
- package/sandbox/addons/ccusage/package-lock.json +111 -0
- package/sandbox/addons/ccusage/package.json +7 -0
- package/sandbox/addons/context7/package-lock.json +1313 -0
- package/sandbox/addons/context7/package.json +7 -0
- package/sandbox/addons/headroom/requirements.in +1 -0
- package/sandbox/addons/headroom/requirements.txt +2922 -0
- package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
- package/sandbox/addons/playwright-mcp/package.json +7 -0
- package/sandbox/csharp/PtyHost.cs +20 -0
- package/schemas/config.schema.json +78 -4
- package/schemas/sandbox-config.schema.json +2 -20
- package/dist/cli/commands/maintenance/audit.d.ts +0 -3
- package/dist/cli/commands/maintenance/audit.js +0 -58
- package/dist/web/main-K2JMCAZI.js +0 -219
package/README.md
CHANGED
|
@@ -24,8 +24,9 @@ irm https://github.com/JoshDiDuca/windows-development-sandbox/releases/latest/do
|
|
|
24
24
|
|
|
25
25
|
It checks your PC, offers to turn on Windows Sandbox, and asks **1) Just me** or **2) Everyone on this PC** (needs
|
|
26
26
|
admin). It brings its own private copy of Node.js; nothing else is installed system-wide, and no Start menu shortcut
|
|
27
|
-
unless you ask for one. `sandbox update` installs newer versions (and can do it by itself)
|
|
28
|
-
|
|
27
|
+
unless you ask for one. `sandbox update` installs newer versions (and can do it by itself), even while a sandbox runs:
|
|
28
|
+
the sandbox keeps its version until it restarts. A version that fails to start is rolled back automatically, and
|
|
29
|
+
`sandbox update --rollback` goes back one by hand.
|
|
29
30
|
|
|
30
31
|
Needs Windows 10/11 **Pro, Enterprise or Education**. Have Node.js 20+? `npm install -g windows-development-sandbox`
|
|
31
32
|
works too. [Scopes, options (`-Version`, `-StartMenuShortcut` ...), upgrading, uninstall](docs/install.md)
|
|
@@ -54,12 +55,14 @@ The first start downloads what the project needs (a few minutes, once). After th
|
|
|
54
55
|
|---|---|
|
|
55
56
|
| **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. |
|
|
56
57
|
| **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 ... |
|
|
57
|
-
| **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 |
|
|
58
|
+
| **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. Agent add-ons too: rtk and Headroom (fewer tokens), ccusage, Playwright and Context7 MCP servers, claude-mem, REA. Playwright works out of the box with Edge |
|
|
58
59
|
| **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` |
|
|
59
60
|
| **Agents** | Claude Code, Codex, Gemini, opencode, Qwen, Crush, Copilot, Kilo, Amp, Cline, Continue, Kimi, Aider, DeepSeek agents |
|
|
60
61
|
| **Smart** | Detects what the project needs from its files, respects version files, installs in parallel and reuses everything |
|
|
61
62
|
| **Several projects** | `sandbox claude` from another project joins the running sandbox: its own folder, tools, versions, databases and environment, set up on demand. Start several agents at once (`--session claude@A --session codex@B`, or the menu) and attach to any of them from any terminal |
|
|
62
63
|
| **Picks up where you left off** | After a crash or a restart, `sandbox resume` reopens the last sessions; `sandbox restart` restarts the agents in place and they continue their conversation |
|
|
64
|
+
| **Leaves nothing behind** | When an agent's session ends, everything it started ends with it, background servers included. The web UI's server stops when no browser uses it, and nothing of this tool keeps running on your PC once the sandbox is closed |
|
|
65
|
+
| **GitHub, safely** | Give sandboxes a fine-grained GitHub token you made for them (`sandbox github login`, `github.shareToken`): `gh` and `git push` work inside, and the token never lands in the sandbox's files |
|
|
63
66
|
| **Feels like home** | Your desktop look, Claude settings, MCP servers and conversation history come along; history syncs back. Logins you save (`sandbox account`) are kept encrypted for your Windows user |
|
|
64
67
|
|
|
65
68
|
## Built for heavy workloads
|
|
@@ -92,16 +95,19 @@ Details and settings: [troubleshooting](docs/troubleshooting.md#the-sandbox-clos
|
|
|
92
95
|
| `sandbox <agent>`, `claude-sandbox` | start an agent; `-t` for this terminal, `--headless` for no window |
|
|
93
96
|
| `sandbox start` | the sandbox without an agent |
|
|
94
97
|
| `sandbox attach` / `sessions` / `status` | work with a running sandbox (this folder's project first) |
|
|
98
|
+
| `sandbox shell` / `close <id>` | open PowerShell (or cmd) in a project as a session you can attach to; end a session |
|
|
95
99
|
| `sandbox restart` / `resume` | restart the agents in place; reopen the last sessions |
|
|
96
100
|
| `sandbox projects` | the projects a sandbox can open without a restart: roots, `all`, exclude |
|
|
97
101
|
| `sandbox db [start\|stop <name>]` | list, start or stop databases (`sbx-db` inside) |
|
|
98
102
|
| `sandbox exec -- <cmd>` | run a command inside |
|
|
99
103
|
| `sandbox config get\|set\|reset\|list` | read and change any setting from the command line (`export` / `import` to share them) |
|
|
100
104
|
| `sandbox account` | save, switch and rename agent logins |
|
|
105
|
+
| `sandbox github` / `vpn` | the GitHub token for sandboxes; the VPN login and status |
|
|
101
106
|
| `sandbox web` | (Beta) the web UI: start, stop, status, open |
|
|
102
107
|
| `sandbox stop` / `hide` / `show` | shut down, hide or show the window |
|
|
103
108
|
| `sandbox data` / `storage` | what's stored and how big, freeing space, moving the data folder |
|
|
104
109
|
| `sandbox setup` / `doctor` / `repair` | check this PC, see what will be installed, fix common problems |
|
|
110
|
+
| `sandbox engine` | the engines and what each can do on this PC; `engine clean` stops HCS systems left running |
|
|
105
111
|
| `sandbox logs [-f] [--crash]` | the sandbox log, or the report of the last crash |
|
|
106
112
|
| `sandbox update [--rollback]` / `--version` | update (or go back one version); the installed version |
|
|
107
113
|
|
|
@@ -117,14 +123,15 @@ keeps every feature it has.
|
|
|
117
123
|
| **Web UI** (`sandbox web`, `web.enabled`) | Beta | Agents, sessions with live terminals, the sandbox's status, apps, accounts and every setting in your browser, with search (Ctrl+K) and a folder picker; on this PC only (loopback, signed in with a token). `web.openOnLaunch` opens it with every sandbox. [web-ui.md](docs/web-ui.md) |
|
|
118
124
|
| **Agent commands** (`sandbox agent-commands on`) | Beta | Typing `claude`, `codex` ... in any folder starts it in a hidden sandbox and attaches your terminal |
|
|
119
125
|
| **Contained storage** (`sandbox storage contained on`) | Beta | Tools, logins and logs in one disk image per data set, mounted only while a sandbox runs. One admin prompt per launch. [engines](docs/design/engines.md) |
|
|
120
|
-
| **VPN** (`sandbox vpn`, `vpn.enabled`) | Alpha | The sandbox's traffic through NordVPN, your own OpenVPN profile or a WireGuard config, with a kill switch
|
|
121
|
-
| **HCS engine** (`sandbox.engine: hcs`, `sandbox engine`) | Alpha | Headless agents in a Hyper-V isolated Server Core container. `sandbox engine check hcs` shows what your PC lacks; starting it is not enabled yet |
|
|
126
|
+
| **VPN** (`sandbox vpn`, `vpn.enabled`) | Alpha | The sandbox's traffic through NordVPN, your own OpenVPN profile or a WireGuard config, with a kill switch that is checked with a real connection. The first real try fixed the install and the kill switch; a tunnel carrying traffic is not confirmed yet: read [vpn.md](docs/vpn.md) first |
|
|
127
|
+
| **HCS engine** (`sandbox.engine: hcs`, `sandbox engine`) | Alpha | Headless agents in a Hyper-V isolated Server Core container. `sandbox engine check hcs` shows what your PC lacks; starting it is not enabled yet. Its create, start and stop steps pass on GitHub's Windows Server 2022 and 2025 runners |
|
|
122
128
|
|
|
123
129
|
## Known gaps
|
|
124
130
|
|
|
125
131
|
- Adding a folder to a RUNNING sandbox needs Windows 11 24H2+ with the updated Windows Sandbox (`wsb share`,
|
|
126
132
|
untested). On older Windows, folders inside a project root open at once and anything else offers a restart.
|
|
127
|
-
[Details, and how to get `wsb`](docs/projects.md#adding-folders-to-a-running-sandbox-wsb-share).
|
|
133
|
+
[Details, and how to get `wsb`](docs/projects.md#adding-folders-to-a-running-sandbox-wsb-share). A way for older
|
|
134
|
+
Windows through HCS is designed and waiting for a test on a real PC ([design](docs/design/hcs-live-folders.md)).
|
|
128
135
|
- Windows Sandbox runs one sandbox at a time per PC; every project and agent shares it.
|
|
129
136
|
- What is planned next: [roadmap](docs/roadmap.md).
|
|
130
137
|
|
package/config.example.json
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
"include": []
|
|
10
10
|
},
|
|
11
11
|
"audit": {
|
|
12
|
-
"enabled":
|
|
13
|
-
"days":
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"days": 7,
|
|
14
14
|
"maxMb": 100,
|
|
15
15
|
"processes": true,
|
|
16
|
-
"files": true
|
|
16
|
+
"files": true,
|
|
17
|
+
"commits": true
|
|
17
18
|
},
|
|
18
19
|
"mappings": [],
|
|
19
20
|
"sandbox": {
|
|
@@ -227,7 +228,17 @@
|
|
|
227
228
|
"web": {
|
|
228
229
|
"enabled": false,
|
|
229
230
|
"port": 47800,
|
|
230
|
-
"openOnLaunch": false
|
|
231
|
+
"openOnLaunch": false,
|
|
232
|
+
"remote": {
|
|
233
|
+
"windowsSignIn": false,
|
|
234
|
+
"users": [],
|
|
235
|
+
"rememberDays": 30,
|
|
236
|
+
"lan": {
|
|
237
|
+
"enabled": false,
|
|
238
|
+
"port": 47801,
|
|
239
|
+
"address": ""
|
|
240
|
+
}
|
|
241
|
+
}
|
|
231
242
|
},
|
|
232
243
|
"agentCommands": {
|
|
233
244
|
"enabled": false,
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type Page } from '../../kernel/index.js';
|
|
3
|
+
export declare const DeviceRowSchema: z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
role: z.ZodEnum<{
|
|
7
|
+
view: "view";
|
|
8
|
+
control: "control";
|
|
9
|
+
}>;
|
|
10
|
+
address: z.ZodString;
|
|
11
|
+
createdAt: z.ZodString;
|
|
12
|
+
expiresAt: z.ZodString;
|
|
13
|
+
lastSeen: z.ZodNullable<z.ZodString>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export declare const WindowsRowSchema: z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
user: z.ZodString;
|
|
18
|
+
createdAt: z.ZodString;
|
|
19
|
+
expiresAt: z.ZodString;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export declare const AccessStatusSchema: z.ZodObject<{
|
|
22
|
+
lan: z.ZodObject<{
|
|
23
|
+
enabled: z.ZodBoolean;
|
|
24
|
+
running: z.ZodBoolean;
|
|
25
|
+
address: z.ZodNullable<z.ZodString>;
|
|
26
|
+
fingerprint: z.ZodNullable<z.ZodString>;
|
|
27
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
28
|
+
network: z.ZodNullable<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
windowsSignIn: z.ZodBoolean;
|
|
31
|
+
users: z.ZodArray<z.ZodObject<{
|
|
32
|
+
name: z.ZodString;
|
|
33
|
+
role: z.ZodEnum<{
|
|
34
|
+
view: "view";
|
|
35
|
+
control: "control";
|
|
36
|
+
}>;
|
|
37
|
+
}, z.core.$strip>>;
|
|
38
|
+
rememberDays: z.ZodNumber;
|
|
39
|
+
devices: z.ZodArray<z.ZodObject<{
|
|
40
|
+
id: z.ZodString;
|
|
41
|
+
name: z.ZodString;
|
|
42
|
+
role: z.ZodEnum<{
|
|
43
|
+
view: "view";
|
|
44
|
+
control: "control";
|
|
45
|
+
}>;
|
|
46
|
+
address: z.ZodString;
|
|
47
|
+
createdAt: z.ZodString;
|
|
48
|
+
expiresAt: z.ZodString;
|
|
49
|
+
lastSeen: z.ZodNullable<z.ZodString>;
|
|
50
|
+
}, z.core.$strip>>;
|
|
51
|
+
windows: z.ZodArray<z.ZodObject<{
|
|
52
|
+
id: z.ZodString;
|
|
53
|
+
user: z.ZodString;
|
|
54
|
+
createdAt: z.ZodString;
|
|
55
|
+
expiresAt: z.ZodString;
|
|
56
|
+
}, z.core.$strip>>;
|
|
57
|
+
pending: z.ZodNullable<z.ZodObject<{
|
|
58
|
+
role: z.ZodEnum<{
|
|
59
|
+
view: "view";
|
|
60
|
+
control: "control";
|
|
61
|
+
}>;
|
|
62
|
+
expiresAt: z.ZodString;
|
|
63
|
+
}, z.core.$strip>>;
|
|
64
|
+
codeVoided: z.ZodBoolean;
|
|
65
|
+
}, z.core.$strip>;
|
|
66
|
+
export type AccessStatus = z.output<typeof AccessStatusSchema>;
|
|
67
|
+
export declare const accessStatus: import("../../kernel/define.js").QueryDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
68
|
+
lan: z.ZodObject<{
|
|
69
|
+
enabled: z.ZodBoolean;
|
|
70
|
+
running: z.ZodBoolean;
|
|
71
|
+
address: z.ZodNullable<z.ZodString>;
|
|
72
|
+
fingerprint: z.ZodNullable<z.ZodString>;
|
|
73
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
74
|
+
network: z.ZodNullable<z.ZodString>;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
windowsSignIn: z.ZodBoolean;
|
|
77
|
+
users: z.ZodArray<z.ZodObject<{
|
|
78
|
+
name: z.ZodString;
|
|
79
|
+
role: z.ZodEnum<{
|
|
80
|
+
view: "view";
|
|
81
|
+
control: "control";
|
|
82
|
+
}>;
|
|
83
|
+
}, z.core.$strip>>;
|
|
84
|
+
rememberDays: z.ZodNumber;
|
|
85
|
+
devices: z.ZodArray<z.ZodObject<{
|
|
86
|
+
id: z.ZodString;
|
|
87
|
+
name: z.ZodString;
|
|
88
|
+
role: z.ZodEnum<{
|
|
89
|
+
view: "view";
|
|
90
|
+
control: "control";
|
|
91
|
+
}>;
|
|
92
|
+
address: z.ZodString;
|
|
93
|
+
createdAt: z.ZodString;
|
|
94
|
+
expiresAt: z.ZodString;
|
|
95
|
+
lastSeen: z.ZodNullable<z.ZodString>;
|
|
96
|
+
}, z.core.$strip>>;
|
|
97
|
+
windows: z.ZodArray<z.ZodObject<{
|
|
98
|
+
id: z.ZodString;
|
|
99
|
+
user: z.ZodString;
|
|
100
|
+
createdAt: z.ZodString;
|
|
101
|
+
expiresAt: z.ZodString;
|
|
102
|
+
}, z.core.$strip>>;
|
|
103
|
+
pending: z.ZodNullable<z.ZodObject<{
|
|
104
|
+
role: z.ZodEnum<{
|
|
105
|
+
view: "view";
|
|
106
|
+
control: "control";
|
|
107
|
+
}>;
|
|
108
|
+
expiresAt: z.ZodString;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
codeVoided: z.ZodBoolean;
|
|
111
|
+
}, z.core.$strip>, "access.status">;
|
|
112
|
+
export declare const allowDevice: import("../../kernel/define.js").OperationDef<z.ZodObject<{
|
|
113
|
+
role: z.ZodDefault<z.ZodEnum<{
|
|
114
|
+
view: "view";
|
|
115
|
+
control: "control";
|
|
116
|
+
}>>;
|
|
117
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
118
|
+
code: z.ZodString;
|
|
119
|
+
expiresAt: z.ZodString;
|
|
120
|
+
role: z.ZodEnum<{
|
|
121
|
+
view: "view";
|
|
122
|
+
control: "control";
|
|
123
|
+
}>;
|
|
124
|
+
address: z.ZodNullable<z.ZodString>;
|
|
125
|
+
fingerprint: z.ZodNullable<z.ZodString>;
|
|
126
|
+
}, z.core.$strip>, "access.allowDevice">;
|
|
127
|
+
export declare const revokeAccess: import("../../kernel/define.js").OperationDef<z.ZodObject<{
|
|
128
|
+
id: z.ZodString;
|
|
129
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
130
|
+
message: z.ZodString;
|
|
131
|
+
}, z.core.$strip>, "access.revoke">;
|
|
132
|
+
export declare const revokeAllAccess: import("../../kernel/define.js").OperationDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
133
|
+
message: z.ZodString;
|
|
134
|
+
}, z.core.$strip>, "access.revokeAll">;
|
|
135
|
+
export declare const lanOn: import("../../kernel/define.js").OperationDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
136
|
+
message: z.ZodString;
|
|
137
|
+
address: z.ZodString;
|
|
138
|
+
}, z.core.$strip>, "access.lanOn">;
|
|
139
|
+
export declare const lanOff: import("../../kernel/define.js").OperationDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
140
|
+
message: z.ZodString;
|
|
141
|
+
}, z.core.$strip>, "access.lanOff">;
|
|
142
|
+
/** The Remote access page: your network, the devices, the Windows users. */
|
|
143
|
+
export declare function accessPage(o: AccessStatus, now?: number): Page;
|
|
144
|
+
export declare const accessView: import("../../kernel/define.js").ViewDef<{
|
|
145
|
+
status: import("../../kernel/define.js").QueryDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
146
|
+
lan: z.ZodObject<{
|
|
147
|
+
enabled: z.ZodBoolean;
|
|
148
|
+
running: z.ZodBoolean;
|
|
149
|
+
address: z.ZodNullable<z.ZodString>;
|
|
150
|
+
fingerprint: z.ZodNullable<z.ZodString>;
|
|
151
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
152
|
+
network: z.ZodNullable<z.ZodString>;
|
|
153
|
+
}, z.core.$strip>;
|
|
154
|
+
windowsSignIn: z.ZodBoolean;
|
|
155
|
+
users: z.ZodArray<z.ZodObject<{
|
|
156
|
+
name: z.ZodString;
|
|
157
|
+
role: z.ZodEnum<{
|
|
158
|
+
view: "view";
|
|
159
|
+
control: "control";
|
|
160
|
+
}>;
|
|
161
|
+
}, z.core.$strip>>;
|
|
162
|
+
rememberDays: z.ZodNumber;
|
|
163
|
+
devices: z.ZodArray<z.ZodObject<{
|
|
164
|
+
id: z.ZodString;
|
|
165
|
+
name: z.ZodString;
|
|
166
|
+
role: z.ZodEnum<{
|
|
167
|
+
view: "view";
|
|
168
|
+
control: "control";
|
|
169
|
+
}>;
|
|
170
|
+
address: z.ZodString;
|
|
171
|
+
createdAt: z.ZodString;
|
|
172
|
+
expiresAt: z.ZodString;
|
|
173
|
+
lastSeen: z.ZodNullable<z.ZodString>;
|
|
174
|
+
}, z.core.$strip>>;
|
|
175
|
+
windows: z.ZodArray<z.ZodObject<{
|
|
176
|
+
id: z.ZodString;
|
|
177
|
+
user: z.ZodString;
|
|
178
|
+
createdAt: z.ZodString;
|
|
179
|
+
expiresAt: z.ZodString;
|
|
180
|
+
}, z.core.$strip>>;
|
|
181
|
+
pending: z.ZodNullable<z.ZodObject<{
|
|
182
|
+
role: z.ZodEnum<{
|
|
183
|
+
view: "view";
|
|
184
|
+
control: "control";
|
|
185
|
+
}>;
|
|
186
|
+
expiresAt: z.ZodString;
|
|
187
|
+
}, z.core.$strip>>;
|
|
188
|
+
codeVoided: z.ZodBoolean;
|
|
189
|
+
}, z.core.$strip>, "access.status">;
|
|
190
|
+
}, Record<string, never>, "access">;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// Remote access (docs/design/remote-access.md): devices on your network (LAN mode, a one-time code per device) and
|
|
2
|
+
// other Windows users of this PC (Windows sign-in), who is in, and taking access away. Pure: schemas, texts and the
|
|
3
|
+
// page; access.run.ts does the work. Everything here is the owner's: a device or another Windows user never manages
|
|
4
|
+
// access, whatever their role.
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { action, defineOperation, defineQuery, defineView, fieldOf } from '../../kernel/index.js';
|
|
7
|
+
const RoleSchema = z.enum(['view', 'control']);
|
|
8
|
+
export const DeviceRowSchema = z.object({
|
|
9
|
+
id: z.string().describe('Its id (for revoke)'),
|
|
10
|
+
name: z.string().describe('The name it gave itself'),
|
|
11
|
+
role: RoleSchema.describe('view or control'),
|
|
12
|
+
address: z.string().describe('The address it was allowed from'),
|
|
13
|
+
createdAt: z.string().describe('When it was allowed (ISO time)'),
|
|
14
|
+
expiresAt: z.string().describe('When it is signed out (ISO time)'),
|
|
15
|
+
lastSeen: z.string().nullable().describe('When it was last used (ISO time)'),
|
|
16
|
+
});
|
|
17
|
+
export const WindowsRowSchema = z.object({
|
|
18
|
+
id: z.string().describe('Its id (for revoke)'),
|
|
19
|
+
user: z.string().describe('The Windows user (DOMAIN\\name)'),
|
|
20
|
+
createdAt: z.string().describe('When they signed in'),
|
|
21
|
+
expiresAt: z.string().describe('When they are signed out'),
|
|
22
|
+
});
|
|
23
|
+
export const AccessStatusSchema = z.object({
|
|
24
|
+
lan: z.object({
|
|
25
|
+
enabled: z.boolean().describe('web.remote.lan.enabled'),
|
|
26
|
+
running: z.boolean().describe('The web UI listens on your network now'),
|
|
27
|
+
address: z.string().nullable().describe('What a phone types: https://<address>:<port>'),
|
|
28
|
+
fingerprint: z.string().nullable().describe("The certificate's SHA-256 fingerprint"),
|
|
29
|
+
problem: z.string().nullable().describe('Why it is on but not listening'),
|
|
30
|
+
network: z.string().nullable().describe('The network it was turned on for'),
|
|
31
|
+
}),
|
|
32
|
+
windowsSignIn: z.boolean().describe('web.remote.windowsSignIn'),
|
|
33
|
+
users: z.array(z.object({ name: z.string(), role: RoleSchema })).describe('web.remote.users'),
|
|
34
|
+
rememberDays: z.number().describe('web.remote.rememberDays'),
|
|
35
|
+
devices: z.array(DeviceRowSchema),
|
|
36
|
+
windows: z.array(WindowsRowSchema).describe('Windows users signed in to the web UI now'),
|
|
37
|
+
pending: z
|
|
38
|
+
.object({ role: RoleSchema, expiresAt: z.string() })
|
|
39
|
+
.nullable()
|
|
40
|
+
.describe('A device code waiting to be typed (the code itself is never kept)'),
|
|
41
|
+
codeVoided: z.boolean().describe('The last code was voided after too many wrong tries'),
|
|
42
|
+
});
|
|
43
|
+
export const accessStatus = defineQuery({
|
|
44
|
+
id: 'access.status',
|
|
45
|
+
title: 'Remote access',
|
|
46
|
+
summary: 'devices on your network and other Windows users who may use your web UI',
|
|
47
|
+
input: z.object({}),
|
|
48
|
+
output: AccessStatusSchema,
|
|
49
|
+
access: 'owner',
|
|
50
|
+
live: { everyMs: 5000 },
|
|
51
|
+
text: (o) => (o.lan.running && o.lan.address ? 'On your network: ' + o.lan.address : 'LAN mode is off') +
|
|
52
|
+
'. ' +
|
|
53
|
+
o.devices.length +
|
|
54
|
+
' device' +
|
|
55
|
+
(o.devices.length === 1 ? '' : 's') +
|
|
56
|
+
', ' +
|
|
57
|
+
o.windows.length +
|
|
58
|
+
' Windows sign-in' +
|
|
59
|
+
(o.windows.length === 1 ? '' : 's') +
|
|
60
|
+
'.',
|
|
61
|
+
cli: { name: 'web', action: 'devices', json: true },
|
|
62
|
+
});
|
|
63
|
+
export const allowDevice = defineOperation({
|
|
64
|
+
id: 'access.allowDevice',
|
|
65
|
+
title: 'Allow a new device',
|
|
66
|
+
summary: 'show a one-time code that a phone or tablet on your network types to sign in (5 minutes)',
|
|
67
|
+
input: z.object({
|
|
68
|
+
role: RoleSchema.default('view').describe('What the device may do: view (look) or control (everything but access)'),
|
|
69
|
+
}),
|
|
70
|
+
output: z.object({
|
|
71
|
+
code: z.string().describe('The one-time code (ABCD-EFGH)'),
|
|
72
|
+
expiresAt: z.string().describe('When it runs out'),
|
|
73
|
+
role: RoleSchema,
|
|
74
|
+
address: z.string().nullable().describe('What the device opens'),
|
|
75
|
+
fingerprint: z.string().nullable().describe("The certificate's fingerprint, to compare on the device"),
|
|
76
|
+
}),
|
|
77
|
+
access: 'owner',
|
|
78
|
+
keepResult: true,
|
|
79
|
+
text: (o) => 'Code ' +
|
|
80
|
+
o.code +
|
|
81
|
+
' (5 minutes, ' +
|
|
82
|
+
o.role +
|
|
83
|
+
'). On the device open ' +
|
|
84
|
+
(o.address ?? 'the address on your network') +
|
|
85
|
+
' and type the code.' +
|
|
86
|
+
(o.fingerprint ? ' Its certificate should be ' + o.fingerprint.slice(0, 23) + '...' : ''),
|
|
87
|
+
cli: {
|
|
88
|
+
name: 'web',
|
|
89
|
+
action: 'allow-device',
|
|
90
|
+
flags: { role: { flag: '--role', value: 'view|control', choices: ['view', 'control'] } },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
export const revokeAccess = defineOperation({
|
|
94
|
+
id: 'access.revoke',
|
|
95
|
+
title: 'Sign out',
|
|
96
|
+
summary: 'sign out one device or Windows user now (its open pages close)',
|
|
97
|
+
input: z.object({
|
|
98
|
+
id: z
|
|
99
|
+
.string()
|
|
100
|
+
.regex(/^[0-9a-f]{16}$/)
|
|
101
|
+
.describe('The id from sandbox web devices'),
|
|
102
|
+
}),
|
|
103
|
+
output: z.object({ message: z.string() }),
|
|
104
|
+
access: 'owner',
|
|
105
|
+
text: (o) => o.message,
|
|
106
|
+
cli: { name: 'web', action: 'revoke', positional: ['id'] },
|
|
107
|
+
});
|
|
108
|
+
export const revokeAllAccess = defineOperation({
|
|
109
|
+
id: 'access.revokeAll',
|
|
110
|
+
title: 'Sign out everyone',
|
|
111
|
+
summary: 'sign out every device and Windows user now, and void a code that was not used',
|
|
112
|
+
input: z.object({}),
|
|
113
|
+
output: z.object({ message: z.string() }),
|
|
114
|
+
access: 'owner',
|
|
115
|
+
danger: true,
|
|
116
|
+
text: (o) => o.message,
|
|
117
|
+
cli: { name: 'web', action: 'revoke-all', yes: 'sign everyone out without asking' },
|
|
118
|
+
});
|
|
119
|
+
export const lanOn = defineOperation({
|
|
120
|
+
id: 'access.lanOn',
|
|
121
|
+
title: 'Turn on LAN mode',
|
|
122
|
+
summary: 'let devices you allow use the web UI on your network (adds a firewall rule; Windows asks once)',
|
|
123
|
+
input: z.object({}),
|
|
124
|
+
output: z.object({ message: z.string(), address: z.string() }),
|
|
125
|
+
access: 'owner',
|
|
126
|
+
text: (o) => o.message,
|
|
127
|
+
cli: { name: 'web', action: 'lan-on' },
|
|
128
|
+
});
|
|
129
|
+
export const lanOff = defineOperation({
|
|
130
|
+
id: 'access.lanOff',
|
|
131
|
+
title: 'Turn off LAN mode',
|
|
132
|
+
summary: 'stop listening on your network, remove the firewall rule and sign out every device',
|
|
133
|
+
input: z.object({}),
|
|
134
|
+
output: z.object({ message: z.string() }),
|
|
135
|
+
access: 'owner',
|
|
136
|
+
danger: true,
|
|
137
|
+
text: (o) => o.message,
|
|
138
|
+
cli: { name: 'web', action: 'lan-off', yes: 'turn it off without asking' },
|
|
139
|
+
});
|
|
140
|
+
const DAY_MS = 86_400_000;
|
|
141
|
+
/** "in 3 days", "in 5 hours", "today". */
|
|
142
|
+
function untilText(iso, now) {
|
|
143
|
+
const ms = Date.parse(iso) - now;
|
|
144
|
+
if (!Number.isFinite(ms))
|
|
145
|
+
return '?';
|
|
146
|
+
if (ms >= DAY_MS)
|
|
147
|
+
return 'in ' + Math.round(ms / DAY_MS) + ' days';
|
|
148
|
+
if (ms >= 3_600_000)
|
|
149
|
+
return 'in ' + Math.round(ms / 3_600_000) + ' hours';
|
|
150
|
+
return 'in ' + Math.max(1, Math.round(ms / 60_000)) + ' minutes';
|
|
151
|
+
}
|
|
152
|
+
function dateText(iso) {
|
|
153
|
+
return iso ? iso.slice(0, 16).replace('T', ' ') : 'not yet';
|
|
154
|
+
}
|
|
155
|
+
/** The Remote access page: your network, the devices, the Windows users. */
|
|
156
|
+
export function accessPage(o, now = Date.now()) {
|
|
157
|
+
const allow = action(allowDevice, {}, {
|
|
158
|
+
key: 'a',
|
|
159
|
+
verb: 'allow',
|
|
160
|
+
fields: [{ ...fieldOf(allowDevice.input.shape.role, ['role']), name: 'role', label: 'Role' }],
|
|
161
|
+
initial: { role: 'view' },
|
|
162
|
+
});
|
|
163
|
+
const deviceItems = o.devices.map((d) => ({
|
|
164
|
+
id: 'device:' + d.id,
|
|
165
|
+
label: d.name,
|
|
166
|
+
hint: d.role,
|
|
167
|
+
tone: d.role === 'control' ? 'accent' : undefined,
|
|
168
|
+
icon: 'device',
|
|
169
|
+
description: 'From ' + d.address + ', signed out ' + untilText(d.expiresAt, now) + '.',
|
|
170
|
+
details: [
|
|
171
|
+
'Allowed ' + dateText(d.createdAt),
|
|
172
|
+
'Last seen ' + dateText(d.lastSeen),
|
|
173
|
+
'Ends ' + dateText(d.expiresAt),
|
|
174
|
+
'Id ' + d.id,
|
|
175
|
+
],
|
|
176
|
+
keys: 'x signs it out.',
|
|
177
|
+
actions: [action(revokeAccess, { id: d.id }, { key: 'x', verb: 'sign out', label: 'Sign out ' + d.name })],
|
|
178
|
+
}));
|
|
179
|
+
const windowsItems = o.windows.map((w) => ({
|
|
180
|
+
id: 'windows:' + w.id,
|
|
181
|
+
label: w.user,
|
|
182
|
+
hint: o.users.find((u) => w.user.toLowerCase().endsWith('\\' + u.name.toLowerCase()) || w.user.toLowerCase() === u.name.toLowerCase())?.role ?? 'not on the list',
|
|
183
|
+
description: 'Signed in with Windows, signed out ' + untilText(w.expiresAt, now) + '.',
|
|
184
|
+
details: ['Signed in ' + dateText(w.createdAt), 'Ends ' + dateText(w.expiresAt), 'Id ' + w.id],
|
|
185
|
+
keys: 'x signs them out.',
|
|
186
|
+
actions: [action(revokeAccess, { id: w.id }, { key: 'x', verb: 'sign out', label: 'Sign out ' + w.user })],
|
|
187
|
+
}));
|
|
188
|
+
const lan = o.lan;
|
|
189
|
+
return {
|
|
190
|
+
title: 'Remote access',
|
|
191
|
+
notice: {
|
|
192
|
+
text: 'Anyone you let in acts as you: control can read what your agents can read, logins included. Local network only, never the internet.',
|
|
193
|
+
tone: 'warn',
|
|
194
|
+
},
|
|
195
|
+
sections: [
|
|
196
|
+
{
|
|
197
|
+
id: 'lan',
|
|
198
|
+
title: 'Devices on your network',
|
|
199
|
+
facts: [
|
|
200
|
+
{
|
|
201
|
+
id: 'lan-mode',
|
|
202
|
+
label: 'LAN mode',
|
|
203
|
+
value: lan.running ? 'on' : lan.enabled ? 'on, not listening' : 'off',
|
|
204
|
+
tone: lan.running ? 'ok' : lan.enabled ? 'warn' : 'muted',
|
|
205
|
+
description: lan.problem ?? undefined,
|
|
206
|
+
},
|
|
207
|
+
...(lan.address
|
|
208
|
+
? [
|
|
209
|
+
{
|
|
210
|
+
id: 'lan-address',
|
|
211
|
+
label: 'On your network',
|
|
212
|
+
value: lan.address,
|
|
213
|
+
description: "Type this into the phone's browser. The first time it warns about the certificate: compare it with the one below. A warning after that means stop.",
|
|
214
|
+
},
|
|
215
|
+
]
|
|
216
|
+
: []),
|
|
217
|
+
...(lan.fingerprint ? [{ id: 'lan-cert', label: 'Certificate', value: lan.fingerprint }] : []),
|
|
218
|
+
...(o.pending
|
|
219
|
+
? [
|
|
220
|
+
{
|
|
221
|
+
id: 'pending',
|
|
222
|
+
label: 'Code waiting',
|
|
223
|
+
value: 'for a ' + o.pending.role + ' device, ' + untilText(o.pending.expiresAt, now),
|
|
224
|
+
tone: 'accent',
|
|
225
|
+
},
|
|
226
|
+
]
|
|
227
|
+
: []),
|
|
228
|
+
...(o.codeVoided
|
|
229
|
+
? [
|
|
230
|
+
{
|
|
231
|
+
id: 'voided',
|
|
232
|
+
label: 'Last code',
|
|
233
|
+
value: 'voided after too many wrong tries',
|
|
234
|
+
tone: 'warn',
|
|
235
|
+
description: 'Someone on your network may have tried to guess it. Make a new one when you are ready.',
|
|
236
|
+
},
|
|
237
|
+
]
|
|
238
|
+
: []),
|
|
239
|
+
{ id: 'remember', label: 'Stays signed in', value: o.rememberDays + ' days (web.remote.rememberDays)' },
|
|
240
|
+
],
|
|
241
|
+
items: deviceItems,
|
|
242
|
+
actions: lan.enabled
|
|
243
|
+
? [allow, action(lanOff, {}, { key: 'f', verb: 'LAN off' })]
|
|
244
|
+
: [action(lanOn, {}, { key: 'n', verb: 'LAN on' })],
|
|
245
|
+
notes: deviceItems.length ? [] : [{ text: 'No devices yet.', tone: 'muted' }],
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'windows',
|
|
249
|
+
title: 'Other Windows users of this PC',
|
|
250
|
+
facts: [
|
|
251
|
+
{
|
|
252
|
+
id: 'windows-sign-in',
|
|
253
|
+
label: 'Windows sign-in',
|
|
254
|
+
value: o.windowsSignIn ? 'on' : 'off',
|
|
255
|
+
tone: o.windowsSignIn ? 'ok' : 'muted',
|
|
256
|
+
description: 'They run sandbox remote (or sandbox remote --browser) as themselves. Turn on web.remote.windowsSignIn and list them in web.remote.users (Settings).',
|
|
257
|
+
},
|
|
258
|
+
...o.users.map((u, i) => ({ id: 'user-' + i, label: u.name, value: u.role })),
|
|
259
|
+
],
|
|
260
|
+
items: windowsItems,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
actions: [action(revokeAllAccess, {}, { key: 'X', verb: 'sign out all' })],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
export const accessView = defineView({
|
|
267
|
+
id: 'access',
|
|
268
|
+
title: 'Remote access',
|
|
269
|
+
summary: 'Devices on your network and other Windows users of this PC: allow, see, sign out.',
|
|
270
|
+
queries: { status: accessStatus },
|
|
271
|
+
operations: [allowDevice, revokeAccess, revokeAllAccess, lanOn, lanOff],
|
|
272
|
+
build: ({ status }) => accessPage(status),
|
|
273
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { AccessStore } from '../../../host/remote/index.js';
|
|
2
|
+
import { type AppContext } from '../../context.js';
|
|
3
|
+
/** The access list of this user (the folder is the web state's private one). */
|
|
4
|
+
export declare function accessStoreOf(ctx: AppContext): AccessStore;
|
|
5
|
+
export declare const accessStatusRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").QueryDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
6
|
+
lan: import("zod").ZodObject<{
|
|
7
|
+
enabled: import("zod").ZodBoolean;
|
|
8
|
+
running: import("zod").ZodBoolean;
|
|
9
|
+
address: import("zod").ZodNullable<import("zod").ZodString>;
|
|
10
|
+
fingerprint: import("zod").ZodNullable<import("zod").ZodString>;
|
|
11
|
+
problem: import("zod").ZodNullable<import("zod").ZodString>;
|
|
12
|
+
network: import("zod").ZodNullable<import("zod").ZodString>;
|
|
13
|
+
}, import("zod/v4/core").$strip>;
|
|
14
|
+
windowsSignIn: import("zod").ZodBoolean;
|
|
15
|
+
users: import("zod").ZodArray<import("zod").ZodObject<{
|
|
16
|
+
name: import("zod").ZodString;
|
|
17
|
+
role: import("zod").ZodEnum<{
|
|
18
|
+
view: "view";
|
|
19
|
+
control: "control";
|
|
20
|
+
}>;
|
|
21
|
+
}, import("zod/v4/core").$strip>>;
|
|
22
|
+
rememberDays: import("zod").ZodNumber;
|
|
23
|
+
devices: import("zod").ZodArray<import("zod").ZodObject<{
|
|
24
|
+
id: import("zod").ZodString;
|
|
25
|
+
name: import("zod").ZodString;
|
|
26
|
+
role: import("zod").ZodEnum<{
|
|
27
|
+
view: "view";
|
|
28
|
+
control: "control";
|
|
29
|
+
}>;
|
|
30
|
+
address: import("zod").ZodString;
|
|
31
|
+
createdAt: import("zod").ZodString;
|
|
32
|
+
expiresAt: import("zod").ZodString;
|
|
33
|
+
lastSeen: import("zod").ZodNullable<import("zod").ZodString>;
|
|
34
|
+
}, import("zod/v4/core").$strip>>;
|
|
35
|
+
windows: import("zod").ZodArray<import("zod").ZodObject<{
|
|
36
|
+
id: import("zod").ZodString;
|
|
37
|
+
user: import("zod").ZodString;
|
|
38
|
+
createdAt: import("zod").ZodString;
|
|
39
|
+
expiresAt: import("zod").ZodString;
|
|
40
|
+
}, import("zod/v4/core").$strip>>;
|
|
41
|
+
pending: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
42
|
+
role: import("zod").ZodEnum<{
|
|
43
|
+
view: "view";
|
|
44
|
+
control: "control";
|
|
45
|
+
}>;
|
|
46
|
+
expiresAt: import("zod").ZodString;
|
|
47
|
+
}, import("zod/v4/core").$strip>>;
|
|
48
|
+
codeVoided: import("zod").ZodBoolean;
|
|
49
|
+
}, import("zod/v4/core").$strip>, "access.status">, import("../../../cli/commands/context.js").Context>;
|
|
50
|
+
export declare const allowDeviceRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
51
|
+
role: import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
52
|
+
view: "view";
|
|
53
|
+
control: "control";
|
|
54
|
+
}>>;
|
|
55
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
56
|
+
code: import("zod").ZodString;
|
|
57
|
+
expiresAt: import("zod").ZodString;
|
|
58
|
+
role: import("zod").ZodEnum<{
|
|
59
|
+
view: "view";
|
|
60
|
+
control: "control";
|
|
61
|
+
}>;
|
|
62
|
+
address: import("zod").ZodNullable<import("zod").ZodString>;
|
|
63
|
+
fingerprint: import("zod").ZodNullable<import("zod").ZodString>;
|
|
64
|
+
}, import("zod/v4/core").$strip>, "access.allowDevice">, import("../../../cli/commands/context.js").Context>;
|
|
65
|
+
export declare const revokeAccessRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
66
|
+
id: import("zod").ZodString;
|
|
67
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
68
|
+
message: import("zod").ZodString;
|
|
69
|
+
}, import("zod/v4/core").$strip>, "access.revoke">, import("../../../cli/commands/context.js").Context>;
|
|
70
|
+
export declare const revokeAllAccessRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
71
|
+
message: import("zod").ZodString;
|
|
72
|
+
}, import("zod/v4/core").$strip>, "access.revokeAll">, import("../../../cli/commands/context.js").Context>;
|
|
73
|
+
export declare const lanOnRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
74
|
+
message: import("zod").ZodString;
|
|
75
|
+
address: import("zod").ZodString;
|
|
76
|
+
}, import("zod/v4/core").$strip>, "access.lanOn">, import("../../../cli/commands/context.js").Context>;
|
|
77
|
+
export declare const lanOffRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
78
|
+
message: import("zod").ZodString;
|
|
79
|
+
}, import("zod/v4/core").$strip>, "access.lanOff">, import("../../../cli/commands/context.js").Context>;
|