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.
Files changed (246) hide show
  1. package/README.md +13 -6
  2. package/config.example.json +15 -4
  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-rows.d.ts +4 -1
  12. package/dist/app/features/apps/apps-rows.js +7 -2
  13. package/dist/app/features/apps/apps.def.d.ts +19 -0
  14. package/dist/app/features/apps/apps.def.js +50 -12
  15. package/dist/app/features/apps/apps.run.d.ts +2 -0
  16. package/dist/app/features/apps/apps.run.js +1 -1
  17. package/dist/app/features/audit/audit.def.d.ts +350 -0
  18. package/dist/app/features/audit/audit.def.js +457 -0
  19. package/dist/app/features/audit/audit.run.d.ts +110 -0
  20. package/dist/app/features/audit/audit.run.js +133 -0
  21. package/dist/app/features/audit/index.d.ts +2 -0
  22. package/dist/app/features/audit/index.js +3 -0
  23. package/dist/app/features/defs.d.ts +349 -4
  24. package/dist/app/features/defs.js +16 -1
  25. package/dist/app/features/files/files.def.js +1 -0
  26. package/dist/app/features/health/health.def.js +1 -0
  27. package/dist/app/features/sandbox/sandbox.def.js +3 -0
  28. package/dist/app/features/sessions/sessions.def.d.ts +36 -0
  29. package/dist/app/features/sessions/sessions.def.js +77 -9
  30. package/dist/app/features/sessions/sessions.run.d.ts +25 -2
  31. package/dist/app/features/sessions/sessions.run.js +71 -3
  32. package/dist/app/features/settings/settings.def.d.ts +8 -0
  33. package/dist/app/features/settings/settings.def.js +17 -0
  34. package/dist/app/features/settings/settings.run.d.ts +3 -0
  35. package/dist/app/features/settings/settings.run.js +16 -2
  36. package/dist/app/features/web/web.def.d.ts +9 -0
  37. package/dist/app/features/web/web.def.js +26 -1
  38. package/dist/app/features/web/web.run.d.ts +3 -0
  39. package/dist/app/features/web/web.run.js +11 -3
  40. package/dist/app/kernel/access.d.ts +33 -0
  41. package/dist/app/kernel/access.js +44 -0
  42. package/dist/app/kernel/define.d.ts +26 -2
  43. package/dist/app/kernel/index.d.ts +1 -0
  44. package/dist/app/kernel/index.js +1 -0
  45. package/dist/app/kernel/rpc.d.ts +5 -2
  46. package/dist/app/registry.d.ts +4 -1
  47. package/dist/app/registry.js +26 -4
  48. package/dist/cli/args.d.ts +0 -3
  49. package/dist/cli/args.js +3 -4
  50. package/dist/cli/commands/index.d.ts +1 -1
  51. package/dist/cli/commands/index.js +1 -1
  52. package/dist/cli/commands/launch/prepare.js +1 -1
  53. package/dist/cli/commands/maintenance/index.d.ts +0 -1
  54. package/dist/cli/commands/maintenance/index.js +0 -1
  55. package/dist/cli/commands/registry.js +3 -2
  56. package/dist/cli/commands/remote.d.ts +2 -0
  57. package/dist/cli/commands/remote.js +49 -0
  58. package/dist/cli/commands/sessions/reopen.js +3 -0
  59. package/dist/cli/commands/web.js +66 -0
  60. package/dist/cli/manifest.d.ts +63 -45
  61. package/dist/cli/manifest.js +33 -18
  62. package/dist/cli/menu/io-menu.d.ts +5 -3
  63. package/dist/cli/menu/io-menu.js +12 -5
  64. package/dist/cli/menu/pages/apps-page.js +2 -15
  65. package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
  66. package/dist/cli/menu/pages/maintenance-page.js +51 -8
  67. package/dist/cli/menu/remote-menu.d.ts +19 -0
  68. package/dist/cli/menu/remote-menu.js +108 -0
  69. package/dist/cli/menu/views.js +4 -0
  70. package/dist/cli/operations/handler.js +15 -1
  71. package/dist/cli/services/remote-client.d.ts +16 -0
  72. package/dist/cli/services/remote-client.js +141 -0
  73. package/dist/cli/services/sessions-service.d.ts +5 -0
  74. package/dist/cli/services/sessions-service.js +17 -1
  75. package/dist/core/config/defaults.js +12 -2
  76. package/dist/core/local-config/file.js +16 -2
  77. package/dist/core/local-config/overrides.js +7 -0
  78. package/dist/core/run/versions.js +9 -8
  79. package/dist/core/schema/apps.d.ts +9 -0
  80. package/dist/core/schema/apps.js +9 -0
  81. package/dist/core/schema/config.d.ts +23 -3
  82. package/dist/core/schema/config.js +42 -5
  83. package/dist/core/schema/sandbox.d.ts +1 -0
  84. package/dist/core/schema/sandbox.js +11 -3
  85. package/dist/core/schema/web.d.ts +51 -0
  86. package/dist/core/schema/web.js +46 -1
  87. package/dist/core/util/fs.d.ts +5 -0
  88. package/dist/core/util/fs.js +23 -0
  89. package/dist/core/util/index.d.ts +1 -1
  90. package/dist/core/util/index.js +1 -1
  91. package/dist/guest/apps/app-installer.js +50 -4
  92. package/dist/guest/apps/index.d.ts +1 -1
  93. package/dist/guest/apps/index.js +1 -1
  94. package/dist/guest/apps/sources.js +1 -1
  95. package/dist/guest/audit/parse.d.ts +32 -2
  96. package/dist/guest/audit/parse.js +43 -5
  97. package/dist/guest/audit/process-audit.d.ts +3 -1
  98. package/dist/guest/audit/process-audit.js +9 -6
  99. package/dist/guest/boot/boot.js +12 -3
  100. package/dist/guest/boot/guide.d.ts +1 -1
  101. package/dist/guest/boot/guide.js +27 -3
  102. package/dist/guest/daemon/daemon.js +2 -0
  103. package/dist/guest/daemon/idle-shutdown.js +2 -1
  104. package/dist/guest/daemon/sessions.d.ts +23 -2
  105. package/dist/guest/daemon/sessions.js +151 -20
  106. package/dist/guest/entries/daemon.js +2 -1
  107. package/dist/guest/git/decision.js +7 -0
  108. package/dist/guest/sessions/cmdline.d.ts +14 -0
  109. package/dist/guest/sessions/cmdline.js +10 -0
  110. package/dist/guest/setup/addon-mcp.d.ts +2 -0
  111. package/dist/guest/setup/addon-mcp.js +49 -0
  112. package/dist/guest/setup/agent-addons.d.ts +9 -0
  113. package/dist/guest/setup/agent-addons.js +22 -0
  114. package/dist/guest/setup/headroom.d.ts +7 -0
  115. package/dist/guest/setup/headroom.js +60 -0
  116. package/dist/guest/setup/managed-policy.d.ts +2 -1
  117. package/dist/guest/setup/managed-policy.js +15 -6
  118. package/dist/guest/toolchains/plan.js +8 -3
  119. package/dist/guest/toolchains/setup.js +3 -0
  120. package/dist/host/audit/attribute.d.ts +46 -0
  121. package/dist/host/audit/attribute.js +76 -0
  122. package/dist/host/audit/collect.d.ts +29 -3
  123. package/dist/host/audit/collect.js +256 -61
  124. package/dist/host/audit/git-commits.d.ts +29 -0
  125. package/dist/host/audit/git-commits.js +157 -0
  126. package/dist/host/audit/index.d.ts +5 -0
  127. package/dist/host/audit/index.js +6 -0
  128. package/dist/host/audit/log.d.ts +45 -7
  129. package/dist/host/audit/log.js +148 -45
  130. package/dist/host/audit/redact.d.ts +2 -0
  131. package/dist/host/audit/redact.js +67 -0
  132. package/dist/host/channel/channel.d.ts +5 -1
  133. package/dist/host/channel/channel.js +12 -0
  134. package/dist/host/guard/agent-files.js +7 -1
  135. package/dist/host/guard/commands.js +13 -1
  136. package/dist/host/guard/git-config.js +27 -7
  137. package/dist/host/guard/git-dirs.js +78 -3
  138. package/dist/host/guard/mark-of-the-web.js +3 -1
  139. package/dist/host/guard/nested-repos.js +7 -2
  140. package/dist/host/guard/store.js +1 -0
  141. package/dist/host/guard/types.d.ts +2 -0
  142. package/dist/host/launch/mappings.js +11 -1
  143. package/dist/host/launch/project-scan.js +2 -21
  144. package/dist/host/launch/stage.js +4 -0
  145. package/dist/host/life/session-ledger.js +2 -1
  146. package/dist/host/remote/access-store.d.ts +156 -0
  147. package/dist/host/remote/access-store.js +308 -0
  148. package/dist/host/remote/cert.d.ts +20 -0
  149. package/dist/host/remote/cert.js +103 -0
  150. package/dist/host/remote/door-client.d.ts +13 -0
  151. package/dist/host/remote/door-client.js +54 -0
  152. package/dist/host/remote/firewall.d.ts +44 -0
  153. package/dist/host/remote/firewall.js +118 -0
  154. package/dist/host/remote/gate.d.ts +125 -0
  155. package/dist/host/remote/gate.js +391 -0
  156. package/dist/host/remote/index.d.ts +8 -0
  157. package/dist/host/remote/index.js +10 -0
  158. package/dist/host/remote/lan.d.ts +24 -0
  159. package/dist/host/remote/lan.js +88 -0
  160. package/dist/host/remote/tls-store.d.ts +22 -0
  161. package/dist/host/remote/tls-store.js +63 -0
  162. package/dist/host/remote/windows-helper.d.ts +67 -0
  163. package/dist/host/remote/windows-helper.js +129 -0
  164. package/dist/host/web/bridge.d.ts +5 -1
  165. package/dist/host/web/bridge.js +6 -2
  166. package/dist/host/web/browser.js +1 -1
  167. package/dist/host/web/control.d.ts +4 -1
  168. package/dist/host/web/control.js +28 -4
  169. package/dist/host/web/rpc.d.ts +16 -2
  170. package/dist/host/web/rpc.js +37 -4
  171. package/dist/host/web/runner.d.ts +4 -0
  172. package/dist/host/web/runner.js +12 -0
  173. package/dist/host/web/server.d.ts +18 -2
  174. package/dist/host/web/server.js +97 -24
  175. package/dist/host/web/state.d.ts +5 -0
  176. package/dist/host/web/state.js +8 -0
  177. package/dist/runtime/agent.js +184 -18
  178. package/dist/runtime/daemon.js +772 -338
  179. package/dist/runtime/db.js +184 -18
  180. package/dist/runtime/gitguard.js +6 -0
  181. package/dist/runtime/main.js +966 -553
  182. package/dist/runtime/netproxy.js +184 -18
  183. package/dist/runtime/view.js +186 -18
  184. package/dist/selftest/app.test.js +66 -7
  185. package/dist/selftest/audit.test.js +354 -47
  186. package/dist/selftest/channel.test.js +16 -0
  187. package/dist/selftest/cmdline.test.js +16 -0
  188. package/dist/selftest/git-access.test.js +8 -0
  189. package/dist/selftest/hcs.test.js +35 -0
  190. package/dist/selftest/hostile-input.test.js +12 -1
  191. package/dist/selftest/index.js +2 -0
  192. package/dist/selftest/live-daemon.test.js +25 -0
  193. package/dist/selftest/local-config.test.js +30 -0
  194. package/dist/selftest/menu-screens.js +1 -1
  195. package/dist/selftest/menu.test.js +3 -3
  196. package/dist/selftest/remote.test.d.ts +2 -0
  197. package/dist/selftest/remote.test.js +536 -0
  198. package/dist/selftest/repo-guard.test.js +56 -2
  199. package/dist/selftest/resume.test.js +2 -1
  200. package/dist/selftest/sharing.test.js +9 -2
  201. package/dist/selftest/support/engine-golden.js +9 -0
  202. package/dist/selftest/support/fixtures.d.ts +14 -0
  203. package/dist/selftest/support/web-preview.js +157 -0
  204. package/dist/selftest/toolchains.test.js +147 -2
  205. package/dist/selftest/web.test.js +2 -0
  206. package/dist/shared/apps/agent-addons.d.ts +64 -0
  207. package/dist/shared/apps/agent-addons.js +95 -0
  208. package/dist/shared/apps/catalogue.d.ts +33 -2
  209. package/dist/shared/apps/catalogue.js +102 -11
  210. package/dist/shared/apps/index.d.ts +3 -2
  211. package/dist/shared/apps/index.js +3 -2
  212. package/dist/shared/apps/selection.d.ts +2 -0
  213. package/dist/shared/apps/selection.js +7 -0
  214. package/dist/shared/channel/schema.d.ts +23 -0
  215. package/dist/shared/channel/schema.js +8 -0
  216. package/dist/shared/pty/protocol.d.ts +2 -0
  217. package/dist/shared/pty/protocol.js +2 -0
  218. package/dist/sync/watcher.js +9 -1
  219. package/dist/web/index.html +2 -2
  220. package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
  221. package/dist/web/main-Z2WYAE2Z.js +221 -0
  222. package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
  223. package/native/csharp/HcsHost/Hcs.cs +9 -0
  224. package/native/csharp/HcsHost/Program.cs +26 -1
  225. package/native/csharp/HcsHost/Share.cs +173 -0
  226. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  227. package/native/csharp/HostTools/Program.cs +17 -1
  228. package/native/csharp/HostTools/WebHelper.cs +499 -0
  229. package/native/scripts/get-network-profile.ps1 +19 -0
  230. package/native/scripts/set-folder-access.ps1 +11 -0
  231. package/native/scripts/set-web-firewall.ps1 +103 -0
  232. package/package.json +1 -1
  233. package/sandbox/addons/ccusage/package-lock.json +111 -0
  234. package/sandbox/addons/ccusage/package.json +7 -0
  235. package/sandbox/addons/context7/package-lock.json +1313 -0
  236. package/sandbox/addons/context7/package.json +7 -0
  237. package/sandbox/addons/headroom/requirements.in +1 -0
  238. package/sandbox/addons/headroom/requirements.txt +2922 -0
  239. package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
  240. package/sandbox/addons/playwright-mcp/package.json +7 -0
  241. package/sandbox/csharp/PtyHost.cs +20 -0
  242. package/schemas/config.schema.json +78 -4
  243. package/schemas/sandbox-config.schema.json +2 -20
  244. package/dist/cli/commands/maintenance/audit.d.ts +0 -3
  245. package/dist/cli/commands/maintenance/audit.js +0 -58
  246. package/dist/web/main-K2JMCAZI.js +0 -219
