@agimon-ai/doompi-web 0.0.1-alpha.3 → 0.0.1-alpha.4

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 (165) hide show
  1. package/README.md +57 -29
  2. package/dist/pwa/icon-192.png +0 -0
  3. package/dist/pwa/icon-512.png +0 -0
  4. package/dist/pwa/index.html +104 -0
  5. package/dist/pwa/manifest.webmanifest +23 -0
  6. package/dist/pwa/pwa.js +21 -0
  7. package/dist/pwa/sw.js +2 -0
  8. package/dist/src/adapters/bundlePublication.cjs +2 -0
  9. package/dist/src/adapters/bundlePublication.cjs.map +1 -0
  10. package/dist/src/adapters/bundlePublication.mjs +2 -0
  11. package/dist/src/adapters/bundlePublication.mjs.map +1 -0
  12. package/dist/src/adapters/httpServer.cjs +2 -1
  13. package/dist/src/adapters/httpServer.cjs.map +1 -1
  14. package/dist/src/adapters/httpServer.d.cts.map +1 -1
  15. package/dist/src/adapters/httpServer.d.mts.map +1 -1
  16. package/dist/src/adapters/httpServer.mjs +2 -1
  17. package/dist/src/adapters/httpServer.mjs.map +1 -1
  18. package/dist/src/adapters/remoteAccess.cjs +1 -1
  19. package/dist/src/adapters/remoteAccess.cjs.map +1 -1
  20. package/dist/src/adapters/remoteAccess.mjs +1 -1
  21. package/dist/src/adapters/remoteAccess.mjs.map +1 -1
  22. package/dist/src/adapters/remoteRoutes.cjs +1 -1
  23. package/dist/src/adapters/remoteRoutes.cjs.map +1 -1
  24. package/dist/src/adapters/remoteRoutes.mjs +1 -1
  25. package/dist/src/adapters/remoteRoutes.mjs.map +1 -1
  26. package/dist/src/adapters/sessionHub.cjs +1 -1
  27. package/dist/src/adapters/sessionHub.cjs.map +1 -1
  28. package/dist/src/adapters/sessionHub.mjs +1 -1
  29. package/dist/src/adapters/sessionHub.mjs.map +1 -1
  30. package/dist/src/adapters/tunnelProcess.cjs +1 -1
  31. package/dist/src/adapters/tunnelProcess.cjs.map +1 -1
  32. package/dist/src/adapters/tunnelProcess.mjs +1 -1
  33. package/dist/src/adapters/tunnelProcess.mjs.map +1 -1
  34. package/dist/src/adapters/webBundler.cjs +1 -1
  35. package/dist/src/adapters/webBundler.cjs.map +1 -1
  36. package/dist/src/adapters/webBundler.d.cts +1 -1
  37. package/dist/src/adapters/webBundler.d.mts +1 -1
  38. package/dist/src/adapters/webBundler.mjs +1 -1
  39. package/dist/src/adapters/webBundler.mjs.map +1 -1
  40. package/dist/src/adapters/webHubPluginLoader.cjs +1 -1
  41. package/dist/src/adapters/webHubPluginLoader.cjs.map +1 -1
  42. package/dist/src/adapters/webHubPluginLoader.mjs +1 -1
  43. package/dist/src/adapters/webHubPluginLoader.mjs.map +1 -1
  44. package/dist/src/adapters/webPush.cjs +2 -0
  45. package/dist/src/adapters/webPush.cjs.map +1 -0
  46. package/dist/src/adapters/webPush.mjs +2 -0
  47. package/dist/src/adapters/webPush.mjs.map +1 -0
  48. package/dist/src/services/fileMedia.cjs +1 -1
  49. package/dist/src/services/fileMedia.cjs.map +1 -1
  50. package/dist/src/services/fileMedia.mjs +1 -1
  51. package/dist/src/services/fileMedia.mjs.map +1 -1
  52. package/dist/src/services/pairingFlow.cjs +1 -1
  53. package/dist/src/services/pairingFlow.cjs.map +1 -1
  54. package/dist/src/services/pairingFlow.mjs +1 -1
  55. package/dist/src/services/pairingFlow.mjs.map +1 -1
  56. package/dist/src/services/pairingPage.cjs +188 -74
  57. package/dist/src/services/pairingPage.cjs.map +1 -1
  58. package/dist/src/services/pairingPage.mjs +188 -74
  59. package/dist/src/services/pairingPage.mjs.map +1 -1
  60. package/dist/src/services/remoteGuardPolicy.cjs +1 -1
  61. package/dist/src/services/remoteGuardPolicy.cjs.map +1 -1
  62. package/dist/src/services/remoteGuardPolicy.mjs +1 -1
  63. package/dist/src/services/remoteGuardPolicy.mjs.map +1 -1
  64. package/dist/src/services/webauthnPolicy.cjs +1 -1
  65. package/dist/src/services/webauthnPolicy.cjs.map +1 -1
  66. package/dist/src/services/webauthnPolicy.mjs +1 -1
  67. package/dist/src/services/webauthnPolicy.mjs.map +1 -1
  68. package/dist/src/types/hub.cjs.map +1 -1
  69. package/dist/src/types/hub.d.cts +1 -1
  70. package/dist/src/types/hub.d.cts.map +1 -1
  71. package/dist/src/types/hub.d.mts +1 -1
  72. package/dist/src/types/hub.d.mts.map +1 -1
  73. package/dist/src/types/hub.mjs.map +1 -1
  74. package/dist/src/types/remoteAccess.cjs +1 -1
  75. package/dist/src/types/remoteAccess.cjs.map +1 -1
  76. package/dist/src/types/remoteAccess.d.cts.map +1 -1
  77. package/dist/src/types/remoteAccess.d.mts.map +1 -1
  78. package/dist/src/types/remoteAccess.mjs +1 -1
  79. package/dist/src/types/remoteAccess.mjs.map +1 -1
  80. package/dist/src/types/session.cjs +1 -1
  81. package/dist/src/types/session.mjs +1 -1
  82. package/dist/src/types/session.mjs.map +1 -1
  83. package/dist/web/assets/{CodeEditorView-CP55qjoR.js → CodeEditorView-ZJ3fgCgP.js} +3 -3
  84. package/dist/web/assets/{CodeEditorView-CP55qjoR.js.map → CodeEditorView-ZJ3fgCgP.js.map} +1 -1
  85. package/dist/web/assets/{dist-Gdc8iniF.js → dist-4CpY4--O.js} +2 -2
  86. package/dist/web/assets/{dist-Gdc8iniF.js.map → dist-4CpY4--O.js.map} +1 -1
  87. package/dist/web/assets/{dist-C7QLpOTK.js → dist-BAbw37G4.js} +2 -2
  88. package/dist/web/assets/{dist-C7QLpOTK.js.map → dist-BAbw37G4.js.map} +1 -1
  89. package/dist/web/assets/{dist-CElJaC8s.js → dist-BCw5Th7z.js} +2 -2
  90. package/dist/web/assets/{dist-CElJaC8s.js.map → dist-BCw5Th7z.js.map} +1 -1
  91. package/dist/web/assets/{dist-eZCftY6T.js → dist-BZRRewb_.js} +1 -1
  92. package/dist/web/assets/{dist-TyTwU387.js → dist-B_rHH1XB.js} +2 -2
  93. package/dist/web/assets/{dist-TyTwU387.js.map → dist-B_rHH1XB.js.map} +1 -1
  94. package/dist/web/assets/{dist-GONoz8to.js → dist-BhGCoeQZ.js} +2 -2
  95. package/dist/web/assets/{dist-GONoz8to.js.map → dist-BhGCoeQZ.js.map} +1 -1
  96. package/dist/web/assets/{dist-DgVFo7X3.js → dist-C60KVFlU.js} +2 -2
  97. package/dist/web/assets/{dist-DgVFo7X3.js.map → dist-C60KVFlU.js.map} +1 -1
  98. package/dist/web/assets/{dist-B0inTUnL.js → dist-Cj5gWlhe.js} +2 -2
  99. package/dist/web/assets/{dist-B0inTUnL.js.map → dist-Cj5gWlhe.js.map} +1 -1
  100. package/dist/web/assets/{dist-Bqd73gXk.js → dist-DCwjnz7l.js} +2 -2
  101. package/dist/web/assets/{dist-Bqd73gXk.js.map → dist-DCwjnz7l.js.map} +1 -1
  102. package/dist/web/assets/{dist-Cwzviw_U.js → dist-DW9ZoBfU.js} +2 -2
  103. package/dist/web/assets/{dist-Cwzviw_U.js.map → dist-DW9ZoBfU.js.map} +1 -1
  104. package/dist/web/assets/index-DqvMDNrH.js +99 -0
  105. package/dist/web/assets/index-DqvMDNrH.js.map +1 -0
  106. package/dist/web/assets/index-Dx8DvA-4.css +2 -0
  107. package/dist/web/index.html +3 -2
  108. package/package.json +14 -11
  109. package/src/adapters/bundlePublication.ts +80 -0
  110. package/src/adapters/httpServer.ts +179 -10
  111. package/src/adapters/remoteAccess.ts +46 -12
  112. package/src/adapters/remoteRoutes.ts +13 -3
  113. package/src/adapters/sessionHub.ts +67 -4
  114. package/src/adapters/tunnelProcess.ts +192 -17
  115. package/src/adapters/webBundler.ts +2 -2
  116. package/src/adapters/webHubPluginLoader.ts +6 -2
  117. package/src/adapters/webPush.ts +127 -0
  118. package/src/pwa/bundleCache.ts +79 -0
  119. package/src/pwa/index.html +104 -0
  120. package/src/pwa/main.ts +163 -0
  121. package/src/pwa/public/icon-192.png +0 -0
  122. package/src/pwa/public/icon-512.png +0 -0
  123. package/src/pwa/public/manifest.webmanifest +23 -0
  124. package/src/pwa/qrScanner.ts +65 -0
  125. package/src/pwa/serviceWorker.ts +261 -0
  126. package/src/pwa/workerClient.ts +60 -0
  127. package/src/services/fileMedia.ts +1 -0
  128. package/src/services/pairingFlow.ts +13 -10
  129. package/src/services/pairingPage.ts +204 -81
  130. package/src/services/remoteGuardPolicy.ts +9 -7
  131. package/src/services/webauthnPolicy.ts +1 -0
  132. package/src/types/hub.ts +16 -3
  133. package/src/types/remoteAccess.ts +12 -0
  134. package/src/web/app/Providers.tsx +2 -0
  135. package/src/web/app/protocolRuntime.ts +3 -2
  136. package/src/web/app/sessionRuntime.ts +31 -3
  137. package/src/web/features/activity/ActivityDock.tsx +4 -4
  138. package/src/web/features/remote/RemoteAccessPairing.tsx +25 -3
  139. package/src/web/features/session/Composer.tsx +98 -77
  140. package/src/web/features/session/MentionPreviews.tsx +65 -14
  141. package/src/web/features/session/QueueSheet.tsx +113 -0
  142. package/src/web/features/session/Timeline.tsx +46 -21
  143. package/src/web/features/session/ToolCard.tsx +151 -8
  144. package/src/web/features/sessions/ResumeSessionDialog.tsx +150 -0
  145. package/src/web/features/sessions/SessionRail.tsx +8 -9
  146. package/src/web/features/settings/NotificationSettings.tsx +35 -10
  147. package/src/web/features/status/TopBar.tsx +12 -66
  148. package/src/web/index.html +1 -0
  149. package/src/web/lib/commands.ts +4 -0
  150. package/src/web/lib/composition.ts +57 -9
  151. package/src/web/lib/hubApi.ts +49 -1
  152. package/src/web/lib/livePush.ts +116 -0
  153. package/src/web/lib/pluginSlotProps.ts +2 -0
  154. package/src/web/lib/protocolTimeline.ts +19 -3
  155. package/src/web/lib/remoteApi.ts +27 -3
  156. package/src/web/lib/sessionAsset.ts +30 -0
  157. package/src/web/lib/sessionModel.ts +53 -1
  158. package/src/web/routes/CockpitPage.tsx +12 -4
  159. package/src/web/stores/composerStore.ts +92 -0
  160. package/src/web/stores/remoteAccessStore.ts +27 -3
  161. package/src/web/stores/sessionStore.ts +81 -12
  162. package/src/web/stores/usePluginSlotProps.ts +20 -9
  163. package/dist/web/assets/index-BqJnbtrI.css +0 -2
  164. package/dist/web/assets/index-Ck8t7U8J.js +0 -98
  165. package/dist/web/assets/index-Ck8t7U8J.js.map +0 -1
