ccdeck 3.29.11 → 3.31.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 (178) hide show
  1. package/README.md +4 -3
  2. package/bin/agent-dag.js +67 -118
  3. package/bin/cli/help.js +67 -0
  4. package/bin/cli/login-item.js +182 -0
  5. package/bin/cli/one-shot.js +254 -0
  6. package/bin/cli/package.js +39 -0
  7. package/bin/cli/pulse.js +98 -0
  8. package/bin/cli/restart.js +179 -0
  9. package/bin/cli/screen.js +166 -0
  10. package/bin/cli/second-start.js +120 -0
  11. package/bin/cli/startup.js +459 -0
  12. package/bin/cli/uninstall.js +169 -0
  13. package/bin/deck.js +112 -1559
  14. package/dist/web/assets/BrowserWatchModal-n63GjLhv.js +1 -0
  15. package/dist/web/assets/UsageHistoryModal-DEwBX4Fk.js +1 -0
  16. package/dist/web/assets/index-CZJf1Cpp.js +299 -0
  17. package/dist/web/assets/index-D4q1ElXU.css +1 -0
  18. package/dist/web/index.html +2 -2
  19. package/hook/hook.js +15 -14
  20. package/package.json +1 -1
  21. package/release-notes.json +40 -0
  22. package/src/server/account-projects.mjs +25 -11
  23. package/src/server/account-routes.mjs +254 -0
  24. package/src/server/activity.mjs +3 -3
  25. package/src/server/append-budget.mjs +185 -0
  26. package/src/server/append-failures.mjs +147 -0
  27. package/src/server/args.mjs +37 -5
  28. package/src/server/atomic-write.mjs +300 -0
  29. package/src/server/block-notify.mjs +0 -5
  30. package/src/server/boot-lock.mjs +1 -1
  31. package/src/server/brand.mjs +7 -6
  32. package/src/server/browser-react.mjs +10 -2
  33. package/src/server/browser-watch-decks.mjs +146 -0
  34. package/src/server/browser-watch-feed.mjs +69 -0
  35. package/src/server/browser-watch-log.mjs +173 -0
  36. package/src/server/browser-watch-mtime.mjs +18 -0
  37. package/src/server/browser-watch-relay.mjs +112 -0
  38. package/src/server/browser-watch-routes.mjs +172 -0
  39. package/src/server/browser-watch-seen.mjs +127 -0
  40. package/src/server/browser-watch-store.mjs +14 -143
  41. package/src/server/browser-watch.mjs +278 -626
  42. package/src/server/canonical-path.mjs +150 -0
  43. package/src/server/ccusage-failure.mjs +64 -0
  44. package/src/server/ccusage-install.mjs +459 -0
  45. package/src/server/ccusage-runner.mjs +271 -0
  46. package/src/server/ccusage.mjs +21 -756
  47. package/src/server/claude-accounts.mjs +220 -464
  48. package/src/server/claude-collector.mjs +189 -0
  49. package/src/server/claude-dir.mjs +7 -3
  50. package/src/server/claude-identity.mjs +5 -4
  51. package/src/server/claude-reset-credits.mjs +130 -0
  52. package/src/server/claude-verdicts.mjs +174 -0
  53. package/src/server/clear-route.mjs +191 -0
  54. package/src/server/codex-auth.mjs +4 -4
  55. package/src/server/codex-dir.mjs +52 -8
  56. package/src/server/codex-enrichment.mjs +310 -0
  57. package/src/server/codex-quota.mjs +5 -3
  58. package/src/server/codex-translate.mjs +407 -0
  59. package/src/server/codex-usage.mjs +8 -5
  60. package/src/server/codex-watch.mjs +496 -0
  61. package/src/server/cswap-auto-loop.mjs +342 -0
  62. package/src/server/cswap-auto-readers.mjs +303 -0
  63. package/src/server/cswap-auto.mjs +51 -609
  64. package/src/server/cswap-install.mjs +69 -265
  65. package/src/server/cswap-layout.mjs +242 -0
  66. package/src/server/deck-home.mjs +2 -2
  67. package/src/server/deck-prefs.mjs +58 -184
  68. package/src/server/deck-scope.mjs +52 -0
  69. package/src/server/desktop-update-routes.mjs +75 -0
  70. package/src/server/detach.mjs +78 -0
  71. package/src/server/discovery.mjs +298 -0
  72. package/src/server/event-log.mjs +328 -0
  73. package/src/server/event-pipeline.mjs +235 -0
  74. package/src/server/event-ring.mjs +214 -0
  75. package/src/server/event-routes.mjs +352 -0
  76. package/src/server/event-sink.mjs +26 -0
  77. package/src/server/exec-children.mjs +128 -0
  78. package/src/server/exec-not-found.mjs +213 -0
  79. package/src/server/exec-output.mjs +23 -0
  80. package/src/server/exec-spec.mjs +341 -0
  81. package/src/server/exec.mjs +74 -661
  82. package/src/server/health-route.mjs +42 -0
  83. package/src/server/hook-challenge.mjs +37 -0
  84. package/src/server/http-io.mjs +104 -0
  85. package/src/server/index.mjs +174 -7620
  86. package/src/server/install-layout.mjs +506 -0
  87. package/src/server/installer.mjs +79 -608
  88. package/src/server/jsonl-chunks.mjs +139 -0
  89. package/src/server/lan-accounts.mjs +27 -0
  90. package/src/server/lan-beacon.mjs +457 -0
  91. package/src/server/lan-call.mjs +239 -0
  92. package/src/server/lan-copies.mjs +270 -0
  93. package/src/server/lan-deck.mjs +583 -0
  94. package/src/server/lan-dials.mjs +161 -0
  95. package/src/server/lan-engine.mjs +475 -1024
  96. package/src/server/lan-hearing.mjs +49 -0
  97. package/src/server/lan-inbound.mjs +100 -0
  98. package/src/server/lan-invite-offer.mjs +77 -0
  99. package/src/server/lan-invite.mjs +207 -0
  100. package/src/server/lan-lines.mjs +73 -0
  101. package/src/server/lan-manifest.mjs +74 -0
  102. package/src/server/lan-reach.mjs +82 -69
  103. package/src/server/lan-requests.mjs +249 -0
  104. package/src/server/lan-round-record.mjs +45 -0
  105. package/src/server/lan-round-timer.mjs +62 -0
  106. package/src/server/lan-routes.mjs +143 -0
  107. package/src/server/lan-serve.mjs +112 -0
  108. package/src/server/lan-socket.mjs +243 -925
  109. package/src/server/lan-sync.mjs +58 -1026
  110. package/src/server/lan-tailnet-poll.mjs +57 -0
  111. package/src/server/lan-turns.mjs +80 -0
  112. package/src/server/lan-wire.mjs +620 -0
  113. package/src/server/lifecycle.mjs +421 -0
  114. package/src/server/listen.mjs +110 -0
  115. package/src/server/live-decks.mjs +138 -0
  116. package/src/server/load-average.mjs +32 -0
  117. package/src/server/log-election.mjs +235 -0
  118. package/src/server/log-replay.mjs +374 -0
  119. package/src/server/log-writer.mjs +72 -557
  120. package/src/server/login-service.mjs +3 -2
  121. package/src/server/macmon.mjs +1 -1
  122. package/src/server/memory-files.mjs +163 -0
  123. package/src/server/memory-metrics.mjs +178 -0
  124. package/src/server/metrics-history.mjs +94 -0
  125. package/src/server/metrics-run.mjs +100 -0
  126. package/src/server/music-routes.mjs +134 -0
  127. package/src/server/network-metrics.mjs +5 -3
  128. package/src/server/network-sampler.mjs +256 -0
  129. package/src/server/npm-latest.mjs +385 -0
  130. package/src/server/npm-upgrade.mjs +352 -0
  131. package/src/server/npx.mjs +41 -7
  132. package/src/server/output-watch.mjs +5 -4
  133. package/src/server/pinned-build.mjs +110 -0
  134. package/src/server/prefs-give-back.mjs +206 -0
  135. package/src/server/prefs-path.mjs +14 -0
  136. package/src/server/prefs-refusal.mjs +86 -0
  137. package/src/server/prefs-routes.mjs +99 -0
  138. package/src/server/prefs-set-aside.mjs +86 -0
  139. package/src/server/prefs-state.mjs +70 -0
  140. package/src/server/process-command.mjs +104 -0
  141. package/src/server/process-list.mjs +547 -0
  142. package/src/server/provider-status.mjs +307 -0
  143. package/src/server/pulse-line.mjs +132 -0
  144. package/src/server/purge-key.mjs +7 -7
  145. package/src/server/quota-cli.mjs +174 -0
  146. package/src/server/quota-oauth.mjs +190 -0
  147. package/src/server/quota-shape.mjs +229 -0
  148. package/src/server/quota-store-resets.mjs +206 -0
  149. package/src/server/quota.mjs +123 -509
  150. package/src/server/relay-guard.mjs +3 -3
  151. package/src/server/request-gates.mjs +441 -0
  152. package/src/server/restart-note.mjs +180 -0
  153. package/src/server/retire-sound-hook.mjs +3 -3
  154. package/src/server/ring-bounds.mjs +215 -0
  155. package/src/server/running-deck.mjs +1 -1
  156. package/src/server/running-version.mjs +24 -0
  157. package/src/server/self-update.mjs +39 -1371
  158. package/src/server/session-enrichment.mjs +622 -0
  159. package/src/server/session-read-gate.mjs +49 -0
  160. package/src/server/session-tracking.mjs +242 -0
  161. package/src/server/sse-clients.mjs +222 -0
  162. package/src/server/stale-discovery.mjs +30 -0
  163. package/src/server/static-serve.mjs +81 -0
  164. package/src/server/supervisor.mjs +73 -12
  165. package/src/server/system-metrics.mjs +169 -1935
  166. package/src/server/term.mjs +11 -249
  167. package/src/server/thermal-metrics.mjs +520 -0
  168. package/src/server/thermal-sampler.mjs +204 -0
  169. package/src/server/token-redact.mjs +123 -0
  170. package/src/server/transcript-gate.mjs +107 -0
  171. package/src/server/transcript-scan.mjs +563 -0
  172. package/src/server/usage-routes.mjs +108 -0
  173. package/src/server/uv-bootstrap.mjs +1 -1
  174. package/src/server/wordmark.mjs +127 -0
  175. package/dist/web/assets/BrowserWatchModal-CyAFTvAY.js +0 -1
  176. package/dist/web/assets/UsageHistoryModal-KJfli0oa.js +0 -1
  177. package/dist/web/assets/index-Ck2RAla3.css +0 -1
  178. package/dist/web/assets/index-jmrcEsCj.js +0 -298