@@ -499,47 +499,6 @@ declare const COMMAND_LIST: readonly [{
499
499
  readonly group: "Data";
500
500
  readonly args: readonly [];
501
501
  readonly options: readonly [];
502
- }, {
503
- readonly name: "audit";
504
- readonly summary: "what happened in your sandboxes: programs started, files changed, sessions, repo guard (turn it on: sandbox config set audit.enabled true)";
505
- readonly group: "Health";
506
- readonly json: true;
507
- readonly args: readonly [];
508
- readonly options: readonly [{
509
- readonly flag: "--days";
510
- readonly type: "number";
511
- readonly value: "n";
512
- readonly description: "the last n days (default: today)";
513
- readonly default: 1;
514
- }, {
515
- flag: string;
516
- type: "string" | "number" | "boolean" | "list";
517
- description: string;
518
- short?: string | undefined;
519
- value?: string | undefined;
520
- label?: string | undefined;
521
- default?: string | number | undefined;
522
- choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
523
- advanced?: boolean | undefined;
524
- when?: string | undefined;
525
- noMenu?: boolean | undefined;
526
- group?: string | undefined;
527
- shownAs?: string | undefined;
528
- }, {
529
- flag: string;
530
- type: "string" | "number" | "boolean" | "list";
531
- description: string;
532
- short?: string | undefined;
533
- value?: string | undefined;
534
- label?: string | undefined;
535
- default?: string | number | undefined;
536
- choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
537
- advanced?: boolean | undefined;
538
- when?: string | undefined;
539
- noMenu?: boolean | undefined;
540
- group?: string | undefined;
541
- shownAs?: string | undefined;
542
- }];
543
502
  }, {
544
503
  readonly name: "logs";
545
504
  readonly summary: "print the newest sandbox log";
@@ -810,17 +769,76 @@ declare const COMMAND_LIST: readonly [{
810
769
  readonly interactive: true;
811
770
  }, {
812
771
  readonly name: "web";
813
- readonly summary: "(beta) your sandbox sessions in a browser on this PC: start, stop, status or open";
772
+ readonly summary: "(beta) your sandbox sessions in a browser on this PC (and, if you allow it, on your network)";
814
773
  readonly group: "Web";
815
- readonly examples: readonly ["sandbox web open start the web UI and open it in your browser"];
774
+ readonly examples: readonly ["sandbox web open start the web UI and open it in your browser", "sandbox web lan-on let devices you allow use it on your network", "sandbox web allow-device show a one-time code for a phone", "sandbox web devices who may use it; sandbox web revoke <id> signs one out"];
816
775
  readonly args: readonly [{
817
776
  readonly name: "action";
818
- readonly description: "start the server, stop it, show whether it runs, or open it in your browser";
777
+ readonly description: "start, stop, status, open; devices, allow-device, revoke, revoke-all, lan-on, lan-off (remote access)";
819
778
  readonly required: false;
820
779
  readonly choices: readonly ["start", "stop", "status", "open"];
780
+ }, {
781
+ readonly name: "id";
782
+ readonly description: "(revoke) the device or sign-in id from sandbox web devices";
783
+ readonly required: false;
784
+ readonly when: "revoke";
785
+ }];
786
+ readonly options: readonly [{
787
+ flag: string;
788
+ type: "string" | "number" | "boolean" | "list";
789
+ description: string;
790
+ short?: string | undefined;
791
+ value?: string | undefined;
792
+ label?: string | undefined;
793
+ default?: string | number | undefined;
794
+ choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
795
+ advanced?: boolean | undefined;
796
+ when?: string | undefined;
797
+ noMenu?: boolean | undefined;
798
+ group?: string | undefined;
799
+ shownAs?: string | undefined;
800
+ }, {
801
+ flag: string;
802
+ type: "string" | "number" | "boolean" | "list";
803
+ description: string;
804
+ short?: string | undefined;
805
+ value?: string | undefined;
806
+ label?: string | undefined;
807
+ default?: string | number | undefined;
808
+ choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
809
+ advanced?: boolean | undefined;
810
+ when?: string | undefined;
811
+ noMenu?: boolean | undefined;
812
+ group?: string | undefined;
813
+ shownAs?: string | undefined;
821
814
  }];
822
- readonly options: readonly [];
823
815
  readonly json: true;
816
+ }, {
817
+ readonly name: "remote";
818
+ readonly summary: "(beta) use another Windows user's web UI from here, signed in with your Windows account";
819
+ readonly group: "Web";
820
+ readonly examples: readonly ["sandbox remote the menu over the owner of the running sandbox (their program, your role)", "sandbox remote alice --browser open alice's web UI in your browser, signed in"];
821
+ readonly args: readonly [{
822
+ readonly name: "user";
823
+ readonly description: "whose web UI (default: the owner of the running sandbox)";
824
+ readonly required: false;
825
+ }];
826
+ readonly options: readonly [{
827
+ flag: string;
828
+ type: "string" | "number" | "boolean" | "list";
829
+ description: string;
830
+ short?: string | undefined;
831
+ value?: string | undefined;
832
+ label?: string | undefined;
833
+ default?: string | number | undefined;
834
+ choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
835
+ advanced?: boolean | undefined;
836
+ when?: string | undefined;
837
+ noMenu?: boolean | undefined;
838
+ group?: string | undefined;
839
+ shownAs?: string | undefined;
840
+ }];
841
+ readonly interactive: true;
824
842
  }, {
825
843
  readonly name: "_watch";
826
844
  readonly summary: "background history sync";
@@ -386,20 +386,6 @@ const COMMAND_LIST = [
386
386
  json,
387
387
  },
388
388
  { name: 'sync', summary: 'copy conversation history back to this PC now', group: 'Data', args: [], options: [] },
389
- {
390
- name: 'audit',
391
- summary: 'what happened in your sandboxes: programs started, files changed, sessions, repo guard (turn it on: sandbox config set audit.enabled true)',
392
- group: 'Health',
393
- json,
394
- args: [],
395
- options: [
396
- { flag: '--days', type: 'number', value: 'n', description: 'the last n days (default: today)', default: 1 },
397
- text('--kind', 'kind', 'only proc, file, session or guard events', {
398
- choices: ['proc', 'file', 'session', 'guard'],
399
- }),
400
- text('--grep', 'text', 'only events containing this text'),
401
- ],
402
- },
403
389
  {
404
390
  name: 'logs',
405
391
  summary: 'print the newest sandbox log',
@@ -554,20 +540,49 @@ const COMMAND_LIST = [
554
540
  },
555
541
  {
556
542
  name: 'web',
557
- summary: '(beta) your sandbox sessions in a browser on this PC: start, stop, status or open',
543
+ summary: '(beta) your sandbox sessions in a browser on this PC (and, if you allow it, on your network)',
558
544
  group: 'Web',
559
- examples: ['sandbox web open start the web UI and open it in your browser'],
545
+ examples: [
546
+ 'sandbox web open start the web UI and open it in your browser',
547
+ 'sandbox web lan-on let devices you allow use it on your network',
548
+ 'sandbox web allow-device show a one-time code for a phone',
549
+ 'sandbox web devices who may use it; sandbox web revoke <id> signs one out',
550
+ ],
560
551
  args: [
561
552
  {
562
553
  name: 'action',
563
- description: 'start the server, stop it, show whether it runs, or open it in your browser',
554
+ description: 'start, stop, status, open; devices, allow-device, revoke, revoke-all, lan-on, lan-off (remote access)',
564
555
  required: false,
565
556
  choices: ['start', 'stop', 'status', 'open'],
566
557
  },
558
+ {
559
+ name: 'id',
560
+ description: '(revoke) the device or sign-in id from sandbox web devices',
561
+ required: false,
562
+ when: 'revoke',
563
+ },
564
+ ],
565
+ options: [
566
+ text('--role', 'view|control', '(allow-device) what the device may do (default view)', {
567
+ when: 'allow-device',
568
+ choices: ['view', 'control'],
569
+ }),
570
+ bool('--yes', '(revoke-all, lan-off) do not ask first', { short: '-y', noMenu: true }),
567
571
  ],
568
- options: [],
569
572
  json,
570
573
  },
574
+ {
575
+ name: 'remote',
576
+ summary: "(beta) use another Windows user's web UI from here, signed in with your Windows account",
577
+ group: 'Web',
578
+ examples: [
579
+ 'sandbox remote the menu over the owner of the running sandbox (their program, your role)',
580
+ "sandbox remote alice --browser open alice's web UI in your browser, signed in",
581
+ ],
582
+ args: [{ name: 'user', description: 'whose web UI (default: the owner of the running sandbox)', required: false }],
583
+ options: [bool('--browser', 'open it in your browser instead of the menu')],
584
+ interactive: true,
585
+ },
571
586
  { name: '_watch', summary: 'background history sync', group: 'Data', args: [], options: [], hidden: true },
572
587
  {
573
588
  name: '_web',
@@ -1,4 +1,4 @@
1
- import { type Action, type AnyDefinition } from '../../app/kernel/index.js';
1
+ import { type Action, type AnyDefinition, type OperationIO } from '../../app/kernel/index.js';
2
2
  import type { Context } from '../commands/context.js';
3
3
  import { type App } from '../tui/index.js';
4
4
  import type { ShellExit } from './shell.js';
@@ -12,10 +12,12 @@ export interface Performed {
12
12
  readonly notice?: string;
13
13
  readonly reload?: boolean;
14
14
  }
15
+ /** How a definition is run: in this process (the menu), or on the owner's web UI (sandbox remote). */
16
+ export type DefinitionRunner = (id: string, input: unknown, io: OperationIO) => Promise<unknown>;
15
17
  export declare class MenuOperations {
16
18
  private readonly app;
17
- private readonly ctx;
18
- constructor(app: App, ctx: Context);
19
+ private readonly runner;
20
+ constructor(app: App, ctx: Context, runner?: DefinitionRunner);
19
21
  /** Does what an action of a page says: follows a link, or asks for its fields and runs its operation. */
20
22
  perform(action: Action, where: Where, busy?: ShowBusy, onOutput?: (output: unknown) => void): Promise<Performed>;
21
23
  /** The values of an action's fields, from the editors that fit them (undefined: one was backed out of). */
@@ -66,10 +66,10 @@ function follow(target) {
66
66
  }
67
67
  export class MenuOperations {
68
68
  app;
69
- ctx;
70
- constructor(app, ctx) {
69
+ runner;
70
+ constructor(app, ctx, runner = (id, input, io) => runDefinition(id, ctx, input, io)) {
71
71
  this.app = app;
72
- this.ctx = ctx;
72
+ this.runner = runner;
73
73
  }
74
74
  /** Does what an action of a page says: follows a link, or asks for its fields and runs its operation. */
75
75
  async perform(action, where, busy, onOutput) {
@@ -81,7 +81,14 @@ export class MenuOperations {
81
81
  const answers = await this.fields(action, def, where);
82
82
  if (!answers)
83
83
  return {};
84
- return { reload: true, notice: await this.run(def, { ...action.input, ...answers }, where, busy, onOutput) };
84
+ // An operation that opens a session's terminal (a new shell) leaves the menu to attach to it.
85
+ let opened;
86
+ const notice = await this.run(def, { ...action.input, ...answers }, where, busy, (output) => {
87
+ onOutput?.(output);
88
+ opened = def.kind === 'operation' ? def.opens?.(output) : undefined;
89
+ });
90
+ const exit = opened ? follow(opened).exit : undefined;
91
+ return { reload: true, notice, ...(exit ? { exit } : {}) };
85
92
  }
86
93
  /** The values of an action's fields, from the editors that fit them (undefined: one was backed out of). */
87
94
  async fields(action, def, where) {
@@ -110,7 +117,7 @@ export class MenuOperations {
110
117
  log: (text, tone) => said.push(tone === 'warn' || tone === 'danger' ? Theme.warn(text) : text),
111
118
  });
112
119
  try {
113
- const output = await runDefinition(def.id, this.ctx, input, io);
120
+ const output = await this.runner(def.id, input, io);
114
121
  onOutput?.(output);
115
122
  // The notice is one line: an operation that says several lines (accounts use) runs them together.
116
123
  const text = def.text?.(output)?.replace(/\n+/g, ' ');
@@ -4,7 +4,7 @@
4
4
  // (apps.toggle), so the staging stays here: "r" runs the feature's request operation, "c" stages your own programs.
5
5
  import _ from 'lodash';
6
6
  import { errMsg } from '../../../core/util/index.js';
7
- import { APP_GROUPS, appIsOn, appRows, customRows, CUSTOM_KEY, entriesOf, INSTALL, ISSUES_URL, requestApp, REQUEST_APP, SAVED, } from '../../../app/features/apps/index.js';
7
+ import { APP_GROUPS, appDetails, appIsOn, appRows, customRows, CUSTOM_KEY, entriesOf, INSTALL, ISSUES_URL, requestApp, REQUEST_APP, SAVED, } from '../../../app/features/apps/index.js';
8
8
  import { action } from '../../../app/kernel/index.js';
9
9
  import { Theme } from '../../tui/index.js';
10
10
  import { ValueEditor } from '../settings/value-editor.js';
@@ -37,20 +37,7 @@ export function appsCategories(file) {
37
37
  ];
38
38
  }
39
39
  function appItem(row) {
40
- const notes = [
41
- Theme.muted('Download ') +
42
- '~' +
43
- row.size +
44
- ' once, kept in C:\\AgentData\\tools\\' +
45
- (row.setting ? '' : 'apps\\') +
46
- row.id,
47
- Theme.muted('Starts ') + (row.cli ? 'from a terminal (on PATH)' : 'from the desktop or Start > Sandbox apps'),
48
- ...(row.mesa ? [Theme.muted('Graphics ') + 'OpenGL on the CPU (Mesa) is added for it'] : []),
49
- ...(row.fullGitOnly
50
- ? [Theme.warn('Needs full git access (Settings > Projects & setup > Git): it pushes with its own git.')]
51
- : []),
52
- ...(row.setting ? [Theme.muted('Setting ') + row.setting] : []),
53
- ];
40
+ const notes = appDetails(row).map((d) => d.warn ? Theme.warn(d.text) : (d.label ? Theme.muted(d.label.padEnd(10)) : '') + d.text);
54
41
  return {
55
42
  value: row.id,
56
43
  label: row.name,
@@ -3,6 +3,7 @@ import { type Engines, type Leftovers } from '../../../app/features/engine/index
3
3
  import { type HealthLast } from '../../../app/features/health/index.js';
4
4
  import { type ContainedStatus, type StorageShow } from '../../../app/features/storage/index.js';
5
5
  import { type LogFiles } from '../../../app/features/logs/index.js';
6
+ import { type AuditList } from '../../../app/features/audit/index.js';
6
7
  import { type UpdatesStatus } from '../../../app/features/updates/updates.def.js';
7
8
  import { type WebStatus } from '../../../app/features/web/index.js';
8
9
  import type { Context } from '../../commands/context.js';
@@ -31,6 +32,8 @@ export interface MaintenanceModel {
31
32
  readonly engine: Engines | null;
32
33
  /** HCS systems left running, read with the engines. */
33
34
  readonly leftovers?: Leftovers | null;
35
+ /** The audit log today (the audit view's query; null: not read yet or unreadable). */
36
+ readonly audit?: AuditList | null;
34
37
  /** The web UI's server (the web feature's status): running or not, and its address. */
35
38
  readonly web: WebStatus;
36
39
  /** The commands listed under "More commands". */
@@ -52,6 +55,7 @@ export declare class MaintenancePage extends ListPage<string> {
52
55
  private engine;
53
56
  private leftovers;
54
57
  private web;
58
+ private audit;
55
59
  private readonly operations;
56
60
  /** The newer version the last check found (null: none, or not looked yet). */
57
61
  private latest;
@@ -1,6 +1,6 @@
1
1
  // Section 7: keeping things in shape. Updates (the program and your saved settings), Data & storage (the data folder,
2
- // what is stored, clean-up), Health (doctor, repair and this PC's setup together), the logs, and the less common
3
- // commands. Each is a category on the left and loads when you open it.
2
+ // what is stored, clean-up), Health (doctor, repair and this PC's setup together), Activity (the audit log), the logs,
3
+ // and the less common commands. Each is a category on the left and loads when you open it.
4
4
  import { dataPage, showData } from '../../../app/features/data/index.js';
5
5
  import { readQuery } from '../../../app/registry.js';
6
6
  import { errMsg } from '../../../core/util/index.js';
@@ -8,6 +8,7 @@ import { enginePage, engineLeftovers, engineStatus, } from '../../../app/feature
8
8
  import { healthPage, lastHealth } from '../../../app/features/health/index.js';
9
9
  import { containedStatus, showStorage, storagePage, } from '../../../app/features/storage/index.js';
10
10
  import { CRASH, listLogs, logsPage } from '../../../app/features/logs/index.js';
11
+ import { auditArgv, auditPage, listAudit } from '../../../app/features/audit/index.js';
11
12
  import { updatesPage, updatesStatus } from '../../../app/features/updates/updates.def.js';
12
13
  import { webPage, webStatus } from '../../../app/features/web/index.js';
13
14
  import { commandSpec } from '../../manifest.js';
@@ -32,6 +33,11 @@ const CATEGORIES = [
32
33
  label: 'Health',
33
34
  description: 'Checks this PC and your settings, repairs problems, and sets up Windows Sandbox.',
34
35
  },
36
+ {
37
+ id: 'activity',
38
+ label: 'Activity',
39
+ description: 'What your agents ran, changed and committed in the last 24 hours (the audit log). Enter on a session shows all of it.',
40
+ },
35
41
  { id: 'logs', label: 'Logs', description: "The sandbox's own logs." },
36
42
  {
37
43
  id: 'web',
@@ -45,14 +51,13 @@ const CATEGORIES = [
45
51
  },
46
52
  { id: 'more', label: 'More commands', description: 'Less common commands.' },
47
53
  ];
48
- const MORE_COMMANDS = ['sync', 'audit', 'config', 'generate', 'exec', 'agent-commands'];
54
+ const MORE_COMMANDS = ['sync', 'config', 'generate', 'exec', 'agent-commands'];
49
55
  const NO_LOGS = { folder: '', files: [], crash: false };
50
56
  /** Until the status is read: the server counts as not running. */
51
57
  const NO_WEB = { running: false, port: null, address: null };
52
58
  /** Plain names for the commands under More commands. */
53
59
  const MORE_LABELS = {
54
60
  sync: 'Copy history back now',
55
- audit: 'What happened in the sandbox',
56
61
  config: 'Your settings file',
57
62
  generate: 'Preview a start',
58
63
  exec: 'Run a command in the sandbox',
@@ -107,6 +112,10 @@ const NO_UPDATES = {
107
112
  function logsMenu(files) {
108
113
  return menuView({ ...logsPage(files), empty: undefined }, { category: 'logs', refresh: false });
109
114
  }
115
+ /** The audit view (src/app/features/audit) as the Activity category's rows and keys (none until it was read). */
116
+ function auditMenu(m) {
117
+ return m.audit ? menuView(auditPage(m.audit), { category: 'activity', prefix: 'audit:', refresh: false }) : undefined;
118
+ }
110
119
  /** The Web UI view (src/app/features/web) as the menu's rows, keys and Enter labels, for the Web UI category. */
111
120
  function webMenu(m) {
112
121
  return menuView(webPage(m.web), { category: 'web', prefix: 'web:', refresh: false });
@@ -120,6 +129,7 @@ export function maintenanceOptions(m) {
120
129
  const updates = updatesMenu(m);
121
130
  const engine = engineMenu(m);
122
131
  const web = webMenu(m);
132
+ const audit = auditMenu(m);
123
133
  const more = m.more.map((c) => ({
124
134
  value: 'more:' + c.name,
125
135
  label: MORE_LABELS[c.name] ?? Text.humanize(c.name),
@@ -129,7 +139,16 @@ export function maintenanceOptions(m) {
129
139
  return {
130
140
  path: [],
131
141
  categories: CATEGORIES,
132
- items: [...updates.items, ...dataItems(m), ...health.items, ...logs, ...web.items, ...engine.items, ...more],
142
+ items: [
143
+ ...updates.items,
144
+ ...dataItems(m),
145
+ ...health.items,
146
+ ...(audit?.items ?? []),
147
+ ...logs,
148
+ ...web.items,
149
+ ...engine.items,
150
+ ...more,
151
+ ],
133
152
  empty: 'Nothing here yet.',
134
153
  actions: maintenanceActions(m),
135
154
  enterLabel: (category, id) => category === 'health'
@@ -140,9 +159,11 @@ export function maintenanceOptions(m) {
140
159
  ? engine.enterLabel(category, id)
141
160
  : category === 'web'
142
161
  ? web.enterLabel(category, id)
143
- : (category === 'data'
144
- ? storageMenu(m.storage)?.enterLabel(category, id) || dataMenuView(m.data)?.enterLabel(category, id)
145
- : undefined) || enterLabel(id ?? ''),
162
+ : category === 'activity'
163
+ ? (audit?.enterLabel(category, id) ?? '')
164
+ : (category === 'data'
165
+ ? storageMenu(m.storage)?.enterLabel(category, id) || dataMenuView(m.data)?.enterLabel(category, id)
166
+ : undefined) || enterLabel(id ?? ''),
146
167
  };
147
168
  }
148
169
  /**
@@ -201,6 +222,8 @@ function maintenanceActions(m) {
201
222
  return [{ key: 'l', label: m.engine ? 'check again' : 'check' }, ...engineMenu(m).actions];
202
223
  case 'web':
203
224
  return webMenu(m).actions;
225
+ case 'activity':
226
+ return auditMenu(m)?.actions ?? [];
204
227
  default:
205
228
  return [];
206
229
  }
@@ -217,6 +240,7 @@ export class MaintenancePage extends ListPage {
217
240
  engine = null;
218
241
  leftovers = null;
219
242
  web = NO_WEB;
243
+ audit = null;
220
244
  operations;
221
245
  /** The newer version the last check found (null: none, or not looked yet). */
222
246
  latest = null;
@@ -235,6 +259,7 @@ export class MaintenancePage extends ListPage {
235
259
  engine: this.engine,
236
260
  leftovers: this.leftovers,
237
261
  web: this.web,
262
+ audit: this.audit,
238
263
  more: MORE_COMMANDS.flatMap((name) => {
239
264
  const spec = commandSpec(name);
240
265
  return spec ? [{ name, summary: spec.summary }] : [];
@@ -259,6 +284,13 @@ export class MaintenancePage extends ListPage {
259
284
  }
260
285
  this.last = (await readQuery(lastHealth, this.ctx)).last;
261
286
  this.updates = await readQuery(updatesStatus, this.ctx);
287
+ try {
288
+ this.audit = await readQuery(listAudit, this.ctx, { limit: 200 });
289
+ }
290
+ catch {
291
+ // An unreadable log: the category says there is nothing yet.
292
+ this.audit = null;
293
+ }
262
294
  try {
263
295
  this.web = await readQuery(webStatus, this.ctx);
264
296
  }
@@ -306,6 +338,17 @@ export class MaintenancePage extends ListPage {
306
338
  return await this.operations.perform(chosen.action, [this.label], (text) => this.busy(text));
307
339
  return {};
308
340
  }
341
+ // Activity: a session's row shows its events on the normal screen (sandbox audit --session ...); turning the
342
+ // log on runs the settings operation.
343
+ if (this.category === 'activity') {
344
+ const chosen = auditMenu(this.model())?.resolve(result);
345
+ if (chosen?.kind !== 'action')
346
+ return {};
347
+ const target = chosen.action.kind === 'link' ? chosen.action.target : undefined;
348
+ if (target?.kind === 'view')
349
+ return { exit: { kind: 'run', argv: auditArgv(target.params) } };
350
+ return await this.operations.perform(chosen.action, [this.label], (text) => this.busy(text));
351
+ }
309
352
  if (key === 'm' || id === 'data:measure')
310
353
  return await this.measure();
311
354
  // Only the open category's rows and keys: `f` and Enter mean something else in Health.
@@ -0,0 +1,19 @@
1
+ import { type AnyView, type Caller } from '../../app/kernel/index.js';
2
+ import type { Context } from '../commands/context.js';
3
+ import type { RemoteClient } from '../services/remote-client.js';
4
+ import { type App } from '../tui/index.js';
5
+ /** The views of the owner's program this role may open (remote access itself is the owner's). */
6
+ export declare function remoteViews(role: Caller): AnyView[];
7
+ export declare class RemoteMenu {
8
+ private readonly app;
9
+ private readonly client;
10
+ private readonly owner;
11
+ private readonly caller;
12
+ private readonly operations;
13
+ constructor(app: App, ctx: Context, client: RemoteClient, owner: string, caller: Caller);
14
+ private title;
15
+ /** The list of areas, until Esc. */
16
+ start(): Promise<void>;
17
+ private page;
18
+ private showView;
19
+ }
@@ -0,0 +1,108 @@
1
+ // `sandbox remote`: the menu drawn over another Windows user's program (docs/design/remote-access.md). Every view the
2
+ // web UI has is drawn by the menu's own renderer (menuView) from the shared core's pure definitions; its queries and
3
+ // operations run on the owner's web UI over the RPC socket, which checks the role this user was given and writes each
4
+ // action to the owner's audit log. Nothing here runs on this user's own settings or data.
5
+ import { NAV, navViews, viewById } from '../../app/features/defs.js';
6
+ import { NO_QUESTIONS, allows, viewInputs, } from '../../app/kernel/index.js';
7
+ import { errMsg, safeText } from '../../core/util/index.js';
8
+ import { Theme } from '../tui/index.js';
9
+ import { MenuOperations } from './io-menu.js';
10
+ import { menuView } from './render-view.js';
11
+ /** The views of the owner's program this role may open (remote access itself is the owner's). */
12
+ export function remoteViews(role) {
13
+ return navViews(NAV)
14
+ .map((id) => viewById(id))
15
+ .filter((v) => v !== undefined)
16
+ .filter((v) => Object.values(v.queries).every((q) => allows(role, q.access ?? 'view')));
17
+ }
18
+ export class RemoteMenu {
19
+ app;
20
+ client;
21
+ owner;
22
+ caller;
23
+ operations;
24
+ constructor(app, ctx, client, owner, caller) {
25
+ this.app = app;
26
+ this.client = client;
27
+ this.owner = owner;
28
+ this.caller = caller;
29
+ this.operations = new MenuOperations(app, ctx, (id, input, io) => client.run(id, input, io));
30
+ }
31
+ title() {
32
+ return safeText(this.owner) + "'s sandbox";
33
+ }
34
+ /** The list of areas, until Esc. */
35
+ async start() {
36
+ const views = remoteViews(this.caller);
37
+ const role = this.caller.kind === 'owner' ? 'owner' : this.caller.role;
38
+ let at;
39
+ for (;;) {
40
+ const picked = await this.app.list({
41
+ path: [this.title()],
42
+ intro: [
43
+ 'You are using ' + safeText(this.owner) + "'s program with the role " + role + '.',
44
+ 'What you do runs as ' + safeText(this.owner) + ' and is written to their audit log.',
45
+ ],
46
+ items: views.map((v) => ({ value: v.id, label: v.title, description: v.summary })),
47
+ startAt: at,
48
+ escapeLabel: 'quit',
49
+ enterLabel: 'open',
50
+ });
51
+ if (picked.kind !== 'pick')
52
+ return;
53
+ at = picked.value;
54
+ const view = views.find((v) => v.id === picked.value);
55
+ if (view)
56
+ await this.showView(view);
57
+ }
58
+ }
59
+ async page(view) {
60
+ const inputs = viewInputs(view, {});
61
+ const data = {};
62
+ for (const [name, query] of Object.entries(view.queries))
63
+ data[name] = await this.client.run(query.id, inputs[name], NO_QUESTIONS);
64
+ return view.build(data, {});
65
+ }
66
+ async showView(view) {
67
+ let notice;
68
+ for (;;) {
69
+ let page;
70
+ try {
71
+ page = await this.page(view);
72
+ }
73
+ catch (e) {
74
+ await this.app.message({ path: [this.title(), view.title], lines: [safeText(errMsg(e))], tone: 'error' });
75
+ return;
76
+ }
77
+ const mv = menuView(page);
78
+ const result = await this.app.list({
79
+ path: [this.title(), view.title],
80
+ intro: page.notice ? [safeText(page.notice.text)] : undefined,
81
+ items: mv.items,
82
+ actions: mv.actions,
83
+ enterLabel: mv.enterLabel,
84
+ notice,
85
+ empty: page.empty?.text,
86
+ });
87
+ if (result.kind === 'back')
88
+ return;
89
+ const chosen = mv.resolve(result);
90
+ notice = undefined;
91
+ if (!chosen || chosen.kind === 'refresh')
92
+ continue;
93
+ const action = chosen.action;
94
+ if (action.kind === 'link') {
95
+ notice =
96
+ action.target.kind === 'terminal'
97
+ ? Theme.muted('Attach to it from your own terminal: sandbox attach ' + safeText(action.target.session))
98
+ : Theme.muted('That opens on ' + safeText(this.owner) + "'s side only.");
99
+ continue;
100
+ }
101
+ const done = await this.operations.perform(action, [this.title(), view.title], (text) => {
102
+ notice = text;
103
+ this.app.redraw();
104
+ });
105
+ notice = done.notice;
106
+ }
107
+ }
108
+ }
@@ -7,6 +7,7 @@ import { accountsView } from '../../app/features/accounts/index.js';
7
7
  import { appsView } from '../../app/features/apps/index.js';
8
8
  import { healthView } from '../../app/features/health/index.js';
9
9
  import { logsView } from '../../app/features/logs/index.js';
10
+ import { auditView } from '../../app/features/audit/index.js';
10
11
  import { projectsView } from '../../app/features/projects/index.js';
11
12
  import { sandboxView } from '../../app/features/sandbox/index.js';
12
13
  import { sessionsView } from '../../app/features/sessions/index.js';
@@ -17,6 +18,7 @@ import { updatesView } from '../../app/features/updates/index.js';
17
18
  import { webView } from '../../app/features/web/index.js';
18
19
  import { vpnView } from '../../app/features/vpn/index.js';
19
20
  import { githubView } from '../../app/features/github/index.js';
21
+ import { accessView } from '../../app/features/access/index.js';
20
22
  export const MENU_VIEWS = [
21
23
  { view: agentsView, section: 'Agents' },
22
24
  { view: sessionsView, section: 'Sessions' },
@@ -29,9 +31,11 @@ export const MENU_VIEWS = [
29
31
  { view: dataView, section: 'Maintenance' },
30
32
  { view: storageView, section: 'Maintenance' },
31
33
  { view: logsView, section: 'Maintenance' },
34
+ { view: auditView, section: 'Maintenance' },
32
35
  { view: updatesView, section: 'Maintenance' },
33
36
  { view: engineView, section: 'Maintenance' },
34
37
  { view: webView, section: 'Maintenance' },
38
+ { view: accessView, section: 'Maintenance' },
35
39
  // The Settings page draws the VPN row itself (its login is staged with the settings); the view lists its operations.
36
40
  { view: vpnView, section: 'Settings' },
37
41
  // The same for the GitHub token row.