package/README.md CHANGED
@@ -120,8 +120,27 @@ perform the handshake itself: `attach` frames sent from the browser are refused.
120
120
  them.
121
121
  - Subagent, workflow, and runner surfaces report summaries only; per-run detail has no RPC source
122
122
  to render from yet.
123
- - Browser notifications require a live, connected page. There is no service worker, Web Push, replay,
124
- or durable queue, so notifications emitted while every page is closed are not delivered later.
123
+ - Open pages show full live browser notifications. An installed paired PWA can also receive a generic
124
+ zero-TTL Web Push alert while closed. There is no durable subscription database, outbox, replay, or
125
+ historical delivery.
126
+
127
+ ## iPhone Home Screen app
128
+
129
+ Use a stable named HTTPS tunnel such as `doompi.agimon.win`. Quick-tunnel hostnames rotate, so they cannot
130
+ provide durable PWA identity, passkeys, or reliable Push subscriptions. Open `/pair` in Safari, add it to the
131
+ Home Screen, then launch the installed app and scan the QR shown by the host.
132
+
133
+ The install shell, scanner, manifest, and verifier worker are package-owned. The QR pins the host signing key
134
+ and minimum bundle revision. The worker verifies every host and plugin asset before committing it to the
135
+ active cache, and the host never serves its SPA directly. A failed update keeps the last-known-good bundle.
136
+ Signer loss, signer rotation, or a host/container transition requires explicit fingerprint confirmation and
137
+ re-pairing. Session file previews remain sealed `no-store` HTTP Blob URLs and never enter the application
138
+ cache, IndexedDB, a signed manifest, or a Push payload.
139
+
140
+ Closed-app alerts are opt-in under settings. The server keeps subscriptions only in memory and sends fixed
141
+ generic copy with `TTL: 0` only when the device has no connected cockpit socket. Opening the app after a host
142
+ restart re-registers the browser subscription. Disabling alerts, revoking or expiring the device, switching
143
+ off remote access, or rotating process credentials removes it.
125
144
 
