windows-development-sandbox 1.12.0 → 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.
Files changed (160) hide show
  1. package/README.md +1 -0
  2. package/config.example.json +11 -1
  3. package/dist/app/features/access/access.def.d.ts +190 -0
  4. package/dist/app/features/access/access.def.js +273 -0
  5. package/dist/app/features/access/access.run.d.ts +79 -0
  6. package/dist/app/features/access/access.run.js +129 -0
  7. package/dist/app/features/access/index.d.ts +2 -0
  8. package/dist/app/features/access/index.js +3 -0
  9. package/dist/app/features/agents/agent-entry.js +5 -0
  10. package/dist/app/features/agents/agents.def.js +1 -0
  11. package/dist/app/features/apps/apps.def.js +1 -0
  12. package/dist/app/features/audit/audit.def.d.ts +11 -2
  13. package/dist/app/features/audit/audit.def.js +7 -4
  14. package/dist/app/features/audit/audit.run.d.ts +3 -0
  15. package/dist/app/features/defs.d.ts +155 -3
  16. package/dist/app/features/defs.js +12 -1
  17. package/dist/app/features/files/files.def.js +1 -0
  18. package/dist/app/features/health/health.def.js +1 -0
  19. package/dist/app/features/sandbox/sandbox.def.js +3 -0
  20. package/dist/app/features/sessions/sessions.def.d.ts +36 -0
  21. package/dist/app/features/sessions/sessions.def.js +77 -9
  22. package/dist/app/features/sessions/sessions.run.d.ts +25 -2
  23. package/dist/app/features/sessions/sessions.run.js +71 -3
  24. package/dist/app/features/settings/settings.def.d.ts +8 -0
  25. package/dist/app/features/settings/settings.def.js +17 -0
  26. package/dist/app/features/web/web.def.d.ts +9 -0
  27. package/dist/app/features/web/web.def.js +26 -1
  28. package/dist/app/features/web/web.run.d.ts +3 -0
  29. package/dist/app/features/web/web.run.js +11 -3
  30. package/dist/app/kernel/access.d.ts +33 -0
  31. package/dist/app/kernel/access.js +44 -0
  32. package/dist/app/kernel/define.d.ts +14 -1
  33. package/dist/app/kernel/index.d.ts +1 -0
  34. package/dist/app/kernel/index.js +1 -0
  35. package/dist/app/kernel/rpc.d.ts +5 -2
  36. package/dist/app/registry.d.ts +4 -1
  37. package/dist/app/registry.js +24 -4
  38. package/dist/cli/commands/registry.js +2 -0
  39. package/dist/cli/commands/remote.d.ts +2 -0
  40. package/dist/cli/commands/remote.js +49 -0
  41. package/dist/cli/commands/sessions/reopen.js +3 -0
  42. package/dist/cli/commands/web.js +66 -0
  43. package/dist/cli/manifest.d.ts +63 -4
  44. package/dist/cli/manifest.js +33 -4
  45. package/dist/cli/menu/io-menu.d.ts +5 -3
  46. package/dist/cli/menu/io-menu.js +12 -5
  47. package/dist/cli/menu/remote-menu.d.ts +19 -0
  48. package/dist/cli/menu/remote-menu.js +108 -0
  49. package/dist/cli/menu/views.js +2 -0
  50. package/dist/cli/operations/handler.js +15 -1
  51. package/dist/cli/services/remote-client.d.ts +16 -0
  52. package/dist/cli/services/remote-client.js +141 -0
  53. package/dist/cli/services/sessions-service.d.ts +5 -0
  54. package/dist/cli/services/sessions-service.js +17 -1
  55. package/dist/core/config/defaults.js +11 -1
  56. package/dist/core/local-config/file.js +16 -2
  57. package/dist/core/local-config/overrides.js +7 -0
  58. package/dist/core/run/versions.js +9 -8
  59. package/dist/core/schema/config.d.ts +22 -3
  60. package/dist/core/schema/config.js +39 -2
  61. package/dist/core/schema/web.d.ts +51 -0
  62. package/dist/core/schema/web.js +46 -1
  63. package/dist/core/util/fs.d.ts +5 -0
  64. package/dist/core/util/fs.js +23 -0
  65. package/dist/core/util/index.d.ts +1 -1
  66. package/dist/core/util/index.js +1 -1
  67. package/dist/guest/daemon/daemon.js +2 -0
  68. package/dist/guest/daemon/idle-shutdown.js +2 -1
  69. package/dist/guest/daemon/sessions.d.ts +16 -2
  70. package/dist/guest/daemon/sessions.js +126 -20
  71. package/dist/guest/git/decision.js +7 -0
  72. package/dist/guest/sessions/cmdline.d.ts +14 -0
  73. package/dist/guest/sessions/cmdline.js +10 -0
  74. package/dist/guest/toolchains/setup.js +3 -0
  75. package/dist/host/audit/log.d.ts +3 -2
  76. package/dist/host/audit/log.js +3 -3
  77. package/dist/host/channel/channel.d.ts +5 -1
  78. package/dist/host/channel/channel.js +12 -0
  79. package/dist/host/guard/agent-files.js +7 -1
  80. package/dist/host/guard/commands.js +13 -1
  81. package/dist/host/guard/git-config.js +27 -7
  82. package/dist/host/guard/git-dirs.js +78 -3
  83. package/dist/host/guard/mark-of-the-web.js +3 -1
  84. package/dist/host/guard/nested-repos.js +7 -2
  85. package/dist/host/guard/store.js +1 -0
  86. package/dist/host/guard/types.d.ts +2 -0
  87. package/dist/host/launch/mappings.js +11 -1
  88. package/dist/host/launch/project-scan.js +2 -21
  89. package/dist/host/launch/stage.js +4 -0
  90. package/dist/host/life/session-ledger.js +2 -1
  91. package/dist/host/remote/access-store.d.ts +156 -0
  92. package/dist/host/remote/access-store.js +308 -0
  93. package/dist/host/remote/cert.d.ts +20 -0
  94. package/dist/host/remote/cert.js +103 -0
  95. package/dist/host/remote/door-client.d.ts +13 -0
  96. package/dist/host/remote/door-client.js +54 -0
  97. package/dist/host/remote/firewall.d.ts +44 -0
  98. package/dist/host/remote/firewall.js +118 -0
  99. package/dist/host/remote/gate.d.ts +125 -0
  100. package/dist/host/remote/gate.js +391 -0
  101. package/dist/host/remote/index.d.ts +8 -0
  102. package/dist/host/remote/index.js +10 -0
  103. package/dist/host/remote/lan.d.ts +24 -0
  104. package/dist/host/remote/lan.js +88 -0
  105. package/dist/host/remote/tls-store.d.ts +22 -0
  106. package/dist/host/remote/tls-store.js +63 -0
  107. package/dist/host/remote/windows-helper.d.ts +67 -0
  108. package/dist/host/remote/windows-helper.js +129 -0
  109. package/dist/host/web/bridge.d.ts +5 -1
  110. package/dist/host/web/bridge.js +6 -2
  111. package/dist/host/web/browser.js +1 -1
  112. package/dist/host/web/control.d.ts +4 -1
  113. package/dist/host/web/control.js +28 -4
  114. package/dist/host/web/rpc.d.ts +16 -2
  115. package/dist/host/web/rpc.js +37 -4
  116. package/dist/host/web/runner.d.ts +4 -0
  117. package/dist/host/web/runner.js +12 -0
  118. package/dist/host/web/server.d.ts +18 -2
  119. package/dist/host/web/server.js +97 -24
  120. package/dist/host/web/state.d.ts +5 -0
  121. package/dist/host/web/state.js +8 -0
  122. package/dist/runtime/agent.js +74 -3
  123. package/dist/runtime/daemon.js +205 -24
  124. package/dist/runtime/db.js +74 -3
  125. package/dist/runtime/gitguard.js +6 -0
  126. package/dist/runtime/main.js +77 -3
  127. package/dist/runtime/netproxy.js +74 -3
  128. package/dist/runtime/view.js +76 -3
  129. package/dist/selftest/app.test.js +41 -6
  130. package/dist/selftest/channel.test.js +16 -0
  131. package/dist/selftest/cmdline.test.js +16 -0
  132. package/dist/selftest/git-access.test.js +8 -0
  133. package/dist/selftest/hostile-input.test.js +12 -1
  134. package/dist/selftest/index.js +2 -0
  135. package/dist/selftest/live-daemon.test.js +25 -0
  136. package/dist/selftest/local-config.test.js +30 -0
  137. package/dist/selftest/remote.test.d.ts +2 -0
  138. package/dist/selftest/remote.test.js +536 -0
  139. package/dist/selftest/repo-guard.test.js +56 -2
  140. package/dist/selftest/resume.test.js +2 -1
  141. package/dist/selftest/sharing.test.js +9 -2
  142. package/dist/selftest/support/fixtures.d.ts +13 -0
  143. package/dist/selftest/support/web-preview.js +63 -0
  144. package/dist/selftest/web.test.js +2 -0
  145. package/dist/shared/channel/schema.d.ts +23 -0
  146. package/dist/shared/channel/schema.js +8 -0
  147. package/dist/shared/pty/protocol.d.ts +2 -0
  148. package/dist/shared/pty/protocol.js +2 -0
  149. package/dist/web/index.html +1 -1
  150. package/dist/web/main-Z2WYAE2Z.js +221 -0
  151. package/native/csharp/HostTools/Program.cs +17 -1
  152. package/native/csharp/HostTools/WebHelper.cs +499 -0
  153. package/native/scripts/get-network-profile.ps1 +19 -0
  154. package/native/scripts/set-folder-access.ps1 +11 -0
  155. package/native/scripts/set-web-firewall.ps1 +103 -0
  156. package/package.json +1 -1
  157. package/sandbox/csharp/PtyHost.cs +20 -0
  158. package/schemas/config.schema.json +65 -0
  159. package/schemas/sandbox-config.schema.json +0 -18
  160. package/dist/web/main-UD4QCXZR.js +0 -219
package/README.md CHANGED
@@ -95,6 +95,7 @@ Details and settings: [troubleshooting](docs/troubleshooting.md#the-sandbox-clos
95
95
  | `sandbox <agent>`, `claude-sandbox` | start an agent; `-t` for this terminal, `--headless` for no window |
96
96
  | `sandbox start` | the sandbox without an agent |
97
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 |
98
99
  | `sandbox restart` / `resume` | restart the agents in place; reopen the last sessions |
99
100
  | `sandbox projects` | the projects a sandbox can open without a restart: roots, `all`, exclude |
100
101
  | `sandbox db [start\|stop <name>]` | list, start or stop databases (`sbx-db` inside) |
@@ -228,7 +228,17 @@
228
228
  "web": {
229
229
  "enabled": false,
230
230
  "port": 47800,
231
- "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
+ }
232
242
  },
233
243
  "agentCommands": {
234
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>;