@@ -0,0 +1,143 @@
1
+ // The Local network panel's four routes: what the panel draws, and the three
2
+ // writes it makes — an invite, a verb on one deck, and a round now.
3
+ //
4
+ // These lived in src/server/index.mjs, after the LAN engine, and moved once the
5
+ // engine had (see lan-deck.mjs). Each is a body read and handed to the engine,
6
+ // and the one that writes the settings — the peer route's accept and alias —
7
+ // writes through prefs-state.mjs, as the engine's own callbacks do. The route
8
+ // table and the gates in front of it stay in index.mjs, where the order they
9
+ // are asked in is written down.
10
+ import { cleanAlias, isAliasKey, lanEnabled, withAlias, withManualEntry } from "./deck-prefs.mjs";
11
+ import { heldPrefs } from "./prefs-state.mjs";
12
+ import { lanEngine, lastReach, refreshReach, tailnet } from "./lan-deck.mjs";
13
+ import { IDLE_MS as TAILNET_IDLE_MS } from "./tailscale.mjs";
14
+ import { readBody, send } from "./http-io.mjs";
15
+
16
+ /** What the panel draws: the switch, this deck's own name and address, and who
17
+ * else is in the group. No passphrase, for the reason prefsPayload gives in
18
+ * prefs-routes.mjs. */
19
+ export function handleLanStatus(req, res) {
20
+ refreshReach();
21
+ // Behind the answer, like the reach probe: the dialog's poll is what finds a
22
+ // Tailscale somebody installed while the deck was running.
23
+ if (lanEnabled(heldPrefs.current())) void tailnet.freshen(TAILNET_IDLE_MS);
24
+ return send(res, 200, { ok: true, ...lanEngine.status(), reach: lastReach() });
25
+ }
26
+
27
+ /**
28
+ * Make an invite, put one away, or join on somebody else's.
29
+ *
30
+ * The token is the secret and it is never stored: minted on request, held in
31
+ * memory until it expires or is used, and gone with the process. Nothing about
32
+ * it reaches prefs.json, so a deck that restarts is offering nothing rather
33
+ * than offering something its owner has forgotten they sent.
34
+ */
35
+ export async function handleLanInvite(req, res) {
36
+ const raw = await readBody(req, res).catch(() => null);
37
+ let body = null;
38
+ try { body = JSON.parse(raw ?? ""); } catch { /* handled below */ }
39
+ switch (body?.action) {
40
+ case "make": {
41
+ const made = lanEngine.invite();
42
+ if (!made) return send(res, 409, { ok: false, reason: "not_running" });
43
+ // AFTER the spread, not before: status carries its own `invite` — the
44
+ // token and its expiry, which is all a panel needs — and letting that
45
+ // overwrite this one dropped the address list the caller asked for.
46
+ return send(res, 200, { ok: true, ...lanEngine.status(), invite: made });
47
+ }
48
+ case "withdraw":
49
+ lanEngine.withdraw();
50
+ return send(res, 200, { ok: true, ...lanEngine.status() });
51
+ case "join": {
52
+ const out = await lanEngine.join(body.token);
53
+ if (!out.ok) return send(res, 200, { ok: false, ...out, ...lanEngine.status() });
54
+ return send(res, 200, { ok: true, ...out, ...lanEngine.status() });
55
+ }
56
+ default:
57
+ return send(res, 400, { ok: false, reason: "unknown_action" });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Accept a deck, dismiss a request, or unpair one.
63
+ *
64
+ * ONE ROUTE, THREE VERBS, in the shape the accounts panel already uses for its
65
+ * own multi-verb writes. Each one takes a fingerprint and nothing else: the key
66
+ * being pinned comes from what this deck actually saw on the wire, never from
67
+ * the page, so a page cannot pair this deck with a key nobody has met.
68
+ */
69
+ export async function handleLanPeer(req, res) {
70
+ const raw = await readBody(req, res).catch(() => null);
71
+ let body = null;
72
+ try { body = JSON.parse(raw ?? ""); } catch { /* handled below */ }
73
+ const fp = body && typeof body.fp === "string" ? body.fp : null;
74
+ if (!fp) return send(res, 400, { ok: false, reason: "bad_request" });
75
+ switch (body.action) {
76
+ case "accept": {
77
+ const added = lanEngine.accept(fp);
78
+ if (!added) return send(res, 409, { ok: false, reason: "not_seen" });
79
+ // A deck we only HEARD is not pinned, it is dialled — see accept. The
80
+ // address has to reach prefs or the next write of the settings wipes it:
81
+ // setPeers replaces the dial list wholesale, on purpose, so an address
82
+ // that lives only in memory disappears the first time anything else is
83
+ // saved.
84
+ if (added.dialled) {
85
+ const entry = `${added.addr}:${added.port}`;
86
+ // Inside the job, like `onDial` — the same whole-array patch computed
87
+ // from the same stale copy, and the same address lost when two of them
88
+ // land in one turn.
89
+ try { await heldPrefs.update(withManualEntry(entry)); }
90
+ catch { /* it is dialled this session; the next accept re-adds it */ }
91
+ }
92
+ return send(res, 200, { ok: true, added, ...lanEngine.status() });
93
+ }
94
+ case "dismiss":
95
+ return send(res, 200, { ok: lanEngine.dismiss(fp), ...lanEngine.status() });
96
+ // The undo for the one above. A deck that was told no stops asking; this is
97
+ // how somebody who changed their mind lets it ask again.
98
+ case "allow":
99
+ return send(res, 200, { ok: lanEngine.allow(fp), ...lanEngine.status() });
100
+ case "unpair":
101
+ return send(res, 200, { ok: lanEngine.unpair(fp), ...lanEngine.status() });
102
+ // WHAT THIS DECK CALLS THAT ONE, and nobody else sees it. Keyed by the
103
+ // fingerprint, so it follows the machine across a new address or a new
104
+ // name of its own choosing. An empty name takes the alias away.
105
+ //
106
+ // The whole map is rebuilt from the one on disk rather than sent by the
107
+ // page, so two tabs renaming two decks cannot undo each other. It is
108
+ // rebuilt inside the write's own job for that to be true: the held prefs
109
+ // are an in-memory copy refreshed only when a previous write resolves, and
110
+ // the patch is the whole map — so two renames in one turn both read before
111
+ // either job ran, both answered 200, and the first name was never written.
112
+ case "alias": {
113
+ if (!isAliasKey(fp)) return send(res, 400, { ok: false, reason: "bad_request" });
114
+ const name = cleanAlias(body.name);
115
+ const next = await heldPrefs.update(withAlias(fp, name));
116
+ await lanEngine.apply({ aliases: next.lan.aliases });
117
+ return send(res, 200, { ok: true, ...lanEngine.status() });
118
+ }
119
+ default:
120
+ return send(res, 400, { ok: false, reason: "unknown_action" });
121
+ }
122
+ }
123
+
124
+ /** Ask every peer now rather than at the next tick — the button beside the
125
+ * list, for somebody who has just fixed a login on the other machine and does
126
+ * not want to wait a minute to see it arrive.
127
+ *
128
+ * Or ONE peer, when the body names it: the `check now` in a deck's own
129
+ * dialog. A deck that only calls in has no address here, and says so rather
130
+ * than reporting a round that asked nobody. */
131
+ export async function handleLanSync(req, res) {
132
+ const raw = await readBody(req, res).catch(() => null);
133
+ let body = null;
134
+ try { body = JSON.parse(raw ?? ""); } catch { /* the whole-list press sends nothing to read */ }
135
+ const fp = body && typeof body.fp === "string" ? body.fp : null;
136
+ if (fp) {
137
+ const done = await lanEngine.roundOne(fp);
138
+ if (done == null) return send(res, 409, { ok: false, reason: "no_address" });
139
+ return send(res, 200, { ok: true, done, ...lanEngine.status() });
140
+ }
141
+ const done = await lanEngine.round();
142
+ return send(res, 200, { ok: true, done, ...lanEngine.status() });
143
+ }
@@ -0,0 +1,112 @@
1
+ // What this deck answers a paired deck that asks — the list of logins it
2
+ // offers, or one of them sealed for the asker — lifted out of createEngine in
3
+ // lan-engine.mjs. The asking is the engine's round; this is the other end of
4
+ // the same two questions, with the checks that decide whether either is
5
+ // answered at all. It keeps nothing of its own: every check reads the
6
+ // engine's settings, listener and key at the moment it is made, because any
7
+ // of them can change while the store is read.
8
+ import { credentialAad, seal, slotFor, transferChallenge, trustedPeer } from "./lan-sync.mjs";
9
+ import { liveLoginIs } from "./account-health.mjs";
10
+
11
+ /**
12
+ * `settings`, `serverNow` and `myFp` answer the engine's settings in force,
13
+ * its listener (null while it is down) and this deck's fingerprint, each at
14
+ * the moment of asking. `inbound` keeps who called (see lan-inbound.mjs), and
15
+ * `learnCaller` is the engine's dial-back for a paired deck nothing here
16
+ * dials. `keepManifest` and `manifestFrame` are lan-manifest.mjs's,
17
+ * `localAccounts` reads the store in the rules' shape, and `liveLogin`,
18
+ * `exportAccount` and `onError` are the engine's own.
19
+ */
20
+ export function createServe({
21
+ settings, serverNow, myFp, inbound, learnCaller, keepManifest, manifestFrame, localAccounts, liveLogin, exportAccount,
22
+ onError,
23
+ }) {
24
+ /** Frames from a deck that finished the handshake AND that somebody here has
25
+ * accepted. Nothing reaches this before both, which is the whole point of
26
+ * where the two checks sit. `ctx.key` is this connection's key and no other
27
+ * connection's — see sessionKey. `ctx.send` seals whatever it is handed when
28
+ * both ends said they seal, so nothing below has to know which kind of deck
29
+ * asked — see frameChannel. */
30
+ const serve = async (msg, ctx) => {
31
+ // Authentication happened at connection setup; a previously trusted deck
32
+ // may have been unpaired while this socket remained open.
33
+ const mayAnswer = () => settings().enabled && !!serverNow() && !!trustedPeer(settings().trusted, ctx?.peerFp);
34
+ if (!mayAnswer()) return ctx.send({ t: "no", why: "not paired" });
35
+ // Before the verbs, and for every one of them: something that proved it
36
+ // holds a key this deck accepted is talking, now.
37
+ if (ctx?.peerFp) {
38
+ inbound.spoke(ctx);
39
+ learnCaller(ctx);
40
+ }
41
+ try {
42
+ if (msg.t === "manifest") {
43
+ // THE CALLER'S CARD RIDES THE QUESTION, which is the only way a deck
44
+ // that calls in ever says what it is: nothing here dials it, so nothing
45
+ // here ever asks. A seal that does not open is a deck that said nothing.
46
+ // AND SO DOES ITS LIST, from a deck new enough to send one: what it
47
+ // offers, and which of those it is on. This is the only way a deck
48
+ // nothing here dials is ever known by what it offers.
49
+ keepManifest(ctx.key, msg, ctx.peerFp);
50
+ const accounts = await localAccounts();
51
+ // Reading the store can take long enough for the owner to unpair this
52
+ // deck. Do not disclose account identities or the active account from
53
+ // a manifest assembled before that decision.
54
+ if (!mayAnswer()) return ctx.send({ t: "no", why: "not paired" });
55
+ return ctx.send(manifestFrame(accounts, ctx.key, ctx.peerFp));
56
+ }
57
+ if (msg.t === "want") {
58
+ // A listener may have authenticated this socket before its owner
59
+ // unpaired the caller or switched sharing off. Recheck at the moment
60
+ // a credential is requested and again after every asynchronous read.
61
+ const maySend = () => settings().enabled && !!serverNow() && !!trustedPeer(settings().trusted, ctx.peerFp)
62
+ && settings().shared.includes(msg.key);
63
+ if (!maySend()) return ctx.send({ t: "no", why: "not shared" });
64
+ // A SECOND PROOF, for the one operation that moves a credential. The
65
+ // session says who connected; this says they are asking for this
66
+ // account, now. A long-lived connection authenticated an hour ago is
67
+ // not a statement about now.
68
+ const want = transferChallenge(ctx.key, {
69
+ nonce: msg.nonce, accountKey: msg.key, fromFp: ctx.peerFp, toFp: myFp(),
70
+ });
71
+ if (typeof msg.proof !== "string" || msg.proof !== want) {
72
+ return ctx.send({ t: "no", why: "proof" });
73
+ }
74
+ // Only what the user ticked, checked again here rather than trusted
75
+ // from the manifest we sent: the list can change between the two, and
76
+ // the answer that matters is the one at the moment of sending.
77
+ const accounts = await localAccounts();
78
+ if (!maySend()) return ctx.send({ t: "no", why: "not shared" });
79
+ // The slot manifestFor told the peer about, by the same rule: with two
80
+ // slots for one identity, an expired one listed first must not hide a
81
+ // live one behind it.
82
+ const mine = slotFor(accounts, msg.key);
83
+ if (!mine) return ctx.send({ t: "no", why: "not mine to give" });
84
+ // A LOGIN THIS DECK CANNOT READ IS SAID SO, from state rather than from
85
+ // a failed export: no subprocess, and nothing the CLI printed. The
86
+ // asking deck prints it under the account, so the person learns which
87
+ // machine to unlock instead of reading "export failed".
88
+ if (!mine.readable) return ctx.send({ t: "no", why: mine.unreadableWhy });
89
+ if (!mine.alive) return ctx.send({ t: "no", why: "not mine to give" });
90
+ // The active slot exports the live CLI login, and its verdict can
91
+ // predate a `/login` as somebody else, so ask the CLI who it is now.
92
+ if (mine.active && liveLogin && !liveLoginIs(await liveLogin(), mine.email, mine.org)) {
93
+ return ctx.send({ t: "no", why: "export failed" });
94
+ }
95
+ if (!maySend()) return ctx.send({ t: "no", why: "not shared" });
96
+ const blob = await exportAccount(mine.num, msg.key);
97
+ if (!maySend()) return ctx.send({ t: "no", why: "not shared" });
98
+ // A Mac's failed export refreshes the verdict behind `readable` in the
99
+ // background, so when the Keychain was why, the next ask is answered by
100
+ // the line above.
101
+ if (!blob) return ctx.send({ t: "no", why: "export failed" });
102
+ const aad = credentialAad(myFp(), ctx.peerFp, msg.key);
103
+ return ctx.send({ t: "have", key: msg.key, sealed: seal(ctx.key, blob, aad) });
104
+ }
105
+ } catch (err) {
106
+ onError?.("serve", err);
107
+ ctx.send({ t: "no", why: "error" });
108
+ }
109
+ };
110
+
111
+ return { serve };
112
+ }