126
145
  ## Remote access
127
146
 
@@ -137,12 +156,13 @@ so the socket a connection arrived on is the only thing about it that cannot be
137
156
 
138
157
  Pairing takes **two** things, not one:
139
158
 
140
- 1. The host shows a QR encoding `https://<tunnel-host>/pair#c=<code>`. The code rides in the URL
141
- fragment, which no browser sends to any server, so it never reaches Cloudflare's logs, this
142
- process's log, or a `Referer`.
143
- 2. Scanning it raises an approve or deny prompt **on the host**. Nothing is paired until someone at
144
- the machine answers. A screen is visible over a shoulder and in a screenshare, so the code alone
145
- is one factor for a credential that ends in shell access.
159
+ 1. The host shows a QR encoding `https://<tunnel-host>/pair#c=<code>&k=<channel-key>&s=<signer>&r=<revision>`.
160
+ Every value rides in the URL fragment, which no browser sends to a server, so the relay cannot replace the
161
+ channel or bundle key without changing what was shown on the host. Manual pairing requires comparing the
162
+ signing-key fingerprint shown on both devices.
163
+ 2. Scanning raises an approve or deny prompt **on the host**. Nothing is paired until someone at the machine
164
+ answers. A screen is visible over a shoulder and in a screenshare, so the code alone is one factor for a
165
+ credential that ends in shell access.
146
166
 
147
167
  A paired device then has the same powers as the person at the keyboard, with one exception: it
148
168
  cannot mint pairing codes, approve devices, or change these settings. A device able to approve
@@ -160,18 +180,24 @@ setting in force at the moment of the check rather than stamped onto a session w
160
180
  Turning expiry on therefore drops sessions that are already idle, and turning it off brings them
161
181
  back.
162
182
 
163
- With both off, the only expiry is you: a tunnel stays up until it is closed or the hub restarts, and
164
- a paired device keeps its session until it is revoked or remote access is switched off. Switching
165
- remote access off always revokes every paired session, and that is not a toggle.
183
+ With both off, a paired device keeps its session until it is revoked, remote access is switched off, or
184
+ the hub restarts. Switching remote access off always revokes every paired session, and that is not a
185
+ toggle.
166
186
 
167
187
  Before reporting success, the tunnel is **self-tested through its own public URL**: the pairing page
168
188
  must answer 200 and `/api/health` must answer 401. Anything else means the agent is on the public
169
189
  internet unguarded, so the tunnel is killed immediately rather than warned about.
170
190
 
171
- Requires `cloudflared` on `PATH` (`brew install cloudflared`). A quick tunnel needs no account but
172
- gets a new hostname on every start, so every restart means scanning again, and Cloudflare does not
173
- support SSE on quick tunnels, which breaks the workflow and runner-log plugin surfaces. A named
174
- tunnel on your own domain has neither problem and is the mode to use if you do this daily.
191
+ Requires `cloudflared` on `PATH` (`brew install cloudflared`). A quick tunnel needs no account but gets a new
192
+ hostname on every start. It does not provide a durable PWA, passkey, or Push identity, and Cloudflare does not
193
+ support SSE on quick tunnels, which breaks workflow and runner-log plugin surfaces. Use a stable named tunnel
194
+ for the Home Screen app.
195
+
196
+ If a named tunnel's connector exits unexpectedly, the hub makes three backoff restart attempts and
197
+ self-tests every replacement. The existing listener and paired sessions stay valid while recovery is
198
+ in progress. A quick tunnel cannot recover transparently because its origin changes, so it still fails
199
+ closed immediately. Exhausting named-tunnel recovery also switches remote access off and revokes the
200
+ paired sessions.
175
201
 
176
202
  ### Passkeys and step-up
177
203
 
@@ -187,18 +213,20 @@ so rather than letting the ceremony fail in the browser.
187
213
 
188
214
  ### Tunnel-provider trust and payload privacy
189
215
 
190
- Cloudflare is a trusted delivery boundary. It terminates TLS and delivers both the pairing page and the
191
- cockpit SPA. A browser cannot independently verify its first executable page against the same edge that
192
- served it: a malicious replacement could omit any in-page verifier, read the QR fragment, and control a
193
- paired agent. DoomPi therefore does not claim that browser-side bundle signing protects against a
194
- compromised Cloudflare edge.
216
+ Cloudflare terminates TLS and delivers the package-owned pairing and verifier bootstrap. A browser cannot
217
+ independently authenticate that first executable shell against the same edge that served it: a malicious
218
+ replacement could omit the worker and steal a QR fragment. DoomPi does not claim protection from a malicious
219
+ code-delivery edge.
220
+
221
+ After that bootstrap, **bundle signing is mandatory**. The QR pins the host ECDSA key and revision floor. The
222
+ worker verifies the signed manifest plus every raw asset before any host JavaScript executes, serves only the
223
+ atomically committed verified cache, and retains the last-known-good revision on failure. Cloudflare may
224
+ alter the raw bytes, but altered bytes do not execute.
195
225
 
196
- Within that trust model, **the payload is sealed**. A QR carries an ephemeral P-256 public key alongside the
197
- pairing code, in the URL fragment, which no browser sends to any server. A returning device that proves a
198
- passkey receives the current public key in the successful authentication response instead. Both paths then
199
- complete ECDH and derive AES-256-GCM keys, separate per direction. Socket frames and API bodies travel as
200
- ciphertext. This limits routine relay visibility, but it still depends on Cloudflare delivering the intended
201
- pairing page and SPA.
226
+ **The payload is also sealed.** A QR carries an ephemeral P-256 public key in the fragment. A returning
227
+ passkey device restores its channel only after the pinned bundle worker accepts the same signer. Both paths
228
+ complete ECDH and derive separate AES-256-GCM keys per direction. Socket frames and API bodies travel as
229
+ ciphertext.
202
230
 
203
231
  The channel implementation lives in `@agimon-ai/doompi-web-security`, which ships the envelope contract,
204
232
  a `node:crypto` half, and a WebCrypto half. **A web plugin that calls `fetch` directly sends plaintext to
@@ -343,9 +371,9 @@ and the host's own slot contributions) and an optional hub entry (`webHubChannel
343
371
  dist). This package's own bundle carries no plugins; the full set is assembled on your machine:
344
372
  `doompi sync` discovers the installed composition's manifests, generates the import entries, and
345
373
  rebuilds the SPA through the `./bundler` subpath into `~/.doompi/web/current`, which the server
346
- prefers over the packaged bundle (`--assets` and `DOOMPI_WEB_DIST` override it). The synced bundle
347
- carries `webPlugins.server.json` naming each plugin's built hub entry, imported lazily: a missing
348
- plugin package logs a notice and its tab shows an empty state. The subagents tab in
374
+ prefers over the packaged bundle (`--assets` and `DOOMPI_WEB_DIST` override it). Server-only
375
+ `webPlugins.server.json` sits beside the public `web/` directory, never inside the signed or served bundle.
376
+ It names each plugin's built hub entry, imported lazily: a missing plugin package logs a notice and its tab
349
377
  `@agimon-ai/doompi-team` is the reference plugin; the workflows tab ships with
350
378
  `@agimon-ai/doompi-workflow`. Contracts come from `@agimon-ai/doompi-web-contracts`.
351
379
 
Binary file
Binary file
@@ -0,0 +1,104 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <meta name="theme-color" content="#14161a" />
7
+ <meta name="apple-mobile-web-app-capable" content="yes" />
8
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9
+ <meta name="robots" content="noindex, nofollow" />
10
+ <link rel="manifest" href="/pwa/manifest.webmanifest" />
11
+ <link rel="apple-touch-icon" href="/pwa/icon-192.png" />
12
+ <title>Pair with DoomPi</title>
13
+ <style>
14
+ :root {
15
+ color-scheme: dark;
16
+ }
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+ body {
21
+ margin: 0;
22
+ min-height: 100vh;
23
+ min-height: 100dvh;
24
+ display: grid;
25
+ place-items: center;
26
+ padding: max(1.25rem, env(safe-area-inset-top)) max(1.25rem, env(safe-area-inset-right))
27
+ max(1.25rem, env(safe-area-inset-bottom)) max(1.25rem, env(safe-area-inset-left));
28
+ background: #14161a;
29
+ color: #e6e8ec;
30
+ font:
31
+ 16px/1.5 ui-sans-serif,
32
+ system-ui,
33
+ -apple-system,
34
+ sans-serif;
35
+ }
36
+ main {
37
+ width: min(32rem, 100%);
38
+ text-align: center;
39
+ }
40
+ h1 {
41
+ margin: 0 0 0.5rem;
42
+ font-size: 1.35rem;
43
+ }
44
+ p {
45
+ margin: 0 0 1rem;
46
+ color: #a2a8b4;
47
+ }
48
+ #pwa-status[data-tone='good'] {
49
+ color: #6bd68a;
50
+ }
51
+ #pwa-status[data-tone='bad'] {
52
+ color: #ff7b7b;
53
+ }
54
+ video {
55
+ width: 100%;
56
+ max-height: 24rem;
57
+ margin: 0.75rem 0;
58
+ border: 1px solid #2c313a;
59
+ border-radius: 0.75rem;
60
+ background: #090a0c;
61
+ object-fit: cover;
62
+ }
63
+ .actions {
64
+ display: flex;
65
+ gap: 0.75rem;
66
+ margin-top: 1.25rem;
67
+ }
68
+ button {
69
+ flex: 1;
70
+ min-height: 3rem;
71
+ border: 1px solid #2f6feb;
72
+ border-radius: 0.6rem;
73
+ background: #2f6feb;
74
+ color: #fff;
75
+ font: inherit;
76
+ font-weight: 650;
77
+ }
78
+ button.secondary {
79
+ border-color: #2c313a;
80
+ background: #1b1e24;
81
+ }
82
+ button:disabled {
83
+ opacity: 0.55;
84
+ }
85
+ [hidden] {
86
+ display: none !important;
87
+ }
88
+ </style>
89
+ <script type="module" crossorigin src="/pwa/pwa.js"></script>
90
+ </head>
91
+ <body data-marker="doompi-pwa-shell">
92
+ <main>
93
+ <h1>Pair this device</h1>
94
+ <p>This cockpit can run shell commands on the host. Pair only with your own DoomPi.</p>
95
+ <p id="pwa-status" role="status">Checking browser support.</p>
96
+ <video id="camera" playsinline muted hidden></video>
97
+ <div class="actions">
98
+ <button id="scan" type="button">Scan pairing QR</button>
99
+ <button id="cancel" class="secondary" type="button" hidden>Cancel</button>
100
+ </div>
101
+ <noscript><p>Pairing needs JavaScript.</p></noscript>
102
+ </main>
103
+ </body>
104
+ </html>
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "/pair",
3
+ "name": "DoomPi Cockpit",
4
+ "short_name": "DoomPi",
5
+ "description": "Securely pair with and open your DoomPi cockpit.",
6
+ "start_url": "/",
7
+ "scope": "/",
8
+ "display": "standalone",
9
+ "background_color": "#14161a",
10
+ "theme_color": "#14161a",
11
+ "icons": [
12
+ {
13
+ "src": "/pwa/icon-192.png",
14
+ "sizes": "192x192",
15
+ "type": "image/png"
16
+ },
17
+ {
18
+ "src": "/pwa/icon-512.png",
19
+ "sizes": "512x512",
20
+ "type": "image/png"
21
+ }
22
+ ]
23
+ }